標籤:
1. ARM彙編基礎
在逆向一個功能的時候,往往需要分析大量的彙編代碼,在iOS逆向中,ARM彙編是必須掌握的語言,本文總結了ARM彙編的基礎知識,如果你想瞭解更多,請參考狗神的小黃書《iOS逆向逆向工程》或ARM官方手冊.
1.1 寄存器,記憶體和棧
在ARM彙編裡,操作對象是寄存器,記憶體和棧
ARM的棧遵循先進後出,是滿遞減的,向下增長,也就是開口向下,新的變數被存到棧底的位置;越靠近棧底,記憶體位址越小
一個名為stackPointer的寄存器儲存棧的棧底地址,成為棧地址.
可以把一個變數給入棧(push)以儲存它的值,也可以讓它出(pop棧),恢複變數的原始值.在實際操作中,棧地址會不斷變化;但是在執行一塊代碼的前後,棧地址應該是不變的,不然程式就要出問題,
1.2 特殊用途的寄存器
ARM處理器中的部分寄存器有特殊用途 如下所示:
| 寄存器 |
用途 |
| R0-R3 |
傳遞參數與傳回值 |
| R7 |
幀指標,指向母函數於被調用子函數在棧中的交接 |
| R9 |
在iOS3.0以前被系統保留 |
| R12 |
內部程序呼叫儲存空間,dynamic linker會用到它 |
| R13 |
sp寄存器 |
| R14 |
LR寄存器,儲存函數返回地址 |
| R15 |
PC寄存器 |
1.3 分支跳轉與條件判斷
處理器名為”Program counter”(簡稱PC)的寄存器用於存放下一條指令的地址.一般情況下,電腦一條接一條地順序執行指令,處理器執行完一條指令後將PC加1,讓它指向下一條指令.(1-1)
處理器順序執行指令1到指令5(2-2),但是如果把PC的值變一變,指令執行的順序就完全不同
指令執行順序被打亂,變成了指令1,指令5,指令4,指令2,指令3,指令6,這種亂序的學名叫做”分支”,或者”跳轉”,它使迴圈和subroutime成為可能,例如:
```// endless() 函數endless: 操作 運算元1, 運算元2 分支 endless 返回 // 死迴圈,執行不到這裡啦!```
在實際情況中,滿足一定條件才得以觸發的分支是最實用的,這種分支成為條件分支.if else 和 while都是基於條件分支實現的,在ARM彙編中,分支的條件一般有4種:
- □ 操作結果為0(或不為0);
- □ 操作結果為負數;
- □ 操作結果有進位;
- □ 運算溢出(比如兩個正數相加得到的數超過了寄存器位元).
這些條件的判斷準則(falg)存放在程式狀態寄存器(Program Status Register,PSR)中,資料處理相關指令會改變這些flag,分支指令再根據這些flag決定是否跳轉.下面的虛擬碼展示了一個for迴圈
for: 相加 A,#1 比較 A,#16 不為0則跳轉到for /* 此迴圈將A和#16作比較,如果兩者不相等,則將A加1,繼續比較. 如果兩者相等,則不再迴圈,繼續往下執行. */
2. ARM/THUMB指令解讀
ARM處理器用到的指令集分為ARM和THUMB兩種:ARM指令長度均為32bit,THUMB指令長度為16bit.所有指令可大致分為三類,分別為,數組操作指令,記憶體操作指令和分支指令.
2.1 資料操作指令
資料操作指令有以下2條規則:
* 所有的運算元均為32bit;
* 所有的結果均為32bit,且只能存放在寄存器當中.
總的來說,資料操作指令的基本格式是:cp{cond}{s} Rd,Rn,Op2
其中,”cond”和”s”是另個可懸尾碼;”cond”的作用是指定指令”op”在什麼條件下執行,共有17中條件:
| 指令 |
條件 |
| EQ |
結果為0(EQual to 0) |
| NE |
結果不為0(Not Equal to 0) |
| CS |
有進位或借位(Carry Set) |
| HS |
同CS(unsigned Higer or Same) |
| CC |
沒有進位或借位(Carry Clear) |
| LO |
同CC(unsigned LOwer) |
| MI |
結果小於0(MInus) |
| PL |
結果大於等於0(PLus) |
| VS |
溢出(Overflow Set) |
| VC |
無溢出(Overflow Clear) |
| HI |
無符號比較大於(unsigned HIger) |
| LS |
無符號比較小於等於(unsigned Lower or Same) |
| GE |
有符號比較大於等於(signed Greater than or Equal) |
| LT |
有符號比較小於(signed Less Than) |
| GT |
有符號比較大於(signed Greater Than) |
| LE |
有符號比較小於等於(signed Less than or Equal) |
| AL |
無條件(Always,預設) |
“cond”的用法很簡單,例如:
比較 R0, R1移動 GE R2, R0移動 LT R2, R1
比較R0和R1的值,如果R0大於等於R1,則R2 = R0;否則R2 = R1.
“s”的作用是指定指令”op”是否設定了flag,共有下面4中flag:
N(Negative)
如果結果小於0則置1,否則置0;
Z(zero)
如果結果是0則置1,否則置0;
C(Carry)
對於加操作(包括CMN)來說,如果產生進位則置1,否則置0;對於減操作(包括CMP來說),Carry相當於Not-Borrow,如果產生借位則置0,否則置1;對於有移位的非加/減操作來說,C置移出值得最後一位;對於其他的非加/減操作來說,C的值一般不變;
V(overflow)如果操作導致溢出,則置1,否則置0
需要注意一點的是,C flag表示無符號數運算結果是否溢出;V flag表示有符號數運算結果是否溢出.
算數操作指令可以大致分為4類:
ADD R0,R1,R2; ——————> R0 = R1 + R2
ADC R0,R1,R2; ——————> R0 = R1 + R2 + C(array)
SUB R0,R1,R2; ——————> R0 = R1 - R2
SBC R0,R1,R2; ——————> R0 = R1 - R2 - !C
RSB R0,R1,R2; ——————> R0 = R2 - R1
RSC R0,R1,R2; ——————> R0 = R2 - R1 - !C
算數操作中,ADD和SUB為基礎操作,其他均為兩者的變種.RSB是”Reverse Sub”的縮寫,僅僅是把SUB的兩個運算元調換了位置而已;以”C”結尾的變種代表沒有進位和借位的加減法,當產生進位或者借位時,將Carrry flag 置為1.
AND R0,R1,R2; ——————> R0 = R1 & R2
ORR R0,R1,R2; ——————> R0 = R1 | R2
EOR R0,R1,R2; ——————> R0 = R1 ^ R2
BIC R0,R1,R2; ——————> R0 = R1 &~ R2
MOV RO,R2; ——————> R0 = R2
MVN R0,R2; ——————> R0 = ~R2
邏輯操作指令都已經用C操作符說明了作用,但是C操作符裡的移位操作並沒有對位的邏輯操作指令,ARM採用了桶式移位,共有四種指令:
LSL 邏輯左移
LSR 邏輯右移
ASR 算術右移
ROR 迴圈右移
- 3.比較操作
CMP R1,R2; ——————> 執行R1 - R2並依結果設定flag
CMN R1,R2; ——————> 執行R1 + R2並依結果設定flag
TST R1,R2; ——————> 執行R1 & R2並依結果設定flag
TEQ R1,R2; ——————> 執行R1 ^ R2並依結果設定flag
比較操作其實就是改變flag的算術操作或邏輯操作,只是操作結果不保留在寄存器裡而已.
MUL R4,R3,R2 ——————> R4 = R3 * R2
MLA R4,R3,R2,R1 ——————> R4 = R3 * R2 + R1
乘法操作的運算元必須來自寄存器
2.2 記憶體操作指令
記憶體操作指令的基本格式是:
op{cond}{type} Rd,[Rn,Op2]
其中Rn是基底位址暫存器,用於存放基地址;”cond”的作用與資料操作指令相同;”type”指定指令”op”操作的資料類型,共有四種:
B(unsigned Byte)無符號byte(執行時擴充到32bit,以0填充);SB(signed Byte)有符號byte(僅用於LDR指令;執行時擴充到32bit,以符號位填充);H(unsigned Halfword)無符號halfword(執行時擴充到32bit,以0填充);SH(Signed Halfword)有符號halfword(僅用於LDR指令;執行時擴充到32bit,以符號位填充).
如果不指定”type”,則預設是word
ARM記憶體操作基礎指令只有2個,LDR(loaD Register)將資料從記憶體中讀出來,存到寄存器中;STR(STore Register)將數組從寄存中讀出來,存到記憶體中.兩個指令的使用方式如下:
LDR Rt,[Rn {,#offset}] ; Rt = *(Rn {+ offset}),{}代表可選LDR Rt,[Rn, #offset]! ; Rt = *(Rn + offset);Rn = Rn + offsetLDR Rt,[Rn], #offset ; Rt = *Rn;Rn = Rn + offset
STR Rt,[Rn {,#offset}] ; *(Rn {+ offset}) = RtSTR Rt,[Rn, #offset]! ; *(Rn + offset) = Rt; Rn = Rn + offsetSTR Rt,[Rn], #offset ; *Rn = Rt; Rn = Rn + offset
此外,LDR和STR的變種LDRD和STRD還可以操作雙字(DoubleWord),即一次性操作兩個寄存器,其基本格式如下:op{cond} Rt,Rt2, [Rn {, #offset}]
其用法與原型類似,如下:
SRTD R4,R5, [R9,#offset] ; *(R9 + offset) = R4;*(R9 + offset + 4) = R5
LDRD R4,R5,[R9,#offset] ; R4 = *(R9 + offset); R5 = *(R9+offset+4)
除LDR和STR外,還可以通過LDM(LoaD Multiple)和STM(STore Multipe)進行塊傳輸,一次性操作多個寄存器.塊傳輸指令的基本格式是
op{cond}{}mode] Rd{!},reglist
其中Rd是基底位址暫存器,可選的”!”制定Rd變化後的值是否寫會Rd, reglist是一系列寄存器,用大括弧括起來,它們之間可以用”,”分割,也可以用”-“表示一個範圍,比如,{R4-R6,R8}表示寄存器,R4,R5,R6,R8;這些寄存器的順序是按照自身的編號由小到大排列的,與大括弧內的排列順序無關.
需要特別注意的是,LDM和STM的操作方向與LDR和STR完全相反:LDM是把從Rd開始,地址連續的記憶體資料存入reglist中,STM是把reglist中的值存入從Rd開始,地址連續的記憶體中.此處特別容易混淆
“cond” 的作用與資料操作指令相同.”mode”指定R4值得變化的4中規律,如下所示:
IA(Increament After)每次傳輸後增加Rd的值;IB(Increament Before)每次傳輸前增加Rd的值DA(Decrement After) 每次傳輸後減少Rd的值;DB(Decreament Before)每次傳輸前減少Rd的值.
這是什麼意思呢?下面以LDM為代表,舉一個簡單的例子,相信大家一看就明白了.在(塊傳輸指令類比環境)中,R0指向的值是5.
在執行以下命令後,R4,R5,R6的值分別變成:
foo():LDMIA R0, {R4 - R6}; R4 = 5, R5 = 6, R6 = 7LDMIB R0, {R4 - R6}; R4 = 6, R5 = 7, R6 = 8LDMDA R0, {R4 - R6}; R4 = 5, R5 = 4, R6 = 2LDMDB R0, {R4 - R6}; R4 = 4, R5 = 3, R6 = 3
STM指令的作用方式與此類似,不再贅述.LDM和STM的操作與LDR和STR完全相反
2.3 分支指令
分支指令可以分為無條件分支和條件分支兩種.
B Label;PC = LabelBL Label;LR = PC - 4;PC = LabelBX Rd ;PC = Rd並切換指令集eg:foo(): B Label ; 跳轉到Label處並往下執行 ...... ; 得不到執行Label: ......
跳轉分支的cond是依照掐面的flag來判斷的,它們的對應關係如下:
| cond |
flag |
| EQ |
Z = 1 |
| NE |
Z = 0 |
| CS |
C = 1 |
| HS |
C = 1 |
| CC |
C = 0 |
| LO |
C = 0 |
| MI |
N = 1 |
| PL |
N = 0 |
| VS |
V = 1 |
| VC |
V = 0 |
| HI |
C = 1 & Z = 0 |
| LS |
C = 0 |
| GE |
N = V |
| LT |
N != V |
| GT |
Z = 0 & N = V |
| LE |
Z = 1 |
在條件分支指令錢會有一條資料操作指令來設定flag,分支指令根據falg的值來決定代碼走向,舉例如下:
Label:Lable1: LDR R0, [R1], #4 CMP R0, 0; 如果R0 == 0,Z =1 ; 否則Z = 0 BNE Label ; Z == 0則跳轉
2.4 THUMB指令
THUMB指令集是ARM指令集的一個子集,每條THUMB指令均為16bit;因此THUMB指令比ARM指令更節省空間的,且在16位元據匯流排上的傳輸效率更高.有得必有失,除了”b”之外,所有的THUMB指令均無法條件執行;桶式移位無法結合其他指令執行;大多數THUMB指令只能使用R0-R7這8個寄存器等.相對於ARM指令,THUMB指令的特點如下:
- 指令數量減少
- 沒有條件執行
- 所有指令預設附帶*
- 桶式移位無法結合其他指令執行
- 寄存器使用受限
- 立即數和第二運算元使用有限
- 不支援資料寫回
ARM彙編基礎(iOS逆向)