分析原因

  • 了解程序执行流程
  • 通过逆向反向提升自身编程水平
  • 专杀

动态调试

快捷键

1. 调试与控制(最核心)

  • F2:在当前光标处设置或取消断点。
  • F7:单步步入(每次执行一条指令,遇到 CALL 会进入子程序内部)。
  • F8:单步步过(每次执行一条指令,遇到 CALL 会跳过,直接执行下一条)。
  • F4:执行到光标所在行(程序运行到你光标选中的那一行代码时暂停)。
  • F9:直接运行程序,直到遇到断点或程序结束。
  • F12:暂停正在运行的被调试程序。
  • Ctrl + F2:重新启动调试程序(清除当前状态重新载入)。
  • Alt + F2:关闭当前被调试的程序。
  • Ctrl + F9:执行到函数返回(在进入某个 CALL 后,快速执行到 RETN 处,用于跳出函数)。
  • Alt + F9:执行到用户代码(当程序停在系统领空/系统 DLL 时,按此键可快速返回用户自己编写的代码区)。

2. 窗口切换(Alt 系列)

  • Alt + C:打开/切换到 CPU 窗口(主主视图)。
  • Alt + M:打开/切换到 内存映射窗口 (Memory Map)。
  • Alt + E:打开/切换到 模块窗口 (Executable Modules)。
  • Alt + L:打开/切换到 日志窗口 (Log Window)。
  • Alt + B:打开/切换到 断点窗口 (Breakpoints)。
  • Alt + W:打开/切换到 窗口列表 (Windows)。

3. 数据搜索与定位(Ctrl 系列)

  • Ctrl + G:输入表达式/跳转到指定地址(快速定位到某个内存地址或 API 函数)。
  • Ctrl + N:打开应用程序输入表(显示当前模块调用的所有 API 函数列表)。
  • Ctrl + F:在当前窗口中查找命令(汇编指令)。
  • Ctrl + B:在当前窗口中查找二进制文本(Hex 字节或字符串)。
  • Ctrl + R:查找对当前所选地址的所有参考/引用。

4. 编辑与条件断点

  • 空格键 (Space):修改汇编指令(在当前行直接输入新的汇编代码进行反汇编微调)。
  • Shift + F2:设置条件断点(只有满足特定寄存器或内存条件时才会中断)。
  • Shift + F4:设置条件记录断点(每次经过该行时记录特定日志,不一定会中断程序)。
  • ;(分号):给当前行代码添加注释。
  • :(冒号):给当前行代码添加标签。

5. 其它全局通用键

  • F3:弹出打开文件对话框,用于加载一个新的 32 位可执行程序。
  • Alt + F5:让 OllyDbg 窗口总在最前面。

常见语句调试

逻辑分析

选择结构

if语句

#include <stdio.h>
#include <stdlib.h>

int main() {
    int nFlag = 0;
    scanf("%d", &nFlag);
    if (nFlag == 10) {
        printf("Flag=%d", nFlag);
    }
    system("pause");
    return 0;
}

翻译成汇编

