1. 逆向分析入门:从SimpleRev看CTF题目设计
第一次接触BUUCTF的SimpleRev题目时,我完全被它精巧的设计吸引了。这道题表面看起来是个简单的字符处理程序,但深入分析后会发现其中蕴含着典型的CTF逆向工程考点。我们先来看看这个程序的基本行为:运行后会提示输入d/D开始或q/Q退出,选择开始后进入Decry函数,要求用户输入flag并进行验证。
逆向工程中最关键的是理解程序的数据流。在SimpleRev中,有几个关键变量需要注意:
- key1和key3是硬编码在程序中的字符串
- src和v9这两个变量需要特别注意字节序问题
- text变量由key3和v9拼接而成
- key变量由key1和src拼接而成
我在分析时犯过一个典型错误:直接看IDA反编译的伪代码,忽略了数据存储方式。src和v9在内存中是以整型形式存储的,需要右键转换为char数组才能看到实际字符串内容。更坑的是这里还涉及大小端问题,字符串需要手动反转才能得到正确值。比如src显示为'SLCDN',实际应该是'NDCLS'。
2. 深入解析字符变换算法
2.1 密钥预处理过程
程序首先会对key进行预处理,这个过程很有意思:
for ( i = 0; i < key_len; ++i ) { if ( key[key_p % key_len] > 64 && key[key_p % key_len] <= 90 ) { key[i] = key[key_p % key_len] + 32; } ++key_p; }这段代码的作用是将key中的所有大写字母转换为小写。但实现方式很特别:它使用key_p % key_len来循环遍历key,而不是直接使用循环变量i。这种写法在CTF题目中很常见,目的是增加一点分析难度。
我实际调试时发现,经过预处理后,key从"ADSFKNDCLS"变成了"adsfkndcls"。这个细节很重要,因为后续的加密算法会用到处理后的key。
2.2 核心加密算法剖析
真正的加密逻辑在用户输入处理部分:
if ( input <= 96 || input > 122 ) { if ( input > 64 && input <= 90 ) { str2[input_not_return_cnt] = (input - 39 - key[key_p % key_len] + 97) % 26 + 97; ++key_p; } } else { str2[input_not_return_cnt] = (input - 39 - key[key_p % key_len] + 97) % 26 + 97; ++key_p; }这个算法有几个特点:
- 同时处理大小写输入,但处理方式相同
- 使用key循环对输入字符进行变换
- 变换公式为:(input - 39 - key_char + 97) % 26 + 97
- 每处理一个非空格字符,key_p递增
我花了些时间推导这个公式的实际含义。经过多次测试发现,它本质上是一种基于字母表的位移加密,类似于凯撒密码但更复杂。公式中的-39和+97看似随意,实际上是为了将ASCII码调整到0-25范围内进行模运算。
3. 逆向破解实战:从静态分析到动态验证
3.1 关键数据提取
要破解这个加密,首先需要获取所有必要的数据:
- text = "killshadow"(这是目标输出)
- key = "adsfkndcls"(预处理后的密钥)
- 加密算法已知
在IDA中查找这些字符串时,要注意字符串可能被拆分成多个部分。比如key1是"ADSFK",key3是"kills",而v9是"hadow"(需要反转字节序)。
3.2 枚举破解的实现
由于加密算法是可逆的,我们可以尝试枚举所有可能的输入:
for (int i = 0; i < 10; i++) { for (int j = 0; j < 26; j++) { char put = 'A' + j; if (text[i] == (put - 39 - key[i % key_len] + 97) % 26 + 97) { cout << put; break; } } }这个枚举算法尝试每个位置的大写字母(A-Z),检查加密后是否匹配目标text中的对应字符。我最初尝试同时枚举大小写,但发现只有大写字母能得到有意义的flag。
在实际测试中,我发现当输入为"KLDQCUDFZO"时,加密结果正好是"killshadow"。这就是我们要找的flag。有趣的是,如果尝试小写字母组合,虽然也能得到一些结果,但不符合题目要求。
4. 经验总结与技巧分享
逆向工程中最耗时的往往不是算法分析,而是数据提取和预处理。在SimpleRev这道题中,我踩过几个坑:
字节序问题:src和v9的整型表示需要转换为正确的字符串形式。我一开始忽略了这点,导致后续分析完全错误。
密钥预处理:没注意到程序会修改key的内容,直接用原始key进行解密尝试,自然得不到正确结果。
枚举范围:最初同时枚举大小写,导致结果不唯一。后来发现题目预期flag是全大写的,才缩小了枚举范围。
对于类似的题目,我建议:
- 仔细检查所有数据提取是否正确
- 动态调试验证静态分析结果
- 注意题目给出的暗示(如flag格式要求)
- 从简单情况开始测试(如不加空格)
逆向工程就像解谜,需要耐心和系统性的方法。SimpleRev虽然名字简单,但包含了许多典型的逆向考点,是非常好的练习题目。