常见逻辑语句逆向分析

If else 语句逆向分析

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int main(int argc, char* argv[]) {

int sum = 0;

for (int i = 0; i <= argc; i++) {

sum += i;

}

return sum;

汇编代码

00401006 mov dword ptr [ebp-8], 0 ;sum=0

{

0040100D mov dword ptr [ebp-4], 0 ;赋初值语句代码块,i=0

}

00401014 jmp short loc_40101F ;跳转到for语句代码块

{

00401016 mov eax, [ebp-4] ;步长语句代码块

00401019 add eax, 1

0040101C mov [ebp-4], eax ;i+=1

}

{

0040101F mov ecx, [ebp-4] ;for语句代码块

00401022 cmp ecx, [ebp+8]

00401025 jg short loc_401032 ;如果i>argc,则跳转到for结束语句块

00401027 mov edx, [ebp-8]

0040102A add edx, [ebp-4]

0040102D mov [ebp-8], edx ;sum+=i}

00401030 jmp short loc_401016 ;跳转到步长语句代码块

00401032 mov eax, [ebp-8] ;for结束语句块

逆向框架总结

赋初值语句代码块

JMP跳转到for语句代码块

{

步长语句代码块

...

}

{

for语句代码块

执行影响标志位的指令

JXX跳转到for结束语句块

...

}

JMP跳转到步长语句代码块

for结束语句块

Switch 语句逆向分析

非线性switch结构(索引表 最大case值与最小case值差值<256)

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int main(int argc, char* argv[]) {

int n = 0;

scanf("%d", &n);

switch (n) {

case 1:

printf("n == 1");

break;

case 2:

printf("n == 2");

break;

case 255:

printf("n == 255");

break;

}

return 0;

}

汇编代码:

00401006 mov dword ptr [ebp-8], 0

0040100D lea eax, [ebp-8]

00401010 push eax

00401011 push offset unk_417160

00401016 call sub_401290

0040101B add esp, 8

0040101E mov ecx, [ebp-8]

00401021 mov [ebp-4], ecx

00401024 mov edx, [ebp-4]

00401027 sub edx, 1

0040102A mov [ebp-4], edx

0040102D cmp dword ptr [ebp-4], 0FEh ; switch 255 cases

00401034 ja short loc_40109F ; jumptable 00401040 default case

00401036 mov eax, [ebp-4]

00401039 movzx ecx, ds:byte_4010C4[eax]

00401040 jmp ds:off_4010A8[ecx*4] ; switch jump

00401047 push offset aN1 ; jumptable 00401040 case 0

0040104C call sub_401250

00401051 add esp, 4

00401054 jmp short loc_40109F ; jumptable 00401040 default case

00401056 push offset aN2 ; jumptable 00401040 case 1

0040105B call sub_401250

00401060 add esp, 4

00401063 jmp short loc_40109F ; jumptable 00401040 default case

00401065 push offset aN3 ; jumptable 00401040 case 2

0040106A call sub_401250

0040106F add esp, 4

00401090 jmp short loc_40109F ; jumptable 00401040 default case

00401092 push offset aN255 ; jumptable 00401040 case 254

00401097 call sub_401250

0040109C add esp, 4

0040109F xor eax, eax ; jumptable 00401040 default case

非线性索引优化: 如果两个case值间隔较大,仍然使用switch的结尾地址或default地址代替地址表中缺少的case地址,这样则会造成极大的空间浪费。非线性的switch结构,可采用制作索引表的方式进行优化。

两张表:case语句块地址表和case语句块索引表。地址表中的每一项保存一个case语句块的首地址,有几个case语句块就有几项。default语句块也在其中,如果没有则保存一个switch结束地址。这个结束地址在地址表中只会保存一份。索引表中会保存地址表的编号,索引表的大小等于最大case值和最小case值的差。

总内存大小:

(MAX-MIN)* 1字节 = 索引表大小

SUM * 4字节 = 地址表大小

占用总字节数 =((MAX-MIN)* 1字节)+(SUM * 4字节)

逆向总结:

mov reg, mem ; 取出switch变量

sub reg,1 ; 调整对齐到索引表的下标0

mov mem, reg

; 影响标记位的指令

jxx xxxx ; 超出范围跳转到switch结尾或 default

mov reg, [mem] ; 取出switch变量

; eax不是必须使用的,但之后的数组查询用到的寄存器一定是此处使用到的寄存器

xor eax,eax

mov al,byte ptr (xxxx)[reg] ; 查询索引表,得到地址表的下标

jmp dword ptr [eax*4+xxxx] ; 查询地址表,得到对应的case块的首地址

有两次查找地址表的过程,先分析第一次查表代码,byte ptr指明了表中的元素类型为byte。然后分析是否使用在第一次查表中获取的单字节数据作为下标,从而决定是否使用相对比例因子的寻址方式进行第二次查表。最后检查基址是否指向了地址表

非线性switch结构(判定树 最大case值与最小case值差值>255)

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int main(int argc, char* argv[]) {

int n = 0;

scanf("%d", &n);

switch (n) {

case 2:

printf("n == 2\n");

break;

case 10:

printf("n == 10\n");

break; case 35:

printf("n == 35\n");

break;

case 666:

printf("n == 666\n");

break;

default:

printf("default\n");

break;

}

return 0;

}

汇编代码:

00401006 mov dword ptr [ebp-8], 0

0040100D lea eax, [ebp-8]

00401010 push eax

00401011 push offset unk_417160

00401016 call sub_4011A0

0040101B add esp, 8

0040101E mov ecx, [ebp-8]

00401021 mov [ebp-4], ecx

00401024 cmp dword ptr [ebp-4], 0Ah

00401028 jg short loc_401047 ;如果n>10,则跳转到判断n>10代码块

0040102A cmp dword ptr [ebp-4], 0Ah

0040102E jz short loc_40108B ;如果n==10,则跳转case10语句代码块

00401030 cmp dword ptr [ebp-4], 2

00401034 jz short loc_40105E ;如果n==2,则跳转case2语句代码块

00401042 jmp loc_4010C7 ;跳转default代码块

00401047 cmp dword ptr [ebp-4], 23h

0040104B jz short loc_40109A ;如果n==35,则跳转case35语句代码块

00401053 cmp dword ptr [ebp-4], 29Ah

0040105A jz short loc_4010B8 ;如果n==666,则跳转case666语句代码块

0040105C jmp short loc_4010C7 ;跳转default代码块

总结:

采用多个比较和jcc跳转指令

采用case地址表,直接通过该表首地址+偏移跳转到对应的地址

采用case地址表的同时,也使用偏移表,两表共同作用来找到地址

当switch语句中的case数量≤3或case中的最大数值和最小数值相差≥6时,两种语句的效率几乎相同

其它情况下一般为switch语句的效率更高

当switch语句有序且连续且case数量>3时,其运行的效率最高,也解释了开发过程中为什么要使用连续的case

posted @   风花赏秋月  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示