逆向知識第九講,switch case語句在匯編中表達的方式


一丶Switch Case語句在匯編中的第一種表達方式 (引導性跳轉表)

第一種表達方式生成條件:

case 個數偏少,那么匯編中將會生成引導性的跳轉表,會做出 if else的情況(類似,但還是能分辨出來的)

1.高級代碼:

復制代碼
// MyCode.cpp : Defines the entry point for the console application. //  #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 1: printf("case 1\n"); break; case 2: printf("case 2\n"); break; } printf("HelloWorld\n"); return 0; }
復制代碼

2.匯編代碼在Debug版本下:

可以看出,生成的跳轉表

比較和跳轉在一起,而且跳轉的時候是一個跳轉表. (注意,這里可能不是比較,只要影響標志位即可,也可能是 Dec inc add ....等等但是沒有實質性的代碼)

注意最后一個跳轉 JMP,當JMP的位置,或者代碼中JMP的位置(代碼中也就是 跳轉地址過去后的地方)都是同一個地址,那么則是跳轉到SWITCH_END(也就是switch case 語句塊結束)

注意這里是代碼邏輯分開的.

先判斷邏輯,然后進行跳轉執行.而在跳轉過后的里面判斷是否有JMP,有JMP的話,則代表是break

如果內部沒有JMP則代表沒有Break,那么語法是支持這樣寫法的.

3.匯編中Release版本

Release版本其實是一樣的.只不過需要注意的是,它有代碼外提的情況下.

因為我們每一個case里面都有打印輸出函數,而且其參數都是一樣的,所以跳轉過來之后,里面只需要PUSH即可.Switch case完畢之后則會調用printf打印.

4.擴展知識(Default在case上面,或者中間)

4.1直接偽代碼:

  case 0 ... break;

       default ....  break

       case 2 ....break

4.2匯編代碼 Debug

debug版本不用說,直接JMP位置到default位置.

4.3.Release版本下.

 

對於Release版本,有的時候可能直接變為Default語句

有的時候會變成JNZ執行.這個時候JNZ下面要還原成原來的CASE語句

 

第一變化的還原手法

第一變化還原挺簡單的

1.遇到cmp比較的, 和誰比較,那么跳轉的地址變為對應的Case即可.注意,你點擊地址過去之后則是Case語句的代碼,但是要注意是否代碼里面有JMP(沒有則沒有break)

2.有的時候不是CMP, 有的時候是 dec Reg32,這個時候就要算一下的,比如  dec Reg32結果是多少,則對應還原成多少,比如dec Reg32結果是0,那么其還原成Case0,但是還原第二個的時候要注意

第一個已經Dec了一下,第二個又還原的Dec 所以要和第一個相關聯才可以.

二丶Switch Case語句在匯編中的第二種表達方式(簡介尋址表)

case 語句塊有多個,且其中間隔不算多, 間隔的地方填寫Default

2.1高級代碼:

復制代碼
// MyCode.cpp : Defines the entry point for the console application. //  #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 1: printf("case 1\n"); break; case 2: printf("case 2\n"); break; case 3: printf("case 3\n"); case 5: printf("case 5\n"); break; case 6: printf("case 6\n"); break; case 7: printf("case 7\n"); break; default: printf("default \n"); break; } printf("HelloWorld\n"); return 0; }
復制代碼

2.2 Debug版本下的匯編代碼表現形式

首先這里有兩個問題

1. 為什么是JA  (A是無符號的高於比較)

2. 為什么出現了數組尋址公式.

解答第一個問題:

  是這樣的,第二種優化方式它會生成跳轉表,而跳轉表就是在第二種里面出現的尋址.而做表的前提下就是要保證Case語句是排序好的.

所以在這里 case語句首先排序好,而后坐標平移到0位置

什么是坐標平移到0位置?

比如我們有代碼 

case -1

case 2

case 3

那么此時坐表的條件就是

case -1 變為case 0 

case 2  變為 2

這樣坐表之后則是從0開始,而里面保存的則是case的地址.