.text:00411940 ; =============== S U B R O U T I N E =======================================
.text:00411940
.text:00411940 ; Attributes: bp-based frame
.text:00411940
.text:00411940 ; int __cdecl main()
.text:00411940 _main           proc near               ; CODE XREF: j__main↑j
.text:00411940
.text:00411940 var_10          = byte ptr -10h
.text:00411940 nFlag           = dword ptr -0Ch
.text:00411940 var_4           = dword ptr -4
.text:00411940
.text:00411940                 push    ebp
.text:00411941                 mov     ebp, esp
.text:00411943                 sub     esp, 0D0h
.text:00411949                 push    ebx
.text:0041194A                 push    esi
.text:0041194B                 push    edi
.text:0041194C
.text:0041194C __$EncStackInitStart_3:
.text:0041194C                 lea     edi, [ebp+var_10]
.text:0041194F                 mov     ecx, 4
.text:00411954                 mov     eax, 0CCCCCCCCh
.text:00411959                 rep stosd
.text:0041195B
.text:0041195B __$EncStackInitEnd_3:
.text:0041195B                 mov     eax, ___security_cookie
.text:00411960                 xor     eax, ebp
.text:00411962                 mov     [ebp+var_4], eax
.text:00411965                 mov     ecx, offset _16073121_main@cpp ; JMC_flag
.text:0041196A                 call    j_@__CheckForDebuggerJustMyCode@4 ; __CheckForDebuggerJustMyCode(x)
.text:0041196F                 nop
.text:00411970                 mov     [ebp+nFlag], 0
.text:00411977                 lea     eax, [ebp+nFlag]
.text:0041197A                 push    eax
.text:0041197B                 push    offset _Format  ; "%d"
.text:00411980                 call    j__scanf
.text:00411985                 add     esp, 8
.text:00411988                 cmp     [ebp+nFlag], 0Ah
.text:0041198C                 jnz     short loc_41199F
.text:0041198E                 mov     eax, [ebp+nFlag]
.text:00411991                 push    eax
.text:00411992                 push    offset aFlagD   ; "Flag=%d"
.text:00411997                 call    j__printf
.text:0041199C                 add     esp, 8
.text:0041199F
.text:0041199F loc_41199F:                             ; CODE XREF: _main+4C↑j
.text:0041199F                 mov     esi, esp
.text:004119A1                 push    offset Command  ; "pause"
.text:004119A6                 call    ds:__imp__system
.text:004119AC                 add     esp, 4
.text:004119AF                 cmp     esi, esp
.text:004119B1                 call    j___RTC_CheckEsp
.text:004119B6                 nop
.text:004119B7                 xor     eax, eax
.text:004119B9                 push    edx
.text:004119BA                 mov     ecx, ebp        ; frame
.text:004119BC                 push    eax
.text:004119BD                 lea     edx, v          ; v
.text:004119C3                 call    j_@_RTC_CheckStackVars@8 ; _RTC_CheckStackVars(x,x)
.text:004119C8                 pop     eax
.text:004119C9                 pop     edx
.text:004119CA                 pop     edi
.text:004119CB                 pop     esi
.text:004119CC                 pop     ebx
.text:004119CD                 mov     ecx, [ebp+var_4]
.text:004119D0                 xor     ecx, ebp        ; cookie
.text:004119D2                 call    j_@__security_check_cookie@4 ; __security_check_cookie(x)
.text:004119D7                 add     esp, 0D0h
.text:004119DD                 cmp     ebp, esp
.text:004119DF                 call    j___RTC_CheckEsp
.text:004119E4                 mov     esp, ebp
.text:004119E6                 pop     ebp
.text:004119E7                 retn
.text:004119E7 ; ---------------------------------------------------------------------------

其中

.text:00411988                 cmp     [ebp+nFlag], 0Ah
.text:0041198C                 jnz     short loc_41199F

是逻辑的关键,一旦相等,两数相减为0,ZF为1,不进行跳转;

switch语句

switch-3case

逆向原理:当 case 数量较少(通常 且跨度较大,或数量极少时),编译器往往不会生成跳转表,而是直接将其优化为类似 if-else 的线性比较结构

#include <stdio.h>

void test_switch_3case(int x) {
    switch (x) {
        case 10:
            printf("Case 10\n");
            break;
        case 20:
            printf("Case 20\n");
            break;
        case 30:
            printf("Case 30\n");
            break;
        default:
            printf("Default Case\n");
            break;
    }
}

逆向成汇编

.text:00401080
.text:00401080 ; Attributes: bp-based frame
.text:00401080
.text:00401080 ; int __cdecl main()
.text:00401080 _main           proc near               ; CODE XREF: __scrt_common_main_seh+13C↓p
.text:00401080
.text:00401080 x               = dword ptr -8
.text:00401080 var_4           = dword ptr -4
.text:00401080
.text:00401080                 push    ebp
.text:00401081                 mov     ebp, esp
.text:00401083                 sub     esp, 8
.text:00401086                 mov     eax, ___security_cookie
.text:0040108B                 xor     eax, ebp
.text:0040108D                 mov     [ebp+var_4], eax
.text:00401090                 lea     eax, [ebp+x]
.text:00401093                 push    eax
.text:00401094                 push    offset _Format  ; "%d"
.text:00401099                 call    _scanf
.text:0040109E                 mov     ecx, [ebp+x]
.text:004010A1                 add     esp, 8
.text:004010A4                 cmp     ecx, 10
.text:004010A7                 jz      short loc_4010C7
.text:004010A9                 cmp     ecx, 20
.text:004010AC                 jz      short loc_4010C0
.text:004010AE                 cmp     ecx, 30
.text:004010B1                 mov     eax, offset aDefaultCase ; "Default Case\n"
.text:004010B6                 mov     edx, offset aCase30 ; "Case 30\n"
.text:004010BB                 cmovz   eax, edx
.text:004010BE                 jmp     short loc_4010CC

