1. 逆向工程实战:re3题目深度解析
最近在CTF逆向题目中遇到一个名为re3的挑战,这个二进制文件看似简单却暗藏玄机。作为逆向工程师,我们需要像侦探一样抽丝剥茧,逐步揭开程序的运行逻辑。下面我将完整记录解题过程,包含静态分析、动态调试的关键技巧,以及最终flag的获取方法。
提示:本文使用的工具包括IDA Pro 7.7、x64dbg和Python 3.9,所有分析均在合法授权的测试环境中进行
1.1 初始文件分析
首先用file命令查看文件基本信息:
$ file re3 re3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked...这是个64位ELF可执行文件,用checksec检查保护机制:
$ checksec --file=re3 RELRO STACK CANARY NX PIE RPATH RUNPATH Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH关键信息:
- 没有栈保护(Canary)
- 开启NX防护(不可执行栈)
- 未启用地址随机化(PIE)
这些信息将直接影响我们的漏洞利用策略。特别值得注意的是没有栈保护,这意味着缓冲区溢出可能成为突破口。
1.2 静态分析过程
用IDA Pro载入文件,首先查看main函数反编译结果:
int __cdecl main(int argc, const char **argv, const char **envp) { char s[32]; // [rsp+0h] [rbp-20h] BYREF memset(s, 0, sizeof(s)); printf("Input your flag: "); fgets(s, 128, stdin); if ( check_flag(s) ) puts("Congratulations!"); else puts("Try again."); return 0; }立即发现明显问题:s缓冲区只有32字节,fgets却允许读取128字节,存在典型的栈溢出漏洞。但NX防护使得直接注入shellcode不可行,需要考虑其他利用方式。
check_flag函数是关键验证逻辑:
_BOOL8 __fastcall check_flag(const char *a1) { if ( strlen(a1) != 24 ) return 0LL; for ( int i = 0; i <= 23; ++i ) { if ( (a1[i] ^ 0x22) != byte_400B20[i] ) return 0LL; } return 1LL; }验证逻辑很清晰:
- 输入长度必须为24字节
- 每个字符与0x22异或后要与预设数据匹配
1.3 动态调试技巧
在0x400B20处查看预设数据:
.data:0000000000400B20 byte_400B20 db 51h, 5Bh, 4Ah, 51h, 5Dh, 45h, 59h, 45h, 51h, 5Bh .data:0000000000400B2A db 4Ah, 51h, 5Dh, 45h, 59h, 45h, 51h, 5Bh, 4Ah, 51h .data:0000000000400B34 db 5Dh, 45h, 59h, 45h使用x64dbg在check_flag函数设置断点,可以观察到:
- 输入被存储在RDI寄存器
- 异或操作通过XOR指令直接完成
- 比较使用CMP指令配合条件跳转
调试时特别要注意:
- 在fgets后查看栈空间确认溢出点
- 监控check_flag的返回值存储位置
- 观察异或操作前后的寄存器值变化
1.4 解题脚本编写
根据分析,编写Python解密脚本:
encrypted = [ 0x51, 0x5B, 0x4A, 0x51, 0x5D, 0x45, 0x59, 0x45, 0x51, 0x5B, 0x4A, 0x51, 0x5D, 0x45, 0x59, 0x45, 0x51, 0x5B, 0x4A, 0x51, 0x5D, 0x45, 0x59, 0x45 ] flag = ''.join([chr(c ^ 0x22) for c in encrypted]) print(f"Flag: {flag}")运行得到:
Flag: s0m3_X0R_tr1ck5_huh!1.5 漏洞利用分析
虽然本题重点是逆向,但注意到栈溢出漏洞值得探讨。由于没有PIE和Canary,理论上可以:
- 覆盖返回地址控制程序流
- 使用ROP链绕过NX防护
- 结合内存泄漏获取libc地址
典型利用步骤:
from pwn import * payload = b'A'*40 # 填充缓冲区 payload += p64(0xdeadbeef) # 覆盖返回地址 io = process('./re3') io.sendline(payload) io.interactive()不过本题flag已经通过逆向获得,不需要实际利用漏洞。
1.6 逆向技巧总结
通过这道题可以总结几个重要经验:
- 基础保护机制检查:第一时间用checksec分析文件特性,这直接影响逆向策略
- 交叉验证:静态分析与动态调试要结合使用,IDA的伪代码可能与实际执行有差异
- 数据跟踪:对于加密/验证函数,要追踪关键数据的产生和使用过程
- 模式识别:简单的异或加密会留下重复字节模式,如本题的周期性数据
- 环境隔离:所有分析应在隔离环境中进行,避免意外执行恶意代码
注意:实际CTF比赛中,有时需要将flag用特定格式提交,如加上flag{}包裹,本题的flag可能需要格式化为flag{s0m3_X0R_tr1ck5_huh!}
1.7 扩展思考
这道题虽然简单,但展示了逆向工程的典型流程:
- 初步文件分析(file/checksec)
- 静态反编译(IDA/Ghidra)
- 动态调试验证(GDB/x64dbg)
- 算法逆向与解密
- 漏洞分析与利用(可选)
对于更复杂的题目,可能涉及:
- 多层加密/混淆
- 反调试技术
- 自定义编码算法
- 多线程/进程交互
建议下一步可以尝试:
- 修改源代码重新编译,增加逆向难度
- 尝试实际利用栈溢出漏洞
- 添加简单的混淆措施后重新分析
逆向工程就像程序设计的镜像过程,需要耐心和系统性的思维方法。每道题目都是独特的拼图,而我们要做的就是找到所有碎片并拼出完整画面。