CrackMe是逆向工程的入门练习。本文通过IDA分析一个简单的密码验证程序,演示从字符串搜索到定位关键逻辑、最终破解的完整过程。
1. 目标程序
这是一个典型的CTF入门题:程序要求输入密码,输入正确则显示"Correct!",否则显示"Wrong!"。我们的目标是找到正确密码或绕过验证。
程序行为:
Enter password: hello
Wrong! Try again.
Enter password: ???
Correct! You got the flag!
2. IDA加载二进制
用IDA打开这个ELF可执行文件:
- File -> Open,选择文件
- IDA自动识别为ELF 64-bit,处理器选x86-64
- 选"Load as code",点OK
等待自动分析完成(左下角显示"idle")。IDA会自动识别函数、字符串引用和调用关系。
3. 字符串搜索定位关键函数
逆向的第一步通常是找字符串。按Shift+F12打开Strings窗口,搜索关键字符串:
"Enter password: "
"Correct! You got the flag!"
"Wrong! Try again."
双击"Correct!"字符串,跳转到.rodata段的字符串定义处。然后按x查看交叉引用(Cross References),找到引用这个字符串的代码位置。
交叉引用显示该字符串在sub_401196函数中被引用。双击跳转过去。
4. 反汇编代码分析
进入sub_401196(按F5切换到伪代码视图更直观)。先看整体结构:
sub_401196:
push rbp
mov rbp, rsp
sub rsp, 0x50
...
lea rdi, aEnterPassword ; "Enter password: "
call printf
...
lea rdi, [rbp-0x40]
call gets
...
lea rdi, [rbp-0x40] ; 用户输入
call sub_401146 ; 关键验证函数
test eax, eax
jnz loc_4011E2 ; 不为0则跳转到Wrong
...
lea rdi, aCorrect ; "Correct!"
call puts
jmp loc_4011F0
loc_4011E2:
lea rdi, aWrong ; "Wrong!"
call puts
流程很清晰:
- 打印提示,用gets读取输入到
[rbp-0x40] - 调用
sub_401146验证输入 test eax, eax+jnz:验证函数返回0表示正确,非0表示错误
5. 分析验证函数
双击进入sub_401146:
sub_401146:
push rbp
mov rbp, rsp
mov [rbp-0x18], rdi ; 保存输入字符串指针
; 先检查长度
mov rdi, [rbp-0x18]
call strlen
cmp rax, 8
jne .fail ; 长度不是8直接失败
; 逐字符比较
mov rax, [rbp-0x18]
movzx eax, byte ptr [rax]
cmp al, 0x52 ; 'R'
jne .fail
mov rax, [rbp-0x18]
movzx eax, byte ptr [rax+1]
cmp al, 0x33 ; '3'
jne .fail
mov rax, [rbp-0x18]
movzx eax, byte ptr [rax+2]
cmp al, 0x76 ; 'v'
jne .fail
...
看到这里已经很明了了:验证函数逐字符比较输入与硬编码的密码。通过查看每个cmp指令的立即数,我们可以还原出完整密码。
IDA的注释栏会自动将常见的ASCII值标注出来。按照顺序记录每个比较的字节值:
| 偏移 | 十六进制 | ASCII |
|---|---|---|
| [0] | 0x52 | R |
| [1] | 0x33 | 3 |
| [2] | 0x76 | v |
| [3] | 0x33 | 3 |
| [4] | 0x72 | r |
| [5] | 0x73 | s |
| [6] | 0x33 | 3 |
| [7] | 0x21 | ! |
密码是:R3v3rs3!
6. x86-64调用约定回顾
分析过程中需要了解x86-64 System V调用约定:
- 前6个整数/指针参数依次放在:RDI, RSI, RDX, RCX, R8, R9
- 返回值在RAX
- 调用者负责对齐栈到16字节
所以看到mov rdi, [rbp-0x18]后跟call sub_401146,就知道[rbp-0x18]是第一个参数(用户输入的字符串指针)。
7. Patch二进制(另一种思路)
如果不想找密码,可以直接patch程序逻辑。回到主函数中的分支点:
test eax, eax
jnz loc_4011E2 ; 75 xx
jnz(Jump if Not Zero)的opcode是75。我们可以改成jz(Jump if Zero,opcode 74),这样逻辑就反转了——输入任何错误密码都会跳到"Correct!"。
在IDA中:Edit -> Patch Program -> Change Byte,将75改为74。然后Edit -> Patch Program -> Apply Patches to Input File保存。
更激进的做法是将jnz改为nop(90 90),直接消除跳转,让程序无论如何都继续执行到"Correct!"分支。
8. 破解流程总结
- 字符串搜索:从可见的输出字符串入手,用交叉引用定位相关代码
- 流程分析:理清主函数的输入-验证-输出流程
- 深入验证函数:找到具体的比较逻辑
- 提取密码:从比较指令中还原出硬编码的密码
- (备选)Patch:修改跳转指令绕过验证
这个CrackMe非常基础——实际的软件保护会用到混淆、反调试、加密等手段。但分析思路是通用的:找字符串、跟引用、理流程、抓关键逻辑。
做更多CTF逆向题(如CrackMes.one、reversing.kr)是提升技能的最佳途径。