ARM彙編逆向iOS 實戰_IOS

來源:互聯網
上載者:User

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

基礎知識部分:

首先你介紹一下寄存器:

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

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

R7:棧幀指標(Frame Pointer).指向前一個儲存的棧幀(stack frame)和連結寄存器(link register, lr)在棧上的地址。

R9:作業系統保留

R12:又叫IP(intra-procedure scratch), 要說清楚要費點筆墨,後續再詳細介紹

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來標識的棧上的一塊一塊的儲存空間。棧幀包括:

參數地區(parameter area),存放調用函數傳遞的參數。對於32位ARM,前4個參數通過r0-r3傳遞,多餘的參數通過棧來傳遞,就是存放在這個地區的。

連結地區(linkage area),存放調用者(caller)的下一條指令。

棧幀指標存放地區(saved frame pointer),存放調用函數的棧幀的底部,標識著調用者(caller)棧幀的結束及被調用函數(callee)的棧幀開始。

局部變數儲存區(local storage area)。用於存被調函數(callee)的局部變數及在被調用函數(callee)結束後反回調用函數(call)之前需要恢複的寄存器內容。

寄存器儲存區(saved registers area)。Apple的文檔中是這樣說的。但我認為這個地區和local storage area相鄰且乾的事也是存放需要恢複的寄存器內容,因此我覺得要不就把這個地區在概念上不區分出來,要不就把存放需要恢複的寄存器這項功能從local storage area中分出來。 當然這些都只是概念上的,其實實質上是沒有區別的。

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

調用開始:

LR入棧R7入棧R7 = SP地址。在經過前面兩條入棧指令後,SP指向的地址向下移動,再把SP賦值給R7, 標誌著caller棧幀的結束及callee的棧幀的開始將callee會修改且在返回caller時需要恢複的寄存器入棧。分配棧空間給子程式使用。由於棧是從高地址向低地址生長,所以通常使用sub sp, #size來分配。

調用結尾:

釋放棧空間。add sp, #size指令。恢複所儲存的寄存器。恢複R7將之前存放的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_startprocLfunc_begin0:  add r0, r1Ltmp0:  ldr.w  r12, [sp]  add r0, r2  ldr.w  r9, [sp, #4]  add r0, r3  add r0, r12  add r0, r9  bx lrLtmp2:Lfunc_end0:

_func:表示接下來是func函數的內容。Lfunc_begin0及Lfunc_end0標識函數定義的起止。函數起止一般是"xxx_beginx:"及"xxx_endx:"

下面來一行行代碼解釋:

add r0, r1 將參數a和參數b相加再把結果賦值給r0ldr.w r12, [sp] 把最的一個參數f從棧上裝載到r12寄存器add r0, r2 把參數c累加到r0上ldr.w r9, [sp, #4] 把參數e從棧上裝載到r9寄存器add r0, r3 累加d累加到r0add r0, r12 累加參數f到r0add 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_startprocLfunc_begin2:@ BB#0:  push  {r7, lr}  mov r7, sp  sub sp, #4  movs  r0, #55  movs  r1, #22Ltmp6:  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_startprocLfunc_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_startprocLfunc_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 lrLfunc_end0:

逐行解釋之:

add r0, r1         r0 += r1ldr.w r12, [sp]      把sp指向的內容load到r12寄存器。從圖(三)我們知道sp指向66,因此r12存的66add r0, r2         r0 += r2ldr.w r9, [sp, #4]    從圖(三) sp加4個位元組存的是55, r9存的55add r0, r3         r0 += r3add r0, r12        r0 += r12add r0, r9        r0 += r9。 至此r0-r4存的11,22,33,44,及棧上存的55,66想加存到了r0上。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

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.