iOS逆向工程之Hopper中的ARM指令

雖然前段時間ARM被日本軟銀收購了,可是科技是無國界的,因此呢ARM相關知識該學的學。如今看ARM指令集仍是倍感親切的,畢竟大學裏開了ARM這門課,而且作了很多的實驗,當時自我感受ARM這門課學的仍是能夠的。雖然當時感受學這門課之後彷佛不怎麼用的上,可曾想這不就用上了嗎,不過以前學的都差很少忘了,還得撿起來呢。ARM指令集是精簡指令集,從名字咱們就能看出指令的個數比那些負責指令集要少一些。固然本篇所涉及的ARM指令集是冰山一角,不過也算是基礎,能夠閱讀Hopper中的彙編了,實踐出真知,看多了天然而然的就會了。編程

 

1、Hopper中的ARM指令數據結構

ARM處理器就很少說了,ARM處理器由於低功耗等緣由,因此大部分移動設備上用的基本上都是ARM架構的處理器。固然做爲移動設備的Android手機,iPhone也是用的ARM架構的處理器。若是你想對iOS系統以及你的應用進一步的瞭解,那麼對ARM指令集的瞭解是必不可少的,ARM指令集應該也算得上是iOS逆向工程的基礎了。架構

當你使用Hopper進行反編譯時,裏邊全是ARM的指令,那是看的一個爽呢。下面就是使用Hopper打開MobileNote.app的一個Hopper的界面。從主窗口中能夠看到全是ARM的指令呢,若是你對ARM指令不瞭解,那麼如何進行分析呢,對吧。因此對ARM指令的瞭解,是iOS逆向工程的基礎呢。今天這篇博客就總結一下ARM指令集的基礎指令。app

  

 

Hopper的功能是很是強大的,在Hopper中你能夠對ARM指令進行修改,而且生成一個新的可執行文件。固然Hopper強大的功能能夠幫助你更好的理解ARM彙編語言的業務邏輯,Hopper會根據ARM彙編生成相關的邏輯圖,以下所示。從下方的邏輯圖中你就能清楚的看到相關ARM彙編的指令邏輯。紅線代表條件不成立時的跳轉,藍線則代表條件成立時的跳轉函數

  

 

Hopper的功能強大到能夠將ARM彙編生成相應的僞代碼,若是你看ARM指令不直觀的話,那麼僞代碼對你來講會更好一些。下方就是Hopper根據ARM指令生成的僞代碼,以下所示。測試

  

貌似有點跑偏了,今天的主題是ARM指令集,Hopper的東西就不作過多贅述了。ui

 

2、ARM指令集綜述spa

ARM指令主要是對寄存器,棧、內存的操做。寄存器位於CPU中,個數少速度快,ARM指令集中大部分指令都是對寄存器操做,但有些指令是對棧和內存的操做。下方會對操做棧、寄存器以及內存的指令進行介紹。blog

 

1.棧操做---- pushpopip

先簡單的聊一下棧的概念,「棧」說白了就是數據結構的一種,棧的數據結構具備LIFO(last in first out) ---- 後進先出的特色。棧在ARM中所指的實際上是一塊具備棧數據結構特色內存區。棧中主要用來暫存寄存器中的值得,好比R0寄存器正在使呢,但是如今有一個優先級比較高的函數要使用R0, 那麼就先把R0的值Push到棧中暫存,而後等R0被優先級更高的函數使用完畢後在從棧中Pop出以前的值。在函數調用時通常會對棧進行操做。

對棧操做的命令就是push和pop了,通常會成對出現,在函數開始時將該函數執行時要使用的寄存器中的值push入棧,而後在函數結束時將以前push到棧中的值在pop到相應的寄存器中。

下方就是push和pop的用法的一個實例。在下方函數開始執行前,將該函數要使用的寄存器r4, r5, r7, lr使用push進行入棧操做,lr是該函數執行後要返回的地址。在函數執行完畢後,使用pop命令將函數執行前入棧的值在pop到相應的寄存器中。有一點須要注意的是將lr寄存器中的值在函數結束後pop到pcProgram Counter)寄存器中,pc寄存器中存儲的是將要執行的命令的地址。這樣一來,函數執行後就會返回到以前執行的地址上繼續執行。

  

 

2. pc寄存器中的中的標誌位

此處咱們以32位指令爲例,pc寄存器中的後四位是標誌位,第28 - 31位分別對應着V (oVerflow),C (Carry),Z (Zero),N (Negative)。下面分別來介紹一下這四種符號所表示的狀態。

  • N (Negative): 若是結果是負數則置位。
  • Z (Zero): 若是結果是零則置位。
  • C (Carry): 若是有進位則置位。
  • V (Overflow): 在發生溢出的時候置位。

 

