最近對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來標識的棧上的一塊一塊的存儲空間。棧幀包括:
接下來看看在調用子函數開始及結尾時所要作的事情。(官方叫序言和結語, prologs and epilogs)
調用開始:
調用結尾:
-----------------------------------------------------------華麗的分割線-------------------------------------------------------------
實戰部分(一):
用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:"
下面來一行行代碼解釋:
至此,所有的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,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:
同樣,咱們一行行來看:
在指令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:
逐行解釋之:
你們應該有注意到由於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