雖然前段時間ARM被日本軟銀收購了,但是科技是無國界的,所以呢ARM相關知識該學的學。現在看ARM指令集還是倍感親切的,畢竟大學裡開了ARM這門課,並且做了不少的實驗,當時自我感覺ARM這門課學的還是可以的。雖然當時感覺學這門課以後似乎不怎麼用的上,可曾想這不就用上了嗎,不過之前學的都差不多忘了,還得撿起來呢。ARM指令集是精簡指令集,從名字我們就能看出指令的個數比那些負責指令集要少一些。當然本篇所涉及的ARM指令集是冰山一角,不過也算是基礎,可以閱讀Hopper中的匯編了,實踐出真知,看多了自然而然的就會了。
一、Hopper中的ARM指令
ARM處理器就不多說了,ARM處理器因為低功耗等原因,所以大部分移動設備上用的基本上都是ARM架構的處理器。當然作為移動設備的Android手機,iPhone也是用的ARM架構的處理器。如果你想對iOS系統以及你的應用進一步的了解,那麼對ARM指令集的了解是必不可少的,ARM指令集應該也算得上是iOS逆向工程的基礎了。
當你使用Hopper進行反編譯時,裡邊全是ARM的指令,那是看的一個爽呢。下面就是使用Hopper打開MobileNote.app的一個Hopper的界面。從主窗口中可以看到全是ARM的指令呢,如果你對ARM指令不了解,那麼如何進行分析呢,對吧。所以對ARM指令的了解,是iOS逆向工程的基礎呢。今天這篇博客就總結一下ARM指令集的基礎指令。
Hopper的功能是非常強大的,在Hopper中你可以對ARM指令進行修改,並且生成一個新的可執行文件。當然Hopper強大的功能可以幫助你更好的理解ARM匯編語言的業務邏輯,Hopper會根據ARM匯編生成相關的邏輯圖,如下所示。從下方的邏輯圖中你就能清楚的看到相關ARM匯編的指令邏輯。紅線表明條件不成立時的跳轉,藍線則表明條件成立時的跳轉。
Hopper的功能強大到可以將ARM匯編生成相應的偽代碼,如果你看ARM指令不直觀的話,那麼偽代碼對你來說會更好一些。下方就是Hopper根據ARM指令生成的偽代碼,如下所示。
貌似有點跑偏了,今天的主題是ARM指令集,Hopper的東西就不做過多贅述了。
二、ARM指令集綜述
ARM指令主要是對寄存器,棧、內存的操作。寄存器位於CPU中,個數少速度快,ARM指令集中大部分指令都是對寄存器操作,但有些指令是對棧和內存的操作。下方會對操作棧、寄存器以及內存的指令進行介紹。
1.棧操作---- push 與pop
先簡單的聊一下棧的概念,“棧”說白了就是數據結構的一種,棧的數據結構具有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到pc (Program 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
4、寄存器的裝載和存儲
有時我們需要將內存中的數據裝載到寄存器中進行操作,或者將寄存器中運算後的數據存儲到內存中,此時我們就會用到寄存器的裝載和存儲的相關命令。下方就一一的總結了這些命令。
(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) = Rn; 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位。
5、比較、分支與條件指令
分支與條件指令是編程中不可或缺的指令,在處理一些特定的業務邏輯時會經常使用到分支與條件指令。分支說白了就是跳轉,而分支與條件結合使用就是當滿足一定條件後進行特定的跳轉。接下來,將總結一下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)
循環右移,見名知意,就是循環著往右移動,右邊移除的位往高位進行填補。
以上所述是小編給大家介紹的iOS逆向工程之Hopper中的ARM指令詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!