3. 命令操做符

下方是ARM指令集中經常使用的算術操做:

(1)加法操做

  • ADD R0, R1, R2       ; R0 = R1 + R2
    • 上面的命令就比較簡單,就是講兩個數值進行相加。
  • ADC R0, R1, R2       ; R0 = R1 + R2 + C (Carry)
    • 帶進位的加法,ADC將把兩個操做數加起來,並把結果放置到目的寄存器中。ADC使用了C--進位標誌,這樣就能夠作比32位大的加法了。下方就是128位的數字進行加法操做的彙編代碼。
    • 咱們如今要對一個128位的數字進行加法操做,由於咱們使用的是32位的寄存器,因此要存儲一個128位的數字,咱們須要4個(128 / 32 = 4)寄存器。因此咱們假設R0,R1,R2,R3寄存器中分別由低到高存儲着第一個數字,而R4, R5, R6, R7存儲着第二個數字。下方就是兩個128數字相加操做的ARM彙編指令。咱們將結果存儲在R8, R9, R10, R11這四個寄存器中。首先咱們執行的是將兩個數的最低位相加並設置C標誌位(ADDS R8, R0, R4),而後在進行下一位的操做,對R1和R5中的值進行相加,在相加後再加上上次操做的進位,而後再設置標誌位,以此類推。這樣咱們最終的值就存儲在了R8-R11這四個寄存器中。

     

(2)減法操做

  • SUB R0, R1, R2       ; R0 = R1 - R2
    • 這個命名比較簡單,就是使用R1寄存器中的值減去R2寄存器中的值,而後存儲到R0中。
  • SBC R0, R1, R2       ; R0 = R1 - R2 - !C
    • 帶借位的減法,假如咱們當前的寄存器是32Bit, 若是兩個64bit的數值進行減法操做就要使用到SBC借位操做。由於當兩個數值在進行減法操做時,若是須要借位時就會把C標誌位進行清零操做,因此在進行SBC操做時須要將C標誌位進行取反操做。下面咱們一128位數值相減爲例。該實例與上述的ADC命令相似,在此就不作過多贅述了。

     

  • RSB R0, R1, R2       ; R0 = R2 - R1
    • 反向減法
  • RSC R0, R1, R2       ; R0 = R2 - R1 - !C
    • 帶借位的反向減法,上面這兩個命令與SUB和SBC命令差很少,都是進行減法操做的,不過操做數的計算順序不一樣。

(3)、乘法指令

在ARM指令集中,乘法指令有兩種第一個是MUL, 第二個是帶累加的乘法MLA。固然,這兩個指令使用起來都不復雜。

  • MUL: 乘法指令 MUL{條件}{S} R0, R1, R2     ;R0 = R1 * R2
  • MLA: 乘法累加指令 MLA{條件}{S} R0, R1, R2, R3   ;R0 = R1 * R2 + R3

 

(4)、邏輯操做

邏輯操做比較好理解一些,與咱們編程中使用的邏輯操做大同小異,無非是一些與、或、非、異或這些操做。

  • AND R0, R1, R2       ; R0 = R1 & R2 
    • 與操做, 1 & 1 = 1, 1 & 0 = 1, 0 & 1 = 1,0 & 0 = 0
  • ORR R0, R1, R2       ; R0 = R1 | R2
    • 或操做, 1 | 1 = 1, 1 | 0 = 1, 0 | 1 = 1, 0 | 0 = 0
  • EOR R0, R1, R2       ; R0 = R1 ^ R2
    • 異或,1 ^ 1 =  1,  1 ^ 0 = 0,  0 ^ 1 = 0, 0 ^ 0 = 1;
  • BIC R0, R1, R2         ; R0 = R1 &~ R2
    • 位清除指令,現將R2進行取反,而後再與R1進行與操做。R1 & (~R2)
    • 將R0的後四位清零:BIC R0, R0,#0x0F
  • MOV R0, R1      ;R0 = R1
    • 賦值操做,將R1的值賦給R0
  • MVN R0, R1      ;R0 = ~R1
    • 按位取反操做,將R1的每一位進行取反操做,而後賦值給R0

 

四、寄存器的裝載和存儲

有時咱們須要將內存中的數據裝載到寄存器中進行操做,或者將寄存器中運算後的數據存儲到內存中,此時咱們就會用到寄存器的裝載和存儲的相關命令。下方就一一的總結了這些命令。

(1)、傳送單一數據

LDR{條件} Rd, <地址>   ;將地址中的數據加載到Rd寄存器中

STR{條件} Rd, <地址>   ;將寄存器Rd中的數值存儲到<地址>中的內存中

LDR{條件}B  Rd, <地址>   ;將內存地址所對應值得低8位加載到Rd的寄存器中。

