文章目录
- 1. 先把与 for 循环直接相关的指令抽出来
- 2. 这一类 for 循环在逆向中的典型控制流特征
- 2.1 前测试循环(pre-test loop)的模式
- 3. 循环变量的存储特征(Debug 版)
- 4. 与编译器 / 调试模式相关的「环境特征」
- 5. 结合本例,总结 for 循环的逆向特征
- 6. 如果用一句话概括本例 for 循环的逆向特征
voidfunc(){for(intindex=0;index<10;index++){printf("%d",index);}return;}1. 先把与 for 循环直接相关的指令抽出来
和for (int index = 0; index < 10; index++)对应的关键汇编是:
; 初始化部分:index = 0 00F51B86 mov dword ptr [ebp-8],0 ; 跳到“条件判断”位置 00F51B8D jmp 00F51B98h ; 递增部分:index++ 00F51B8F mov eax,dword ptr [ebp-8] 00F51B92 add eax,1 00F51B95 mov dword ptr [ebp-8],eax ; 条件判断:index < 10 ? 00F51B98 cmp dword ptr [ebp-8],0Ah 00F51B9C jge 00F51BB1h ; index >= 10 则跳出循环 ; 循环体:printf("%d", index); 00F51B9E mov eax,dword ptr [ebp-8] 00F51BA1 push eax 00F51BA2 push offset string "%d" 00F51BA7 call _printf 00F51BAC add esp,8 ; 回到“递增”位置 00F51BAF jmp 00F51B8Fh其对应的 C 代码就是:
for(intindex=0;index<10;index++){printf("%d",index);}2. 这一类 for 循环在逆向中的典型控制流特征
2.1 前测试循环(pre-test loop)的模式
调试版 MSVC 生成的这种 for 循环,控制流大致是:
初始化:把循环变量写入栈上局部变量位置
mov [ebp-8], 0无条件跳到“判断”位置:
jmp 条件判断标签循环体之后是“递增”代码块:
index++在体之后单独一块:mov eax, [ebp-8] add eax, 1 mov [ebp-8], eax条件判断块:
cmp [ebp-8], 0Ah jge 退出标签 ; index >= 10 则退出如果条件不满足退出,则顺序往下执行循环体,体结尾再
jmp回递增块。
用伪控制流写出来就是:
init: index = 0 jmp cond ; 先不执行递增,直接去做第一次条件判断 inc: index = index + 1 cond: if (index >= 10) goto end body: printf("%d", index); goto inc end: ...这个结构是“前测试循环”:每次执行 body 前都先走条件判断(第一次迭代也通过那条jmp跳到判断位置)。
逆向特征总结:
- 有一个init 区块:给某个
[ebp-偏移]赋初值。 - init 后面立即是一个无条件 jmp,跳到后面某处。
- 在 jmp 跳到的位置之前,有一段代码(inc),负责对同一个
[ebp-偏移]做自增/自减。 - 在 jmp 的目标位置,有:
cmp [ebp-偏移], 常数- 接着一个条件跳转(如 jge、jl、jle、jg、jb、ja 等)跳到循环外。
- 条件跳转“失败”(即不跳出)时,顺序执行到循环体,循环体尾部有一个
jmp回到递增位置。
只要你在逆向时看到:
mov [ebp-x], 初值 jmp cond inc: ... 修改 [ebp-x] ... cond: cmp [ebp-x], 常量 jcc out ; 某种条件跳出 body: ... jmp inc就可以高度怀疑这是一个for或while循环,其中:
- init + inc + cond 更像
for - init + cond + body + inc 典型
for展开结构
3. 循环变量的存储特征(Debug 版)
在这段汇编中,循环变量index存在这里:
[ebp-8] ; 一个 4 字节的局部变量典型特征:
- 使用 MSVC Debug 编译时,局部变量几乎都放在栈上,通过
[ebp-偏移]访问,而不是一直放在寄存器中。 - 对同一个
[ebp-8]:- 先有
mov [ebp-8], 0—— 初始化 - 后面反复有读改写:
mov eax,[ebp-8]/add eax,1/mov [ebp-8],eax - 在比较和函数参数中也一直从
[ebp-8]取值。
- 先有
所以在逆向时,可以通过:
- 查找某个固定的栈偏移
[ebp-8]被重复使用:- 初始化一次
- 比较一次(或多次)
- 递增/递减一次
- 多次作为函数参数或计算参与者
来锁定“这是一个循环变量”。
4. 与编译器 / 调试模式相关的「环境特征」
你贴出的完整函数还有一些明显的MSVC Debug 模式印记,这些在逆向时常见,用来判断编译设置,而不是循环本身,但对理解整体有帮助:
函数头部:
push ebp mov ebp, esp sub esp, 0CCh push ebx push esi push edi lea edi, [ebp-0Ch] mov ecx, 3 mov eax, 0CCCCCCCCh rep stos dword ptr es:[edi] call @__CheckForDebuggerJustMyCode@4特征:
sub esp, 0CCh:申请一大块栈空间,常见于 Debug。mov eax,0CCCCCCCCh+rep stos dword ptr:用 0xCC 填充局部变量区域,这就是 VS 的“调试毒值”,帮助发现未初始化使用。@__CheckForDebuggerJustMyCode@4:VS 调试器用的检查函数。
函数结尾:
add esp,0CCh cmp ebp,esp call __RTC_CheckEsp mov esp,ebp pop ebp ret特征:
__RTC_CheckEsp:运行时检查(Runtime Check),验证栈平衡。
这些说明:这是MSVC + Debug 配置 + 开启运行时检查下生成的未优化代码,因此:
- 循环不会被优化成更紧凑的寄存器型实现;
- 控制流非常“直白”:看起来比 Release 版本“啰嗦”,反而更有利于教学和肉眼逆向。
5. 结合本例,总结 for 循环的逆向特征
针对你这段代码中的for (int index = 0; index < 10; index++),可以归纳出以下逆向特征:
控制流形态(前测试 for 循环)
- 初始化块(对某个局部变量赋初值)
- 紧跟一个无条件
jmp跳到条件判断处 - 在条件处:
cmp [局部变量], 上界常量- 满足某条件(大于等于、等于等)则
jcc跳出到循环外
- 若未跳出,则顺序执行循环体
- 循环体末尾
jmp回到递增块 - 递增块对同一个局部变量进行自增/自减,然后
jmp或落回条件判断处
变量使用模式
- 一个固定的
[ebp-偏移]:- 初始化一次;
- 在比较指令里和常量比较;
- 通过 add/sub/dec/inc 做线性更新;
- 在循环体中被多次读取并参与计算或当作参数。
- 一个固定的
条件比较与跳转
- 常见比较形式:
cmp [ebp-8], 0Ah - 常见跳转指令:
jl/jle/jg/jge/jb/ja等,用来控制“继续/退出”。 - 判断逻辑可译回:
while (index < 10)/for (index=0; index<10; index++)等。
- 常见比较形式:
在本例中的具体映射
mov [ebp-8], 0→int index = 0;cmp [ebp-8], 0Ah+jge→index < 10mov/add/mov(对[ebp-8])→index++- 中间的
_printf调用部分就是循环体。
6. 如果用一句话概括本例 for 循环的逆向特征
在 MSVC Debug 版中,这个for (int index = 0; index < 10; index++)的逆向特征是:
以栈上局部变量
[ebp-8]为循环计数器,通过「mov [ebp-8],0初始化 →jmp到条件块 → 在条件块cmp [ebp-8], 0Ah / jge控制退出 → 循环体后jmp回到一个对[ebp-8]做+1的递增块」这样一个init → jmp → cond(cmp+jcc) → body → jmp(inc)的典型前测试 for 循环控制流结构,配合 Debug 模式的栈上局部变量和 0xCC 填充环境,很容易从汇编恢复出原始的 for 循环逻辑。