文章目录
- 逆向实战:我是怎么从这段汇编一眼看出它是 `switch`,而不是一串 `if/else`
- 1. 先把源码和目标放在脑子里
- 2. 第一眼看到的异常感:**比较链太“整齐”了**
- 2.1 同一个变量,被连续拿来和多个“干净的常量”比较
- 3. 再往下看:每个分支都“自己干完事就跳走”
- 为什么?
- 4. 最后一个确认点:那个“兜底的 jmp”
- 5. 到这里,我在脑子里已经还原出完整结构了
- 6. 顺手对照一下完整示例代码(你给的)
- 7. 我自己在逆向里总结的“线性 switch 判断口诀”
逆向实战:我是怎么从这段汇编一眼看出它是switch,而不是一串if/else
有时候在逆向里,最关键的不是“会不会写 C”,而是看到一坨汇编时,你脑子里第一反应是什么结构。
这篇就直接拿你这段汇编来说:我是怎么一步步判断它是switch-case,而不是普通if / else if / else的。
1. 先把源码和目标放在脑子里
源码是一个非常简单的 switch:
intvar=10;switch(var){case1:printf("1\r\n");break;case2:printf("2\r\n");break;case3:printf("3\r\n");break;default:printf("default\r\n");break;}逆向时我并不是一开始就假设它是 switch,而是先看汇编在“干什么”。
关键汇编如下(无关代码已省略):
; var = 10; 00C114FE mov dword ptr [ebp-4],0Ah ; switch (var) 00C11505 mov eax,dword ptr [ebp-4] 00C11508 mov dword ptr [ebp-8],eax ; 依次比较 1 / 2 / 3 00C1150B cmp dword ptr [ebp-8],1 00C1150F je 00C1151F ; case 1 00C11511 cmp dword ptr [ebp-8],2 00C11515 je 00C1152E ; case 2 00C11517 cmp dword ptr [ebp-8],3 00C1151B je 00C1153D ; case 3 00C1151D jmp 00C1154C ; default2. 第一眼看到的异常感:比较链太“整齐”了
我在逆向里看到这段时,第一个感觉是:
这不像人手写的 if/else,更像编译器生成的结构。
为什么?
2.1 同一个变量,被连续拿来和多个“干净的常量”比较
cmp [ebp-8], 1 je case_1 cmp [ebp-8], 2 je case_2 cmp [ebp-8], 3 je case_3注意几个细节:
- 比较对象完全一样:全是
[ebp-8] - 比较值非常规整:1、2、3
- 每次 cmp 后立刻 je 跳走
这在逆向里是一个非常强的信号。
如果是普通 if/else:
- 有可能是
<、>、<= - 有可能中途就
return - 有可能比较对象变来变去
但这里是一模一样的比较模板重复 N 次,只是立即数不同。
👉这非常像 switch 的 case 常量。
3. 再往下看:每个分支都“自己干完事就跳走”
我顺着je跳到每个目标地址去看:
; case 1: 00C1151F push 0C17B30h ; "1\r\n" 00C11524 call printf 00C11529 add esp,4 00C1152C jmp 00C11559 ; ← 关键 ; case 2: 00C1152E push 0C17B34h ; "2\r\n" 00C11533 call printf 00C11538 add esp,4 00C1153B jmp 00C11559 ; case 3: 00C1153D push 0C17B38h ; "3\r\n" 00C11542 call printf 00C11547 add esp,4 00C1154A jmp 00C11559这一步我基本已经 80% 确认是 switch 了。
为什么?
因为它们全部在做一件事:
执行各自的 case 代码 →无条件跳到同一个地址
这在 C 里只有一个非常典型的来源:
caseX:...break;也就是说:
jmp 00C11559≈break;00C11559≈ switch 之后的统一出口
如果是 if/else,很多时候:
- 不需要手动
jmp - 可以顺着自然流控制流往下走
但switch + break 的汇编特征就是:多个分支 → 多个 jmp → 同一出口。
4. 最后一个确认点:那个“兜底的 jmp”
回头再看比较链的结尾:
cmp [ebp-8], 3 je case_3 jmp default这条jmp default非常关键。
因为它说明一件事:
所有 case 都没命中 → 强制跳到某一个固定分支
这在 C 里,只能是:
default:而且这个default块:
00C1154C push 0C17B3Ch ; "default\r\n" 00C11551 call printf 00C11556 add esp,4- 没有被任何
je直接命中 - 只通过这一条 fallback
jmp进来
👉100% 的 default 分支形态。
5. 到这里,我在脑子里已经还原出完整结构了
在没看源码前,我在逆向里已经可以直接写出这样的伪代码:
if(v==1){printf("1");}elseif(v==2){printf("2");}elseif(v==3){printf("3");}else{printf("default");}然后再结合:
- 比较对象完全一致
- 常量是离散整数
- 所有分支都有 break 行为
👉这不是人手写的 if/else,而是编译器生成的线性 switch。
6. 顺手对照一下完整示例代码(你给的)
#include<stdio.h>voidfunc(){intvar=10;switch(var){case1:printf("1\r\n");break;case2:printf("2\r\n");break;default:printf("default\r\n");break;}}intmain(){func();return0;}和汇编一一对应,没有任何矛盾点。
7. 我自己在逆向里总结的“线性 switch 判断口诀”
最后用人话总结一下这类 switch,我在逆向时脑子里的判断流程是这样的:
- 同一个变量被连续 cmp 多次
- cmp 的都是干净的常量(1、2、3…)
- 每个 cmp 后面都是 je,直接跳走
- 比较链最后有一个无条件 jmp → 兜底分支
- 每个分支末尾都 jmp 到同一个地址
只要这五条同时出现,我几乎不会再怀疑:
这是 switch,而且是 case 数量少、没用跳转表的那种。
如果你愿意,下一步我们可以直接接着写一篇:
- “从汇编里区分:线性 switch vs 跳转表 switch”
- 或者“switch 没有 break(fall-through)在汇编里的真实样子”
这些在真实逆向里,比语法本身更容易踩坑。