
BUUCTF 的 reverse2 我其实很早就刷过但前几天带一个学弟重新走了一遍这道题发现自己以前很多细节是靠猜的尤其是字符替换那一步如果不看反汇编里那个 for 循环很容易拿着原始字符串直接交结果一直 wrong。reverse 方向的入门题难点通常不在算法而在愿不愿意把一个几行代码的程序从黑盒变成白盒。这篇文章就用 reverse2 当例子把从下载附件到最终跑出 flag 的完整流程拆开讲一遍新手可以直接照着做有点基础的人也能看看我在关键节点是怎么判断的。1. 刷 reverse2 之前先把“题目类型”搞清楚1.1 reverse2 到底在考什么BUUCTF 里的 reverse2 是一道很典型的 CTF 逆向入门题。它不会让你去逆一个几百 MB 的大程序也不会一上来就让你面对内核驱动或者混淆引擎而是把最常见的“用户输入 字符串比较 正确/错误判断”压缩在一个很小的二进制文件里。说白了就是出题人把一个 flag 藏在一段程序逻辑里程序会先对内存里的某些字符做处理再拿处理后的结果和你输入的内容比较。如果你输入的和它最终比较的字符串一致就会输出 right flag 一类提示。这种题型的价值不在于“难”而在于它把逆向的几个基础动作都串起来了文件类型识别、静态分析、交叉引用、动态验证、脚本还原。做完 reverse2后面的 reverse3、SimpleRev 一些题目里还会反复出现类似的东西所以值得认真做一遍。1.2 准备一个能反复折腾的环境如果你在 BUUCTF 上刷到 reverse2下载下来多半是一个 Linux ELF 文件。我的建议是尽量准备一个 Linux 环境Windows 上用 VMware、VirtualBox 装 Ubuntu或者直接使用 WSL 都行。很多逆向工具链在 Linux 下更顺尤其是后面要用的file、checksec、gdb这些几乎一条命令就能装好。纯 Windows 环境不是不能做IDA 和 Ghidra 都能跨平台分析 ELF但你不能直接运行附件来观察行为。遇到需要动态验证的地方会难受一些。所以我个人的原则是分析工具可以复杂运行环境最好简单一个干净的 Linux 虚拟机足够解决 90% 的入门 reverse 题。用到的工具也不需要一次装全。reverse2 这个难度核心就是file看文件类型checksec看保护属性strings看可见字符串再用 IDA 或者 Ghidra 做静态反编译最后用 Python 把 flag 还原出来。gdb 属于加分项能让你看到内存里真实的比较内容。1.3 工具分工不是装得越多越好很多新手一开始就去找各种商业级逆向工具甚至想上脱壳机、反混淆框架结果遇到 reverse2 这种题根本用不上。我的经验是先分清每个工具解决什么问题。file判断文件是不是 ELF、是不是 64 位、有没有剥离符号。checksec看栈保护、NX、PIE、RELRO决定你后面需不需要绕过保护。strings快速抽取程序里的可见字符串往往能直接看到关键提示。IDA/Ghidra把汇编反编译成伪代码是静态分析的主力。gdb动态调试打断点看寄存器、内存和函数调用过程。Python最后把逻辑翻译成脚本快速得到 flag。reverse2 没有加壳也没有反调试所以不需要一上来就研究怎么过壳。先老老实实把工具链跑通比记一堆高级技巧有用。2. 信息收集阶段第一眼能看出很多门道2.1 用 file 和 checksec 快速判断文件特性我在拿到真实样本后第一步不是丢进 IDA而是先在终端里执行file reverse2输出大概是reverse2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped这里有两个信息很关键第一它是个 64 位 ELF不能用 32 位思路去想第二not stripped表示符号表没有被剥掉说明 main 函数这类符号很可能还在静态分析会轻松很多。接着用 checksec 看保护checksec --filereverse2如果只关心做题看到 NX 开启而 PIE 关闭并不影响什么因为 reverse2 不需要你构造溢出或 ROP。但对新手来说养成 checksec 的习惯很重要后面做到 pwn 方向或者更复杂的 reverse 题时这些信息会直接影响你的思路。2.2 strings 能看到什么不能看到什么快速扫描字符串是我个人很喜欢的侦察手段。reverse2 这类题里程序至少要输出提示语所以能用 strings 看到类似内容strings reverse2 | grep -iE flag|input|right|wrong常见输出里可能会看到input your flag: right flag! wrong flag! flag{hacking_for_fun}看到flag{hacking_for_fun}的时候很多人会兴奋地直接把它提交上去结果发现错了。这里就隐藏着 reverse2 最重要的一个坑strings看到的是程序编译时存放的初始字符串不一定是最终用于比较的字符串。程序可能在运行过程中修改它比如把某个字符替换成另一个字符。所以 strings 只负责给你线索不能给你结论。真正决定答案的是程序在比较发生之前到底对这段字符串做了什么。2.3 先运行一次观察输入输出特征分析之前先运行一次可以让思路落地。我在终端输入./reverse2程序会输出类似input your flag:然后等你输入。我随手输入flag{hacking_for_fun}结果返回wrong flag!。这个结果其实非常有价值它说明我对程序逻辑的理解还差一步它要的不是原始字符串而是经过某种变换后的字符串。运行附件还有一个额外好处可以确认程序是否依赖外部输入、是否有交互、是否在特定条件下才输出关键信息。很多逆向题不是“一步到位”的先看清楚程序行为后面看伪代码时会更容易对上号。3. 静态分析核心逻辑其实藏在一个 for 循环里3.1 用 IDA 或 Ghidra 定位 mainreverse2 既然是not stripped的 ELF用 IDA 打开后通常能直接在函数列表里找到main。双击进去按一下 F5 转换成伪代码比直接读汇编快很多。如果你用的是 Ghidra操作也差不多导入文件后用自动分析在 Symbol Tree 里找到 main双击查看反编译结果。我自己的习惯是优先用 IDA 看如果遇到识别不出来的情况再换 Ghidra。两个工具的反编译结果可能略有差异但核心逻辑不会变。对于没有符号表的程序main 不会直接出现在列表里。这时候可以去看 entry 函数找到传给__libc_start_main的第一个参数那个才是真正的 main。reverse2 没到这一步但还是建议知道这个定位方法后面题目难度上来会用到。3.2 主函数伪代码逐段拆解从 reverse2 的实际样本反编译结果来看核心结构大概是下面这样。变量名我按自己习惯重命名了你实际看到的是 v1、v4 之类也没关系#include stdio.h #include string.h int main() { char input[64]; char flag[64]; strcpy(flag, flag{hacking_for_fun}); for (int i 0; i strlen(flag); i) { if (flag[i] i || flag[i] r) { flag[i] 1; } } printf(input your flag: ); scanf(%63s, input); if (strcmp(input, flag) 0) puts(right flag!); else puts(wrong flag!); return 0; }如果你看到的变量名和函数名不完全一样不用慌。重要的是抓住几个点strcpy拷贝了一个初始字符串for循环里有一个字符判断判断条件是把i或r替换成1最后strcmp比较的是被替换后的flag而不是最开始的flag{hacking_for_fun}。这道题的题眼就在那个 for 循环。它并不是什么复杂算法但恰恰是这种类似“小动作”的代码最容易在 strings 侦察阶段被忽略。3.3 为什么会产生“原始字符串是对的”的错觉很多人交了flag{hacking_for_fun}却提示 wrong原因就是我前面说的程序在比较前已经把内存里的字符串改掉了。这里有一个逆向里很常见的认知误区以为静态数据区里的字符串就是最终答案。实际上程序运行后数据区是可写的编译优化后的代码也可能把字符串挪到栈上再一段一段修改。reverse2 还在很基础的阶段它把初始字符串拷到栈上然后在栈上做替换最后拿栈上的结果去比较。你需要还原的是程序在比较前一刻的“内存状态”而不是二进制文件里静态存储的“初始状态”。理解了这一点后面做 UPX、虚拟机保护、自修改代码时至少不会一看到题目就蒙圈。4. 还原 flag 的两种姿势手算和脚本4.1 手算思路照着循环做一次替换现在逻辑已经清楚了我用最笨但最不容易错的方法把flag{hacking_for_fun}从头到尾过一遍。初始字符串f l a g { h a c k i n g _ f o r _ f u n }程序规则是遇到字符i或者字符r就把它改成1。于是看每一个字符hacking里的i变成1得到hack1ngfor里的r变成1得到f1注意是f1不是fo1因为只有r被替换了前面的f和o都不变。整体拼起来就是flag{hack1ng_f1_fun}手动替换看起来简单但真实做题时建议在纸上写一遍。很多入门题错误都出在“眼睛看会了手没跟上”比如把for整体替换成f1r或者漏掉字符串里的某一次出现。once你多写几步这种错误就能避免。4.2 Python 脚本把逻辑固定下来题目简单的时候手算没问题但逆向题后面经常出现几层变换手算容易心态崩。所以要用脚本思维把程序里的处理逻辑翻译成 Python让脚本替你做枯燥的替换和计算。我的还原脚本长这样ori flag{hacking_for_fun} result [] for ch in ori: if ch i or ch r: result.append(1) else: result.append(ch) print(.join(result))也可以写成更简洁的列表推导式ori flag{hacking_for_fun} print(.join(1 if c in ir else c for c in ori))运行结果flag{hack1ng_f1_fun}这个字符串就是程序最终拿去和scanf输入比较的内容。你在终端里输入它程序就会返回 right flag。脚本还原的最大好处是逻辑可复用。后面遇到 base64 变表、异或、RC4、TEA 这类真实算法题时你往往不是去手算几十轮加密而是把逆向出来的逻辑翻译成 Python再拿已知的明文或常量反推 flag。4.3 用 gdb 动态验证关键内存静态分析已经能得出结论但我还是习惯用 gdb 做一次动态验证尤其在给学弟讲解的时候。这样做的目的是证明程序里真正参与比较的字符串确实不是文件里的初始字符串。可以在 gdb 中运行gdb ./reverse2然后在 main 函数上下断点b main r程序会停在 main 开头接着反汇编 main找到调用strcmp之前的位置下断点后继续运行。64 位程序调用strcmp时第一个参数在rdi第二个参数在rsi。在这两个寄存器指向的内存里你就能看到真正参与比较的字符串。如果不想分析汇编地址也可以先输入正确内容再观察结果./reverse2 input your flag: flag{hack1ng_f1_fun} right flag!这一步实际跑通了答案才算是验证过而不是“我觉得是这样”。5. 实操过程中的高频坑与排查思路5.1 常见问题速查表我把新手做 reverse2 时容易踩的坑整理成一张表后面做题卡住时可以对着排查。现象可能原因处理方向提交 strings 看到的字符串提示 wrong程序运行中修改了原字符串去看 for、while 等循环逻辑F5 失败或伪代码不全函数识别失败或存在花指令在 IDA 中手动创建函数或换 Ghidra找不到 main 函数符号被剥离从 entry 找__libc_start_main参数gdb 断点地址报错PIE 开启地址随机化先start再用相对偏移下断点Linux 下无法运行附件文件不是当前架构的 ELF用file确认必要时用 IDA 静态分析输入包含空格导致判断错误scanf(%s)遇到空格会停止确认 flag 格式是否含有空格一般没有reverse2 本身没有壳也没有反调试但这些问题在后面的逆向题里非常常见。提前建立排查意识比背一堆命令更值钱。5.2 我最想提醒新手的三个习惯第一个习惯是看到字符串不要直接开心要右键查看交叉引用。字符串被谁使用往往比字符串本身更重要。reverse2 里的flag{hacking_for_fun}被strcpy引用而strcpy的下一段代码就是循环变换。如果你只盯着 strings 输出就永远看不到这个关系。第二个习惯是遇到循环把每次迭代写出来。很多人读到if (flag[i] i || flag[i] r) flag[i] 1;就以为自己懂了但要问你for变成什么还得想半天。我的做法是拿一小段草稿纸把原始字符串写出来然后把变化的字符一个一个替换掉。慢是慢一点但不容易错。第三个习惯是提交前一定用正确输入跑一次程序。逆向题的 flag 不是猜出来的而是还原出来的。如果你还原之后连程序自身的校验都过不了那说明你的理解有问题。哪怕只是./reverse2输入一遍也能拦住至少一半的低级失误。5.3 遇到复杂程序时怎么保持分析节奏reverse2 很小一个人很容易盯完所有代码。但实际做题会遇到上万行的大程序所以从一开始就要训练自己的节奏。不要把时间浪费在理解每一行汇编上先抓住scanf、strcmp、printf这类 IO 函数再顺着它们找到比较逻辑最后反向搜索谁修改了比较数据。这个过程很像看一篇长文章先看开头和结尾再找段落中心句最后才去啃细节。reverse2 正好给了你一个机会练习这种“先找关键函数再补上下文”的阅读方式。6. 从 reverse2 向后的延伸这道题的真正价值6.1 reverse2 其实覆盖了好几个基础知识点虽然 reverse2 的解题过程只有短短几分钟但它覆盖的内容并不少你需要理解 ELF 的基本概念知道file输出代表什么你需要熟悉静态分析工具的 F5 流程能够从伪代码里找到字符串比较你还需要意识到程序运行时的内存可变性理解“静态字符串 ≠ 最终比较字符串”。这些知识在之后的逆向学习中会反复出现。尤其是“数据在运行中会被修改”这个点很多人到后面才真正理解。reverse2 是用一个简单的字符替换让你建立概念后面的题目则会用加密、解密、解码、自修改代码来考验你。如果这道题就靠猜答案蒙过去后面会吃大亏。6.2 从这道题往后应该练什么做完 reverse2我建议按顺序尝试几个方向第一找一道加 UPX 壳的题练手学会识别壳并用工具脱壳因为脱壳是逆向里绕不开的坎第二做一道使用 base64 或简单异或算法的题体会一下真正加密函数的还原过程第三试着用 gdb 动态跟踪一遍调用过程看看strcmp之前寄存器和栈的变化。如果还想扩展可以尝试写一个小工具把 IDA 或 Ghidra 的伪代码翻译成 Python 脚本。这个过程有点像翻译外语翻译多了逆向速度会明显提升。6.3 一点个人实操心得我把 reverse2 做完之后给自己定了一个规矩不管题目看起来多简单都要把完整流程走一遍而不是直接搜 flag。原因很简单逆向能力的提升不在最终答案里而在每一步的判断过程中。你对“看到字符串后下一步怎么做”的反应越自然真正比赛或工作中遇到问题时才越不容易慌。reverse2 是一道值得反复咀嚼的题。现在再回看这道题我最大的感受是程序员的代码是给人看的逆向就是把编译和优化过程“怼”回去直到看见那个人当时的想法。你不需要一上来就懂所有底层细节先把最简单的字符串比较和内存修改看懂这条路就算正式开始了。