這個時候只會和case最大值作比較.

我們看下動態調試中是什么表現形式把.

坐標平移之后,則可以建立一個表格,用的時候查表即可.所以我們查表的時候可以看到很多case語句的地址

從0開始. 沒有的則填寫Default即可. 一般JA后面的地址就是Default或者Switch End地址,從表中我們也看出來了.第五項填寫的是Switch End的地址.

正好我們的代碼中缺少的第五項沒有

Debug還原手法:

Debug還原很簡單,首先是比較case最大值,(看下是否調整了,也就是坐標平移了,如果平移了,比如以前有個-1,而case 最大值是3,那么會比較 4,因為-1 要+1其余各個的case語句也要加1)

4.3Release版本下的匯編代碼

Release版本下也是一樣,此時它也是會用 case值和最大值比較,然后去數組中尋址查找表去跳轉

注意: 這里數組尋址*4的意思是,地址是4個字節對其

比如我們的表格中存放的是case地址

00401000     case0 Address

00401004     case1 Address

00401008    case 2 Address

首先會和最大值比較,沒有超過,那么直接用case的值 *4去表中找地址即可.

比如我們的case值是2, 比較是否大於7,

然后 jA指令判斷是否高於7,高於7跳轉到 Default或者Switch End,不高於,則 根據case值去表中查找跳轉

比如現在是2,那么 2* 4 = 8 + 數組首地址(00401000) = 401008 然后取出內容跳轉,此時內容也是case2的地址.

 

擴展思路:

上面講的是Switch原型知識,我們要學會擴展.比如如果我case中有個-1怎么辦

那么此時-1 +1 坐標平移到0的位置,然后其余各個的case  +1

那么比較的時候和case最大的值相比,現在最大的值也+1了. 所以還原的時候,最大值要-1,其余各個case值都要-1則可以.

代碼還原方式.

第二種代碼還原方式

1.判斷是否調整,調整了可以得出最小值,比如 add eax,2 那么得出最小值是-2,因為坐表要從0開始,這里調整則得出-2了,

2.得出case最大值比較,這里也要看是否調整了,沒有調整那么最大值是多少就是多少,調整的那么case最大值則是 當前值 - 調整的值

3.得出最小值和最大值之后,去地址表中尋址即可.因為是排好序的,如果沒有調整,那么就是從0開始,還原的時候就是case0

調整過后,那么減去調整的值即可,比如調整了2,那么第0項則是 0 - 2 則是case - 2 比較.

 

 

 

三丶Switch Case語句在匯編中的第三種表達方式(多表聯合查詢)

這種表達式相比前兩種有點難理解,但是我們說過,編譯器做的越多,那么我們還原的越快.

第三種表達式形成的條件:

當最大case值 - 最小case值的值在255直接,則使用第三種優化方式.

3.1高級代碼

復制代碼
// MyCode.cpp : Defines the entry point for the console application. //  #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 100: printf("case 100\n"); break; case 200: printf("case 200\n"); break; case 35: printf("case 35\n"); case 45: printf("case 45\n"); break; case 60: printf("case 60\n"); break; case 70: printf("case 70\n"); break; default: printf("default \n"); break; } printf("HelloWorld\n"); return 0; }
復制代碼

3.2 Debug版本下的匯編代碼

此時我們可以看出有兩次查表的動作

下標表:

這里我們說下第一個表,第一個表我們成為下標表

下標表,里面存放的是Case排好序的值的位置,比如我們的02 在下表中找一下,則是對應45的位置.  那么此時從里面取出來做第二個case,去地址表中尋到case 45 的地址.

下標表的作用,這里的下標表主要是一種優化,一種空間換時間的做法,這里的優化主要是,比如我們屏幕的顏色,白色很多,那么我就優化為,當有白色的時候,去下標表中尋找白色

就和我們的下標表中的07一樣,只要知道這個,那么都是default

地址表:

地址表則和第二種優化一樣,存放的case的真實地址.

 

debug還原手法:

