这是一套Linux Pwn入门教程系列,作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程。

课程回顾>>Linux Pwn入门教程第一章:环境配置

更多Pwn视频课程:https://www.ichunqiu.com/courses/pwn?from=weixin

本系列教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,来自历年赛事的原题和带有注释的python脚本。

教程中的题目和脚本若有使用不妥之处,欢迎各位大佬批评指正。

今天是Linux Pwn入门教程第二章:栈溢出基础,阅读用时约10分钟。

函数的进入与返回

要想理解栈溢出,首先必须理解在汇编层面上的函数进入与返回。首先我们用一个简单执行一次回显输入的程序hello开始。用IDA加载hello,定位到main函数后我们发现这个程序的逻辑十分简单,调用函数hello获取输入,然后输出“hello,”加上输入的名字后退出。使用F5看反汇编后的C代码可以非常方便的看懂逻辑。

我们选中IDA-View窗口或者按Tab键切回到汇编窗口,在main函数的call hello一行下断点,开启32位的Docker环境,启动调试服务器后直接按F9进行调试。

如图,这是当前IDA的界面。在这张图中我们需要重点注意到的东西有栈窗口,EIP寄存器,EBP寄存器和ESP寄存器。

首先我们可以看到EIP寄存器始终指向下一条将要执行的指令,也就是说如果我们可以通过某种方式修改EIP寄存器的值,我们就可以控制整个程序的执行,从而“pwn”掉程序(要验证这一点,我们可以在EIP后面的数字上点击右键选择Modify value.......把数值改成080484DE然后F9继续执行,从而跳过call hello一行)。

剩下的东西都和栈相关。顾名思义,栈就是一个数据结构中的栈结构,遵循先入后出的规则。这个栈的最小单位是函数栈帧,一个函数栈帧的结构如图所示:

栈的生长方式是向低地址生长,也就是说这张图的方向和IDA中栈窗口的方向是一样的,越往上地址值越小。同样的,新入栈的栈帧在IDA的窗口中会把原来的栈帧“压”在下面。

ESP和EBP两个寄存器负责标定当前栈帧的范围。图中标黑的部分即为实际上ESP和EBP中间的最大区域(为了方便讲解,我们把EIP和参数也列入一个函数的函数栈帧)。

图中的局部变量和参数很好理解,但EBP和EIP又是什么意思呢?我们回到IDA调试窗口。按照程序的逻辑,接下来应该是执行call hello这行指令调用hello这个函数,函数执行完后回到下一行的mov eax, 0,其地址为080484DE.然后我们再把当前ESP和EBP的值记下来(受地址空间随机化ASLR的影响,每台电脑每次运行到此处的ESP和EBP值不一定相同),然后按F7进入hello函数。

如图,执行完call hello这一行指令后发生了如下改变。由此我们可以得知call指令是可以改变EIP“始终指向下一条指令地址”的行为的,且call指令会把call下一条指令地址压栈。我们可以理解为call hello等价于push eip; mov eip, [hello]。所以我们的第一个问题“栈帧中的EIP是什么意思”的回答就是:栈帧中的EIP是call指令的下一条指令的地址,我们继续F8单步执行。

如图,通过依次执行三条指令,程序为hello函数开辟了新的栈帧,同时把原来的栈帧,即执行了call hello函数的main函数的栈帧的栈底EBP保存到栈中。继续往下执行到read函数,然后随便输入一些比较有标志性的内容,比如12345678,我们就会发现存储输入的局部变量buf就在这片新开辟的栈帧中。

我们已经接触到了栈帧的开辟与被使用情况,接下来我们再通过调试继续学习栈帧的销毁。继续F8到leave一行,此时我们会发现栈帧再次回到了刚执行完sub esp, 18h的状态。

执行完leave一行指令后栈帧被销毁,整体状态回到了call hello执行前的状态。即leave指令相当于add esp, xxh; mov esp, ebp; pop ebp

再次F8,发现EIP指向了call hello的下一行指令,同时栈中保存的EIP值被弹出,栈顶地址+4. 即retn等同于pop eip

此时hello函数代码执行完毕,控制流程返回到了调用hello函数的main函数中。

