计算机基础实验Lab4:逆向工程与二进制补丁实战(通关指南)

📅 发布时间:2026/7/6 17:59:46 👁️ 浏览次数:
计算机基础实验Lab4:逆向工程与二进制补丁实战(通关指南)
1. 实验环境准备与工具安装大家好我是你们的老朋友一个在底层系统里摸爬滚打了十多年的“老码农”。今天咱们不聊那些高深的理论就来手把手、一步步地带你“速通”计算机基础实验里那个让人头疼的Lab4。我知道很多同学一看到“逆向工程”、“二进制补丁”这些词就发怵感觉是黑客电影里才有的东西。别怕咱们今天的目标很纯粹在不求甚解的情况下用最直接的工具和方法把实验通关。就像玩游戏抄近道一样咱们先拿到结果成就感有了兴趣自然就来了以后再慢慢研究原理也不迟。首先你得把“战场”准备好。这个实验的核心是跟编译好的二进制文件那些.o目标文件打交道所以你需要一个Linux环境。我强烈建议你在自己的电脑上装个Ubuntu虚拟机版本20.04或22.04都行稳定又好用。如果你用的是Windows可以用WSL2但有些工具操作起来可能没原生Linux那么顺手。准备好系统后打开终端咱们来安装几个“神兵利器”。第一个是GDB这是咱们的“显微镜”用来动态跟踪程序执行。安装命令很简单sudo apt update sudo apt install gdb装好后在终端输入gdb --version确认一下。第二个是Vim和xxd。Vim是文本编辑器但我们将用它以十六进制模式直接修改二进制文件xxd则是十六进制转换工具。它们通常系统自带如果没有安装命令是sudo apt install vim xxd。第三个是readelf属于binutils工具包用来查看目标文件的详细信息比如符号表。一般系统都自带可以用readelf --version检查。最后确保你有实验包里的linklab4链接脚本和各个phaseX.o文件。把它们都放在一个干净的目录下比如~/lab4。准备工作就这么多不复杂吧咱们的工具箱里现在有了调试器、十六进制编辑器和文件分析器足够应对接下来的挑战了。记住咱们的策略是“黑盒”操作先不管它内部怎么转找到关键点直接“动手术”。2. Phase 2函数劫持与栈上“偷梁换柱”Phase 2 通常是很多同学遇到的第一个真正需要“动手脚”的关卡。原始文章里提到目标是输出学号而关键思路是把do_phase函数的执行劫持到puts函数上并把学号字符串的地址作为参数传给它。听起来有点绕我打个比方原本的程序流程是“去A房间拿东西”我们现在要把它改成“去B房间并且告诉B房间的人一句暗号你的学号”。我们不需要理解A房间和B房间的构造只需要找到修改路标的方法。第一步定位关键调用点。我们需要知道main函数是在哪里调用do_phase的。这里就要用到GDB了。首先将目标文件链接成可执行程序gcc -m32 -no-pie -o lab4_phase2 phase2.o linklab4注意-m32是生成32位程序很多实验是基于32位环境的-no-pie是关闭地址随机化这样每次运行的地址是固定的便于我们定位。然后启动GDB调试gdb ./lab4_phase2在GDB中输入disas main来反汇编main函数。你会看到一大段汇编代码别慌找关键的一行。通常它长这样0x8049201: mov 0x804b0bc,%eax 0x8049206: call *%eax 0x8049208: jmp 0x804921a0x8049201这一行是把一个地址0x804b0bc里的值加载到eax寄存器。这个值就是do_phase函数的入口地址。紧接着的call *%eax就是调用这个函数。我们的目标就是让程序不执行do_phase而是跳转到puts函数。从反汇编代码往下看你很可能在稍后的位置看到call 0x8048730 putsplt这就是调用puts的地方地址是0x8048730。那么暴力破解的思路来了我们能不能直接把call *%eax这条指令改成跳转到puts的指令呢同时我们还得在跳转前把学号字符串的地址压入栈中作为参数。这就需要我们构造一小段机器码来替代原来的指令。这段机器码需要做三件事1. 将学号字符串地址入栈push2. 跳转到puts函数地址call3. 处理一下栈平衡可选。原始文章里给出了一串十六进制机器码那是作者根据自己学号地址和跳转地址生成的。你绝对不能直接复制你必须自己计算。如何计算你需要知道你的学号字符串存放在哪个地址。在GDB里你可以用x/s 0x8049628这个地址是原文例子里的试试看输出是不是你的学号。如果不是你需要在反汇编代码里找通常main函数里会有push $0xXXXXXX这样的指令后面跟着call puts那个0xXXXXXX就是学号字符串地址。假设你找到的学号地址是0x8049636puts的地址是0x8048730。那么对应的汇编指令和机器码可能是32位环境push $0x8049636 ; 机器码: 68 36 96 04 08 call 0x8048730 ; 机器码: e8 25 f5 ff ff (注意这是相对调用计算偏移量)计算call的相对偏移量有点麻烦。一个更简单粗暴的方法是我们直接覆盖do_phase函数的入口内容用objdump -d phase2.o查看do_phase函数记下它的前几条指令的机器码长度。然后我们用vim打开phase2.o找到do_phase函数开始的十六进制位置直接将其替换成jmp 0x8048730跳转到puts的机器码并在跳转前插入push学号地址的机器码。这需要对ELF文件结构和机器码有一定了解操作更底层。对于小白我推荐原文的“栈上替换”思路但我们要更清晰地操作用vim phase2.o打开文件然后输入:%!xxd切换到十六进制模式。找到main函数中call *%eax指令对应的机器码区域可以通过搜索上下文的机器码特征来定位。将这块区域替换为你自己生成的、包含push学号地址和call puts的机器码。替换完成后输入:%!xxd -r转换回二进制再:wq保存。最后重新链接运行如果看到你的学号被打印出来Phase 2 就成功闯过了这个过程就像在做一次精确的外科手术替换掉一小段“基因”让程序行为彻底改变。3. Phase 3弱符号分析与“填字游戏”来到 Phase 3画风突变。它不再要求你修改代码流程而是玩一个“填字游戏”。这个阶段考察的是对弱符号Weak Symbol和全局变量的理解。不过别担心我们依然可以“暴力”破解。首先用readelf -a phase3.o命令查看这个目标文件的符号表。你会看到一大堆符号其中需要特别关注那些类型为WEAK的全局符号GLOBAL。原文里提到了一个弱符号数组比如wWNPbCIIMo大小为256字节。你的任务就是创建一个同名的强符号来覆盖它。为什么可以覆盖这是链接器Linker的规则当链接多个目标文件时如果存在同名的强符号比如你在自己.c文件里定义的全局变量和弱符号链接器会选择强符号的定义。所以我们的策略就是“以强凌弱”。具体怎么做新建一个C文件比如phase3_patch.c在里面定义一个256字节的字符数组名字必须和你在phase3.o里找到的那个弱符号名字一模一样。就像这样#include stdio.h // 假设你查到的弱符号名是 wWNPbCIIMo char wWNPbCIIMo[256] { a, b, c, ... }; // 这里先填上256个任意字符然后编译这个C文件为目标文件gcc -m32 -c phase3_patch.c -o phase3_patch.o。接着将你的补丁文件和原始文件一起链接gcc -m32 -o lab4_phase3 phase3.o phase3_patch.o linklab4。运行生成的可执行文件你会发现程序打印出一堆“乱码”。这些“乱码”就是钥匙它们其实是程序用你提供的256字节数组里的某些字符经过特定算法计算后显示出来的。你的学号就隐藏在这堆乱码字符的映射关系里。原文作者发现输出的乱码字符和他学号中的字符有对应关系比如输出里频繁出现j而他的学号里有2于是他就把数组里所有的j都替换成2。这本质上是一个单表替换的密码游戏。我给你的操作建议是首先运行程序把输出的那串乱码字符完整地记录下来。然后对比你的学号。观察乱码中哪些字符是你的学号里没有的而你的学号里哪些字符是乱码里没有的。建立一个映射猜测比如乱码里的j, l, p可能对应学号里的2, 0, 2。接下来就是繁琐但简单的“查找-替换”工作。打开你的phase3_patch.c文件利用编辑器的全部替换功能在Vim里是:%s/j/2/g在VS Code或记事本里也有类似功能将你猜测的映射关系批量替换进去。这里有个重要技巧如果同一个字符可能对应学号里不同的数字比如乱码j可能对应学号里不同位置的2和3或者替换有顺序依赖比如先把a换成1结果1本身又是另一个字符要替换的目标就会出错。所以我建议你先在文本编辑器如VS Code里操作替换前备份原数组。替换一轮后重新编译、链接、运行看输出是否更接近你的学号。如果不完全对就调整映射关系再来一轮。这个过程可能需要几次迭代但一旦映射关系全部找对程序就会完美打印出你的学号。这就像破译一份简单的密码靠的是耐心和观察。4. Phase 4反汇编与全局替换的“暴力美学”Phase 4 在很多方面是 Phase 3 的升级版或者说它把“暴力”发挥到了更直接的层面。这个阶段通常涉及直接修改二进制文件中的机器码来改变常量的值。首先我们得知道要改哪里。对phase4.o使用反汇编命令objdump -d phase4.o phase4_asm.txt把反汇编代码输出到文件里慢慢看。同时链接并运行程序gcc -m32 -o lab4_phase4 phase4.o linklab4 ./lab4_phase4。程序很可能会打印出一串乱码这串乱码就是线索它是由程序内部某些数据很可能就是你的学号但被错误编码或替换了生成的。我们的目标是让这串乱码变成正确的学号。怎么变思路是乱码中的每一个错误字符都对应着二进制文件中某个或某几个错误的字节值。我们找到所有存储这些错误字符的字节把它们改成正确字符对应的字节值。这听起来工作量巨大但有个取巧的办法我们假设乱码和学号之间是一种简单的全局字符映射。比如乱码中所有的分号;都应该被替换成数字2所有的[都应该被替换成0等等。那么如何修改二进制文件呢再次请出我们的“手术刀组合”Vim xxd。用vim phase4.o打开文件输入:%!xxd进入十六进制编辑模式。现在整个文件以十六进制字节和对应ASCII字符的形式展现在你面前。你需要找到所有代表错误字符的十六进制字节。例如分号;的ASCII码十六进制是0x3b。你需要在文件中找到所有的0x3b字节把它们改成数字2对应的ASCII码0x32。在Vim的十六进制模式下你可以直接移动光标到3b上按r键进入替换模式然后输入32来修改。关键在于要替换所有出现的地方而不仅仅是可见字符串部分。因为程序可能把这些字符作为数据分散存储在代码段或数据段的不同位置。这就是“暴力”之处——我们进行全局替换。在Vim的命令模式下你可以使用搜索命令/3b来定位每一个0x3b然后手动或半自动地替换。当然更高效的方法是在转换回二进制之前利用Vim的替换命令但十六进制模式下的替换比较复杂。一个实用的“笨”方法是先仔细观察乱码字符串和你的学号确定几个最明显的映射关系。然后在十六进制视图里用眼睛扫描手动修改几十处最可疑的0x3b、0x5b[等。改一批就保存退出转换回去:%!xxd -r重新链接运行测试一下。如果输出更接近学号了就继续如果变乱了就回退重来记得备份原文件。这个过程非常考验耐心和细心但也是理解程序数据在二进制文件中如何存储的绝佳实践。当你最终看到屏幕上跳出正确的学号时那种亲手“修正”了程序数据的成就感是无与伦比的。5. Phase 5模式识别与终极补丁终于来到 Phase 5这通常是Lab4的终极挑战。原始文章里提到这个阶段分析起来很复杂但作者和同学们发现了一个“惊天秘密”每个人的最终答案需要修改的机器码竟然是一样的这其实暗示了这个Phase可能是一个固定的“后门”或者“彩蛋”考察的是你是否能通过合作或模式识别来找到通用解法。那么作为“速通”玩家我们的策略就是直接应用这个通用补丁。但在此之前我们至少要做一点基本的侦查工作以确保我们修改的位置是正确的。首先用objdump -d phase5.o反汇编重点关注do_phase函数。你会看到一段汇编代码。同时链接运行程序./lab4_phase5看看它输出什么错误信息或陷入什么状态比如死循环、段错误。这能帮助我们判断程序原本想做什么以及它在哪里卡住了。接下来就是应用“通用补丁”的时刻。这个补丁通常是一小段特定的机器码用来替换do_phase函数开头的一部分指令。这段机器码的作用可能是直接设置一个正确的返回值或者跳转到一个能打印成功信息的函数。重要警告你必须在自己的phase5.o文件上找到与原文示例图中完全相同偏移地址的位置进行修改不能盲目照抄字节。因为不同人编译的文件符号地址可能略有不同但函数内部的相对结构通常是稳定的。具体操作我们回到熟悉的Vimxxd流程。用vim phase5.o打开:%!xxd。现在我们需要定位到do_phase函数的起始位置。如何找一个方法是结合objdump -d phase5.o的输出。在反汇编结果中do_phase函数开头几行会显示类似55 89 e5对应push ebp; mov ebp, esp这样的机器码。在Vim的十六进制视图中搜索这一串连续的字节例如/55 89 e5就能定位到函数开头。找到之后对比原文中给出的机器码修改图将对应偏移处的原始字节修改成通用补丁的字节。比如原文可能显示在偏移0x30处将74 15改为eb 1e这只是举例。修改时务必精确一个字节都不能错。改完后:%!xxd -r转换并保存。重新链接运行。如果运气好或者说这个通用补丁确实有效你会看到程序输出了成功的提示或者直接通过了检查。至此Lab4的所有关卡都被你“暴力”征服了。回顾这五个阶段我们从修改函数调用Phase 2到覆盖全局变量Phase 3再到直接修补数据Phase 4和代码Phase 5实际上把二进制程序修改的几种常见手段都体验了一遍。虽然我们可能没有深入理解每一个步骤背后的编译原理和系统知识但这种“黑盒”实战带来的手感和对工具的熟悉是无比珍贵的。它打破了二进制程序的神秘感让你意识到再复杂的程序在底层也不过是一串可以操控的字节而已。