iOS 逆向之ARM彙編

最近對iOS逆向工程很感興趣。html

目前iOS逆向的書籍有: 《Hacking and Securing IOS Applications》, 《iOS Hacker's Handbook》中文書籍有《iOS應用逆向工程:分析與實戰》ios

中文博客有: 程序員念茜的《iOS安全攻防系列》 英文博客有:Prateek Gianchandani的iOS 安全系列博客程序員

這些資料中都涉及到有ARM彙編,但都只是很泛地用到,並無對iOS上的ARM彙編進行比較詳細的講解。所以,通過一系列的學習對iOS下的ARM有了必定的理解。在此打算用幾篇博文記錄下來,備忘之,分享之, 限於本人水平有限,若有錯誤請不吝賜教。安全

 

咱們先講一些ARM彙編的基礎知識。(咱們以ARMV7爲例,最新iPhone5s上的64位暫不討論)app

基礎知識部分:jsp

 

首先你介紹一下寄存器:ide

R0-R3:用於函數參數及返回值的傳遞函數

R4-R6, R8, R10-R11:沒有特殊規定,就是普通的通用寄存器佈局

R7:棧幀指針(Frame Pointer).指向前一個保存的棧幀(stack frame)和連接寄存器(link register, lr)在棧上的地址。學習

R9:操做系統保留

R12:又叫IP(intra-procedure scratch ), 要說清楚要費點筆墨,參見http://blog.csdn.net/gooogleman/article/details/3529413

R13:又叫SP(stack pointer),是棧頂指針

R14:又叫LR(link register),存放函數的返回地址。

R15:又叫PC(program counter),指向當前指令地址。

CPSR:當前程序狀態寄存器(Current Program State Register),在用戶狀態下存放像condition標誌中斷禁用等標誌的。

     在其它系統狀態中斷狀等狀態下與CPSR對應還有一個SPSR,在這裏不詳述了。

另外還有VFP(向量浮點運算)相關的寄存器,在此咱們略過,感興趣的能夠從後面的參考連接去查看。

 

基本的指令:

add 加指令

sub 減指令

str 把寄存器內容存到棧上去

ldr  把棧上內容載入一寄存器中

.w是一個可選的指令寬度說明符。它不會影響爲此指令的行爲,它只是確保生成 32 位指令。Infocenter.arm.com的詳細信息

bl 執行函數調用,並把使lr指向調用者(caller)的下一條指令,即函數的返回地址

blx 同上,可是在ARM和thumb指令集間切換。

bx  bx lr返回調用函數(caller)。

 

 

接下來是函數調用的一些規則。

一. 在iOS中你須要使用BLX,BX這些指令來調用函數,不能使用MOV指令(具體意義下面會說)

二. ARM使用一個棧來來維護函數的調用及返回。ARM中棧是向下生長(由高地址向低地址生長的)。

函數調用先後棧的佈局如圖一(引用的蘋果iOS ABI Reference):

              圖(一)

SP(stack pointer)指向棧頂(棧低在高地址)。棧幀(stack frame)其實就是經過R7及存在棧上的舊R7來標識的棧上的一塊一塊的存儲空間。棧幀包括:

  1. 參數區域(parameter area),存放調用函數傳遞的參數。對於32位ARM,前4個參數經過r0-r3傳遞,多餘的參數經過棧來傳遞,就是存放在這個區域的。
  2. 連接區域(linkage area),存放調用者(caller)的下一條指令。
  3. 棧幀指針存放區域(saved frame pointer),存放調用函數的棧幀的底部,標識着調用者(caller)棧幀的結束及被調用函數(callee)的棧幀開始。
  4. 局部變量存儲區(local storage area)。用於存被調函數(callee)的局部變量及在被調用函數(callee)結束後反回調用函數(call)以前須要恢復的寄存器內容。
  5. 寄存器存儲區(saved registers area)。Apple的文檔中是這樣說的。但我認爲這個區域和local storage area相鄰且乾的事也是存放須要恢復的寄存器內容,所以我以爲要不就把這個區域在概念上不區分出來,要不就把存放須要恢復的寄存器這項功能從local storage area中分出來。 固然這些都只是概念上的,其實實質上是沒有區別的。

接下來看看在調用子函數開始及結尾時所要作的事情。(官方叫序言和結語, prologs and epilogs)

調用開始:

  1. LR入棧
  2. R7入棧
  3. R7 = SP地址。在通過前面兩條入棧指令後,SP指向的地址向下移動,再把SP賦值給R7, 標誌着caller棧幀的結束及callee的棧幀的開始
  4. 將callee會修改且在返回caller時須要恢復的寄存器入棧。
  5. 分配棧空間給子程序使用。因爲棧是從高地址向低地址生長,因此一般使用sub sp, #size來分配。

調用結尾:

  1. 釋放棧空間。add sp, #size指令。
  2. 恢復所保存的寄存器。
  3. 恢復R7
  4. 將以前存放的LR從棧上彈出到PC,這樣函數就返回了。

-----------------------------------------------------------華麗的分割線-------------------------------------------------------------

實戰部分(一):

用XCode建立一個Test工程,新建一個.c文件,添加以下函數:

#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 在真機下編譯,這樣產生的纔是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函數的內容。Lfunc_begin0及Lfunc_end0標識函數定義的起止。函數起止通常是"xxx_beginx:"及"xxx_endx:"

