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