栈溢出实战

通过上一节的调试,我们大概理解了函数栈的初始化和销毁过程。我们发现随着我们的输入变多,输入的内容离栈上保存的EIP地址越来越近,那么我们可不可以通过输入修改掉栈上的EIP地址,从而在retn指令执行完后“pwn”掉程序呢?我们按Ctrl+F2结束掉当前的调试,再试一次。为了节约时间,这回我们直接把断点下在hello函数里的call _read一行。

启动调试,程序中断后界面如下:

通过观察read函数的参数和栈中的保存的EIP地址,我们计算出两者的偏移是0x16个字节,也就是说输入0x16=22个字节的数据,我们的输入就会和栈中的EIP“接上”,输入22+4=26个字节,我们的输入就会覆盖掉EIP。那么我们构造payload为‘A’*22+‘B’*4

即AAAAAAAAAAAAAAAAAAAAAABBBB,根据我们的推测,在EIP寄存器指向retn指令所在地址时,栈顶应该是‘BBBB’。即retn执行完之后,EIP里的值将不再是图中框起来的080484DE,而是42424242(BBBB的ASCII值),按F8使IDA挂起,在docker环境中输入payload:

栈中的EIP果然按照我们的推测被修改成42424242了。显然,这是一个非法的内存地址,它所在的内存页此时对我们来说并没有访问权限,所以我们运行完retn后程序将会报错。

选择OK,继续F8并且选择将错误传递给系统,这个进程接收到信号后将会结束,调试结束。我们通过一个程序本身的bug构造了一个特殊输入结束掉了它。

结合pwntools打造一个远程代码执行漏洞exp

通过上一节的内容,我们已经可以做到远程使一个程序崩溃。不要小看这个成果。如果我们能挖掘到安全软件或者系统的漏洞从而使其崩溃,我们就可以让某些保护失效,从而使后面的入侵更加轻松。当然,我们也不应该满足于这个成果,如果可以继续扩大这个漏洞的利用面,制造一个著名的RCE(远程代码执行),为所欲为,岂不是更好?

当然,CTF中的绝大部分pwn题也同样需要通过暴露给玩家的一个IP地址和端口号的组合,通过对端口上运行的程序进行挖掘,使用挖掘到的漏洞使程序执行不该执行的代码,从而获取到flag,这也是我们学习的目标。

为了降低难度,我在编写hello这个小程序的时候已经预先埋了一个后门——位于0804846B的名为getShell的函数。

如图,这个函数唯一的作用就是调用system("/bin/sh")打开一个bash shell,从而可以执行shell命令与系统本身进行交互。

正常的程序流程并不会调用这个函数,所以我们将会利用上一节中发现的漏洞劫持程序执行流程,从而执行getShell函数。

首先我们把hello的IO转发到10001端口上。

然后我们从Docker环境中获取其IP地址(我的是172.17.0.2,不同环境下可能不同)

然后在kali中启动python,导入pwntools库并且打开一个与Docker环境10001端口(即hello程序)的连接。

此时我们可以像上一篇文章一样打开IDA进行附加调试,在这里我就不再次演示了。从上一节的分析我们知道payload的组成应该是22个任意字符+地址。但是我们要怎么把16进制数表示的地址转换成4个字节的字符串呢?

我们可以选用structs库,当然pwntools提供了一个更方便的函数p32( )(即pack32位地址,同样的还有unpack32位地址的u32( )以及不同位数的p16( ),p64( )等等),所以我们的payload就是22*'A'+p32(0x0804846B)。

由于读取输入的函数是read,我们在输入时不需要以回车作为结束符(printf,getc,gets等则需要),我们使用代码io.send(payload)向程序发送payload。

由于我在这里没有设置IDA附加调试,显然程序也不会被断点中断,那么这个时候hello回显我们的输入之后应该成功地被payload劫持,跳转到getShell函数上了。为了与被pwn掉的hello进行交互,我们使用io.interactive( )

可以看到我们已经成功地pwn掉了这个程序,取得了其所在环境的控制权。为了增加一点气氛,我们在/home下面放了一个flag文件。让我们来看一下flag:

如图,我们成功的做出了第一个pwn题。为了加深对栈溢出的理解,我选了几个真实的CTF赛题作为作业,注意不要将思维固定在获取shell上哦。

课后例题和练习题非常重要,小伙伴请务必下载练习。后台回复“课后练习题”即可获得练习文档!

以上是今天的内容,大家看懂了吗?后面我们将持续更新Linux Pwn入门教程的相关章节,希望大家及时关注。

CTF必备技能丨Linux Pwn入门教程——栈溢出基础的更多相关文章

  1. CTF必备技能丨Linux Pwn入门教程——stack canary与绕过的思路

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  2. CTF必备技能丨Linux Pwn入门教程——PIE与bypass思路

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  3. CTF必备技能丨Linux Pwn入门教程——格式化字符串漏洞

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  4. CTF必备技能丨Linux Pwn入门教程——利用漏洞获取libc

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  5. CTF必备技能丨Linux Pwn入门教程——ROP技术(下)

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  6. CTF必备技能丨Linux Pwn入门教程——ROP技术(上)

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  7. CTF必备技能丨Linux Pwn入门教程——ShellCode

    这是一套Linux Pwn入门教程系列,作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程. 课程回顾>> Linu ...

  8. CTF必备技能丨Linux Pwn入门教程——环境配置

    说在前面 这是一套Linux Pwn入门教程系列,作者依据Atum师傅在i春秋上的Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程. 问:为 ...

  9. CTF必备技能丨Linux Pwn入门教程——调整栈帧的技巧

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

随机推荐

  1. Gradle Wrapper简述

    个人博客:http://www.enjoytoday.cn Gradle更多介绍参考:http://www.enjoytoday.cn/categorys/Gradle 案例源码:GitHub gra ...

  2. linux 安装PostgreSQL12

    一.安装步骤 1.设置保存安装包的目录 # cd /usr/local/src 2.开始下载源包 # wget https://ftp.postgresql.org/pub/source/v12.1/ ...

  3. Shell命令-网络操作之基础之ping、route

    文件及内容处理 - ping.route 1. ping:测试主机之间网络的连通性 ping命令的功能说明 ping 命令用于检测主机.执行 ping 指令会使用 ICMP 传输协议,发出要求回应的信 ...

  4. 12Java基础_数组定义格式/动态初始化/静态初始化

    /* Java数组 格式一: int[] array; 格式二: int array[]; 数组初始化: 为数组中的元素分配内存空间 动态初始化: int[] array=new int[数组长度] ...

  5. web-文件包含

    提示 构造payload ?file=flag.php 得到一串字符,那么我们用PHP伪协议尝试一下 构造payload ?file=php://filter/read=convert.base64- ...

  6. 移动端(手机端)页面自适应解决方案1(rem布局)---750设计稿

    设计稿尺寸为750 * 1340.结合网易.淘宝移动端首页html元素上的动态font-size属性.设计稿尺寸.前端与设计之间协作流程一般分为下面两种: 网易做法: 页面开头处引入下面这段代码,用于 ...

  7. C++面向对象程序设计学习笔记(6)

    多态性 编译时的多态性与运行时的多态性 在面向对象方法中,所谓多态性就是不同对象收到相同信息时,产生不同的行为.在c++程序设计中,即"一个接口,多种方法" 在C++中,多态性的实 ...

  8. 在net Core3.1上基于winform实现依赖注入实例

    目录 在net Core3.1上基于winform实现依赖注入实例 1.背景 2.依赖注入 2.1依赖注入是什么? 2.1依赖注入的目的 2.2依赖注入带来的好处 2.2.1生命周期的控制 2.2.2 ...

  9. Wireshark使用入门

    目录 1. Wireshark介绍 1.1 客户端界面 1.2 Display Filter 的常用方法 1.3 界面上一些小TIPS 2. 使用Wireshark分析TCP三次握手过程 2.1 三次 ...

  10. win7 64bit安装redis

    win7 64bit安装redis 1 先安装redis客户端 1.下载Redis的压缩包 https://github.com/dmajkic/redis/downloads 我下载的是redis- ...