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