先说个结论如果你正在找逆向入门的练手题[FlareOn2] very_success 和 [FlareOn3] Challenge1 是两堂绕不开的必修课。前者是一道标准的 Windows PE 动态调试题考的是在调试器里观察程序行为、定位校验分支、修改内存状态让校验“按你的想法走”后者则是一道纯粹的 JavaScript 混淆逆向题考的是从一堆故意写乱的前端代码里还原出真正的验证逻辑。两道题一个偏二进制、一个偏脚本正好覆盖了逆向工程里两个最基础也最核心的能力读懂运行时的程序以及剥离混淆直达本质。无论你是刚看完汇编基础想找题练手的新手还是准备参加 CTF 但还没碰过 FlareOn 的选手这两个题都值得花一个下午认真做一遍。1. 题目定位与考点拆解1.1 FlareOn 关卡设计逻辑与这两道题的坐标FlareOn 是每年一度的逆向挑战赛题目从易到难排列前几题通常承担着一个共同任务让参赛者热手。它的题目有个很明显的特点不会故意堆砌超高难度的花指令或虚拟机壳而是把精力放在“模拟真实工作中会遇到的情况”上。比如一个程序不给你源码不告诉你校验逻辑是什么甚至故意把代码写得绕来绕去你要做的就是在有限的信息里找到突破口。[FlareOn2] very_success 在当年的关卡序列里属于前段题目难度定位是“会使用调试器就能做出来”。它不要求你拥有高深的密码学知识也不要求你精通脱壳核心考点就是一件事程序里有个校验逻辑你能否找到它并且通过修改内存或寄存器状态让程序进入成功分支。这道题对新手最友好的地方在于它的算法逻辑并不复杂你完全可以用静态分析还原整个校验过程也可以完全不看算法直接在动态调试里修改一个字节就过关。两条路都能走通这对培养“逆向思维”非常有帮助。[FlareOn3] Challenge1 则换了一个完全不同的方向。很多人一提到逆向就想到 PE 文件、汇编指令但现实中大量业务逻辑都跑在脚本引擎里JavaScript 混淆是 Web 前端和个人软件里最常见的保护手段之一。这道题给你的是一个网页文件里面有一段被人为打乱的 JavaScript 代码你需要从这段代码里分析出它到底在做什么然后构造出正确的输入或者说直接还原出最终答案。它考察的不是汇编阅读能力而是对脚本语言执行逻辑的理解以及在混乱代码中保持清醒的能力。两道题放在一起看恰好构成了一组完整的入门训练先学会在二进制程序里用调试器观察和干预程序行为再学会在动态语言代码里做静态分析和动态验证。很多新手一上来就扎进复杂的壳和反调试里反而把这两块基本功忽略了。1.2 两道题覆盖的能力矩阵与学习价值如果你仔细拆解这两道题会发现它们共同覆盖了四层能力。第一层是信息收集能力。拿到 very_success 的 exe 文件你要先判断它是 32 位还是 64 位有没有加壳用的什么编译特征拿到 Challenge1 的 HTML 文件你要先判断 JavaScript 代码是内联的还是外链的有没有经过打包器处理。很多新手拿到文件直接双击运行看到程序提示输入错误就一脸茫然这其实已经输在了起点上。第二层是静态分析能力。对 very_success 来说你要在 IDA 或 Ghidra 里定位到校验函数理解输入的字符串经过什么变换之后和什么数据比较对 Challenge1 来说你要在格式化的 JavaScript 里追踪变量的流向找到最终的比较函数和数据。这一层不要求你把每一条指令都读明白但要求你能把程序的大致逻辑框架勾画出来。第三层是动态调试能力。二进制题里你需要在 x64dbg 或 OllyDbg 中下断点、单步跟踪、查看寄存器与内存在合适的时机修改关键数据JavaScript 题里你需要在浏览器开发者工具中下断点观察函数调用栈和变量值甚至直接在控制台里调用内部函数来验证你的判断。动态调试是这两道题共同的核心也是很多自学逆向的人最薄弱的一环。第四层是耐心与沉淀能力。这两道题的难度都不算高但它们要求你养成一个习惯遇到看不懂的逻辑先记下来换一个角度再看而不是死磕一处。逆向工程的学习曲线很陡很多人不是被题目难倒的而是被自己的急躁情绪打败的。2. very_success动态调试内存改写的经典课2.1 文件侦察先搞清楚对象再动手拿到 very_success 这道题第一步不是急着运行而是先把它放到工具里过一遍。我当时用的流程是先用 DIE 或者 ExeinfoPE 看文件类型和编译信息确认它是一个 32 位还是 64 位的 PE 文件有没有壳有没有常见的混淆特征。然后丢进 IDA 里看导入表重点关注它调用了哪些 API。一个程序只要调用了输入输出相关的函数你就有了第一个下断点的位置。印象中这道题是个很规矩的 PE 文件没有加壳导入表里有和用户输入、控制台输出相关的 API。这就意味着你可以直接在关键 API 上下断点快速定位到程序处理用户输入的位置。如果你用的是 x64dbg可以在入口点直接运行然后通过命令行参数或者交互输入的方式触发程序逻辑再观察它调用了哪些函数。这里有一个新手容易犯的错误上来就在 IDA 里从头到尾按 F5 看伪代码试图把整个程序的逻辑完全搞清楚再动手。这个思路不能说错但对这类题目来说效率太低了。更好的做法是先在调试器里让程序跑起来观察它的行为再带着问题回到静态分析里。比如说你看到程序先接收输入然后进入一个循环做了一堆运算最后有一个跳转指令决定输出成功还是失败那你的核心目标就变得非常明确找到那个跳转指令。2.2 静态还原校验逻辑找到关键分支在 IDA 或 Ghidra 里定位到校验函数之后你会发现 very_success 的校验逻辑并不复杂。它大致上就是把输入的字符串逐字节取出来和某个或某几个固定的值做异或运算然后再和内存中另一段预先存放的数据做比较。比较的结果直接决定程序走向哪个分支。这种模式在 CTF 里太常见了几乎可以说是一道送分题。它的核心就是一个 XOR 循环把用户输入作为 key把一段密文逐字节解密然后看解密结果是否为某个预期的明文。如果你对密码学稍有了解就会知道 XOR 有一个很好的性质如果 A XOR B C那么 A XOR C B。所以当你看到这种模式时你甚至不需要去修改程序行为直接在静态分析里把参与异或的两个值提取出来自己异或一下就能把预期的输入或者最终的 flag 算出来。不过我要提醒一句静态分析能还原逻辑但还原逻辑和实际调试是两回事。考试的时候可能时间充裕你可以慢慢算但如果在实战或比赛中动态调试往往更快、更直接。这也是 very_success 这道题最有意思的地方它给了你两条路你可以选择做数学题也可以选择做手术。我个人的建议是两条路都走一遍先静态算出来再用动态调试去验证这样你对程序的理解会深得多。2.3 动态调试实操断点、单步、内存修补动态调试的核心思路很简单你不是去逆向它的算法而是直接干预它的决策。具体到 very_success 这道题操作流程一般是下面这样的。第一步在调试器里加载程序在接收输入之后的函数调用处下断点。你可以通过搜索字符串的方式快速定位比如在 x64dbg 里用字符串搜索功能找到提示输入的那段文字然后在引用它的指令处下断。这样程序一执行到这里就会停下来你就可以单步跟踪。第二步单步跟踪进入校验循环。你会看到程序逐字节处理你的输入然后在一个比较指令处停下。这个比较指令通常是一个 CMP紧跟着一个条件跳转指令比如 JNZ 或 JZ。如果你输入的字符串是错的条件跳转会走向失败分支如果你想让程序认为你输入对了最简单的办法就是在调试器里修改标志寄存器的 ZF 位或者直接把跳转指令改成 NOP甚至把跳转条件反过来。第三步修改内存。比较指令通常是比较两个内存地址里的数据其中一个是用户输入的变换结果另一个是程序内置的预期值。你可以在内存窗口里直接找到那个预期值把它改成和你输入变换结果一致的值。这样哪怕你输入的字符串完全是乱写的程序也会认为校验通过。关键的问题来了改跳转和改数据哪个更优雅这个没有标准答案但在实际调试中有一个经验法则如果能通过修改数据达到目的优先修改数据而不是修改代码流程。原因是修改数据往往能让你观察到程序原本设计好的输出行为而修改跳转虽然也能过关但可能会跳过程序里的一些后续处理导致输出结果不完整或者程序崩溃。我在做 very_success 时就遇到过类似情况只改跳转虽然能看到成功提示但程序后续还有一些收尾工作没做完直接退出反而不如老老实实把内存里的数据改掉让整个流程自然走完。2.4 这道题真正的价值点很多人做完 very_success 之后会觉得就这改一个字节就完了如果你只停留在“改字节过关”这个层面那确实浪费了这道题。这道题真正的价值在于它让你第一次体会到“程序不是黑盒子而是可以干预的”。你在调试器里修改内存的那个瞬间其实就是在和程序作者博弈他设计了一个校验但你通过观察找到了校验的位置并且在运行时改变了程序的状态。这种能力在真实的恶意代码分析、漏洞研究、软件破解里都是最基础也最核心的。另外这道题还训练了你对汇编指令的敏感度。CMP 和 JNZ 的组合你看过一次之后以后再遇到类似的模式就不再需要一条一条去读指令了扫一眼就能判断出哪个是校验点、哪个是分支点。这种“模式识别”能力是逆向工程中非常重要的加速器而它只能通过大量看题做题培养出来。3. Challenge1JavaScript 混淆的逆向破译3.1 认识题目形态一个 HTML 文件里的乾坤[FlareOn3] Challenge1 换了个赛道它直接给你一个 HTML 文件。很多做惯了 PE 逆向的人看到这个会有点懵不知道从哪下手。其实思路是相通的先看文件内容再找代码入口再追踪逻辑。打开 HTML 文件之后你会发现里面有一段比较“壮观”的 JavaScript 代码。所谓壮观不是说代码量有多大而是说它被故意写得非常难看变量名是一堆毫无意义的字母组合字符串被拆成碎片再拼接逻辑里穿插着大量看似无用的计算和判断。这些都是典型的混淆手法。第一步要做的事情不是急着分析代码内容而是先看清楚代码的整体结构。建议你把 HTML 里的 JavaScript 代码复制出来放到一个独立的 JS 文件里然后用 Prettier 之类的格式化工具把代码排版整理一下。这一操作不会改变代码逻辑但能让变量的作用域、函数调用的嵌套关系一目了然。很多新手一看到压缩成一行甚至几行的代码就头皮发麻其实格式化之后往往发现逻辑并没有想象中那么复杂。格式化之后你还需要给变量重新命名。这一步可以手动做也可以用一些辅助工具半自动做。比如看到一个函数接收两个参数作用是把两个字符串拼接起来你就可以在分析过程中把它重命名为 concat 函数看到一个变量最终和输入比较你就可以把它命名为 expected。重命名的过程其实就是你理解代码的过程等你把代码里的关键变量都改成了有意义的名字整个程序的逻辑通常也就浮出水面了。3.2 代码格式化和混淆模式识别JavaScript 混淆有很多流派有的侧重字符串加密有的侧重控制流平坦化有的侧重变量名混淆。Challenge1 这道题采用的是一些比较基础的混淆手段但组合在一起还是能劝退不少新手。你需要学会识别这些模式。最常见的模式是字符串拼接。正常代码里写的是flag{混淆后可能是一大串字符通过 charCodeAt 和 fromCharCode 动态生成。当你看到代码里频繁出现 String.fromCharCode 或者大段数字数组时基本可以确定字符串被加密了。处理方法也很简单在浏览器控制台里直接执行这段代码看看它输出什么。第二个常见模式是函数封装。正常逻辑是 a 调用 bb 调用 c混淆后可能变成 a 通过一个中间函数去调用 b而那个中间函数可能是通过数组下标动态选择的。这种模式的目的就是打断你从头到尾线性阅读代码的节奏让你没办法一眼看出函数调用关系。应对方法是找一个 JavaScript 调试器在关键函数上下断点运行时看调用栈。第三个常见模式是逻辑冗余。代码里会插入大量永远不被执行的分支或者计算出来但不影响结果的表达式。这些冗余代码的目的就是消耗你的精力让你在无关的信息里迷失方向。识别这些代码的一个技巧是如果你的静态分析进行到某一步之后发现某段代码无论怎么执行都不会影响后面的比较结果那就大胆跳过它不用纠结每一条语句的含义。3.3 关键逻辑的定位与复现在处理混淆代码时我习惯先向后看再向前看。所谓向后看就是先找到程序的最终输出点比如页面上的某个提示文字、某个显示 flag 的区域或者某个提交按钮的点击事件。从这个点往前回溯就能找到它依赖的数据是什么。Challenge1 这道题的逻辑最终会落在某个比较函数上把你输入的内容做一系列变换之后和一个预先设定的值做比较。你在格式化后的代码里搜索比较运算符或者搜索成功提示字符串就能找到这个比较函数的所在位置。找到之后不要急着去逐行理解整个变换流程。先看它调用了哪些函数每个函数做什么。把每个函数单独拎出来在控制台里用不同的输入去调用观察它的输出。这个“黑盒测试”的思路非常管用你不需要知道函数内部怎么写的只要知道输入是什么、输出是什么就能推算它的行为。当你把整个链路上每个函数的输入输出关系都摸清楚之后最后一步就是数学问题了哪个输入经过变换之后能变成那个预设值。这个过程通常叫“逆推”在 Challenge1 里可以通过手动构造输入或者写一段小脚本来完成。我当时做这道题的时候遇到一个比较典型的坑我发现代码里有一个函数对字符串做了一轮看似很复杂的变换但运行之后其实什么都没变。后来才意识到那是作者故意放进去的干扰项。这种干扰在真实场景里也很常见所以做 JavaScript 逆向时一定要保持警觉代码里出现的每一个函数不都是有用处的你要学会找到那条真正影响结果的主线。3.4 动态验证与其他思路静态分析到一定程度之后一定要回到动态验证上来。浏览器开发者工具是你最强大的盟友。你可以在比较函数上下断点然后随便输入一个字符串触发校验观察实际参与比较的值。这个时候如果静态分析得出的预期值和动态观察到的实际值不一致那说明前面的分析里有某个环节理解错了需要回头排查。一种很实用的操作是在控制台里直接修改变量的值。比如你知道比较函数比较的是两个值一个来自用户输入另一个是程序内置的预期值那你可以在断点暂停时直接修改比较结果让程序走成功分支。这个操作和 very_success 里修改内存数据本质上是一回事只是平台从汇编调试器换成了浏览器控制台。还有一种思路是直接导出函数或者解密数据。如果你发现预期值在内存中是以加密形式存放的那你可以在加密函数执行完毕之后从内存或变量里把解密后的值提取出来。这有点像二进制逆向里的 dump 内存只不过这里 dump 的是 JavaScript 运行时中的变量。你甚至不需要完全理解加密算法只需要在正确的时间点读取正确的变量即可。4. 两道题串起来的方法论与踩坑记录4.1 静态与动态的配合节奏做完这两道题我发现它们有一个共同的节奏先静态搭框架再动态填细节。如果你一上来就沉浸在汇编指令或者混淆代码的细节里很容易被绕晕反过来如果你只依赖动态调试盲目地单步跟踪也容易迷失方向。正确的方式是用静态分析找到程序的关键路径再通过动态分析确认这条路径上的每个关键点。具体来说very_success 这道题你在 IDA 里花二十分钟找到校验函数和分支位置然后在调试器里花五分钟验证并修改数据整个过程非常高效。Challenge1 这道题你在格式化代码里花半小时找到比较函数和数据然后在浏览器控制台里花几分钟验证也是同样的节奏。静态分析负责告诉你“程序大概在做什么”动态调试负责告诉你“程序实际在做什么”两者结合才能形成完整的判断。4.2 常见问题速查表我把做这两道题时最容易遇到的问题整理成了表格方便你遇到问题时快速对照排查。常见问题出现原因排查方法运行 exe 后程序直接崩溃可能对输入格式有要求或程序存在反调试先用静态分析查看输入处理的入口确认输入格式修改跳转后看得到成功提示但程序异常退出只改了分支跳过了后续必要的处理逻辑优先修改内存数据而不是修改跳转指令调试器里找不到比较指令搜索字符串时可以尝试搜索成功或失败的提示文本再通过交叉引用定位在字符串引用处下断点运行时观察寄存器JS 代码格式化后仍然看不懂变量名混淆和逻辑冗余分散了注意力给变量重命名删除不影响结果的冗余分支浏览器控制台执行报错部分变量依赖原有 HTML 环境直接在页面的开发者工具里执行或补充缺失的对象找到了比较函数但不知道预期值预期值可能是动态生成的断点暂停时查看变量或调用生成函数得到结果输入正确却仍然失败可能还存在二次校验搜索所有比较和跳转逐一排查4.3 个人实操中的几条经验第一不要迷信某一种工具。very_success 这道题我用 IDA 做静态分析用 x64dbg 做动态调试Challenge1 这道题我用 VS Code 做代码编辑用浏览器开发者工具做动态调试。每个工具都有自己的长处重要的是组合使用而不是在一个工具里死磕到底。第二调试时要大胆修改但要记录修改了什么。我曾经在 x64dbg 里同时改了好几个字节结果程序行为变得很奇怪又说不清是哪个修改导致的。后来养成习惯每次只改一处记录地址和原始值这样即使改错了也能快速还原。第三做 JavaScript 逆向时控制台是你的好朋友。很多你觉得难分析的函数直接在控制台里调用一下把返回值打印出来立刻就能明白它的作用。不要害怕运行代码在可控的环境里大胆实验效率远高于纸上谈兵。第四也是最重要的一点做完题之后要回头总结。这两道题的价值不在答案本身而在你通过做题建立起来的分析流程。做完 very_success你总结一下在调试器里定位校验分支的方法做完 Challenge1你总结一下识别混淆模式和逆推逻辑的方法。这些流程才是真正属于你的能力。最后再分享一个小技巧做这类题目时把最终的正确输入和成功输出都截图保存下来。一方面是为了写 writeup 时有材料另一方面是当你过段时间再回顾时看到这些截图能快速唤醒当时的分析思路。我自己的习惯是每做完一道题都写一个简短的记录哪怕只有几百字也远比做完就忘要有价值得多。希望这两个题目能成为你逆向路上的两块坚实垫脚石。