IDA反汇编实战:分析一个简单的CrackMe

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

流程很清晰:

  1. 打印提示,用gets读取输入到[rbp-0x40]
  2. 调用sub_401146验证输入
  3. 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改为nop90 90),直接消除跳转,让程序无论如何都继续执行到"Correct!"分支。

8. 破解流程总结

  1. 字符串搜索:从可见的输出字符串入手,用交叉引用定位相关代码
  2. 流程分析:理清主函数的输入-验证-输出流程
  3. 深入验证函数:找到具体的比较逻辑
  4. 提取密码:从比较指令中还原出硬编码的密码
  5. (备选)Patch:修改跳转指令绕过验证

这个CrackMe非常基础——实际的软件保护会用到混淆、反调试、加密等手段。但分析思路是通用的:找字符串、跟引用、理流程、抓关键逻辑。

做更多CTF逆向题(如CrackMes.one、reversing.kr)是提升技能的最佳途径。