文章总结: 本文详细解析了一道CTF逆向工程题目EasyRE,通过静态分析识别出程序使用Base64编码和RC4加密算法,逆向推导出完整解密流程,最终获取flag{eb3564f89sf45b24oac12db8ec}。文章从文件基本信息、函数结构分析、算法识别到编写解密脚本,提供了完整的逆向工程方法论,包括x86-64汇编分析技巧、常见加密算法识别方法和Python解密脚本实现,是逆向工程初学者的实用教程。
综合评分: 92
文章分类: CTF,逆向分析,二进制安全,代码审计,WEB安全
解析2025古剑山EasyRE
原创
破镜安全
破镜安全
2025年12月5日 08:01
北京
EasyRE:从零到一的CTF逆向工程实战详解
前言
本文详细记录了一道CTF逆向工程题目(EasyRE)的完整分析过程。作为一道入门级的逆向题目,它涉及了多层加密、算法识别、反汇编分析等核心技术。通过本文,你将学习到:
- 如何系统地分析一个未知的二进制文件
- 如何通过汇编代码识别常见加密算法
- 如何从验证逻辑逆推原始数据
- 如何编写解密脚本获取flag
本文面向逆向工程初学者,会详细解释每一步的原理和方法。即使你没有深厚的汇编基础,也能跟随本文完成分析。
目标文件:easyre.exe(Windows x86-64 PE可执行文件,129KB)
分析环境:Linux + objdump + Python3
第一阶段:初步侦察
1.1 文件基本信息
首先使用file命令查看文件类型:
ounter(lineounter(line$ file easyre.exeeasyre.exe: PE32+ executable (console) x86-64, for MS Windows
关键信息:
- PE32+:Windows可执行文件(64位)
- console:控制台应用程序
- x86-64:64位x86架构
1.2 字符串提取
使用strings命令提取可读字符串,寻找线索:
ounter(lineounter(lineounter(lineounter(lineounter(line$ strings easyre.exe | grep -i -E "(flag|input|pass|key|success|wrong|error)"plz input:WrongSuccess!flag{do_H
重要发现:
plz input:– 程序需要用户输入Wrong/Success!– 有输入验证flag{do_H– 字符串片段,可能是关键信息
字符串flag{do_H很特殊,CTF的flag格式通常是flag{...},这个不完整的字符串很可能是密钥或提示。
1.3 程序行为测试
在Wine或Windows环境中运行程序:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line$ wine easyre.exeplz input:test123Wrong
$ wine easyre.exeplz input:flag{test}Wrong
观察:
- 程序等待用户输入
- 输入后立即判断对错
- 没有其他提示信息
这是典型的”输入验证”型逆向题目,我们需要找出正确的输入。
第二阶段:函数结构分析
2.1 提取函数符号
使用objdump查看符号表,找出所有函数:
ounter(lineounter(lineounter(lineounter(lineounter(line$ objdump -t easyre.exe | grep "F .text"0000000000401450 g F .text 0000000000000078 main00000000004018b3 g F .text 000000000000015c crypt0000000000401a10 g F .text 00000000000001d0 first0000000000401740 g F .text 0000000000000173 _init1
函数清单:
| 地址 | 函数名 | 大小 | 推测功能 |
| — | — | — | — |
| 0x401450 | main | 120字节 | 主函数 |
| 0x4018b3 | crypt | 348字节 | 加密函数 |
| 0x401a10 | first | 464字节 | 第一步处理 |
| 0x401740 | _init1 | 371字节 | 初始化函数 |
函数名已经给了我们很好的提示:
first– 第一步处理crypt– 加密_init1– 初始化(可能为crypt准备)
2.2 调用关系推测
ounter(lineounter(lineounter(lineounter(lineounter(linemain() ├─> first() // 第一步转换 ├─> crypt() // 加密 │ └─> _init1() // 初始化 └─> [验证逻辑] // 比对结果
第三阶段:main函数深度分析
3.1 反汇编main函数
ounter(line$ objdump -d easyre.exe | sed -n '/^0000000000401450 <main>:/,/^$/p' > main_disasm.txt
main函数很长,我们分段分析。
3.2 发现1:神秘字符串的构建
在地址0x401514到0x40153d发现一段有趣的代码:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line401514: 48 b8 66 6c 61 67 7b movabs $0x5f6f647b67616c66,%rax40151b: 64 6f 5f40151e: 48 89 45 00 mov %rax,0x0(%rbp)
401522: 48 b8 79 6f 75 5f 66 movabs $0x646e69665f756f79,%rax401529: 69 6e 6440152c: 48 89 45 08 mov %rax,0x8(%rbp)
401530: c7 45 10 5f 69 74 5f movl $0x5f74695f,0x10(%rbp)401537: 66 c7 45 14 3f 7d movw $0x7d3f,0x14(%rbp)
这段代码在构建一个字符串。让我们解码这些十六进制数:
重要概念 – 小端序(Little Endian): x86-64架构使用小端序,低位字节存储在低地址。例如:
ounter(lineounter(lineounter(line值: 0x67616c66内存布局: [66] [6c] [61] [67] (地址从低到高)实际字符: 'f' 'l' 'a' 'g'
解码过程:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line# 第一个movabs>>> bytes.fromhex('666c61677b646f5f')b'flag{do_'
# 第二个movabs>>> bytes.fromhex('796f755f66696e64')b'you_find'
# movl>>> bytes.fromhex('5f69745f')b'_it_'
# movw>>> bytes.fromhex('3f7d')b'?}'
完整字符串:flag{do_you_find_it_?}
这是一个22字节的字符串,看起来像是某种密钥!
3.3 发现2:两个重要常量
继续分析,在0x4015e2发现:
ounter(line4015e2: c7 45 34 56 49 60 90 movl $0x90604956,0x34(%rbp)
这是一个32位常量:v12 = 0x90604956
在0x4015e9到0x40162f发现一组连续的mov指令:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line4015e9: c7 45 20 0b d1 a8 fc movl $0xfca8d10b,0x20(%rbp)4015f0: c7 45 24 2c 5d 22 43 movl $0x43225d2c,0x24(%rbp)4015f7: c7 45 28 ba 00 da 3d movl $0x3dda00ba,0x28(%rbp)4015fe: c7 45 2c 02 28 8b 7e movl $0x7e8b2802,0x2c(%rbp)401605: c7 45 30 f7 89 c1 61 movl $0x61c189f7,0x30(%rbp)40160c: c7 45 34 07 af ea e4 movl $0xe4eaaf07,0x34(%rbp)401613: c7 45 38 7f e2 0f 80 movl $0x800fe27f,0x38(%rbp)40161a: c7 45 3c 4f 59 c3 d6 movl $0xd6c3594f,0x3c(%rbp)401621: c7 45 40 6a f1 53 56 movl $0x5653f16a,0x40(%rbp)401628: c7 45 44 49 3f ee 22 movl $0x22ee3f49,0x44(%rbp)40162f: c7 45 48 c5 60 37 b1 movl $0xb13760c5,0x48(%rbp)
这是一个包含11个32位整数的数组:v6数组
ounter(lineounter(lineounter(lineounter(lineounter(linev6 = [ 0xfca8d10b, 0x43225d2c, 0x3dda00ba, 0x7e8b2802, 0x61c189f7, 0xe4eaaf07, 0x800fe27f, 0xd6c3594f, 0x5653f16a, 0x22ee3f49, 0xb13760c5]
11个元素 × 4字节 = 44字节,这个长度很重要!
3.4 发现3:函数调用序列
在main函数中部,发现关键的函数调用:
ounter(lineounter(line401570: e8 9b 04 00 00 call 401a10 <first>40157f: e8 2f 03 00 00 call 4018b3 <crypt>
程序流程:
ounter(line用户输入 → first() → crypt() → 验证循环
3.5 发现4:验证逻辑剖析
验证循环从0x4016ad开始,这是整个程序的核心。让我们逐行分析:
ounter(lineounter(lineounter(lineounter(line; 循环开始4016ad: 8b 45 cc mov -0x34(%rbp),%eax4016b0: 33 45 34 xor 0x34(%rbp),%eax4016b3: 89 45 cc mov %eax,-0x34(%rbp)
第一步操作:读取4字节数据,与v12(0x90604956)进行XOR
ounter(lineounter(linev7 = data[i]v7 ^= 0x90604956
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line4016b9: 8b 45 cc mov -0x34(%rbp),%eax4016bc: 48 98 cltq4016be: 89 c2 mov %eax,%edx4016c0: 83 7d 24 00 cmpl $0x0,0x24(%rbp)4016c4: 7e 07 jle 4016cd4016c6: 81 f0 22 22 22 22 xor $0x22222222,%eax4016cc: 89 c2 mov %eax,%edx
第二步操作:条件XOR
cltq– 将eax符号扩展到rax(说明这是有符号数)cmpl $0x0,0x24(%rbp)– 与0比较jle 4016cd– 如果 ≤ 0 则跳过xor $0x22222222,%eax– 否则XOR 0x22222222
ounter(lineounter(lineounter(lineounter(line// 伪代码if ((int32_t)v7 > 0) { v7 ^= 0x22222222;}
ounter(lineounter(lineounter(lineounter(lineounter(line4016ce: 8b 45 20 mov 0x20(%rbp),%eax4016d1: 39 c2 cmp %eax,%edx4016d3: 74 1d je 4016f24016d5: 48 8d 0d 74 09 00 00 lea 0x974(%rip),%rcx # "Wrong"4016dc: e8 ff 08 00 00 call 401fe0 <puts>
第三步操作:比对
- 将处理后的
v7与v6[i]比较 - 不相等则输出”Wrong”并退出
- 相等则继续下一轮循环
完整验证逻辑:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linefor (int i = 0; i < 11; i++) { // 读取4字节(小端序) uint32_t v7 = *(uint32_t*)(encrypted_data + i * 4);
// 第一步XOR v7 ^= 0x90604956;
// 条件XOR(有符号比较) if ((int32_t)v7 > 0) { v7 ^= 0x22222222; }
// 验证 if (v7 != v6[i]) { printf("Wrong\n"); return; }}printf("Success!\n");
第四阶段:first函数算法识别
4.1 反汇编first函数
ounter(line$ objdump -d easyre.exe | sed -n '/^0000000000401a10 <first>:/,/^0000000000401be0/p' > first_disasm.txt
4.2 特征1:除以3的优化
在函数开头发现:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line401a2e: 48 ba 56 55 55 55 55 movabs $0x5555555555555556,%rdx401a35: 55 55 55401a38: 48 0f af c2 imul %rdx,%rax401a3c: 48 c1 e8 3f shr $0x3f,%rax401a40: 48 89 c1 mov %rax,%rcx401a43: 48 d1 f9 sar %rcx401a46: 48 29 c1 sub %rax,%rcx
魔数0x5555555555555556是什么?
这是编译器优化除法的常见技巧。除以3可以优化为:
ounter(linex / 3 ≈ (x * 0x55555556) >> 32
这表明函数在处理”3字节一组”的数据转换!
4.3 特征2:6位分组的位操作
继续分析,发现大量位移和掩码操作:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line401a86: c1 e8 02 shr $0x2,%eax ; 右移2位401a89: 83 e0 03 and $0x3,%eax ; 保留低2位401a8c: c1 e0 04 shl $0x4,%eax ; 左移4位
401aa5: c1 e8 04 shr $0x4,%eax ; 右移4位401aa8: 83 e0 0f and $0xf,%eax ; 保留低4位
401ac7: c1 e8 06 shr $0x6,%eax ; 右移6位401aca: 83 e0 3f and $0x3f,%eax ; 保留低6位(0-63)
分析这些位操作:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line原始字节: [AAAAAAAA] [BBBBBBBB] [CCCCCCCC] (8位) (8位) (8位)
分组为6位:第1组: AAAAAA (前6位)第2组: AABBBB (后2位+前4位)第3组: BBBBCC (后4位+前2位)第4组: CCCCCC (后6位)
每组6位 = 0-63 = 需要64个字符的编码表
这是Base64编码的典型特征!
4.4 特征3:Base64字符表
在位操作后,代码使用查找表:
ounter(line401acc: 0f b6 80 30 50 40 00 movzbl 0x405030(%rax),%eax
查看地址0x405030的数据:
ounter(lineounter(lineounter(lineounter(lineounter(line$ objdump -s -j .rdata easyre.exe | grep "405030" -A 4405030 41424344 45464748 494a4b4c 4d4e4f50 ABCDEFGHIJKLMNOP405040 51525354 55565758 595a6162 63646566 QRSTUVWXYZabcdef405050 6768696a 6b6c6d6e 6f707172 73747576 ghijklmnopqrstuv405060 7778797a 30313233 34353637 38392b2f wxyz0123456789+/
完美!这就是标准的Base64字符表:
ounter(lineABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
4.5 Base64编码原理
Base64将3字节编码为4字节:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line输入: 3 bytes = 24 bits输出: 4 groups × 6 bits = 4 chars
示例:输入: "Man" = 0x4D616E = 01001101 01100001 01101110分组: 010011 | 010110 | 000101 | 101110索引: 19 22 5 46字符: T W F u输出: "TWFu"
长度计算:
ounter(line输入32字节 → Base64 → 输出长度 = ⌈32÷3⌉×4 = 44字节
正好是v6数组的长度(11×4=44)!
结论:first函数实现Base64编码
第五阶段:crypt函数算法识别
5.1 反汇编crypt函数
ounter(line$ objdump -d easyre.exe | sed -n '/^00000000004018b3 <crypt>:/,/^0000000000401a0f/p' > crypt_disasm.txt
5.2 特征1:256字节数组初始化
函数开头调用_init1:
ounter(line4018f5: e8 46 fe ff ff call 401740 <_init1>
查看_init1函数,发现它初始化一个256字节的数组(称为S盒):
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line; _init1 函数401740: <_init1>: ; 第一阶段:S[i] = i 40174d: c6 44 05 a0 00 movb $0x0,-0x60(%rbp,%rax,1) ... ; 第二阶段:使用密钥打乱S盒 4017a1: 0f b6 44 05 a0 movzbl -0x60(%rbp,%rax,1),%eax 4017a6: 01 c2 add %eax,%edx 4017a8: 0f b6 04 11 movzbl (%rcx,%rdx,1),%eax ; 密钥 4017ac: 01 c6 add %eax,%esi ... ; 交换 S[i] 和 S[j] 4017c7: 88 54 05 a0 mov %dl,-0x60(%rbp,%rax,1)
这是RC4的KSA(Key Scheduling Algorithm)!
5.3 特征2:PRGA主循环
crypt函数的主循环:
ounter(lineounter(lineounter(lineounter(lineounter(line; i = (i + 1) % 256401927: 8b 85 bc 00 00 00 mov 0xbc(%rbp),%eax40192d: 83 c0 01 add $0x1,%eax401930: 25 ff 00 00 00 and $0xff,%eax401935: 89 85 bc 00 00 00 mov %eax,0xbc(%rbp)
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line; j = (j + S[i]) % 256401941: 0f b6 44 05 a0 movzbl -0x60(%rbp,%rax,1),%eax ; S[i]401949: 8b 85 b8 00 00 00 mov 0xb8(%rbp),%eax ; j40194f: 01 d0 add %edx,%eax ; j + S[i]401951: 25 ff 00 00 00 and $0xff,%eax ; % 256401956: 89 85 b8 00 00 00 mov %eax,0xb8(%rbp)
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line; swap(S[i], S[j])401962: 0f b6 44 05 a0 movzbl -0x60(%rbp,%rax,1),%eax401967: 88 85 af 00 00 00 mov %al,0xaf(%rbp) ; temp = S[i]40196d: 8b 85 b8 00 00 00 mov 0xb8(%rbp),%eax401973: 0f b6 54 05 a0 movzbl -0x60(%rbp,%rax,1),%edx ; S[j]401978: 8b 85 bc 00 00 00 mov 0xbc(%rbp),%eax40197e: 88 54 05 a0 mov %dl,-0x60(%rbp,%rax,1) ; S[i] = S[j]401982: 8b 85 b8 00 00 00 mov 0xb8(%rbp),%eax401988: 0f b6 95 af 00 00 00 movzbl 0xaf(%rbp),%edx40198f: 88 54 05 a0 mov %dl,-0x60(%rbp,%rax,1) ; S[j] = temp
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line; K = S[(S[i] + S[j]) % 256]401993: 8b 85 bc 00 00 00 mov 0xbc(%rbp),%eax401999: 0f b6 44 05 a0 movzbl -0x60(%rbp,%rax,1),%eax ; S[i]4019a1: 8b 85 b8 00 00 00 mov 0xb8(%rbp),%eax4019a7: 0f b6 44 05 a0 movzbl -0x60(%rbp,%rax,1),%eax ; S[j]4019af: 01 d0 add %edx,%eax ; S[i] + S[j]4019b1: 25 ff 00 00 00 and $0xff,%eax ; % 2564019b6: 89 85 b0 00 00 00 mov %eax,0xb0(%rbp) ; 保存索引4019e5: 0f b6 44 05 a0 movzbl -0x60(%rbp,%rax,1),%eax ; S[K]
ounter(lineounter(lineounter(lineounter(line; output = input ^ K4019dc: 0f b6 08 movzbl (%rax),%ecx ; input[i]4019ea: 31 c8 xor %ecx,%eax ; input[i] ^ K4019ec: 88 02 mov %al,(%rdx) ; output[i]
5.4 RC4算法原理
RC4是一种流密码,包含两个阶段:
1. KSA(Key Scheduling Algorithm)- 密钥调度:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// 初始化S盒for (i = 0; i < 256; i++) { S[i] = i;}
// 使用密钥打乱S盒j = 0;for (i = 0; i < 256; i++) { j = (j + S[i] + key[i % key_length]) % 256; swap(S[i], S[j]);}
2. PRGA(Pseudo-Random Generation Algorithm)- 伪随机生成:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linei = j = 0;for (each byte in data) { i = (i + 1) % 256; j = (j + S[i]) % 256; swap(S[i], S[j]); K = S[(S[i] + S[j]) % 256]; output = input ^ K;}
RC4的特点:
- 对称加密(加密=解密)
- 流密码(逐字节处理)
- 密钥长度可变
结论:crypt函数实现RC4流密码,密钥为flag{do_you_find_it_?}
第六阶段:完整加密流程梳理
现在我们完全理解了程序的工作流程:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line┌─────────────────────────────────────────────────────────────┐│ 用户输入 (32 bytes) ││ 例如: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 【first函数】Base64编码 ││ 3字节 → 4字节转换 ││ 使用字符表: A-Za-z0-9+/ │└─────────────────────────────────────────────────────────────┘ ↓ (44 bytes) ↓┌─────────────────────────────────────────────────────────────┐│ 【crypt函数】RC4加密 ││ 密钥: flag{do_you_find_it_?} ││ KSA初始化S盒 → PRGA生成密钥流 → XOR加密 │└─────────────────────────────────────────────────────────────┘ ↓ 加密后的数据 (44 bytes) ↓┌─────────────────────────────────────────────────────────────┐│ 【main函数】验证循环 ││ 分为11组,每组4字节: ││ for i in 0..10: ││ v7 = data[i*4] ││ v7 ^= 0x90604956 ││ if (int32_t)v7 > 0: ││ v7 ^= 0x22222222 ││ assert v7 == v6[i] │└─────────────────────────────────────────────────────────────┘ ↓ Success! / Wrong
关键数据:
- RC4密钥:
flag{do_you_find_it_?}(22字节) - XOR常量v12:
0x90604956 - 验证数组v6:11个32位整数
第七阶段:逆向解密策略
理解了加密流程后,我们需要逆向每一步。
7.1 逆向思路
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linev6数组 (已知) ↓ 逆向XOR变换加密数据 (44 bytes) ↓ RC4解密(对称)Base64数据 (44 bytes) ↓ Base64解码原始输入 (32 bytes) = FLAG
7.2 步骤1:逆向验证逻辑
验证逻辑是:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(linev7 = data[i];v7 ^= v12;if ((int32_t)v7 > 0) { v7 ^= 0x22222222;}// v7 == v6[i]
逆向过程:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linedef reverse_xor_logic(): """从v6数组反推加密数据""" result = bytearray()
for v6_val in v6: # v6是最终比较值,我们需要反推 # 转换为无符号32位 v6_unsigned = v6_val & 0xFFFFFFFF
# 尝试假设:执行了条件XOR # 如果执行了,那么执行前是 v6 ^ 0x22222222 v7_if_xored = v6_unsigned ^ 0x22222222
# 检查这个假设是否成立 # 转换为有符号整数 if v7_if_xored >= 0x80000000: v7_if_xored_signed = v7_if_xored - 0x100000000 else: v7_if_xored_signed = v7_if_xored
# 条件是 > 0(有符号),如果成立说明假设正确 if v7_if_xored_signed > 0: v7 = v7_if_xored # 假设正确,使用XOR前的值 else: v7 = v6_unsigned # 假设错误,没有执行XOR
# 逆向第一步XOR:data = v7 ^ v12 original = v7 ^ v12
# 转换为小端序字节 result.extend([ original & 0xFF, (original >> 8) & 0xFF, (original >> 16) & 0xFF, (original >> 24) & 0xFF ])
return bytes(result)
关键点:
- 有符号/无符号转换要正确
- 条件判断要准确
- 小端序字节顺序
7.3 步骤2:RC4解密
RC4是对称加密,加密和解密用同一个函数:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linedef rc4_crypt(data, key): """RC4加密/解密(对称算法)""" # KSA - Key Scheduling Algorithm S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) & 0xFF S[i], S[j] = S[j], S[i]
# PRGA - Pseudo-Random Generation Algorithm i = j = 0 output = bytearray() for byte in data: i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) & 0xFF] output.append(byte ^ K)
return bytes(output)
7.4 步骤3:Base64解码
Python标准库提供Base64解码:
ounter(lineounter(lineounter(lineounter(lineimport base64
base64_data = rc4_crypt(encrypted_data, key)flag = base64.b64decode(base64_data).decode('utf-8')
第八阶段:编写完整解密脚本
8.1 完整代码
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line#!/usr/bin/env python3"""EasyRE CTF Challenge - 完整解密脚本基于对二进制文件的完整逆向分析"""import base64
print("=" * 70)print("EasyRE CTF Challenge - 解密脚本")print("=" * 70)
# ==================== 第一部分:从反汇编提取的常量 ====================print("\n[1] 从反汇编中提取的关键常量...")
# RC4密钥(main函数 0x401514-0x40153d)rc4_key = b"flag{do_you_find_it_?}"print(f" RC4密钥: {rc4_key.decode()}")
# XOR常量v12(main函数 0x4015e2)v12 = 0x90604956print(f" v12常量: 0x{v12:08x}")
# 验证数组v6(main函数 0x4015e9-0x40162f)v6_hex = [ 0xfca8d10b, 0x43225d2c, 0x3dda00ba, 0x7e8b2802, 0x61c189f7, 0xe4eaaf07, 0x800fe27f, 0xd6c3594f, 0x5653f16a, 0x22ee3f49, 0xb13760c5]
# 转换为有符号整数(验证逻辑使用有符号比较)v6 = []for val in v6_hex: if val >= 0x80000000: v6.append(val - 0x100000000) else: v6.append(val)print(f" v6数组: {len(v6)} 个元素")
# ==================== 第二部分:理解验证逻辑 ====================print("\n[2] 验证逻辑分析...")print("验证逻辑(从 main 函数 0x4016ad-0x4016db 分析):")print(" for each 4-byte block:")print(" v7 = data[i]")print(" v7 ^= v12 # XOR with 0x90604956")print(" if (int32_t)v7 > 0: # 有符号比较")print(" v7 ^= 0x22222222 # 条件XOR")print(" assert v7 == v6[i]")
# ==================== 第三部分:逆向验证逻辑 ====================print("\n[3] 逆向验证逻辑,从v6反推加密数据...")
def reverse_xor_logic(): """逆向main函数的验证逻辑""" result = bytearray()
for v6_val in v6: # 转换为无符号32位整数 v6_unsigned = v6_val & 0xFFFFFFFF
# 尝试假设:执行了条件XOR # 即 v7 = v6 ^ 0x22222222 v7_if_xored = v6_unsigned ^ 0x22222222
# 转换为有符号数检查条件 if v7_if_xored >= 0x80000000: v7_if_xored_signed = v7_if_xored - 0x100000000 else: v7_if_xored_signed = v7_if_xored
# 如果 v7 > 0(有符号),说明确实执行了条件XOR if v7_if_xored_signed > 0: v7 = v7_if_xored # 使用XOR前的值 else: v7 = v6_unsigned # 没有执行XOR
# 逆向第一步XOR:encrypted = v7 ^ v12 encrypted = v7 ^ v12
# 转换为小端序字节 result.extend([ encrypted & 0xFF, (encrypted >> 8) & 0xFF, (encrypted >> 16) & 0xFF, (encrypted >> 24) & 0xFF ])
return bytes(result)
encrypted_data = reverse_xor_logic()print(f" 恢复的加密数据(44字节):")print(f" {encrypted_data.hex()}")
# ==================== 第四部分:RC4解密 ====================print("\n[4] RC4解密...")print("(RC4是对称加密,加密=解密)")
def rc4_crypt(data, key): """ RC4加密/解密算法(从crypt函数分析得到)
参数: data: 待加密/解密的数据 key: 密钥
返回: 加密/解密后的数据 """ # KSA (Key Scheduling Algorithm) - 密钥调度 S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) & 0xFF S[i], S[j] = S[j], S[i]
# PRGA (Pseudo-Random Generation Algorithm) - 伪随机生成 i = j = 0 output = bytearray() for byte in data: i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) & 0xFF] output.append(byte ^ K)
return bytes(output)
base64_encoded = rc4_crypt(encrypted_data, rc4_key)print(f" RC4解密结果:")print(f" {base64_encoded.decode('ascii')}")
# ==================== 第五部分:Base64解码 ====================print("\n[5] Base64解码...")print("(从first函数分析得到,使用标准Base64编码)")
flag = base64.b64decode(base64_encoded).decode('utf-8')
# ==================== 结果展示 ====================print("\n" + "=" * 70)print(f" 最终FLAG: {flag}")print("=" * 70)
# ==================== 验证流程 ====================print("\n[验证] 完整加密流程验证:")print(f"1. 原始输入: {flag} ({len(flag)} bytes)")
# 正向Base64编码flag_base64 = base64.b64encode(flag.encode()).decode()print(f"2. Base64编码: {flag_base64} ({len(flag_base64)} bytes)")
# 正向RC4加密flag_encrypted = rc4_crypt(flag_base64.encode(), rc4_key)print(f"3. RC4加密: {flag_encrypted.hex()}")
# 验证是否匹配if flag_encrypted == encrypted_data: print("4. 验证XOR变换: 完全匹配!")else: print("4. 验证XOR变换: 不匹配")
print("\n 解密成功!完全基于对二进制文件的逆向分析")print(" 分析涉及:反汇编、算法识别、逆向工程、脚本编写")
8.2 运行结果
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line$ python3 decrypt.py======================================================================EasyRE CTF Challenge - 解密脚本======================================================================
[1] 从反汇编中提取的关键常量... RC4密钥: flag{do_you_find_it_?} v12常量: 0x90604956 v6数组: 11 个元素
[2] 验证逻辑分析...验证逻辑(从 main 函数 0x4016ad-0x4016db 分析): for each 4-byte block: v7 = data[i] v7 ^= v12 # XOR with 0x90604956 if (int32_t)v7 > 0: # 有符号比较 v7 ^= 0x22222222 # 条件XOR assert v7 == v6[i]
[3] 逆向验证逻辑,从v6反推加密数据... 恢复的加密数据(44字节): 5d98c86c583660f1ce6b988f7643c9cc83e283d351e68a7429ab6f101910a3461e9a11e43d54ac9093295721
[4] RC4解密...(RC4是对称加密,加密=解密) RC4解密结果: ZmxhZ3tlYjM1NjRmODlzZjQ1YjI0b2FjMTJkYjhlY30=
[5] Base64解码...(从first函数分析得到,使用标准Base64编码)
====================================================================== 最终FLAG: flag{eb3564f89sf45b24oac12db8ec}======================================================================
[验证] 完整加密流程验证:1. 原始输入: flag{eb3564f89sf45b24oac12db8ec} (32 bytes)2. Base64编码: ZmxhZ3tlYjM1NjRmODlzZjQ1YjI0b2FjMTJkYjhlY30= (44 bytes)3. RC4加密: 5d98c86c583660f1ce6b988f7643c9cc83e283d351e68a7429ab6f101910a3461e9a11e43d54ac90932957214. 验证XOR变换: 完全匹配!
解密成功!完全基于对二进制文件的逆向分析 分析涉及:反汇编、算法识别、逆向工程、脚本编写
第九阶段:知识点总结
9.1 逆向工程方法论
1. 自顶向下的分析方法
ounter(line程序行为观察 → 函数结构 → 算法识别 → 细节分析 → 脚本验证
2. 信息收集优先级
- 字符串(strings)
- 函数符号(objdump -t)
- 主函数逻辑(main)
- 关键常量和数据结构
3. 算法识别技巧
- 通过特征代码模式识别
- 通过魔数和常量识别
- 通过数据结构识别(S盒、查找表等)
9.2 Base64编码识别要点
特征代码:
- 除以3的优化:
0x55555556魔数 - 6位分组:
and $0x3f(保留低6位) - 64字符表:
A-Za-z0-9+/ - 长度转换:
(n/3)*4
编码原理:
ounter(line3 bytes (24 bits) → 4 groups (6 bits each) → 4 Base64 chars
9.3 RC4算法识别要点
特征代码:
- 256字节数组(S盒)
- 两个索引变量i, j(0-255范围)
- 交换操作:
swap(S[i], S[j]) - 密钥流:
K = S[(S[i] + S[j]) & 0xFF] - XOR加密:
output = input ^ K
算法结构:
ounter(lineKSA(初始化) + PRGA(加密) = RC4
9.4 汇编分析技巧
1. 数据类型识别
| 汇编指令后缀 | 数据类型 | 大小 |
| — | — | — |
| movb | byte | 1字节 |
| movw | word | 2字节 |
| movl | long/dword | 4字节 |
| movq | quad | 8字节 |
2. 有符号vs无符号
| 有符号 | 无符号 | 说明 |
| — | — | — |
| jg/jl | ja/jb | 大于/小于 |
| jge/jle | jae/jbe | 大于等于/小于等于 |
| imul | mul | 乘法 |
| idiv | div | 除法 |
| cltq/cdqe | – | 符号扩展 |
3. 小端序处理
x86-64使用小端序:
ounter(lineounter(lineounter(lineounter(lineounter(line值:0x12345678内存:[78] [56] [34] [12]Python转换: bytes.fromhex('78563412')[::-1] 或 struct.unpack('<I', bytes.fromhex('78563412'))[0]
9.5 常见陷阱
陷阱1:有符号/无符号混淆
本题验证逻辑中:
ounter(lineounter(linecmpl $0x0,0x24(%rbp)jle 4016cd # 有符号比较(jump if less or equal)
错误理解:当成无符号比较 正确处理:在Python中正确转换有符号数
陷阱2:字节序错误
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line# 错误:直接使用十六进制>>> int.to_bytes(0x67616c66, 4, 'big')b'flag' # 错误顺序
# 正确:使用小端序>>> int.to_bytes(0x67616c66, 4, 'little')b'flag' # 正确
陷阱3:数据长度计算
Base64编码后长度:
ounter(lineounter(lineounter(line32 bytes → Base64 → ⌈32/3⌉ × 4 = 11 × 4 = 44 bytes ^^^ 注意向上取整(需要填充)
9.6 工具使用技巧
objdump常用选项:
ounter(lineounter(lineounter(lineounter(lineobjdump -t file # 查看符号表objdump -d file # 反汇编代码段objdump -s -j .rdata file # 查看只读数据段objdump -x file # 查看所有头信息
strings有用选项:
ounter(lineounter(lineounter(linestrings -a file # 扫描整个文件strings -t x file # 显示偏移(十六进制)strings -e l file # 小端序UTF-16
Python逆向常用库:
ounter(lineounter(lineounter(lineounter(lineimport base64 # Base64编解码import struct # 二进制数据打包import binascii # 十六进制转换from Crypto.Cipher import ARC4 # RC4(可选)
第十阶段:进阶思考
10.1 如果没有符号表怎么办?
很多实际的恶意软件或商业软件会移除符号信息(strip)。此时:
- 通过交叉引用找主函数
- 程序入口点会调用
__libc_start_main - 第一个参数通常是main函数地址
- 通过字符串引用定位
- 找到”Wrong”/”Success!”字符串
- 查看哪些函数引用了这些字符串
- 通过行为模式识别
- 调用
printf/scanf的可能是main - 有复杂循环的可能是加密函数
10.2 动态分析的价值
本题我们只用了静态分析,但动态分析(调试)也很有价值:
使用GDB/x64dbg:
- 在关键位置下断点
- 观察寄存器和内存变化
- 单步执行验证假设
使用strace/ltrace:
- 追踪系统调用
- 追踪库函数调用
- 了解程序行为
10.3 实战中的变化
实际CTF题目可能会:
- 混淆
- 加入垃圾代码
- 使用花指令
- 控制流平坦化
- 反调试
- 检测调试器
- 时间检测
- 完整性检查
- 加壳/保护
- UPX等压缩壳
- VMProtect等虚拟机保护
- 代码加密
应对策略:
- 学习脱壳技术
- 使用反混淆工具
- 结合动态分析绕过检测
10.4 深入学习路径
基础:
- x86-64汇编语言
- C/C++编程
- 数据结构与算法
进阶:
- 编译原理
- 操作系统原理
- 密码学基础
实战:
- CTF逆向题库(pwnable.kr, reversing.kr)
- 真实软件逆向
- 恶意软件分析
总结
这道EasyRE题目虽然名为”Easy”,但实际上涵盖了逆向工程的多个核心技能:
- 静态分析:反汇编、字符串提取、常量识别
- 算法识别:Base64编码、RC4流密码
- 逆向思维:从验证逻辑反推原始数据
- 编程实现:编写解密脚本
- 调试验证:端到端验证解密流程
通过这道题目,我们学习了:
- 系统的逆向分析方法
- 常见加密算法的识别技巧
- 汇编代码的阅读和理解
- 有符号/无符号、小端序等细节处理
- 从理论到实践的完整流程
逆向工程是一门实践性很强的技术,需要大量的练习和积累。希望本文能为你的逆向学习之路提供一个良好的起点!
最终FLAG:flag{eb3564f89sf45b24oac12db8ec}
解密脚本:已包含在”第八阶段”中
附录:常用资源
A. 工具下载
- Ghidra: https://ghidra-sre.org/ (免费反编译器)
- IDA Free: https://hex-rays.com/ida-free/ (免费版IDA)
- x64dbg: https://x64dbg.com/ (Windows调试器)
- Radare2: https://rada.re/ (开源逆向框架)
B. 学习资源
-
书籍:
-
《逆向工程权威指南》
-
《加密与解密(第4版)》
-
《汇编语言(第3版)》王爽
-
在线课程:
-
CTF Wiki: https://ctf-wiki.org/
-
Reverse Engineering for Beginners: https://beginners.re/
C. 练习平台
-
CTF平台:
-
pwnable.kr
-
reversing.kr
-
crackmes.one
-
靶机平台:
-
HackTheBox
-
TryHackMe
本文所有分析均基于合法的CTF竞赛题目,仅用于学习和教育目的。请勿将相关技术用于非法用途。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:破镜安全 破镜安全《解析2025古剑山EasyRE》