arm c/c++ 基本結構逆向
任何平臺的逆向都是從基本語句開始的,我們需要知道if, for, while, do()while, switch等這些基本語句對應 arm 匯編是什么樣子。
這樣我們的知識就能形成一個閉環,使我們更加深刻的掌握一門語言。
下面就區分兩種狀態,即debug和release,對它們一一描述,重點是優化后不太一樣。
以后看匯編時掃一眼就大概知道是什么語句,方便快速的閱讀匯編代碼。
if {} 語句
if (i < 9) { printf("i < 9\n"); }
; debug
CMP R0, #8 BGT loc_99E ;BGT的含義是大于8跳到loc_99E,反過來if進入的條件就是 if (i <=8); B loc_992 ; --------------------------------------------------------------------------- loc_992 LDR R0, =(aI9 - 0x998) ADD R0, PC ; "i < 9\n" BL sub_9F4 STR R0, [SP,#0x30+var_28] B loc_99E ; debug模式 這兒都要跳一下,算是一個特點吧 ; --------------------------------------------------------------------------- loc_99E
; relese
CMP R0, #8 BGT loc_9AC ;BGT的含義是大于8跳到loc_9AC,反過來if進入的條件就是 if (i <=8); LDR R0, =(aI9 - 0x9AA) ADD R0, PC ; "i < 9" BLX puts loc_9AC
重點是這兩行匯編
CMP R0, #8
BGT loc_99E ;Z清零且(N等于V) 有符號數大于
意思是大于8 跳走, if內部條件就是反過的,if (i <= 8)
debug和release 共同點都是將if (i < 9) 編譯成 if (i <=8) 雖然邏輯上是一樣的。但是,我們在逆向的時候盡可能的向原始的代碼上靠
翻譯匯編時建議寫成 if (i < 9)
if {} else{} 語句
if (i < 9) { printf("i < 9\n"); } else { printf("i >= 9\n"); }
; debug
CMP R0, #8 BGT loc_99E B loc_992 ;這步與單if語句的區別,C里面就是“跳else” ; --------------------------------------------------------------------------- loc_992 LDR R0, =(aI9 - 0x998) ADD R0, PC ; "i < 9\n" BL sub_A04 STR R0, [SP,#0x30+var_28] B loc_9AA ; --------------------------------------------------------------------------- loc_99E LDR R0, =(aI9_0 - 0x9A4) ADD R0, PC ; "i >= 9\n" BL sub_A04 STR R0, [SP,#0x30+var_2C] B loc_9AA ; debug模式這兒要跳一下,算是個特點吧 ; --------------------------------------------------------------------------- loc_9AA
; release
CMP R0, #8 BGT loc_9AA LDR R0, =(aI9 - 0x9AA) ADD R0, PC ; "i < 9" B loc_9AE ;這步與單if語句的區別,C里面就是“跳else” ; --------------------------------------------------------------------------- loc_9AA ; CODE XREF: main+1A↑j LDR R0, =(aI9_0 - 0x9B0) ADD R0, PC ; "i >= 9" loc_9AE ; CODE XREF: main+20↑j BLX puts
if 與 if else 的區別并不大, 只是多了一個else的強制跳轉
if {} else if{} else{} 語句
if (i == 1) { printf("i = 1\n"); } else if ( i == 2) { printf("i = 2\n"); } else { printf("i != 1 && i != 2\n"); }
; debug CMP R0, #1 BNE loc_99E ; 不等于1,檢查下一條 else if B loc_992 ; --------------------------------------------------------------------------- loc_992 LDR R0, =(aI1 - 0x998) ADD R0, PC ; "i = 1\n" BL sub_A1C STR R0, [SP,#0x38+var_28] B loc_9BE ; 直接跳到if語句的結尾 ; --------------------------------------------------------------------------- loc_99E LDR R0, [SP,#0x38+var_18] CMP R0, #2 ; 檢查else if (i == 2) BNE loc_9B2 ; 不等于2 跳到 else語句 B loc_9A6 ; 等于2 ; --------------------------------------------------------------------------- loc_9A6 LDR R0, =(aI2 - 0x9AC) ADD R0, PC ; "i = 2\n" BL sub_A1C STR R0, [SP,#0x38+var_2C] B loc_9BE ; 跳到整個if語句的結尾 ; --------------------------------------------------------------------------- loc_9B2 ; 此處是最后的 else LDR R0, =(aI1I2 - 0x9B8) ADD R0, PC ; "i != 1 && i != 2\n" BL sub_A1C STR R0, [SP,#0x38+var_30] B loc_9BE ; --------------------------------------------------------------------------- loc_9BE
; release CMP R0, #2 BEQ loc_72E ; 等于2 跳轉 CMP R0, #1 BNE loc_734 ; 不等于1 跳轉 LDR R0, =(aI1 - 0x72E) ; 此處是 if (i == 1) 基本塊 ADD R0, PC ; "i = 1" B endif_738 ; --------------------------------------------------------------------------- loc_72E ; CODE XREF: main+A↑j LDR R0, =(aI2 - 0x734) ; 此處是 if (i == 2) 基本塊 ADD R0, PC ; "i = 2" B endif_738 ; --------------------------------------------------------------------------- loc_734 ; CODE XREF: main+E↑j LDR R0, =(aI1I2 - 0x73A) ; 此處是 最后 else 基本塊 ADD R0, PC ; "i != 1 && i != 2" endif_738 ; CODE XREF: main+14↑j ; main+1A↑j BLX puts
if {} else if{} else{} 語句 debug 與 release差別比較大, 但是共同點是每個塊結尾都強制跳轉到if語句的結尾
如果看到這樣的特點,基本上可以確定是if {} else if{} else{}語句,這點與x86上基本一樣。
同時,我發現另一個特點,debug在每個塊完成后都要跳一下,release沒有,這樣在debug下會非常影響性能,所以建議發布時不要用debug編譯。
for 語句
for (int j = 0; j < i; j++) { printf("j = %d\n", j); }
; debug begin_for_9C6 LDR R0, [SP,#0x30+var_1C] ; var_1C 就是變量 j LDR R1, [SP,#0x30+var_18] ; var_18 就是變量 i CMP R0, R1 BGE end_for_9E6 ; var_1C >= var_18就跳出for B loc_9D0 ; --------------------------------------------------------------------------- loc_9D0 LDR R1, [SP,#0x30+var_1C] LDR R0, =(aJD - 0x9D8) ADD R0, PC ; "j = %d\n" BL sub_9F0 STR R0, [SP,#0x30+var_2C] B loc_9DE ; --------------------------------------------------------------------------- loc_9DE LDR R0, [SP,#0x30+var_1C] ; var_1C +1 ADDS R0, #1 STR R0, [SP,#0x30+var_1C] B begin_for_9C6 ; for 循環最典型的特征,從下往上跳 ; --------------------------------------------------------------------------- end_for_9E6
; release MOV R4, R0 ; R4就是變量 i CMP R4, #1 BLT endfor_99C ; i < 1 跳出循環 LDR R6, =(aJD - 0x990) MOVS R5, #0 ; R5就是變量 j ADD R6, PC ; "j = %d\n" loc_98E MOV R0, R6 ; 此處是循環體,相比debug優化很多 MOV R1, R5 BL sub_9A8 ; R0 R1 傳遞參數調用printf ADDS R5, #1 CMP R4, R5 BNE loc_98E ; for 循環的特征 +1向上跳 endfor_99C
可以看出for循環的特點,在循環的尾部 +1向上跳,同時也能看出debug和release在性能的上的差異太大了。
debug版本需要不停的訪問內存,速度上明顯要慢不少!
大多數的for循環都是從0開始的,release版本的for循環,剛開始就判斷i是不是<1,這也算是一個release的特點吧。
while 語句
int j = 0; while(j < i) { printf("j = %d\n", j); j++; }
; debug begin_while_9C6 LDR R0, [SP,#0x30+var_1C] ; var_1C 就是變量 j LDR R1, [SP,#0x30+var_18] ; var_18 就是變量 i CMP R0, R1 BGE end_while_9E4 ; j >= i 跳出循環 B loc_9D0 ; debug模式的特點,跳到下一行 ; --------------------------------------------------------------------------- loc_9D0 LDR R1, [SP,#0x30+var_1C] LDR R0, =(aJD - 0x9D8) ADD R0, PC ; "j = %d\n" BL sub_9F0 LDR R1, [SP,#0x30+var_1C] ADDS R1, #1 STR R1, [SP,#0x30+var_1C] STR R0, [SP,#0x30+var_2C] B begin_while_9C6 ; 強制向上跳, 與for循環很像 ; --------------------------------------------------------------------------- end_while_9E4
; release MOV R4, R0 CMP R4, #1 ; R4是變量 i BLT end_while_99C ; R4 < 1 跳出while循環 LDR R6, =(aJD - 0x990) MOVS R5, #0 ADD R6, PC ; "j = %d\n" loc_98E MOV R0, R6 ; while循環體 MOV R1, R5 BL sub_9A8 ADDS R5, #1 CMP R4, R5 BNE loc_98E ; R4 != R5 end_while_99C
while循環與for循環非常像,區別不大。邏輯上while, do while,都可以寫成for循環。
do while 語句
int j = 0; do { printf("j = %d\n", j); j++; }while(j < i);
; debug begin_dowhile_9C6 LDR R1, [SP,#0x30+var_1C] ; do while體,沒有判斷直接運行塊中pritnf LDR R0, =(aJD - 0x9CE) ADD R0, PC ; "j = %d\n" BL sub_9F0 LDR R1, [SP,#0x30+var_1C] ADDS R1, #1 STR R1, [SP,#0x30+var_1C] STR R0, [SP,#0x30+var_2C] B loc_9DA ; --------------------------------------------------------------------------- loc_9DA LDR R0, [SP,#0x30+var_1C] LDR R1, [SP,#0x30+var_18] CMP R0, R1 ; 在此判斷條件 BLT begin_dowhile_9C6 B end_dowhile_9E4 ; --------------------------------------------------------------------------- end_dowhile_9E4
; release LDR R6, =(aJD - 0x98C) MOV R4, R0 MOVS R5, #0 ADD R6, PC ; "j = %d\n" loc_98A MOV R0, R6 ; 直接進入do while基本塊中 MOV R1, R5 BL sub_9A4 ADDS R5, #1 CMP R5, R4 ; 條件判斷 BLT loc_98A ; do while結尾判斷條件
最大的特點是直接進入執行體沒有判斷,與C代碼完全一致。
switch 語句
switch (i) { case 1: printf("1"); break; case 2: printf("2"); break; case 3: printf("3"); break; case 4: printf("4"); break; default: printf("unknow"); }
; debug STR R0, [SP,#0x38+var_18] SUBS R0, #1 ; switch 4 cases MOV R1, R0 CMP R0, #3 STR R1, [SP,#0x38+var_24] BHI def_9C8 ; jumptable 000009C8 default case LDR R1, [SP,#0x38+var_24] TBB.W [PC,R1] ; switch jump ; switch最典型的特征,有一個跳轉表 ; --------------------------------------------------------------------------- jpt_9C8 DCB 2 ; jump table for switch statement DCB 8 DCB 0xE DCB 0x14 ; --------------------------------------------------------------------------- loc_9D0 LDR R0, =(a1 - 0x9D6) ; jumptable 000009C8 case 1 ADD R0, PC ; "1\n" BL sub_A28 STR R0, [SP,#0x38+var_28] B end_switch_A0C ; --------------------------------------------------------------------------- loc_9DC LDR R0, =(a2 - 0x9E2) ; jumptable 000009C8 case 2 ADD R0, PC ; "2\n" BL sub_A28 STR R0, [SP,#0x38+var_2C] B end_switch_A0C ; --------------------------------------------------------------------------- loc_9E8 LDR R0, =(a3 - 0x9EE) ; jumptable 000009C8 case 3 ADD R0, PC ; "3\n" BL sub_A28 STR R0, [SP,#0x38+var_30] B end_switch_A0C ; --------------------------------------------------------------------------- loc_9F4 ; CODE XREF: main+2C↑j LDR R0, =(a4 - 0x9FA) ; jumptable 000009C8 case 4 ADD R0, PC ; "4\n" BL sub_A28 STR R0, [SP,#0x38+var_34] B end_switch_A0C ; --------------------------------------------------------------------------- def_9C8 LDR R0, =(aUnknow - 0xA06) ; jumptable 000009C8 default case ADD R0, PC ; "unknow" BL sub_A28 STR R0, [SP,#0x38+var_38] B end_switch_A0C ; --------------------------------------------------------------------------- end_switch_A0C
; release SUBS R0, #1 ; switch 4 cases CMP R0, #3 BHI def_9AE ; jumptable 000009AE default case TBB.W [PC,R0] ; switch jump ; release版本也一樣存在 TBB, 后面就是跳轉表,這是最重要的特征 ; --------------------------------------------------------------------------- jpt_9AE DCB 2 ; jump table for switch statement DCB 0xA DCB 0xD DCB 0x10 ; --------------------------------------------------------------------------- loc_9B6 LDR R0, =(a1 - 0x9BC) ; jumptable 000009AE case 1 ADD R0, PC ; "1" B end_switch_9D6 ; --------------------------------------------------------------------------- def_9AE LDR R0, =(aUnknow - 0x9C2) ; jumptable 000009AE default case ADD R0, PC ; "unknow" BL sub_9F4 B loc_9DA ; --------------------------------------------------------------------------- loc_9C6 ; CODE XREF: main+E↑j LDR R0, =(a2 - 0x9CC) ; jumptable 000009AE case 2 ADD R0, PC ; "2" B end_switch_9D6 ; --------------------------------------------------------------------------- loc_9CC ; CODE XREF: main+E↑j LDR R0, =(a3 - 0x9D2) ; jumptable 000009AE case 3 ADD R0, PC ; "3" B end_switch_9D6 ; --------------------------------------------------------------------------- loc_9D2 LDR R0, =(a4 - 0x9D8) ; jumptable 000009AE case 4 ADD R0, PC ; "4" end_switch_9D6
arm的匯編基本上都一樣,以后介紹類 class的各種實現。
class中繼承,派生,虛函數等在arm中是如何實現的。
說明:目前的編譯器都是ndk的編譯器 armeabi-v7a

浙公網安備 33010602011771號