首页 / 资讯中心 / 文章详情

reverse3逆向实战:从UPX脱壳到Base64与异或解密全流程

reverse3逆向实战:从UPX脱壳到Base64与异或解密全流程 ★ FEATURED ARTICLE
拿到“BUUCTF 逆向工程(reverse)之reverse3”这个题目很多刚接触CTF的朋友第一反应可能是“这题是不是很难”但做过一遍你就会发现它其实是一道非常经典的入门级逆向题——难点不在算法而在于你有没有耐心把流程走完整识别文件、查壳、脱壳、定位关键函数、还原加密逻辑、写脚本求解。我最初刷这道题的时候正好是刚把工具装齐、还分不清“入口点”和“主函数”的阶段。做完 reverse3 之后整个逆向的“骨架”算是在脑子里立住了。这篇文章想把从拿到附件到成功拿到flag的完整过程以及过程中容易踩的坑尽量一次讲透。适合刚入门逆向、想找一道题走通全流程的朋友参考也适合那些做完了但没想明白“为什么要这么操作”的人做个复盘。1. 初识reverse3先从“看文件”这一步开始1.1 拿到附件之后先不要急着打开“反汇编器”很多新手拿到一个exe第一件事就是用IDA直接打开结果看到一堆汇编就懵了。我的建议是先花一分钟做“文件体检”搞清楚你面对的是什么东西。这一步不耽误时间却能帮你少走很多弯路。我习惯先在Linux下用file命令看一眼file reverse3.exe输出大概是下面这样reverse3.exe: PE32 executable (GUI) Intel 80386, for MS Windows, UPX compressed这里的信息量其实很大PE32说明这是32位Windows程序Intel 80386说明是x86架构最重要的是结尾那句”UPX compressed“——说明程序被UPX压缩壳处理过。如果放在Windows下也可以用ExeinfoPE或PEiD这类查壳工具它们会把壳的类型直接标出来。为什么要先关心是32位还是64位因为后面选IDA版本时64位程序要用IDA64打开32位程序用IDA32打开用错了虽然也能分析但函数识别、伪代码生成上可能出些小问题。壳信息就更重要了——你总不能在带壳的状态下直接开始看逻辑那样看到的只是壳的解压代码而不是程序真实逻辑。1.2 一套“够用”的环境不用把工具装成全家桶做这道题我实际用到的工具其实很少工具用途备注file / ExeinfoPE文件类型识别与查壳系统自带或免费小工具UPX给UPX壳脱壳官方工具命令行操作IDA Pro静态反汇编、F5伪代码用32位版本打开x64dbg / OllyDbg如有需要做动态调试新手可先跳过Python 3写解密脚本记得装好base64库如果你之前没有逆向环境我建议不要一开始就把什么工具都装上。一个常见的坑是装了一大堆工具结果每次打开都纠结“到底该用哪个”。这道题里核心工作流只需“识别工具 IDA Python”三个环节就够了动态调试只是备选项。提示做逆向题时务必把查毒软件对样本的“实时监控”关掉或者把题目文件放入白名单。有些加了壳的exe会因为特征码被杀软误报导致脱壳后程序根本没法运行你还会以为是自己的操作出了问题。2. 查壳与脱壳UPX壳为什么能这么处理2.1 壳到底是什么UPX又凭什么被人一眼认出来说穿了壳就是一个“打包程序”。UPX这类压缩壳做的事情是把程序原始的机器码压缩成一段数据然后在文件开头放一小段“解压引导代码”。程序运行时这段引导代码先执行在内存里把压缩数据还原成原始指令再跳到真正的程序入口继续执行。你可以把这个过程理解成寄快递你原本想送人一台组装好的电脑主机原始程序觉得直接搬运太占空间就把主机拆成零件、压缩打包加壳。收件人拿到手之后需要先拆包、再把零件重新组装好运行时脱壳才能正常使用。UPX最容易被认出来的原因很简单它是一个开源工具压缩后的文件里大概率会留下“UPX”相关的字符串特征。所以无论是查壳工具还是file命令都能很快把它标出来。知道了这一点脱壳也就有了明确思路——用UPX工具自己就能还原。2.2 两条脱壳路线推荐先试“一键脱壳”对于这道题最简单的方式是直接用UPX官方工具脱壳upx -d reverse3.exe参数-d就是decompress解压。正常情况下一条命令下去工具会把压缩数据还原生成一个新的可执行文件。实际操作时文件体积会明显变大这是壳被去掉的直接证据。为什么我推荐先试这个因为训练比赛中绝大多数UPX壳都是默认压缩参数没有做变形处理官方工具完全有能力还原。与其一开始就上手动脱壳的“猛药”不如先走稳妥路线把精力放到后面的算法分析上。如果upx -d执行后报错说找不到UPX标记或者文件被修改过那说明出题人可能改过壳的特征。这时就需要手动脱壳了。一个经典的思路是ESP定律用调试器x64dbg/OllyDbg加载程序停在入口处第一条指令往往是pushad。执行到pushad之后ESP寄存器的值就是程序原始状态的一个参考点。对ESP所指的地址下硬件访问断点然后让程序全速运行。当断点停下来时说明壳的解压流程已经完成离真正的OEP原始入口点不远了。单步跟踪几下找到一个jmp指令跳转到新地址那个地址通常就是OEP。手动脱壳之后还需要用工具把内存中的程序“Dump”出来必要时还要修复导入表。这一步对新手确实容易劝退但reverse3不需要走这么深。2.3 脱壳后的“验证”比你想象的重要脱壳不是把命令跑完就完事了一定要验证结果。我一般做三个检查用查壳工具再看一眼确认“UPX compressed”字样消失对比文件体积脱壳后的文件通常比加壳前大直接运行一下脱壳后的exe如果正常弹出程序界面说明壳被成功卸掉了。这里多说一句有些文件在脱壳后反而运行报错原因比较多常见的包括UPX版本不匹配、程序本身加了反调试、或者脱壳时Dump的时机太早/太晚。遇到这种情况不要恋战——如果原文件在调试器里能正常运行就可以直接在内存态下“动态脱壳”或者干脆带着壳做静态分析把壳代码跳过不看。3. 静态分析核心逻辑从入口一路追到加密算法3.1 用IDA打开脱壳后的文件先别管入口长啥样用IDA32打开脱壳后的reverse3因为程序没有符号表你看到的入口函数通常是start里面是一堆库函数初始化的调用。如果在这里一直往下看很容易迷失。我的经验是不要顺着入口硬啃而是用“字符串”这个线索反向找关键函数。在IDA里按Shift F12打开字符串窗口你能看到程序运行时用到的一批字符串。reverse3这类题目里几乎必然会出现“输入flag”的提示语或者成功/失败的提示文字。双击那个提示字符串再按交叉引用快捷键Ctrl X就能跳到引用它的函数——那个函数往往就是我们要分析的main或者核心逻辑函数。这一步其实就是逆向里最常用的定位法先用字符串找代码再用代码看逻辑。对于有交互输入输出的小程序来说这个方法几乎百试百灵。3.2 读懂main函数的伪代码它到底做了什么进入关键函数后我习惯先粗略看一眼再按F5生成伪代码。reverse3的main函数不长核心逻辑可以概括成这样一段伪代码int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // 一个局部变量 char Dest[108]; // 存储输入字符串 char Str[108]; // 存储加密后的结果 // 提示输入并接收输入到Dest // 调用一个base64编码函数处理Dest // 对编码结果的每个字符与 0x19也就是25做异或运算 // 把处理后的结果与某个内置字符串比较 // 如果相等提示正确否则提示错误 }注意F5的伪代码里变量名都是IDA自动生成的像v4、Dest、Str这些名称不能当成语义来理解真正有价值的是函数之间的调用关系以及运算符本身。看到这里你其实已经拿到了整道题的核心输入先做Base64编码再做逐字节异或最后和固定密文比较。为什么我能判断它是Base64编码一个特别明显的特征是伪代码或数据段里有一个长字符串通常长这样ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/这就是Base64的码表。只要看到这张表再配合一个“按3字节转4字节”的处理逻辑基本可以确定是Base64。异或特征则更简单一个^ 0x19的表达式或者在一个循环里对每个字节做result[i] Str[i] ^ 0x19之类的操作。3.3 加密流水线的本质Base64和异或为什么要这么设计先解释Base64。它的作用是把任意字节序列转换成只由64个可见字符组成的文本。规则是每3个字节24位分成4组每组6位对应码表中的一个字符。这在网络传输中很常见因为很多传输通道只能安全地传递ASCII字符。异或就更好理解a ^ b是二进制按位运算相同为0、不同为1。它有个很妙的性质——两次异或同一个数能回到原来的值。也就是说明文 ^ key 密文那么密文 ^ key 明文。这也是后面写逆向脚本的基础。CTF题目特别喜欢用“Base64 异或”这个组合道理很简单实现起来代码量小算法特征明显又不至于让新手完全摸不着头脑。它就像一道加了“盐”的家常菜——有基础加工有额外调味但又没搞到分子料理那种程度。还有个细节要提醒题目里那个用于比较的内置密文在IDA中显示出来可能是一串奇怪字符比如带有、_之类的非Base64字符看起来像是乱码。这很正常因为它是“Base64编码结果再异或”之后的产物早已不是标准Base64字符串。你后续写脚本时要复制的就是这串原始“乱码”而不是它看起来像什么。提示做逆向题先别急着追求“一眼看出算法”。很多情况下你需要结合字符串、函数调用、循环结构、常量等多个信息综合判断再验证自己的猜测。猜错了也不丢人改个思路继续试就是了。4. 逆向还原写脚本把flag“倒”出来4.1 解密步骤是加密步骤的逆过程加密链路是这样的输入flag - Base64编码 - 逐字节异或0x19 - 得到密文与内置字符串比较那解密就自然应该反过来从内存中取出密文 - 逐字节异或0x19恢复Base64结果 - Base64解码 - 得到原始输入flag这里最容易犯的错是“只反了异或没反Base64”或者把顺序弄反。要记住加密时先Base64后异或解密时就要先异或再Base64解码。就像穿衣服是先穿内衣再穿外套脱衣服当然要先脱外套直接反过来会卡住。在实际操作中你需要做的第一件事是在IDA中找到那个用于比较的内置字符串并把它完整复制出来。注意不要带引号、不要带IDA注释只复制原始内容最好能对着十六进制视图确认一遍避免复制时多复制了不可见字符。4.2 Python脚本一条龙解密拿到密文串之后写一个简单的Python脚本即可。以题目中的密文为例不同环境下字符串可能略有差异以你实际从IDA里复制的为准import base64 # 从IDA中复制出来的密文字符串 enc_str e3nifIH9b_CndH # 第一步逐字节异或0x19恢复Base64编码结果 xor_bytes bytes(ord(c) ^ 0x19 for c in enc_str) # 第二步Base64解码得到原始输入 flag base64.b64decode(xor_bytes) print(还原结果:, flag)运行后输出就是目标flag。这个结果通常是flag{...}格式的一串内容。这里有几个细节值得展开讲。第一enc_str里的字符要确保是ASCII字符因为异或操作是按字节处理的。第二base64.b64decode接收的是字节串所以先用bytes(...)把异或结果变成字节串再解码。第三有些版本里密文末尾可能有空字节或回车的干扰如果解码报错可以先把末尾的空白字符去掉。如果运行脚本时出现binascii.Error: Invalid base64-64别慌多半是密文复制出了偏差比如带着IDA的引号或者少复制了一个字符。回到IDA里对着数据段重新复制一次仔细对比长度即可。4.3 拿到flag后的复盘把整个链路再走一遍说实话只写出脚本拿到flag并不等于真正理解这道题。我习惯在拿下flag后再从头到尾梳理一遍阶段输入处理输出加密用户输入flagBase64编码一段Base64可见字符串加密Base64字符串逐字节异或0x19非标准字符串密文解密内置密文逐字节异或0x19恢复Base64字符串解密Base64字符串Base64解码原始flag把这张表写清楚之后你会发现自己对“加解密链条”的理解远比第一次看伪代码时扎实。如果你还想再深入一点可以在调试器里给比较函数下个断点输入一个测试字符串观察内存中两个缓冲区的内容在比较前长什么样。这一步能帮你把“静态看代码”和“动态看数据”结合起来对后续做更复杂的题很有帮助。5. 常见问题、排查技巧与新手常见误区5.1 最容易卡住人的5个坑第一IDA打开文件后看不到有效代码。多半是因为文件还带着壳或者文件已经损坏。解决办法是回到脱壳环节确认UPX是否成功去除。第二找到了main函数但看不明白伪代码。这种情况往往是没先看字符串硬从入口往下啃导致迷失。先搜“正确/错误/flag”等提示字符串通常能更快定位。第三脚本跑出来是乱码。原因一般是解密顺序写反了——比如先Base64解码再异或或者异或的对象搞错了。加密顺序是“Base64后异或”解密就必须“异或后Base64解码”顺序反了结果一定不对。第四复制密文时把不可见字符也带进来了。特别是在终端里“选中复制”字符串时你可能多复制了一个换行或符号末尾的结束符。建议用十六进制视图核对密文的字节长度再复制。第五脱壳后文件运行不了怀疑自己脱壳操作有问题。先别崩溃检查一下原文件在调试器里能否正常运行。如果原文件能跑、脱壳后跑不了可以考虑动态脱壳或在内存态下直接分析不一定要把“干净文件”弄到磁盘上。5.2 什么情况下才需要动态调试对于reverse3这类题目纯静态分析其实已经足够。但很多新手会纠结“我是不是一定要打开调试器单步跟一遍”。我的看法是动态调试不是必需但对理解程序执行流很有帮助尤其是当你静态分析遇到以下情况时——搞不清某个条件跳转会走哪个分支不确定某个函数返回后程序下一步执行哪里想确认对比的字符串在内存中的真实形态。操作上可以用调试器加载脱壳后的程序在比较函数上设置断点然后输入一串测试字符串程序运行到断点时会停在比较前一刻。此时看内存窗口你能直接看到加密后的结果这比纸上推演直观得多。建议新手至少体验一次ESP定律脱壳和一次关键函数断点调试不是为了“炫技”而是为了建立对“程序在内存中怎样运行”的整体感觉。这种感觉会在你以后做更复杂的题目时反复起作用。5.3 做完这道题之后接下来练什么reverse3的价值在于它把“文件识别、壳处理、静态分析、算法还原、脚本编写”这一整条链路串了一遍。做完之后我建议不要急着刷大量同类题而是做两件“反刍”式的事一是把这道题自己从零再写一遍。你可以在本地写一个接受字符串输入、做Base64和异或加密的小程序然后尝试从逆向视角去分析它甚至把它当成一道“自产自销”的CTF题去破解。二是尝试变体。比如把异或密钥从0x19改成0x20把Base64换成另一种编码表看看自己的解密脚本能不能快速调整。灵活应对变体比反复做同一种套路更能提升能力。最后聊一点个人体会reverse3这道题我第一次做时用了差不多一个晚上卡在脱壳后不知道该看哪里。后来用“先找字符串再找函数”的方法十分钟就定位到了主逻辑再用脚本秒出flag。回头想想最大的障碍不是技术而是“流程感”——你不知道下一步该干什么才会觉得它难。所以这篇文章与其说是在讲某一道具体的题不如说是在讲“拿到陌生二进制文件后应该按什么顺序思考”。我个人很推荐把这一套流程记录下来识别文件类型、确认壳、找关键字符串、定位主函数、还原算法、脚本求解。多练几道题之后你会发现这套流程会成为肌肉记忆看到题目自然就知道该从哪里下手。如果你刚做完这道题建议顺手把原文件和脱壳后的文件都保留下来时不时再用动态调试的方式跑一遍你会发现每次都能看出点新东西。逆向就是这样很多细节不是一眼能看全的多看几遍才有“原来如此”的瞬间。
阅读完成 · 觉得有帮助?
咨询建站