下面來一行行代碼解釋:

  1. add r0, r1                 將參數a和參數b相加再把結果賦值給r0
  2. ldr.w r12, [sp]           把最的一個參數f從棧上裝載到r12寄存器
  3. add r0, r2                 把參數c累加到r0上
  4. ldr.w r9, [sp, #4]       把參數e從棧上裝載到r9寄存器
  5. add r0, r3                 累加d累加到r0
  6. add r0, r12               累加參數f到r0
  7. add r0, r9                 累加參數e到r0

至此,所有的a到f 共6個值所有累加到r0寄存器上。前面說了r0是存放返回值的。

bx lr: 返回調用函數。

-----------------------------------------------------------華麗的分割線-------------------------------------------------------------

實戰部分(二):

爲了讓你們看清楚函數調用時棧上的變化,下面以一個有三個函數,兩個調用的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;
}

因爲咱們是要看函數調用及棧的變化的,因此在這裏咱們加上__attribute__((noinline))防止編譯器把函數內聯(若是你不懂內聯,請google之)。

在XCode左上角選中targe 在真機下編譯,這樣產生的纔是ARM彙編,否則在模擬器下生成的是x86彙編。

點擊 XCode => Product => Perform Action => Assemble file.c 生成彙編代碼, 以下:

爲了能更符合咱們人的思考方式,咱們從調用函數講起。

 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}                      就是前面基礎知識部分說的函數調用的序言(prologs)部分的1, 2兩條,將lr, r7 存到棧上去
  2. mov r7, sp                         序言(prolog)之3。
  3. sub sp, #4                         在棧上分配一個4字節空間用來存放局部變量, 即參數。前面咱們說過,r0-r3能夠傳遞4個參數,但超過的只能經過棧來傳遞。
  4. movs r0, #55                     把當即數55存入r0
  5. movs r1, #22                     把22存入r1
  6. str r0, [sp]                         把r0的值存入棧指針sp指向的內存。即棧上存了參數55
  7. 接下來三條指令 moves r0, #11   moves r2, #33   moves r3, #44  把相應的當即數存入指定的寄存器。  到目前爲止,r0-r3分別存放了11, 22, 33,44共4個當即數參數,棧上存放了55這一個參數。
  8. bl _fooFunction                   調用fooFunction, 調用後跳轉到fooFunction中的狀況下面再分析。
  9. add sp, #4                         棧指針向上移動4個字節,回收第3個指令 sub sp, #4分配的空間。
  10. pop {r7, pc}                       恢復第一條指令push {r7, lr}到棧中的值, 把以前的lr值賦給pc。注意:在進入initFunction的時候lr是調用initFunction的函數的下一條指令,因此如今把當時的lr中的值賦給pc程序計數器,這樣執行lr指向的這一條指令,函數就反回了。

指令1,2, 3是函數序言(prologs),指令9, 10是結語(epilogs)。這基本上是一個套路,看多了天然就知道了,都不用停下來一條條分析。

爲了方便和棧的變化聯繫起來,咱們畫出指令8,  bl __fooFunction時的棧佈局如圖二:

          圖(二)

在上面的initFunction調用第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}             你應該發現了,此次和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到棧上了,如今sp指向棧上的r4的位置,而棧是向下生長的,因此咱們把sp + #8個字節就是存放舊r7的位置。
  3. sub sp, #8                          在棧上分配8個字節。
  4. ldr r4, [r7, #8]                    r7加8個字節,在棧上的位置正好是在initFunction中咱們存放的參數55的位置。所以,這裏是把55賦值給r4
  5. movs  r5, #66                     當即數賦值,不解釋了
  6. strd r4, r5, [sp]                   把r4, r5中的值存到棧上。咱們在initFunction中已經把11,22,33,44這4個參數存放到了r0-r3,如今55,66咱們存放在棧上
  7. bl _addFunction                   參數已經準備好了,所以如今調用addFunction。
  8. add sp, #8                          回收棧空間
  9. pop {r4, r5, r7, pc}              這最後兩條指令和 initFunction相似,只是多了個恢復r4,r5。不過也是一個指令就完事。

在指令bl _addFunction 調用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:

逐行解釋之:

  1. add r0, r1              r0 += r1
  2. ldr.w r12, [sp]           把sp指向的內容load到r12寄存器。從圖(三)咱們知道sp指向66,所以r12存的66
  3. add r0, r2                 r0 += r2
  4. ldr.w r9, [sp, #4]       從圖(三) sp加4個字節存的是55, r9存的55
  5. add r0, r3                 r0 += r3
  6. add r0, r12               r0 += r12
  7. add r0, r9             r0 += r9。 至此r0-r4存的11,22,33,44,及棧上存的55,66想加存到了r0上。
  8. bx lr                         返回。

你們應該有注意到由於addFunction沒有調用其它的函數,序言和結語與initFunction和fooFunction不同。由於咱們不調用其它函數,就不會有bl, blx這樣的指令,因此不會個性lr, 因此咱們沒有push lr。  

 

在這裏咱們用了r9, r12爲何不須要保存與恢復,我還沒大搞明白,大俠們若能賜教,將不勝感激。

 

 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

R12 is 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函數的彙編。下篇講obj-c函數的彙編,包括objc block。

 

參考:http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf

   http://simplemachines.it/doc/arm_inst.pdf

   iOS ABI Reference

相關文章
相關標籤/搜索