一、实验内容
本周围绕 Linux 可执行文件 pwn1 的缓冲区溢出(BOF)漏洞与 shellcode 注入展开学习,核心是通过三种技术手段篡改程序执行流程,触发原本不可运行的 getShell 函数或自定义 shellcode。具体包括:手动修改可执行文件的机器指令,直接将 main 函数调用目标从 foo 改为 getShell;利用 foo 函数的 BOF 漏洞,构造攻击字符串覆盖返回地址,间接触发 getShell;在关闭系统防护机制后,注入自定义 shellcode 并实现其运行,最终获取交互式 Shell。
整个学习过程不仅涉及汇编指令、栈结构等底层知识,还需熟练使用 gdb 调试、vi 十六进制编辑、patchelf 等工具,理解小端字节序、地址随机化、堆栈权限等关键概念。
二、实验过程
1、直接修改程序机器指令,改变程序执行流程
(1)下载目标文件pwn1,反汇编分析关键代码:
首先获取目标文件 pwn1 并进行反汇编,重点分析 main、foo 与 getShell 三个函数的地址及调用关系。反汇编结果显示,getShell 函数起始地址为 0x0804847d,foo 函数起始地址为 0x08048491,main 函数中调用 foo 函数的指令位于地址 0x080484b5,对应的机器指令为 “e8 d7 ff ff ff”。
其中,“e8” 为调用指令(call)的操作码,后续的 “d7 ff ff ff” 是相对偏移量(补码形式,对应十进制 - 41)。根据 call 指令的执行逻辑,CPU 会将当前指令下一条地址(0x080484ba)与该偏移量相加,得到目标函数地址(0x080484ba - 0x29 = 0x08048491),即 foo 函数地址。若要让 main 函数调用 getShell,需将该偏移量修改为 “getShell 地址 - 0x080484ba” 对应的补码。通过计算,0x0804847d - 0x080484ba = -0x3d,其补码为 “c3 ff ff ff”。
(2)修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff
1)为避免破坏原文件,先复制 pwn1 生成新文件 pwn2311,使用vi编辑它:
cp pwn20232311 pwn2311
vi pwn2311
2)将显示模式转换为16进制模式,便于定位和修改机器指令:%!xxd
3)搜索目标指令 “e8 d7”,找到 main 函数中调用 foo 的机器指令位置,将偏移量部分的 “d7” 修改为 “c3”
4)输入:%!xxd -r将十六进制格式还原为可执行文件格式,保存并退出 vi,再反汇编看一下call指令已经正确调用getshell
5)运行修改后的文件,成功获取 Shell 提示符,表明程序执行流程已成功指向 getShell 函数
2、通过构造输入参数,造成BOF攻击,改变程序执行流
(1)反汇编,了解程序的基本功能,分析漏洞函数
重新反汇编 pwn1,聚焦 foo 函数的内存操作逻辑。foo 函数中通过 “sub $0x38, % esp” 分配栈空间,再通过 “lea -0x1c (% ebp), % eax” 将缓冲区起始地址传入 gets 函数。由于 gets 函数无输入长度限制,而缓冲区仅预留 28 字节(0x1c)空间,当输入字符串长度超过 28 字节时,多余数据会覆盖栈中后续存储的 ebp 寄存器值及函数返回地址,从而引发 BOF 漏洞。
同时,main 函数调用 foo 函数时,会将返回地址(0x080484ba)压入栈中,该地址正是后续攻击需覆盖的关键目标。
(2)确认输入字符串哪几个字符会覆盖到返回地址
先给pwn2023211提权
进行确认哪几个字符会覆盖到返回地址,启动 gdb 调试 pwn1,运行程序并输入测试字符串1111111122222222333333334444444455555555,程序因内存访问错误(SIGSEGV)崩溃,查看寄存器状态(info r),发现 eip 寄存器值为 0x35353535(对应字符 “5” 的 ASCII 码),说明第 33-36 字节的输入覆盖了返回地址;
再次测试,输入1111111122222222333333334444444412345678,崩溃时 eip 值为 0x34333231(对应字符 “1234”),进一步确认输入字符串的第 33-36 字节会覆盖栈中的返回地址。
(3)确认用什么值来覆盖返回地址
getShell的内存地址,通过反汇编时可以看到,即0804847d。
接下来要确认下字节序,简单说是输入11111111222222223333333344444444\x08\x04\x84\x7d,还是输入11111111222222223333333344444444\x7d\x84\x04\x08。
对比之前eip 0x34333231 0x34333231,正确应用输入 11111111222222223333333344444444\x7d\x84\x04\x08。
(4)构造输入字符串
由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先利用 perl 语言生成包含攻击字符串的文件。其中,前 32 字节为填充字符,第 33-36 字节为反转后的 getShell 地址,最后 “\x0a” 为换行符,用于触发 gets 函数读取结束。
使用16进制查看指令xxd查看input文件的内容如同预期。
将input的输入,通过管道符“|”,作为pwn20232311的输入。达到预期成果。
4、注入Shellcode并执行
shellcode就是一段机器指令(code)通常这段机器指令的目的是为获取一个交互式的shell
(1)准备工作,修改一些设置:
设置堆栈可执行并验证
关闭地址随机化,避免 shellcode 地址被随机化导致无法定位
(2)构造要注入的payload。
采用 “填充字符 + 覆盖返回地址 + NOP sled + shellcode” 的 payload 结构,其中:填充字符:32 字节,用于填满缓冲区并覆盖ebp;覆盖返回地址:需设置为 NOP sled 区域的地址,确保 CPU 执行到该地址时能滑入 shellcode;NOP sled:多个 “\x90”(NOP 指令),用于提高 shellcode 地址命中概率;shellcode:自定义的 Shell 获取指令序列。
初步生成 payload 文件:下面最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。现在要确定\x4\x3\x2\x1到底该填什么。
1)打开一个终端注入攻击buf:
2)再开另外一个终端,先找到进程号:
3)启动gdb调试这个进程,通过设置断点,来查看注入buf的内存地址
4)反汇编 foo 函数找到返回指令(ret)地址(0x080484ae),设置断点:break *0x080484ae;在第一个终端中按下回车,触发断点,查看栈布局(x/16x esp),找到临时占位地址“\x01\x02\x03\x04”,确定其相邻的 NOP 区域地址
5)可以找到01020304了,就是返回地址的位置。shellcode就挨着,所以地址是 0xffffd440
6)将返回地址改为0xffffd440,使用16进制查看指令xxd查看文件的内容如同预期。并且也能正确体现shellcode。
三、问题分析
1、问题1:
将显示模式切换为16进制模式时 :%!xxd 返回是command not found :xxd。并且执行sudo apt install xxd后依然报错。
问题1解决方案:查阅资料得知是默认的 Kali 官方源在当前网络环境中不稳定或版本不同步。于是便更换为国内的 Kali 镜像源并导入缺少的阿里云 Kali 源的 GPG 验证公钥。之后就可以顺利安装了
2、问题2:
按照常规方法尝试使用 execstack 工具设置堆栈可执行时,发现系统中未安装该工具,且通过 apt 安装时提示 “无法定位软件包 prelink”(execstack 依赖 prelink),导致堆栈权限设置受阻。
问题2解决方案:查阅资料后,选择使用功能类似且更易获取的 patchelf 工具替代 execstack。通过sudo apt install patchelf安装工具后,执行patchelf --set-execstack ~/Desktop/pwn4成功将 pwn4 的堆栈设置为可执行,满足实验需求。上文中有体现。
四、学习感悟
这次实验让我对网络攻防的底层原理有了更直观的认识,不再是停留在理论层面的 “缓冲区溢出”“shellcode” 等概念,而是真正动手操作时感受到每一个字节、每一条指令的重要性。比如修改机器指令时,偏移量的计算错误会导致程序直接崩溃;构造 BOF 攻击时,返回地址的字节序搞反会让攻击完全失效,这些细节让我明白攻防技术需要极致的严谨。
另外,工具的使用也是本次学习的重要收获。从 gdb 调试定位漏洞、vi 编辑二进制文件,到 perl 生成攻击 payload,每一款工具都有其独特作用,熟练掌握它们是开展攻防实验的基础。遇到工具安装问题时,通过查找资料更换替代工具(如 patchelf 替代 execstack),也锻炼了我的问题解决能力,让我明白灵活变通在技术实践中的重要性。