ARM匯編逆向iOS 實戰(zhàn)
我們先講一些ARM匯編的基礎知識。(我們以ARMV7為例,最新iPhone5s上的64位暫不討論)
基礎知識部分:
首先你介紹一下寄存器:
R0-R3:用于函數(shù)參數(shù)及返回值的傳遞
R4-R6, R8,R10-R11:沒有特殊規(guī)定,就是普通的通用寄存器
R7:棧幀指針(Frame Pointer).指向前一個保存的棧幀(stack frame)和鏈接寄存器(link register, lr)在棧上的地址。
R9:操作系統(tǒng)保留
R12:又叫IP(intra-procedure scratch), 要說清楚要費點筆墨,后續(xù)再詳細介紹
R13:又叫SP(stack pointer),是棧頂指針
R14:又叫LR(link register),存放函數(shù)的返回地址。
R15:又叫PC(program counter),指向當前指令地址。
CPSR:當前程序狀態(tài)寄存器(Current Program State Register),在用戶狀態(tài)下存放像condition標志中斷禁用等標志的。
在其它系統(tǒng)狀態(tài)中斷狀等狀態(tài)下與CPSR對應還有一個SPSR,在這里不詳述了。
另外還有VFP(向量浮點運算)相關的寄存器,在此我們略過,感興趣的可以從后面的參考鏈接去查看。
基本的指令:
add 加指令
sub 減指令
str 把寄存器內(nèi)容存到棧上去
ldr 把棧上內(nèi)容載入一寄存器中
.w
是一個可選的指令寬度說明符。它不會影響為此指令的行為,它只是確保生成 32 位指令。Infocenter.arm.com的詳細信息
bl 執(zhí)行函數(shù)調(diào)用,并把使lr指向調(diào)用者(caller)的下一條指令,即函數(shù)的返回地址
blx 同上,但是在ARM和thumb指令集間切換。
bx bx lr返回調(diào)用函數(shù)(caller)。
接下來是函數(shù)調(diào)用的一些規(guī)則。
一. 在iOS中你需要使用BLX,BX這些指令來調(diào)用函數(shù),不能使用MOV指令(具體意義下面會說)
二. ARM使用一個棧來來維護函數(shù)的調(diào)用及返回。ARM中棧是向下生長(由高地址向低地址生長的)。
函數(shù)調(diào)用前后棧的布局如圖一(引用的蘋果iOS ABI Reference):
圖(一)
SP(stack pointer)指向棧頂(棧低在高地址)。棧幀(stack frame)其實就是通過R7及存在棧上的舊R7來標識的棧上的一塊一塊的存儲空間。棧幀包括:
參數(shù)區(qū)域(parameter area),存放調(diào)用函數(shù)傳遞的參數(shù)。對于32位ARM,前4個參數(shù)通過r0-r3傳遞,多余的參數(shù)通過棧來傳遞,就是存放在這個區(qū)域的。
鏈接區(qū)域(linkage area),存放調(diào)用者(caller)的下一條指令。
棧幀指針存放區(qū)域(saved frame pointer),存放調(diào)用函數(shù)的棧幀的底部,標識著調(diào)用者(caller)棧幀的結束及被調(diào)用函數(shù)(callee)的棧幀開始。
局部變量存儲區(qū)(local storage area)。用于存被調(diào)函數(shù)(callee)的局部變量及在被調(diào)用函數(shù)(callee)結束后反回調(diào)用函數(shù)(call)之前需要恢復的寄存器內(nèi)容。
寄存器存儲區(qū)(saved registers area)。Apple的文檔中是這樣說的。但我認為這個區(qū)域和local storage area相鄰且干的事也是存放需要恢復的寄存器內(nèi)容,因此我覺得要不就把這個區(qū)域在概念上不區(qū)分出來,要不就把存放需要恢復的寄存器這項功能從local storage area中分出來。 當然這些都只是概念上的,其實實質(zhì)上是沒有區(qū)別的。
接下來看看在調(diào)用子函數(shù)開始及結尾時所要做的事情。(官方叫序言和結語, prologs and epilogs)
調(diào)用開始:
LR入棧R7入棧R7 = SP地址。在經(jīng)過前面兩條入棧指令后,SP指向的地址向下移動,再把SP賦值給R7, 標志著caller棧幀的結束及callee的棧幀的開始將callee會修改且在返回caller時需要恢復的寄存器入棧。分配棧空間給子程序使用。由于棧是從高地址向低地址生長,所以通常使用sub sp, #size來分配。
調(diào)用結尾:
釋放??臻g。add sp, #size指令。恢復所保存的寄存器?;謴蚏7將之前存放的LR從棧上彈出到PC,這樣函數(shù)就返回了。
-----------------------------------------------------------華麗的分割線-------------------------------------------------------------
實戰(zhàn)部分(一):
用XCode創(chuàng)建一個Test工程,新建一個.c文件,添加如下函數(shù):
#include <stdio.h> int func(int a, int b, int c, int d, int e, int f) { int g = a + b + c + d + e + f; return g; }
查看匯編語言:
在XCode左上角選中targe 在真機下編譯,這樣產(chǎn)生的才是ARM匯編,不然在模擬器下生成的是x86匯編。
點擊 XCode => Product => Perform Action => Assemble file.c 生成匯編代碼。
代碼很多,有很多"."開頭的".section", ".loc"等,這些是匯編器需要的,我們不用去管。把這些"."開頭的及注釋增掉后,代碼如下:
_func: .cfi_startproc Lfunc_begin0: add r0, r1 Ltmp0: ldr.w r12, [sp] add r0, r2 ldr.w r9, [sp, #4] add r0, r3 add r0, r12 add r0, r9 bx lr Ltmp2: Lfunc_end0:
_func:表示接下來是func函數(shù)的內(nèi)容。Lfunc_begin0及Lfunc_end0標識函數(shù)定義的起止。函數(shù)起止一般是"xxx_beginx:"及"xxx_endx:"
下面來一行行代碼解釋:
add r0, r1 將參數(shù)a和參數(shù)b相加再把結果賦值給r0ldr.w r12, [sp] 把最的一個參數(shù)f從棧上裝載到r12寄存器add r0, r2 把參數(shù)c累加到r0上ldr.w r9, [sp, #4] 把參數(shù)e從棧上裝載到r9寄存器add r0, r3 累加d累加到r0add r0, r12 累加參數(shù)f到r0add r0, r9 累加參數(shù)e到r0
至此,全部的a到f 共6個值全部累加到r0寄存器上。前面說了r0是存放返回值的。
bx lr: 返回調(diào)用函數(shù)。
-----------------------------------------------------------華麗的分割線-------------------------------------------------------------
實戰(zhàn)部分(二):
為了讓大家看清楚函數(shù)調(diào)用時棧上的變化,下面以一個有三個函數(shù),兩個調(diào)用的C代碼的匯編代碼為例講解一下。
上代碼:
#include <stdio.h> __attribute__((noinline)) int addFunction(int a, int b, int c, int d, int e, int f) { int r = a + b + c + d + e + f; return r; } __attribute__((noinline)) int fooFunction(int a, int b, int c, int d, int f) { int r = addFunction(a, b, c, d, f, 66); return r; } int initFunction() { int r = fooFunction(11, 22, 33, 44, 55); return r; }
由于我們是要看函數(shù)調(diào)用及棧的變化的,所以在這里我們加上__attribute__((noinline))防止編譯器把函數(shù)內(nèi)聯(lián)(如果你不懂內(nèi)聯(lián),請google之)。
在XCode左上角選中targe 在真機下編譯,這樣產(chǎn)生的才是ARM匯編,不然在模擬器下生成的是x86匯編。
點擊 XCode => Product => Perform Action => Assemble file.c 生成匯編代碼, 如下:
為了能更符合我們?nèi)说乃伎挤绞?,我們從調(diào)用函數(shù)講起。
initFunction:
_initFunction: .cfi_startproc Lfunc_begin2: @ BB#0: push {r7, lr} mov r7, sp sub sp, #4 movs r0, #55 movs r1, #22 Ltmp6: str r0, [sp] movs r0, #11 movs r2, #33 movs r3, #44 bl _fooFunction add sp, #4 pop {r7, pc} Ltmp7: Lfunc_end2:
還是一行行的解釋:
1.push {r7, lr} 就是前面基礎知識部分說的函數(shù)調(diào)用的序言(prologs)部分的1, 2兩條,將lr, r7 存到棧上去
2.mov r7, sp 序言(prolog)之3。
3.sub sp, #4 在棧上分配一個4字節(jié)空間用來存放局部變量, 即參數(shù)。前面我們說過,r0-r3可以傳遞4個參數(shù),但超過的只能通過棧來傳遞。
4.movs r0, #55 把立即數(shù)55存入r0
5.movs r1, #22 把22存入r1
6.str r0, [sp] 把r0的值存入棧指針sp指向的內(nèi)存。即棧上存了參數(shù)55
7.接下來三條指令moves r0, #11 moves r2, #33 moves r3, #44 把相應的立即數(shù)存入指定的寄存器。 到目前為止,r0-r3分別存放了11, 22, 33,44共4個立即數(shù)參數(shù),棧上存放了55這一個參數(shù)。
8.bl _fooFunction 調(diào)用fooFunction, 調(diào)用后跳轉(zhuǎn)到fooFunction中的情況下面再分析。
9.add sp, #4 棧指針向上移動4個字節(jié),回收第3個指令sub sp, #4分配的空間。
10.pop {r7, pc} 恢復第一條指令push {r7, lr}到棧中的值, 把之前的lr值賦給pc。注意:在進入initFunction的時候lr是調(diào)用initFunction的函數(shù)的下一條指令,所以現(xiàn)在把當時的lr中的值賦給pc程序計數(shù)器,這樣執(zhí)行l(wèi)r指向的這一條指令,函數(shù)就反回了。
指令1,2, 3是函數(shù)序言(prologs),指令9, 10是結語(epilogs)。這基本上是一個套路,看多了自然就知道了,都不用停下來一條條分析。
為了方便和棧的變化聯(lián)系起來,我們畫出指令8, bl __fooFunction時的棧布局如圖二:
圖(二)
在上面的initFunction調(diào)用第8條指令bl _fooFunction之后,進入fooFunction, 其它匯編如下:
fooFunction:
_fooFunction: .cfi_startproc Lfunc_begin1: push {r4, r5, r7, lr} add r7, sp, #8 sub sp, #8 ldr r4, [r7, #8] movs r5, #66 strd r4, r5, [sp] bl _addFunction add sp, #8 pop {r4, r5, r7, pc} Lfunc_end1:
一樣,我們一行行來看:
1.push {r4, r5, r7, lr} 你應該發(fā)現(xiàn)了,這次和initFunction不同,除了lr和r7也把r4, r5 push到棧上去了,這是因為我們下面會用到r4, r5,所以我們先把r4,r5存到棧上,這樣我們在退出fooFunction返回initFunction的時候好恢復r4, r5的值。push到棧上的順序是lr, r7, r4, r5。
2.add r7, sp, #8 在initFunction中我們沒有push r4, r5所以sp指向的位置正好是新的r7的值,但是這里我們把r4, r5也push到棧上了,現(xiàn)在sp指向棧上的r4的位置,而棧是向下生長的,所以我們把sp + #8個字節(jié)就是存放舊r7的位置。
3.sub sp, #8 在棧上分配8個字節(jié)。
4.ldr r4, [r7, #8] r7加8個字節(jié),在棧上的位置正好是在initFunction中我們存放的參數(shù)55的位置。因此,這里是把55賦值給r4
5.movs r5, #66 立即數(shù)賦值,不解釋了
6.strd r4, r5, [sp] 把r4, r5中的值存到棧上。我們在initFunction中已經(jīng)把11,22,33,44這4個參數(shù)存放到了r0-r3,現(xiàn)在55,66我們存放在棧上
7.bl _addFunction 參數(shù)已經(jīng)準備好了,因此現(xiàn)在調(diào)用addFunction。
8.add sp, #8 回收??臻g
9.pop {r4, r5, r7, pc} 這最后兩條指令和 initFunction類似,只是多了個恢復r4,r5。不過也是一個指令就完事。
在指令bl _addFunction 調(diào)用addFunction后,棧的布局如圖(三):
圖(三)
上面的fooFunction第7條指令bl _addFunction之后,進入addFunction。匯編代碼如下:
addFunction:
_addFunction: .cfi_startproc Lfunc_begin0: add r0, r1 ldr.w r12, [sp] add r0, r2 ldr.w r9, [sp, #4] add r0, r3 add r0, r12 add r0, r9 bx lr Lfunc_end0:
逐行解釋之:
add r0, r1 r0 += r1 ldr.w r12, [sp] 把sp指向的內(nèi)容load到r12寄存器。從圖(三)我們知道sp指向66,因此r12存的66 add r0, r2 r0 += r2 ldr.w r9, [sp, #4] 從圖(三) sp加4個字節(jié)存的是55, r9存的55 add r0, r3 r0 += r3 add r0, r12 r0 += r12 add r0, r9 r0 += r9。 至此r0-r4存的11,22,33,44,及棧上存的55,66想加存到了r0上。 bx lr 返回。
大家應該有注意到因為addFunction沒有調(diào)用其它的函數(shù),序言和結語與initFunction和fooFunction不一樣。因為我們不調(diào)用其它函數(shù),就不會有bl, blx這樣的指令,所以不會個性lr, 所以我們沒有push lr。
在這里我們用了r9, r12為什么不需要保存與恢復,我還沒大搞明白,大俠們?nèi)裟苜n教,將不勝感激。
iOS ABI Reference上是這樣說的:
關于R9:
In iOS 2.x, register R9 is reserved for operating system use and must not be used by application code. Failure to do so can result in application crashes or aberrant behavior. However, in iOS 3.0 and later, register R9 can be used as a volatile scratch register. These guidelines differ from the general usage provided for by the AAPCS document.
關于R12
R12is the intra-procedure scratch register, also known as IP. It is used by the dynamic linker and is volatile across all function calls. However, it can be used as a scratch register between function calls.
這是C函數(shù)的匯編。下篇講obj-c函數(shù)的匯編,包括objc block。
相關文章
詳解iOS使用Keychain中的kSecClassGenericPassword存儲數(shù)據(jù)
iOS設備中的Keychain是一個安全的存儲容器,本篇文章主要介紹了iOS使用Keychain中的kSecClassGenericPassword存儲數(shù)據(jù),有興趣的可以了解一下。2016-11-11iOS UIWebView實現(xiàn)禁止用戶復制剪切功能
這篇文章主要給大家介紹了iOS中的UIWebView如何實現(xiàn)禁止用戶復制剪切的功能,文中給出了詳細的示例代碼,有需要的朋友們可以參考借鑒,下面來一起學習學習吧。2016-11-11iOS開發(fā)Quick Actions創(chuàng)建桌面Icon快捷方式
在本文里我們給大家分享了關于iOS開發(fā)Quick Actions創(chuàng)建桌面Icon快捷方式的相關知識點內(nèi)容,需要的讀者們可以參考下。2019-05-05IOS UI學習教程之使用代碼創(chuàng)建button
這篇文章主要為大家詳細介紹了IOS UI學習教程之使用代碼創(chuàng)建button,感興趣的小伙伴們可以參考一下2016-03-03詳解iOS App中UIPickerView滾動選擇欄的添加方法
UIPickerView組件在應用中選擇地區(qū)等方面的運用非常常見,能夠提供多列的選擇項,下買呢我們就來詳解iOS App中UIPickerView滾動選擇欄的添加方法2016-05-05iOS開發(fā)中class和#import的區(qū)別介紹
這篇文章主要介紹了iOS開發(fā)中class和#import的區(qū)別,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2018-02-02iOS開發(fā)中使用Quartz2D繪制上下文棧和矩陣的方法
這篇文章主要介紹了iOS開發(fā)中使用Quartz2D繪制上下文棧和矩陣的方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-11-11