文章目录
- 一、先把原始代码和汇编摆在桌面上
- 原始 C 代码(不改)
- 对应的关键汇编(不改,仅截取核心逻辑)
- 二、第一眼扫过去:这不是 switch,是一条 if-else 链
- 三、从第一个 if 开始,对着汇编“反着读逻辑”
- 四、else if 的本质:上一个条件失败后,立刻 cmp 下一个阈值
- 五、看到补码就要警觉:开始进入负数区间判断
- 六、没有 cmp 的那一段:这就是 else
- 七、把整个 if-else 链“反推”成一张逻辑表
- 八、一句话总结这类结构的“逆向直觉”
一、先把原始代码和汇编摆在桌面上
原始 C 代码(不改)
intcheckNumber(intnum){intresult;if(num>1000)result=10000;elseif(num>500)result=5000;elseif(num>100)result=1000;elseif(num>50)result=500;elseif(num>0)result=100;elseif(num==0)result=0;elseif(num>-50)result=-100;elseif(num>-100)result=-500;elseif(num>-500)result=-1000;elseresult=-5000;returnresult;}对应的关键汇编(不改,仅截取核心逻辑)
002F1576 cmp dword ptr [ebp+8],3E8h 002F157D jle 002F158B 002F157F mov dword ptr [ebp-4],2710h 002F1586 jmp 002F1610 002F158B cmp dword ptr [ebp+8],1F4h 002F1592 jle 002F159D 002F1594 mov dword ptr [ebp-4],1388h 002F159B jmp 002F1610 002F159D cmp dword ptr [ebp+8],64h 002F15A4 jle 002F15AF 002F15A6 mov dword ptr [ebp-4],3E8h 002F15AD jmp 002F1610 002F15AF cmp dword ptr [ebp+8],32h 002F15B6 jle 002F15C1 002F15B8 mov dword ptr [ebp-4],1F4h 002F15BF jmp 002F1610 002F15C1 cmp dword ptr [ebp+8],0 002F15C5 jle 002F15CF 002F15C7 mov dword ptr [ebp-4],64h 002F15CE jmp 002F1610 002F15CF cmp dword ptr [ebp+8],0 002F15D3 jne 002F15D9 002F15D5 mov dword ptr [ebp-4],0 002F15DC jmp 002F1610 002F15D9 cmp dword ptr [ebp+8],0FFFFFFCEh 002F15DD jle 002F15E8 002F15DF mov dword ptr [ebp-4],0FFFFFF9Ch 002F15E6 jmp 002F1610 002F15E8 cmp dword ptr [ebp+8],0FFFFFF9Ch 002F15EC jle 002F15F7 002F15EE mov dword ptr [ebp-4],0FFFFFE0Ch 002F15F5 jmp 002F1610 002F15F7 cmp dword ptr [ebp+8],0FFFFFE0Ch 002F15FB jle 002F1606 002F15FD mov dword ptr [ebp-4],0FFFFFC18h 002F1604 jmp 002F1610 002F1606 mov dword ptr [ebp-4],0FFFFEC78h 002F1610 mov eax,dword ptr [ebp-4]二、第一眼扫过去:这不是 switch,是一条 if-else 链
我在 IDA 里第一眼看到这段的时候,并没有逐条翻译,而是先看结构:
[ebp+8]被反复cmp每次
cmp后面紧跟条件跳转每个分支一旦命中,都会:
mov [ebp-4], 常量jmp 002F1610
002F1610是所有路径都会汇合的统一出口
这在逆向里基本是一个信号:
这是“线性 if → else if → else”的经典编译形态。
三、从第一个 if 开始,对着汇编“反着读逻辑”
cmp dword ptr [ebp+8],3E8h jle 002F158B这里非常关键的一点是:
C 里写的是
if (num > 1000)汇编里判断的是“不满足就跳走”
也就是说:
jle跳走 → 条件失败 → 去试下一个 else if不跳 → 条件成立 → 执行赋值
mov dword ptr [ebp-4],2710h ; result = 10000 jmp 002F1610到这里,第一个 if 分支已经完全闭合。
四、else if 的本质:上一个条件失败后,立刻 cmp 下一个阈值
002F158B cmp dword ptr [ebp+8],1F4h 002F1592 jle 002F159D我在这里通常会做一件事:
看上一个
jle的目标地址,是不是正好跳到这里。
一对上,结构就彻底清楚了:
失败 → 跳到下一个 cmp
成功 → 赋值 → 跳统一出口
mov dword ptr [ebp-4],1388h ; result = 5000 jmp 002F1610这就是:
elseif(num>500)后面的num > 100 / 50 / 0分支,全都是同一个套路,只是阈值和结果不同。
五、看到补码就要警觉:开始进入负数区间判断
cmp dword ptr [ebp+8],0FFFFFFCEh jle 002F15E80xFFFFFFCE是什么?
补码
转成有符号:-50
再看赋值:
mov dword ptr [ebp-4],0FFFFFF9Ch ; -100这一步在逆向时非常“顺”:
结构没变,只是常量换成了负数。
说明 C 里的逻辑大概是:
elseif(num>-50)result=-100;后面的-100 / -500分支,完全同理。
六、没有 cmp 的那一段:这就是 else
002F1606 mov dword ptr [ebp-4],0FFFFEC78h这里有两个非常明显的信号:
前面没有任何比较
执行完就自然落到返回点
这说明:
这是所有条件都失败之后的兜底分支
也就是:
elseresult=-5000;七、把整个 if-else 链“反推”成一张逻辑表
这是我在逆向里经常做的最后一步,用来确认自己没有看漏条件:
| 条件 | result |
|---|---|
| num > 1000 | 10000 |
| num > 500 | 5000 |
| num > 100 | 1000 |
| num > 50 | 500 |
| num > 0 | 100 |
| num == 0 | 0 |
| num > -50 | -100 |
| num > -100 | -500 |
| num > -500 | -1000 |
| else | -5000 |
如果这张表能完整整理出来,说明这个函数已经被你完全逆向成功。
八、一句话总结这类结构的“逆向直觉”
只要在反汇编里看到:同一个变量被一串
cmp连续比较,每个分支内部“赋值 + jmp 同一个出口”,最后还有一个自然落地的分支,那它几乎一定是一个多条件 if-else if-else。
#include<iostream>intcheckNumber(intnum){intresult;// 多条件 if-else if-else 语句if(num>1000){result=10000;}elseif(num>500){result=5000;}elseif(num>100){result=1000;}elseif(num>50){result=500;}elseif(num>0){result=100;}elseif(num==0){result=0;}elseif(num>-50){// 负数的判断,从靠近0的开始result=-100;}elseif(num>-100){result=-500;}elseif(num>-500){result=-1000;}else{result=-5000;}returnresult;}intmain(){// 测试不同的数字inttestNumbers[]={1500,750,200,75,25,0,-25,-75,-150,-300,-1000};intsize=sizeof(testNumbers)/sizeof(testNumbers[0]);std::cout<<"多条件判断测试结果:"<<std::endl;std::cout<<"===================="<<std::endl;for(inti=0;i<size;i++){intresult=checkNumber(testNumbers[i]);std::cout<<"checkNumber("<<testNumbers[i]<<") = "<<result<<std::endl;}return0;}