BUUCTF crackMe 逆向实战:从反调试绕过到密码算法还原 1. 初识crackMe题目第一次打开这个crackMe程序时界面显示Come one! Crack Me~~~要求输入6-16位的用户名和密码。题目背景是一个黑客软件注册场景已知用户名为welcomebeijing但密码需要通过逆向分析得出。这类题目在CTF中很常见主要考察逆向工程能力。我习惯先用PEiD查壳发现是32位无壳程序直接用IDA Pro打开分析。主函数逻辑清晰先检查输入合法性然后调用关键验证函数sub_401830。这里有个细节程序用了两个do-while循环处理输入配合fflush清空缓冲区这种结构在CTF题中很常见。2. 反调试机制剖析2.1 PEB检测技术这个程序的反调试手段相当经典。在sub_401830函数中我发现了三处关键检测if ( *(_DWORD *)(__readfsdword(0x30u) 2) 0xFF ) // 检测BeingDebugged标志 if ( *(_DWORD *)(__readfsdword(0x30u) 104) 0x70 ) // 检测NtGlobalFlag if ( *(_DWORD *)(*(_DWORD *)(__readfsdword(0x30u) 24) 12) ! 2 ) // 检测HeapFlags这些检测都通过PEB结构实现。0x30是FS寄存器指向TEB的偏移2处是BeingDebugged标志位。当程序被调试时这些值会被系统自动设置。我常用的绕过方法是在调试器中手动修改这些内存值或者直接nop掉检测指令。2.2 时间戳干扰代码中还出现了__rdtsc()指令这是另一种反调试手段。它会读取CPU时间戳计数器通常用来检测单步调试。遇到这种情况我会在调试器中设置时钟忽略选项或者直接跳过相关指令。3. 关键算法逆向3.1 密码预处理流程sub_401830函数首先对输入的密码进行预处理while ( v6 strlen(a2) ) { if ( isdigit(a2[v6]) ) { v8 a2[v6] - 48; } else if ( isxdigit(a2[v6]) ) { v8 (a2[v6] | 0x20) - 87; } else { v8 ((a2[v6] | 0x20) - 97) % 6 10; } v9 v8 16 * v9; if ( !((v6 1) % 2) ) { v15[v3] v9; v9 0; } v6; }这段代码将每两个字符组合成一个16进制数。例如输入39d0会被转换为[0x39, 0xd0]。这种处理方式在加密算法中很常见需要特别注意大小写转换和模运算的处理。3.2 核心验证逻辑接下来是关键的异或运算部分while ( v5 8 ) { v10 byte_416050[v11]; v12 byte_416050[v11]; v7 byte_416050[v10]; byte_416050[v10] v12; byte_416050[v11] v7; v16[v5] byte_416050[(v7 v12)] ^ v15[v4]; sub_401710(v16, a1, v5); v4 v5; }这里用到了byte_416050数组通过动态调试可以获取其值为[0x2a, 0xd7, 0x92, 0xe9, 0x53, 0xe2, 0xc4, 0xcd]。算法类似RC4包含数组元素交换和异或操作。3.3 最终校验条件验证的最后阶段调用sub_401470函数sub_401470(v16, v13); return v13 0xAB94; // 43924这个函数对v16进行复杂位运算要求结果必须等于0xAB94。通过逆向分析可以确定v16必须是dbappsec的ASCII码序列。4. 动态调试技巧4.1 获取关键数据使用x32dbg调试时我在异或操作处下断点观察ECX寄存器的值逐步记录byte_416050数组的8个字节0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD4.2 反调试绕过实战遇到反调试时我采用以下步骤在检测指令处下断点修改标志位寄存器值或者直接nop掉整个检测块对于时间戳检测设置调试器忽略异常5. 密码算法还原综合所有分析密码生成逻辑如下用户名welcomebeijing与固定字符串dbappsec逐字节异或结果再与byte_416050数组异或最后组合成16进制字符串用Python实现算法username welcomebeijing key_str dbappsec byte_array [0x2a, 0xd7, 0x92, 0xe9, 0x53, 0xe2, 0xc4, 0xcd] # 第一步用户名与key_str异或 v16 [ord(username[i]) ^ ord(key_str[i]) for i in range(8)] # 第二步结果与byte_array异或 password .join([hex(v16[i] ^ byte_array[i])[2:] for i in range(8)]) print(password) # 输出39d09ffa4cfcc4cc6. 验证与提交将生成的密码39d09ffa4cfcc4cc输入程序验证成功通过检查。但需要注意最终的flag要求对这个密码取MD5哈希import hashlib hashlib.md5(b39d09ffa4cfcc4cc).hexdigest()得到flag格式为flag{md5_hash}