这里很类似于if语句,一旦ecx=10,相减等于0,ZF标志位位,jz跳转至loc_4010C7 然后看看等于30的情况:

.text:004010AE                 cmp     ecx, 30
.text:004010B1                 mov     eax, offset aDefaultCase ; "Default Case\n"
.text:004010B6                 mov     edx, offset aCase30 ; "Case 30\n"
.text:004010BB                 cmovz   eax, edx

先将ecx与30相减,一旦等于0,ZF为1,触发cmovz,将eax的值修改为edx的值

switch-6case

逆向原理:当分支数 或 且 case 值的差值极小(连续)时,编译器会生成一个单级直接跳转表(Jump Table)。汇编中通常表现为:先进行边界检查,随后利用 jmp ds:[Base + reg * 4](32位)或 jmp qword ptr [Base + reg * 8](64位)实现 复杂度的直接跳转。

#include <stdio.h>

void test_switch_6case_continuous(int x) {
    switch (x) {
        case 1:
            printf("Case 1\n");
            break;
        case 2:
            printf("Case 2\n");
            break;
        case 3:
            printf("Case 3\n");
            break;
        case 4:
            printf("Case 4\n");
            break;
        case 5:
            printf("Case 5\n");
            break;
        case 6:
            printf("Case 6\n");
            break;
        default:
            printf("Default Case\n");
            break;
    }
}
switch-6case缺项

逆向原理:case 的值在大体上是连续的,但中间缺失了某些项。编译器依然会生成直接跳转表,但缺失项在跳转表中的地址会直接指向 default 分支的代码块。

#include <stdio.h>

void test_switch_6case_missing(int x) {
    switch (x) {
        case 1:
            printf("Case 1\n");
            break;
        case 2:
            printf("Case 2\n");
            break;
        // 缺项 3
        case 4:
            printf("Case 4\n");
            break;
        // 缺项 5
        case 6:
            printf("Case 6\n");
            break;
        case 7:
            printf("Case 7\n");
            break;
        case 8:
            printf("Case 8\n");
            break;
        default:
            printf("Default Case\n");
            break;
    }
}
switch-6case无线表无break

逆向原理:当没有 break 语句时,会发生 Fall-through(直通/落入) 现象。编译器在生成跳转表时依旧会正常生成,但各个 case

#include <stdio.h>

void test_switch_6case_noline_nobreak(int x) {
    switch (x) {
        case 100:
            printf("Case 100\n");
            // 无 break
        case 200:
            printf("Case 200\n");
            // 无 break
        case 300:
            printf("Case 300\n");
            // 无 break
        case 400:
            printf("Case 400\n");
            // 无 break
        case 500:
            printf("Case 500\n");
            // 无 break
        case 600:
            printf("Case 600\n");
            break; // 最后一个可以有,也可以没有
        default:
            printf("Default Case\n");
            break;
    }
}
switch-6case单项不连续
#include <stdio.h>

void test_switch_6case_discontinuous(int x) {
    switch (x) {
        case 10:
            printf("Case 10\n");
            break;
        case 150:
            printf("Case 150\n");
            break;
        case 500:
            printf("Case 500\n");
            break;
        case 2000:
            printf("Case 2000\n");
            break;
        case 9999:
            printf("Case 9999\n");
            break;
        case 50000:
            printf("Case 50000\n");
            break;
        default:
            printf("Default Case\n");
            break;
    }
}
switch-6case多线表

逆向原理:多线表(也称双重跳转表或间接跳转表)通常用于分支值有跨度,但跨度在合理范围内,且部分分支的值重复映射到相同的代码块,或者需要极致压缩跳转表空间的情况。

#include <stdio.h>

void test_switch_6case_multitable(int x) {
    switch (x) {
        case 10:
            printf("Case A\n");
            break;
        case 11:
            printf("Case A\n"); // 共享相同逻辑
            break;
        case 12:
            printf("Case B\n");
            break;
        case 15:
            printf("Case C\n");
            break;
        case 16:
            printf("Case D\n");
            break;
        case 20:
            printf("Case E\n");
            break;
        default:
            printf("Default Case\n");
            break;
    }
}

关键点

程序用来判断注册码是否正确的位置

注册软件流程

  • 点击注册按钮
  • 读取注册码
  • 判断注册码是否合法
  • 提示用户是否注册成功

爆破

通过对程序流程的修改,让程序判断失误