文章目录
-
- [先看原始 C 代码(心里有个目标)](#先看原始 C 代码(心里有个目标))
- 反汇编关键代码(这是我们真正要读的)
- 一、我第一眼在反汇编里盯的是哪?
-
- [👉 永远先盯:`cmp / test + jXX`](#👉 永远先盯:
cmp / test + jXX)
- [👉 永远先盯:`cmp / test + jXX`](#👉 永远先盯:
- [二、条件不跳时:顺序执行的那一段,就是 then 分支](#二、条件不跳时:顺序执行的那一段,就是 then 分支)
-
- [1️⃣ `result = 100`](#1️⃣
result = 100) - [2️⃣ 一个**很关键的无条件跳转**](#2️⃣ 一个很关键的无条件跳转)
- [1️⃣ `result = 100`](#1️⃣
- [三、被条件跳转"跳到"的地方,通常就是 else 分支](#三、被条件跳转“跳到”的地方,通常就是 else 分支)
- [四、then / else 最终都会到的地方:汇合点](#四、then / else 最终都会到的地方:汇合点)
- [五、站在反汇编视角,总结 if / else 的"长相"](#五、站在反汇编视角,总结 if / else 的“长相”)
-
- [✅ 1. `cmp / test` + `jXX`](#✅ 1.
cmp / test+jXX) - [✅ 2. 条件不跳时,顺序执行的代码块](#✅ 2. 条件不跳时,顺序执行的代码块)
- [✅ 3. 条件跳转的目标地址](#✅ 3. 条件跳转的目标地址)
- [✅ 4. then / else 之后的公共位置](#✅ 4. then / else 之后的公共位置)
- [✅ 1. `cmp / test` + `jXX`](#✅ 1.
- 六、把你这段代码套进去,一切都严丝合缝
- 最后一句"逆向经验总结"
下面这段,是我在反汇编里遇到的一段非常"教科书级"的 if / else,正好可以拿来讲怎么从汇编反推 C 代码的控制结构。
先看原始 C 代码(心里有个目标)
先给自己一个"答案模板",这样看汇编时心里有方向:
c
int checkNumber(int num) {
int result;
if (num > 0) {
result = 100;
}
else {
result = -100;
}
return result;
}
逻辑非常简单:
-
判断
num > 0 -
真:
result = 100 -
假:
result = -100 -
最后返回
result
接下来关键是:这套逻辑在反汇编里是怎么"长出来"的?
反汇编关键代码(这是我们真正要读的)
我在 IDA / x64dbg 里看到的核心部分大概是这样(前后栈帧代码我先不关心):
asm
00CD1506 cmp dword ptr [ebp+8],0 ; 比较 num 和 0
00CD150A jle 00CD1515 ; 若 num <= 0 跳到 else 分支
00CD150C mov dword ptr [ebp-4],64h ; then 分支:result = 100
00CD1513 jmp 00CD151C ; 跳过 else,直接到汇合点
00CD1515 mov dword ptr [ebp-4],0FFFFFF9Ch ; else 分支:result = -100
00CD151C mov eax,dword ptr [ebp-4] ; 汇合点:return result
这段代码非常典型 ,基本把 if / else 的形态全都暴露出来了。
一、我第一眼在反汇编里盯的是哪?
👉 永远先盯:cmp / test + jXX
asm
cmp dword ptr [ebp+8],0
jle 00CD1515
这是我在反汇编里最敏感的一种组合。
-
[ebp+8]一看就知道是函数参数(32 位下第一个参数几乎都是这位置)。
-
cmp [ebp+8], 0在干嘛?很直白:拿参数跟 0 比。
-
紧跟一个
jle
条件跳转出现了 ,说明:👉 这里一定有 if / while / for / 三目运算 之类的控制结构。
这一步我不会急着想 C 代码,而是先翻译成"人话":
如果
[ebp+8] <= 0,就跳到00CD1515
然后再反推一句:
那不跳的情况就是
num > 0
这时候,脑子里基本已经浮现出:
c
if (num > 0) {
...
}
else {
...
}
注意一个很重要的经验点👇
汇编里的条件跳转,往往是 C 里条件的"反向写法"
C 写的是 if (num > 0)
汇编写的是 jle else
这是完全正常的,不是异常。
二、条件不跳时:顺序执行的那一段,就是 then 分支
继续往下看:
asm
00CD150C mov dword ptr [ebp-4],64h
00CD1513 jmp 00CD151C
这里发生了两件非常"C 语言味"的事情。
1️⃣ result = 100
asm
mov dword ptr [ebp-4],64h
-
[ebp-4]:栈上的一个局部变量 -
64h:十六进制 0x64,也就是十进制 100
这行基本可以直接翻译成:
c
result = 100;
2️⃣ 一个很关键的无条件跳转
asm
jmp 00CD151C
这条 jmp 非常重要,我在看反汇编时一看到它,立刻就会想到 if / else。
为什么?
因为它在做一件事:
then 分支执行完了,必须跳过 else,直接去公共结尾
如果没有这个 jmp,执行流就会"顺着掉进 else",那就不是 if/else 了。
三、被条件跳转"跳到"的地方,通常就是 else 分支
现在回头看最开始那条跳转:
asm
jle 00CD1515
跳到哪?👇
asm
00CD1515 mov dword ptr [ebp-4],0FFFFFF9Ch
这里逻辑就非常清楚了:
-
当
num <= 0 -
CPU 直接跳到
00CD1515 -
执行:
asm
mov [ebp-4], 0FFFFFF9Ch
0xFFFFFF9C 是什么?
-
32 位补码
-
十进制就是
-100
所以这一整行,等价于:
c
result = -100;
而且你会发现一个细节:
👉 else 分支这里没有再来一个 jmp
这是因为:
-
else 执行完
-
顺序执行
-
自然就会"掉到"后面的公共代码
四、then / else 最终都会到的地方:汇合点
再往下:
asm
00CD151C mov eax,dword ptr [ebp-4]
这一行我一看就知道是:
c
return result;
为什么?
-
eax:32 位下的返回值寄存器 -
[ebp-4]:刚才两个分支都在写的那个局部变量
不管你是从 then 跳过来的,还是从 else 顺序走过来的,最终都会走到这儿。
这就是 if / else 在控制流上的"汇合点"。
五、站在反汇编视角,总结 if / else 的"长相"
当我在逆向里判断一段代码是不是 if / else,基本就看下面这套组合拳:
✅ 1. cmp / test + jXX
-
一定是某种条件判断
-
jXX的目标地址,往往是 else 的入口
✅ 2. 条件不跳时,顺序执行的代码块
-
这段代码就是 then 分支
-
末尾通常会看到一个
jmp,用来跳过 else
✅ 3. 条件跳转的目标地址
-
这块就是 else 分支
-
执行完后通常不再跳,顺序流入公共代码
✅ 4. then / else 之后的公共位置
-
所有路径都会汇聚到这里
-
常见内容:
return、后续逻辑、函数收尾
六、把你这段代码套进去,一切都严丝合缝
-
cmp + jle👉 判断
num > 0的反向条件 -
不跳 →
result = 100→jmp跳到结尾 -
跳 →
result = -100 -
最终在
00CD151C汇合,返回结果
这是一个非常标准、非常干净 的 if / else 汇编形态。
最后一句"逆向经验总结"
在反汇编里,只要你看到:
cmp/test + 条件跳转,
紧跟一段代码 + 一个jmp,
条件跳转目标处是另一段代码,
最后两边汇合到同一个地址------
那基本可以 99% 确认:这是一个 if / else。
这不是"猜",而是控制流结构在汇编层面不得不长成的样子。
如果你愿意,下一步我可以直接拿这段代码,画一张控制流图(CFG),你会发现它和 C 代码的 if / else 结构一模一样。