你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS 逆向之ARM匯編

iOS 逆向之ARM匯編

編輯:IOS開發綜合
最近對iOS逆向工程很感興趣。   目前iOS逆向的書籍有: 《Hacking and Securing IOS Applications》, 《iOS Hacker's Handbook》中文書籍有《iOS應用逆向工程:分析與實戰》   中文博客有: 程序員念茜的《iOS安全攻防系列》 英文博客有:Prateek Gianchandani的iOS 安全系列博客   這些資料中都涉及到有ARM匯編,但都只是很泛地用到,並沒有對iOS上的ARM匯編進行比較詳細的講解。因此,經過一系列的學習對iOS下的ARM有了一定的理解。在此打算用幾篇博文記錄下來,備忘之,分享之, 限於本人水平有限,如有錯誤請不吝賜教。       我們先講一些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 ), 要說清楚要費點筆墨,參見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來標識的棧上的一塊一塊的存儲空間。棧幀包括:   參數區域(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文件,添加如下函數:   1 2 3 4 5 6 7 #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"等,這些是匯編器需要的,我們不用去管。把這些"."開頭的及注釋增掉後,代碼如下:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 _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:"   下面來一行行代碼解釋:   add r0, r1                 將參數a和參數b相加再把結果賦值給r0 ldr.w r12, [sp]           把最的一個參數f從棧上裝載到r12寄存器 add r0, r2                 把參數c累加到r0上 ldr.w r9, [sp, #4]       把參數e從棧上裝載到r9寄存器 add r0, r3                 累加d累加到r0 add r0, r12               累加參數f到r0 add r0, r9                 累加參數e到r0 至此,全部的a到f 共6個值全部累加到r0寄存器上。前面說了r0是存放返回值的。   bx lr: 返回調用函數。   -----------------------------------------------------------華麗的分割線-------------------------------------------------------------   實戰部分(二):   為了讓大家看清楚函數調用時棧上的變化,下面以一個有三個函數,兩個調用的C代碼的匯編代碼為例講解一下。   上代碼:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #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:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 _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: 還是一行行的解釋:   push {r7, lr}                      就是前面基礎知識部分說的函數調用的序言(prologs)部分的1, 2兩條,將lr, r7 存到棧上去 mov r7, sp                         序言(prolog)之3。 sub sp, #4                         在棧上分配一個4字節空間用來存放局部變量, 即參數。前面我們說過,r0-r3可以傳遞4個參數,但超過的只能通過棧來傳遞。 movs r0, #55                     把立即數55存入r0 movs r1, #22                     把22存入r1 str r0, [sp]                         把r0的值存入棧指針sp指向的內存。即棧上存了參數55 接下來三條指令 moves r0, #11   moves r2, #33   moves r3, #44  把相應的立即數存入指定的寄存器。  到目前為止,r0-r3分別存放了11, 22, 33,44共4個立即數參數,棧上存放了55這一個參數。 bl _fooFunction                   調用fooFunction, 調用後跳轉到fooFunction中的情況下面再分析。 add sp, #44                        棧指針向上移動4個字節,回收第3個指令 sub sp, #4分配的空間。 pop {r7, pc}                       恢復第一條指令push {r7, lr}到棧中的值, 把之前的lr值賦給pc。注意:在進入initFunction的時候lr是調用initFunction的函數的下一條指令,所以現在把當時的lr中的值賦給pc程序計數器,這樣執行lr指向的這一條指令,函數就反回了。 指令1,2, 3是函數序言(prologs),指令9, 10是結語(epilogs)。這基本上是一個套路,看多了自然就知道了,都不用停下來一條條分析。
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved