Linux 二进制漏洞挖掘入门系列之(一)栈溢出
0x10 环境gdb-peda 插件,用来调试程序时,高亮不同寄存器,堆栈的地址和数据Windows 环境下的 IDA Pro,强大的交互式反汇编工具测试程序(即带有栈溢出漏洞的二进制可执行文件)虚拟机装有 Linux 发行版,例如 Ubuntu、Kali,用来运行测试程序测试程序下载地址1链接:https://pan.baidu.com/s/1U3ktIz-veMuktuts...
欢迎访问系列文章
说明:本篇博客属于笔者早期所写,细节方面当时并没有考虑到。
关于栈的相关寄存器、变量说明,可以参考:
从汇编角度理解 ebp&esp 寄存器、函数调用过程、函数参数传递以及堆栈平衡;
关于栈溢出的深入理解与应用,可以参考:
x86 架构中的内存攻击技术 ROP(一)
x86 架构中的内存攻击技术 ROP(二)
0x10 环境
- gdb-peda 插件,用来调试程序时,高亮不同寄存器,堆栈的地址和数据
- Windows 环境下的 IDA Pro,强大的交互式反汇编工具
- 测试程序(即带有栈溢出漏洞的二进制可执行文件)
- 虚拟机装有 Linux 发行版,例如 Ubuntu、Kali,用来运行测试程序
- pwntools,如果要写 POC,会用到此 python 包
测试程序下载地址
1
链接:https://pan.baidu.com/s/1U3ktIz-veMuktutss-oGuA
提取码:9sn2
2
下面文件下载后,放在linux下的 /home/pwn/pwn0/
链接:https://pan.baidu.com/s/11lm17lllPZ5A8rrcuCELAg
提取码:knpf
gdb-peda 插件安装终端输入以下命令,即可安装成功
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
对于非 Kali 的 Linux 发行版,我们还需要安装 checksec,一个用来检查 ELF 二进制可执行文件的保护措施的小程序
apt-get install checksec
0x20 漏洞分析
首先当然是运行目标程序,亲手体验一下程序的作用,然后再反编译代码,进行静态分析,找到其中可能存在的 bug,最后进行利用
0x21 初步分析
在 linux 环境下检测之前下载好的文件类型。从结果不难看出,文件类型是 ELF 32 位的可执行程序,架构是 Intel x86,也就是说该程序无法在 ARM 架构的处理器上运行。
检查以下目标程序有没有保护措施,如果是自己安装的 checksec ,需要加入 -f 参数,即 checksec -f pwn0
- Arch,即我们刚刚说的架构,32 位
- Stack / CANNARY (栈保护),类似于cookie, 防止利用栈溢出覆盖和篡改
- FORTIFY,对字符串格式化漏洞的保护,一般在 gcc 编译时考虑
- NX 即 No-eXecute(不可执行),栈数据没有可执行权限
- RELRO 分为RELRO会有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表
在 linux 环境下运行下载好的测试程序 pwn0
这个程序比较简单,就是你输入什么,屏幕会打印什么,至于有没有漏洞存在,需要使用工具来进行分析源码
0x22 静态分析
使用 IDA Pro 打开目标程序 pwn0,由于测试程序是 32 位的,需要使用 32 位 IDA 打开。打开之后,给我们呈现的是反汇编代码,如果你不习惯使用汇编,可以按 F5,得到 IDA 反编译的伪码(C语言),接近源码,这也是 IDA 强大的原因之一
我们发现,foo() 函数传递的 a1 参数值是固定的,也就是 foo 函数第 8 行的判断永远不成立,导致 getFlag() 函数无法运行,我们也就拿不到里面的值
这里存在的问题就是,gets() 函数不会限制用户输入的数据长度,而变量 s 的大小不是无限的,它在栈中,分配一个固定的地址,并且大小有限制。
上图是一个典型的函数调用栈的示意图,由于没有对用户的输入做判断,我们可以写入大量的值到 s,直到 s 覆盖到函数形参 a1,将 a1 改写为 0x61616161,那么就可以使得 if 条件判断成立,从而得到 flag。现在只需要知道变量 s 离栈底的距离即可
这里,穿插一下基本概念
ESP寄存器用来记录栈顶的地址。随着数据项的入栈和出栈,其值在不断变化
EBP寄存器用于引用当前栈帧中的变量。
事实上 IDA 已经给出答案,在上面静态分析截图的代码中,foo() 函数第 4 行后面,IDA 已给出注释,s 的地址是,ebp - 1Ch,也就是距离 EBP 1C个字节的距离。为了了解 IDA 的计算过程,我们使用 gdb 调试来分析
0x23 gdb 调试分析
我们需要在内存给变量 s 分配地址时,查看栈空间,并观察各个寄存器的状态,这就需要在分配给变量 s 的代码处,设置断点
在 IDA Pro 中,点击 Options > General > Disassembly,勾选下面的选项,这样就能够查看代码段的相关地址,才能根据地址设置断点
查看定义变量 s 的代码
这是 foo() 函数的汇编代码,不难看出,在 0x080485AB 处,开始给变量 s 分配内存。也就是说,需要利用 gdb 调试,在该地址处设置断点,并查看此时的寄存器和函数栈分配情况
如上图,此时栈顶位置就是分配给 s 变量的位置,即 EAX 寄存器存放的数据,它与 EBP 的距离为 0xffffd328 - 0xffffd30c = 0x1c,与 IDA 分析的一致
所以,最终,变量 s 距离栈底,形参 a1
(EBP - EAX)+ EBP + EIP
0xffffd328 - 0xffffd30c + 0x4 + 0x4 # 寄存器存放 32 位数据,即 4 字节
(上述结果为十进制数 36)
0x30 验证
运行 pwn0,输入 36 个 A(也可以是其他填充数据),再输入1234(也是测试数据),那么填充数据理论上覆盖了 s 以下的栈空间,1234 刚好覆盖形参 a1,还是用 gdb 来验证吧
0x31 gdb 验证
接着 0x23 节设置的断点(s 赋值前),我们再在后面设置一个断点,断点位置不用太固定,从下图随便选一个地址作为点(s 赋值后)即可,重要的是,必须是在 s 赋值后
首先观察 s 赋值前,栈空间的数据,此时形参 a1 仍然是 0x12345678
然后设置第二个断点,在 gdb 中输入 c,继续第一个断点后运行程序,此时程序提示输入,我们按照 0x20 的分析,输入以下数据
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2222
数据来源:
payload = A' * (0xffffd328 - 0xffffd30c) + 'A' * 0x4 + 'A' * 0x4
payload += 2222 # 字符 A 用来填充,可以随意更换,字符 1234 用来覆盖形参 a1
print payload
此时,再用 gdb 查看栈空间
达到相应效果,形参 a1 的值被替换
0x32 编写代码验证
注意事项:内存存储数字可能未必是我们输入的数字,可能需要转换为字符串,然后输入
# -*- coding: utf-8 -*-
import pwn
pwn_object = pwn.process("./pwn0")
print pwn_object.recvline() # 读取一行程序输出后,print打印出来(so,can you find flag?)
payload = 'A' * (0xffffd328 - 0xffffd30c) + 'A' * 0x4 + 'A' * 0x4
payload += pwn.p32(0x61616161)
pwn_object.sendline(payload) # 用p32转换字符,加在payload末尾
print pwn_object.recvline()
print pwn_object.recvline()
程序输出结果:
至此,我们已经成功的让程序运行到,它原本不可能执行的地方了
——————————————————————————————
原创文章,转载请注明出处!
更多推荐
所有评论(0)