1.线性switch case语句逆向特征
本文通过分析一段汇编代码,详细讲解了如何识别switch-case结构而非if/else判断链的关键特征。作者指出,当看到同一变量被连续与规整常量(如1、2、3)比较,每个比较后都直接跳转,且所有分支末尾都无条件跳转到同一地址时,这明显是编译器生成的switch-case结构。特别强调了最后一个无条件跳转对应default分支的特征,并总结出五条判断口诀:连续cmp同一变量、干净常量比较、直接je
文章目录
逆向实战:我是怎么从这段汇编一眼看出它是 switch,而不是一串 if/else
有时候在逆向里,最关键的不是“会不会写 C”,而是看到一坨汇编时,你脑子里第一反应是什么结构。
这篇就直接拿你这段汇编来说:我是怎么一步步判断它是 switch-case,而不是普通 if / else if / else 的。
1. 先把源码和目标放在脑子里
源码是一个非常简单的 switch:
int var = 10;
switch (var) {
case 1: printf("1\r\n"); break;
case 2: printf("2\r\n"); break;
case 3: 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 ; default
2. 第一眼看到的异常感:比较链太“整齐”了
我在逆向里看到这段时,第一个感觉是:
这不像人手写的 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 里只有一个非常典型的来源:
case X:
...
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");
}
else if (v == 2) {
printf("2");
}
else if (v == 3) {
printf("3");
}
else {
printf("default");
}
然后再结合:
- 比较对象完全一致
- 常量是离散整数
- 所有分支都有 break 行为
👉 这不是人手写的 if/else,而是编译器生成的线性 switch。
6. 顺手对照一下完整示例代码(你给的)
#include <stdio.h>
void func()
{
int var = 10;
switch (var) {
case 1:
printf("1\r\n");
break;
case 2:
printf("2\r\n");
break;
default:
printf("default\r\n");
break;
}
}
int main() {
func();
return 0;
}
和汇编一一对应,没有任何矛盾点。
7. 我自己在逆向里总结的“线性 switch 判断口诀”
最后用人话总结一下这类 switch,我在逆向时脑子里的判断流程是这样的:
- 同一个变量被连续 cmp 多次
- cmp 的都是干净的常量(1、2、3…)
- 每个 cmp 后面都是 je,直接跳走
- 比较链最后有一个无条件 jmp → 兜底分支
- 每个分支末尾都 jmp 到同一个地址
只要这五条同时出现,我几乎不会再怀疑:
这是 switch,而且是 case 数量少、没用跳转表的那种。
如果你愿意,下一步我们可以直接接着写一篇:
- “从汇编里区分:线性 switch vs 跳转表 switch”
- 或者 “switch 没有 break(fall-through)在汇编里的真实样子”
这些在真实逆向里,比语法本身更容易踩坑。
更多推荐



所有评论(0)