1.首先判斷是否調整,調整了,可以得出最小case的值, (0 - 調整的值)  沒調整則默認從0開始

2.比較的時候會和最大case值比較(沒有調整則最大case值就是本身)調整了(最大case值 - 調整后的值 = 真實的最大case值)

3.遇到ja則后邊現修改為 default或者switchend

4.從下標表中尋得case當前的位置,(比如存放的是3,其位置在下表表中是35)那么此時代表的就是 case 35 (屬於第三個case語句)

5.從下標表中得出case語句的位置,(第幾個case)以及case的值是多少(case 35)那么去地址表中尋找其case35的地址即可.

 

3.3 Release版本下的匯編

和Debug版本下相差無異,只不過有代碼外提

此時還原很好還原

1.展開下表表格

2.展開地址表格

展開后則是上面的模樣,第一個是地址表,第二個是下標表格.

注意展開的時候可能會遇到問題

1.不是我這種顯示,沒有這么整齊, 此時 按鍵盤上的  * 鍵,可以設置按照數組顯示, 設置數組大小,間隔.等等..

2.沒有顯示 offset addr...  可能顯示的直接是一個地址, 此時可以選擇快捷鍵 ctrl + 0 顯示成當前段偏移

3.數據類型不對,我的是dd 而你們的可能解釋為 db dw 等等.. 此時按鍵盤快捷鍵 d即可.可以調整數據類型

 

還原方法;

和Debug一樣, (因為我沒有調整所以不用算了,而且從0開始)此時先去下標表中尋找0, 這個0找的的是case語句的位置 也就是第一條case語句是0

然后在尋找在數組的那個位置, 這個位置指的是 case xxx  xxx的值是多少

此時我能找到0,那么去數組表中尋找0地址,改為case 0,  按n鍵修改

修改1的值,也就是找case 第2條語句是什么

在35位置尋找到1,那么此時 第二條語句代表的case則是case 35

利用里面的下邊去地址表中尋址,則找到第二個地址則是case 35

我們修改過后則上面會顯示了.此時我們正常的還原即可.

四丶Switch Case語句在匯編中的第四種表達方式(二分優化查表,四種變化混合)

第四種表達方式,生成條件,其case值之間的間隔大於255則會使用第四種表達方式.

4.1高級代碼:

復制代碼
// MyCode.cpp : Defines the entry point for the console application. //  #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 256: printf("case 256\n"); break; case 34: printf("case 34\n"); break; case 500: printf("case 500\n"); break; case 510: printf("case 510\n"); break; default: printf("default \n"); break; } printf("HelloWorld\n"); return 0; }
復制代碼

首先說下如果遇到這種情況,編譯器會怎么做.

因為對於編譯器來說,不知道你命中率高的那個case語句會執行,所以只能從中間切開

會生成匯編代碼  jg 或者 jle  (大於或者小於代碼 )還有中間判斷相等.

比如我們有一組case值

0

200

300

310

320

此時匯編代碼會先判斷是否 jz(等於,可能不是jz反正判斷相等的跳轉即可) 找到中間的case,所以此時還原這個值即可.

不用管大於小於,最后的時候管.

4.2Debug下的匯編代碼:

可以看出一大堆的判斷 相等的代碼.

還原手法:

看到JE比較,那么看上方條件和誰比較(沒有調整的情況下)那么還原當前地址為這個case即可.

4.3Release版本下的匯編.

這個是還原過后的,只需要判斷 相等即可,根據它的條件來判斷,只不過Release版本有代碼外提

JNZ先修改為 Default ,此時如果進去每個case語句塊中,按到的JMP的跳轉地址和JNZ跳轉的地址是一樣的話,那么就是SWITCH_END

只需要還原相等即可.

注意:

  當我還原case 510的時候,有沒有發現其上面還原500的時候都是減掉了,所以510的時候又減掉了一次所以此時判斷是510

大體的規模已經出來了,下面就是還原具體代碼了,代碼很簡單,不還原了,看下和高級代碼對比即可.

看看case是否一樣.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM