这是一套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在Android中的简单使用

    Gradle在Android中简单的使用 还望支持个人博客站:http://www.enjoytoday.cn Android Studio 使用gradle进行工程构建,为了更好的了解整个andro ...

  2. 利用python去实现数学基本值的计算

    def get_nums(): nums = []#获取列表 num = input('请输入数字:').strip() while num != '': nums.append(num)#添加数字 ...

  3. BayaiM__oracle切换归档模式步骤:

    BayaiM__oracle切换归档模式(步骤): ------------------oracle11g设置归档模式和非归档模式--------------------------[root@tes ...

  4. LVM 在线扩容磁盘(ubuntu 14.04 server)

    mware workstation 8 或者 vmware vsphere client 6.0( exsi 6.0) (前提:你的服务器需要有已经存在的卷组,才可以添加新的物理卷到卷组,然后再扩容逻 ...

  5. Java重定向标准输入/输出

    在System类中提供了三个重定向标准输入/输出的方法static void setErr(PrintStream err) 重定向“标准”错误输出流static void setIn(InputSt ...

  6. 文件名工具类 MoFileNameUtil

    文件名工具类 MoFileNameUtil MoFileNameUtil public class MoFileNameUtil { //不包含点号 public static String getF ...

  7. zz自动驾驶中轨迹规划的探索和挑战

    大家好,今天我们主要介绍一下轨迹规划的探索和挑战,我主要从四个方面介绍: 轨迹规划的概念 决策 横向规划 纵向规划 轨迹规划的概念: 轨迹规划的核心就是要解决车辆该怎么走的问题.比如我们知道了附近有行 ...

  8. CF1225A Forgetting Things

    CF1225A Forgetting Things 洛谷评测传送门 题目描述 Kolya is very absent-minded. Today his math teacher asked him ...

  9. 零基础入门 实战mpvue2.0多端小程序框架

    第1章 课程快速预览(必看!!!)在这一章节中,老师讲带领你快速预览课程整体.其中,涉及到为什么要做这么一门实战课程.制作一个小程序的完整流程是怎么样的,以及如何做项目的技术选型. 第2章 30 分钟 ...

  10. 腾讯云短信服务+Node.js给手机发送验证码

    最近公司需要些一个登陆验证和修改密码验证,需要用到验证码,我用Node.js写了一个给手机发验证码的代码,下面实现的功能有:生产验证码,(计时器)验证码失效时间,给手机发送短信. 首先看官方文档,在给 ...