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