常見邏輯語句逆向分析

风花赏秋月發表於2024-10-18

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

相關文章