MoeCTF2025逆向工程第一周:从UPX脱壳到TEA解密的实战解析

张开发
2026/4/11 13:01:19 15 分钟阅读

分享文章

MoeCTF2025逆向工程第一周:从UPX脱壳到TEA解密的实战解析
1. UPX脱壳逆向工程的敲门砖逆向工程的第一步往往是处理被加壳保护的二进制文件。UPX作为最常用的压缩壳之一经常出现在CTF比赛中。我第一次遇到UPX加壳的程序时完全不知道从何下手直到发现原来用upx -d就能轻松脱壳。实际操作中我们经常会遇到这样的场景拿到一个可疑的可执行文件用file命令查看显示UPX compressed这时候就该祭出我们的脱壳三板斧upx -d target_file脱壳后的文件用IDA Pro打开代码逻辑顿时清晰可见。以MoeCTF2025的题目为例脱壳后能看到明显的加密逻辑程序将用户输入与固定数组进行异或运算后比较。这里有个小技巧如果知道flag的固定前缀比如moectf{可以大幅简化爆破过程v6 [35,43,39,54,51,60,3,72,100,11,29,118,123,16,11,58,63,101,118,41,21,55,28,10,8,33,62,60,61,22,11,36,41,36,86] known_start bmoe n len(v6) buffer [0]*n buffer[0] known_start[0] buffer[1] known_start[1] buffer[2] known_start[2] for j in range(2, n-1): buffer[j1] v6[j] ^ (buffer[j] ^ 0x21) print(bytes(buffer))这种正向爆破的方法在知道部分明文时特别有效。记得我第一次成功解出这类题目时那种豁然开朗的感觉至今难忘。不过要注意实际比赛中可能会遇到修改过的UPX壳这时候就需要手动脱壳了。2. IDA静态分析逆向工程师的显微镜IDA Pro是逆向工程中不可或缺的工具但新手常常被其复杂的界面吓到。我的建议是从基础功能开始逐步掌握。比如在分析MoeCTF的ez3题目时通过IDA的交叉引用(Xrefs)功能很快就能定位到关键加密函数。遇到算法题时我习惯先用IDA生成伪代码然后重点关注以下几个地方明显的字符串比较循环结构数学运算密集的区域外部函数调用以ez3题目为例IDA显示程序将输入传入sub_4047C5处理跟进后发现是个解方程组的问题。这时候Z3求解器就派上用场了from z3 import * dword_5D9140 [45488, 22136, 32754, 41778, 41192, 13900, 11220, 51454, 19068, 24, 11236, 16708, 15270, 48780, 36734, 13816, 25002, 11082, 26664, 45982, 46402, 13292, 51160, 17548, 37648, 34824, 44500, 15554, 1942, 51520, 20018, 20014, 37450, 23388, 4216640] flag_len 34 flag_chars [BitVec(ff{i}, 8) for i in range(flag_len)] dword_5DB600 [BitVec(fd{i}, 32) for i in range(flag_len)] mod_val 51966 xor_const 0x114514 s Solver() for i in range(flag_len): mul_val 47806 * (ZeroExt(24, flag_chars[i]) i) if i 0: s.add(dword_5DB600[i] mul_val % mod_val) else: val mul_val ^ dword_5DB600[i - 1] ^ xor_const s.add(dword_5DB600[i] val % mod_val) s.add(dword_5DB600[i] dword_5D9140[i]) for c in flag_chars: s.add(c 0x20, c 0x7e) if s.check() sat: model s.model() flag .join([chr(model[c].as_long()) for c in flag_chars]) print(Flag:, flag)这种将逆向问题转化为数学问题的方法在CTF中非常常见。记得添加字符可打印的约束条件可以显著减少求解时间。3. 反调试技巧与迷宫求解flower题目给我上了深刻的一课逆向工程不仅仅是静态分析。程序中有明显的反调试技巧直接运行会异常退出。通过字符串检索我找到了反调试的位置在IDA中下断点后发现程序在检测调试器。绕过方法很简单修改ZF标志位。这个小技巧让我意识到动态调试的重要性。在OllyDbg或x64dbg中我们可以在检测点手动修改寄存器值来绕过保护。迷宫题是CTF中的经典题型。mazegame题目给出了完整的迷宫数据解题思路很明确确定起点和终点坐标将迷宫数据转换为二维数组使用BFS或DFS算法寻找路径maze [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,...] # 完整迷宫数据 def BFS(maze, x, y): queue [(x, y, )] n 56 visited [0] * (n * n) while queue: x, y, path queue.pop(0) if 0 x n and 0 y n and not visited[x*ny] and maze[x*ny] ! 1: visited[x*ny] 1 if maze[x*ny] 2: return path queue.append((x1, y, paths)) queue.append((x, y-1, patha)) queue.append((x, y1, pathd)) queue.append((x-1, y, pathw)) return maze[15*5632] 2 # 设置终点 print(BFS(maze, 1, 1)) # 从(1,1)开始这类题目考察的是将实际问题转化为代码实现的能力。我第一次做迷宫题时花了半天时间才想到用广度优先搜索现在这已经成为我的标准解题流程了。4. TEA加密算法还原实战A cup of tea题目展示了CTF中常见的加密算法还原题型。TEA(Tiny Encryption Algorithm)因其简洁性经常出现在比赛中。识别TEA的关键特征32轮循环使用delta常量0x9E3779B9涉及位移和异或操作题目给出的加密函数是标准TEA没有魔改可以直接套用解密脚本#includestdio.h #include stdint.h #includestring.h void decrypt(uint32_t* v, uint32_t* k) { uint32_t v0 v[0], v1 v[1], i; uint32_t delta 0x114514; uint32_t sum delta * 32; for (i 0; i 32; i) { v1 - ((v0 4) k[2]) ^ (v0 sum) ^ ((v0 5) k[3]); v0 - ((v1 4) k[0]) ^ (v1 sum) ^ ((v1 5) k[1]); sum - delta; } v[0] v0; v[1] v1; } int main() { uint32_t key[4] {0x11451419,0x19810114,0x51419198,0x10114514}; uint32_t v[10] {0x78C594AB,0x22813B59,0x472A3144,0xF255108A,0x45CFB34,0x3949EA0C,0xCB760968,0x1559C979,0xDEF9929D,0x71D1AAB}; for (int i 0; i 10; i 2) { uint32_t temp[2] {v[i], v[i1]}; decrypt(temp, key); for (int j 0; j 2; j) { printf(%c%c%c%c, temp[j] 0xff, (temp[j] 8) 0xff, (temp[j] 16) 0xff, (temp[j] 24) 0xff); } } return 0; }在实际比赛中TEA可能会有各种变种比如修改delta值、轮数或者加入其他运算。这时候就需要仔细分析加密函数的每个细节。我第一次遇到修改版TEA时因为没有注意到delta值被改变卡了整整一天。这个教训让我明白逆向工程中细节决定成败。

更多文章