分析原因
- 了解程序执行流程
- 通过逆向反向提升自身编程水平
- 专杀
动态调试
快捷键
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;
}
}
关键点
程序用来判断注册码是否正确的位置
注册软件流程
- 点击注册按钮
- 读取注册码
- 判断注册码是否合法
- 提示用户是否注册成功
爆破
通过对程序流程的修改,让程序判断失误