STR{條件}B  Rd, <地址>   ;將寄存器Rd的後8爲存的到內存地址中。

  • LDR (Load Register) : 將數據從內存中取出,加載到寄存器。
    • LDR Rt, [Rn], #offset   ;Rt = *Rn; Rn = Rn + offset
    • LDR Rt, [Rn, #offset]!  ; Rt = *(Rn + offset); Rn = Rn + offset

 

  • STR (Store Register): 將寄存器中的數據,存儲到內存。
    • STR Rt, [Rn], #offset   ;*Rn = Rt; Rn = Rn + offset
    • STR Rt, [Rn, #offset]!  ;*(Rn + offset) = Rt; Rn = Rn + offset(地址回寫)

 

(2)、一次傳送兩個數據

  • LDRD (Load Register Double): 一次填充兩個寄存器
    • LDRD R4, R5, [R6, #offset]    ;R4 = *(R6 + offset); R5 = *(R6 + offset + 4)

 

  • STRD (Store Register Double):一次存儲兩個值到內存
    • STRD R4, R5, [R6, #offset]    ;*(R6 + offset) = R4; *(R6 + offset + 4) = R5

 

(3)、塊數據存取 

  • LDM (Load Mutiple): 將一塊數據從寄存器中加載到內存中(reg list)。
  • STM (Store Multiple): 將塊數據從內存中加載到寄存器。
  • LDM與STM塊內存操做都有一個後綴,下方就是這四種條件,咱們假設下方R0寄存器中存儲的值是0(R0 = 6)
    • IA (Increment After): 傳輸後再增長值,
      • 如:LDMIA R0, {R1 - R3}    ;R1 = 6, R2 = 7, R3 = 8
    • IB (Increment Befor): 傳輸前增長值
      • 如:LDMIB R0, {R1 - R3}    ;R1 = 7, R2 = 8, R3 = 9
    • DA (Decrement After):傳輸後減小值
      • 如: LDMDA R0, {R1 - R3}    ;R1 = 6, R2 = 5, R3 = 4
    • DB (Decrement Before):傳輸前減小值
      • 如:LDMDB R0, {R1 - R3}    ;R1 = 5, R2 = 4, R3 = 3

 

(4)、單一數據交換:SWP

SWP命令用來交換寄存器與內存直接的值,下方是SWP的指令格式:

SWP{條件}{B} Rd, Rm, [Rn]

上述命令表示將Rn中內存地址所指向內存中的數據加載到Rd中,而後將寄存器Rm中的值存儲到該內存地址指向的區域中。若是Rd = Rm, 那麼Rn指向的內存中的值就會與Rd進行交換。若是加上條件後綴的話,就說明在知足該條件時進行操做,後綴B則是操做低8位。

 

五、比較、分支與條件指令

分支與條件指令是編程中不可或缺的指令,在處理一些特定的業務邏輯時會常用到分支與條件指令。分支說白了就是跳轉,而分支與條件結合使用就是當知足必定條件後進行特定的跳轉。接下來,將總結一下ARM指令集中經常使用的分支指令與條件指令,更確切的說是條件後綴。

(1)、比較指令

在ARM指令集中使用到的比較指令有CMN、CMP、TEQ、TST。有一點須要注意的是CMN與CMP是算術指令TEQ和TST屬於邏輯指令。比較指令在執行後老是會設置標誌位(N、Z、C、V), 由於條件後綴是根據被設置的標誌位來判斷比較結果是否知足條件的。下方會給出詳細的條件後綴。比較命令後方也是能夠添加條件後綴的。

  • CMN (Compare Negative) ---- 比較負值, CMN相同於CMP, 但他容許你對負值進行比較
    • CMN R0, R1       ;Status = R0 - R1
  • CMP (Compare) ---- 之因此說CMP,CMN指令是算術指令,是由於他們講操做數進行減法操做,而且設置相應的標誌位,可是不記錄計算結果。CMN與CMP進行的是算術減法操做,因此會影響C -- Carry標誌。
    • CMP R0, R1       ;Status = R0 - R1
  • TEQ (Test Equivalence) ---- 測試等價,TEQ對操做數進行異或(EOR)邏輯操做,來判斷兩個操做數是否相同。由於TEQ作的是異或運算,因此不會影響Carry標誌位。
    • TEQ R0, R1           ;Status = R0 EOR R1
  • TST (Test bits) ---- 測試位,使用TST命令來檢查是否設置了特定的位。TST命中令實際上是將兩個操做數進行按位與(AND)操做,將結果存儲在標誌位中。可使用TST來測試寄存器中某些位的特定值。
    • TST R0, R1        ;Status = R0 AND R1

(2)、分支指令

經常使用的分支指令是B、BL、BX這三個指令。

  • B Lable ;該指令表示將PC設置成Lable, 而PC就是指向下一條將要執行的指令,因此B Lable執行後,接下來就會跳轉到Label出進行下一條命令的執行。
  • BL Label ; 執行該指令說明將LR設置成PC - 4, 而後再將PC設置成Lable。在執行BL Lable這條命令時,PC中存儲的就是當前BL這條命令,而PC - 4就是上一條指令的地址,將PC - 4賦值給LR,也就是記錄下跳轉執行完指令後要返回的地址。若是BL在添加上一些條件,那麼BL{條件}就能夠進行循環了。
  • BX Rd ; 該指令說明將Rd賦值給PC, 而後切換指令集(如從ARM指令集切換到Thumb指令集)。

 

(3)、條件後綴

上述的分支指令與條件後綴結合才能發揮其強大的功能和做用,解析這部分介紹的是就是咱們的條件後綴。條件後綴不能單獨的使用,要和其餘命令一塊結合使用,而後根據條件的結果來作一些操做。下方是全部條件後綴,條件是否成立是根據NZCV這四個標誌位來判斷的,由於咱們在對一些數值進行比較時,會設置相應的標誌位。而後咱們就可使用這些標誌位來判斷條件是否成立。NZCV就是咱們以前所提到的幾個標誌位,Z(是否爲零), C(是否進位), N(是否爲負), V(是否溢出)四種標準位來判斷的。

  • EQ: Equal   等於,(Z = 1)
  • NE: Not Equal   不等於 (Z = 0)
  • CS: Carry Set   有進位 (C = 1)
  • HS: (unsigned Higher Or Same) 同CS (C = 1)
  • CC: (Carry Clear) 沒有進位 (C = 0)
  • LO: (unsigned Lower) 同CC  (C = 0)
  • MI: (Minus) 結果小於0  (N = 1)
  • PL: (Plus) 結果大於等於0 (N = 0)
  • VS: (oVerflow Set) 溢出 (V = 1)
  • VC: (oVerflow Clear) 無溢出 (V = 0)
  • HI : (unsigned Higher) 無符號比較,大於 (C = 1 & Z = 0)
  • LS: (unsigned Lower or Same) 無符號比較,小於等於 (C = 0 & Z = 1)
  • GE: (signed Greater than or Equal) 有符號比較,大於等於 (N = V)
  • LT: (signed Less Than) 有符號比較,小於  (N != V)
  • GT: (signed Greater Than) 有符號比較,大於 (Z = 0 & N = V)
  • LE: (signed Less Than or Equal) 有符號比較,小於等於 (Z = 1 | N != V)
  • AL: (Always) 無條件,默認值
  • NV: (Never) 從不執行

 

6. 移位操做(LSL、ASL、LSR、ASR、ROR、RRX)

移位操做在ARM指令集中不做爲單獨的命令使用,它在指令格式中是一個字段。接下來將會介紹一下各類移位操做。若是你以前學過「數字電路」這門課的話,那麼你確定對這些移位操做並不陌生。

(1)、LSL ---- 邏輯左移(Logical Shift Left)與 ASL ---- 算術左移 (Arithmetic Shift Left)

邏輯左移與算術左移的操做是同樣的,都是將操做數向左移位,低位補零,移除的高位進行丟棄。接下來咱們來看一個示例,根據這個示例來看一下LSL或者ASL的工做方式。

MOV  R0, #5

MOV  R1, R0, LSL #2

上述命令,就是將5存儲到R0寄存器上(R0 = 5), 而後將R0邏輯左移2位後傳送到R1寄存器中。十進制5的二進制數值是0101,進行邏輯左移2位就是0001_0100, 也就是十進制中的20。其實沒邏輯左移1位就至關於原數值進行乘2操做,5邏輯左移2位其實就是5 x 2^2 = 20。下方是該操做的原理圖

  

 

(2)、LSR ---- 邏輯右移(Logical Shift Right)

邏輯右移與邏輯左移是相對的,邏輯右移其實就是往右移位,左邊補零。用法與LSL相似,在此就不作過多贅述了。

 

(3)、ASR ---- 算術右移(Arithmetic Shift Right)

ASR與LSR相似,惟一不一樣的是,LSR的高位補零,而ASR的高位補符號位。符號位爲1,那麼就補1,符號位爲0那麼就補零。

 

(4)、ROR ---- 循環右移(Rotate Right)

循環右移,見名知意,就是循環着往右移動,右邊移除的位往高位進行填補。

 

今天的博客就先到這吧,畢竟篇幅有限,上面的命令是一些基本命令。其餘的浮點指令在此就不作過多贅述了,如ABS--絕對值、ACS--反餘弦、ASN--反正弦等等,就留個讀者本身去看吧。

相關文章
相關標籤/搜索