前言
今天開始學習逆向(掉頭發)啦!
在逆向開發中,非常重要的一個環節就是靜態分析.首先我們是逆向iOS系統上面的APP.那么我們知道,一個APP安裝在手機上面的可執行文件本質上是二進制文件.因為iPhone手機本質上執行的指令是二進制.是由手機上的CPU執行的.所以靜態分析是建立在分析二進制上面.所以今天我們接下來的課程從非常基礎的東西開始講解.
一 、 匯編初識
我們的代碼在終端設備上是這樣的過程:
-
匯編語言
與機器語言
一一對應,每一條機器指令都有與之對應的匯編指令
-
匯編語言
可以通過編譯得到機器語言
,機器語言
可以通過反匯編
得到匯編語言
-
高級語言
可以通過編譯得到匯編語言 \ 機器語言
,但匯編語言\機器語言幾乎不可能還原成
高級語言
匯編語言的特點
可以
直接訪問
、控制
各種硬件設備,比如存儲器、CPU等,能最大限度地發揮硬件的功能
能夠不受編譯器的限制,對生成的二進制代碼進行完全的控制
目標代碼簡短,占用
內存少
,執行速度快
匯編指令是機器指令的助記符,同機器指令一一對應。每一種CPU都有自己的機器指令集\匯編指令集,所以匯編語言不具備可移植性
知識點過多,開發者需要對CPU等硬件結構有所了解,不易于編寫、調試、維護
不區分大小寫,比如mov和MOV是一樣的
匯編的用途
- 編寫驅動程序、操作系統(比如Linux內核的某些關鍵部分)
- 對性能要求極高的程序或者代碼片段,可與高級語言混合使用(內聯匯編)
- 軟件安全
- 病毒分析與防治
逆向
\加殼\脫殼\破解\外掛\免殺\加密解密\漏洞\黑客- 理解整個計算機系統的最佳起點和最有效途徑
- 為編寫高效代碼打下基礎
- 弄清代碼的本質
- 函數的本質究竟是什么?
- ++a + ++a + ++a 底層如何執行的?
- 編譯器到底幫我們干了什么?
- DEBUG模式和RELEASE模式有什么關鍵的地方被我們忽略
越底層越單純!真正的程序員都需要了解的一門非常重要的語言,匯編!
匯編語言的種類
-
目前討論比較多的匯編語言有
- 8086匯編(8086處理器是16bit的CPU)
- Win32匯編
- Win64匯編
-
ARM匯編
(嵌入式、Mac
、iOS
)
......
我們iPhone里面用到的是ARM匯編,但是不同的設備也有差異.因CPU的架構不同.
架構 | 設備 |
---|---|
armv6 | iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch |
armv7 | iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 |
armv7s | iPhone5, iPhone5C, iPad4(iPad with Retina Display) |
arm64 | iPhone5S 以后 iPhoneX , iPad Air, iPad mini2以后 |
二、幾個必要的常識
- 要想學好匯編,首先需要了解CPU等硬件結構
- APP/程序的執行過程
- 硬件相關最為重要是CPU/內存
- 在匯編中,大部分指令都是和CPU與內存相關的
2.1 總線
- 每一個CPU芯片都有許多管腳,這些管腳和總線相連,CPU通過總線跟外部器件進行交互
- 總線:一根根導線的集合
- 總線的分類
- 地址總線
- 數據總線
- 控制總線
舉個例子:
- 地址總線
- 它的寬度決定了
CPU
的尋址能力
- 8086的地址總線寬度是20,所以尋址能力是1M( 220 )
- 它的寬度決定了
- 數據總線
- 它的
寬度
決定了CPU
的單次數據傳送量
,也就是數據傳送速度
; - 8086的數據總線寬度是16,所以單次最大傳遞2個字節的數據;
- 它的
- 控制總線
- 它的寬度決定了CPU對其他器件的控制能力、能有多少種控制;
小練習:
2.2 內存
內存地址空間的大小受CPU地址總線寬度的限制。8086的地址總線寬度為20,可以定位220個不同的內存單元(內存地址范圍0x00000~0xFFFFF),所以8086的內存空間大小為1MB
0x00000~0x9FFFF:主存儲器。可讀可寫
0xA0000~0xBFFFF:向顯存中寫入數據,這些數據會被顯卡輸出到顯示器。可讀可寫
0xC0000~0xFFFFF:存儲各種硬件\系統信息。只讀
2.3 進制
進制的定義
- 八進制由8個符號組成:0 1 2 3 4 5 6 7 逢八進一
- 十進制由10個符號組成:0 1 2 3 4 5 6 7 8 9逢十進一
- N進制就是由N個符號組成:
逢 N 進 一
2.4 數據的寬度
數學上的數字,是沒有大小限制的,可以無限的大。但在計算機中,由于受硬件的制約,數據都是有長度限制的(我們稱為數據寬度),超過最多寬度的數據會被丟棄。
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int test(){
int cTemp = 0x1FFFFFFFF;
return cTemp;
}
int main(int argc, char * argv[]) {
printf("%x\n",test());
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
計算機中常見的數據寬度
- 位(Bit): 1個位就是1個二進制位.0或者1
- 字節(Byte): 1個字節由8個Bit組成(8位).內存中的最小單元Byte.
- 字(Word): 1個字由2個字節組成(16位),這2個字節分別稱為高字節和低字節.
- 雙字(Doubleword): 1個雙字由兩個字組成(32位)
那么計算機存儲數據它會分為有符號數
和無符號數
.那么關于這個看圖就理解了!
無符號數,直接換算!
有符號數:
正數: 0 1 2 3 4 5 6 7
負數: F E D B C A 9 8
-1 -2 -3 -4 -5 -6 -7 -8
三、CPU&寄存器
內部部件之間由總線連接
CPU除了有控制器、運算器還有寄存器。其中寄存器的作用就是進行數據的臨時存儲。
CPU的運算速度是非常快的,為了性能CPU在內部開辟一小塊臨時存儲區域,并在進行運算時先將數據從內存復制到這一小塊臨時存儲區域中,運算時就在這一小快臨時存儲區域內進行。我們稱這一小塊臨時存儲區域為寄存器。
-
對于arm64系的CPU來說, 如果
寄存器以x開頭則表明的是一個64位的寄存器
,如果以w開頭則表明是一個32位的寄存器
,在系統中沒有提供16位和8位的寄存器供訪問和使用。其中32位的寄存器是64位寄存器的低32位部分并不是獨立存在的
。- 對程序員來說,
CPU中最主要部件是寄存器
,可以通過改變寄存器的內容來實現對CPU的控制
- 不同的CPU,寄存器的個數、結構是不相同的
- 對程序員來說,
浮點和向量寄存器
因為浮點數的存儲以及其運算的特殊性,CPU中專門提供浮點數寄存器來處理浮點數
-
浮點寄存器 64位
: D0 - D31 32位: S0 - S31
現在的CPU支持向量運算.(向量運算在圖形處理相關的領域用得非常的多)為了支持向量計算系統了也提供了眾多的向量寄存器.
-
向量寄存器
128位:V0-V31
通用寄存器
-
通用寄存器
也稱數據地址寄存器
通常用來做數據計算的臨時存儲
、做累加
、計數
、地址保存
等功能。定義這些寄存器的作用主要是用于在CPU指令中保存操作數
,在CPU中當做一些常規變量
來使用。 - ARM64 擁有有 32個64位的通用寄存器 x0 到 x30,以及XZR(零寄存器), 這些通用寄存器有時也有特定用途。
- 那么w0 到 w28 這些是32位的. 因為64位CPU可以兼容32位. 所以可以只使用64位寄存器的低32位.
- 比如 w0 就是 x0的低32位!
注意:
了解過8086匯編的同學知道,有一種特殊的寄存器段寄存器:CS,DS,SS,ES四個寄存器來保存這些段的基地址,這個屬于Intel架構CPU中.在ARM中并沒有
- 通常,CPU會先將內存中的數據存儲到通用寄存器中,然后再對通用寄存器中的數據進行運算
- 假設內存中有塊紅色內存空間的值是3,現在想把它的值加1,并將結果存儲到藍色內存空間
- CPU首先會將紅色內存空間的值放到X0寄存器中:mov X0,紅色內存空間
- 然后讓X0寄存器與1相加:add X0,1
- 最后將值賦值給內存空間:mov 藍色內存空間,X0
pc寄存器(program counter)
- 為
指令指針寄存器
,它指示了CPU當前要讀取指令的地址
- 在內存或者磁盤上,指令和數據沒有任何區別,都是
二進制信息
- CPU在工作的時候把有的信息看做指令,有的信息看做數據,為同樣的信息賦予了不同的意義
- 比如 1110 0000 0000 0011 0000 1000 1010 1010
- 可以當做數據 0xE003008AA
- 也可以當做指令 mov x0, x8
- CPU根據什么將內存中的信息看做指令?
CPU將pc指向的內存單元的內容看做指令
如果內存中的某段內容曾被CPU執行過,那么它所在的內存單元必然被pc指向過
高速緩存
iPhoneX上搭載的ARM處理器A11它的1級緩存的容量是64KB,2級緩存的容量8M.
CPU每執行一條指令前都需要從內存中將指令讀取到CPU內并執行。而寄存器的運行速度相比內存讀寫要快很多,為了性能,CPU還集成了一個高速緩存存儲區域.當程序在運行時,先將要執行的指令代碼以及數據復制到高速緩存中去(由操作系統完成).CPU直接從高速緩存依次讀取指令來執行.
bl指令
CPU從何處執行指令是由
pc中的內容
決定的,我們可以通過改變pc的內容
來控制CPU執行目標指令
-
ARM64提供了一個
mov
指令(傳送指令
),可以用來修改
大部分寄存器的值,比如- mov x0,#10、mov x1,#20
但是,mov指令不能用于設置pc的值,ARM64沒有提供這樣的功能
ARM64提供了另外的指令來修改PC的值,這些指令統稱為
轉移指令
,最簡單的是bl指令
狀態寄存器
???CPU內部的寄存器中,有一種特殊的寄存器(對于不同的處理器,個數和結構都可能不同).這種寄存器在ARM中,被稱為狀態寄存器就是CPSR(current program status register)寄存器
CPSR和其他寄存器不一樣,其他寄存器是用來存放數據的,都是整個寄存器具有一個含義.而CPSR寄存器是按位起作用的,也就是說,它的每一位都有專門的含義,記錄特定的信息.
注:CPSR寄存器是32位的
- CPSR的低8位(包括I、F、T和M[4:0])稱為控制位,程序無法修改,除非CPU運行于特權模式下,程序才能修改控制位!
- N、Z、C、V均為條件碼標志位。它們的內容可被算術或邏輯運算的結果所改變,并且可以決定某條指令是否被執行!意義重大!
N(Negative)標志
CPSR的第31位是 N,符號標志位。它記錄相關指令執行后,其結果是否為負.如果為負 N = 1,如果是非負數 N = 0.
???注意,在ARM64的指令集中,有的指令的執行時影響狀態寄存器的,比如add\sub\or等,他們大都是運算指令(進行邏輯或算數運算);
Z(Zero)標志
CPSR的第30位是Z,0標志位。它記錄相關指令執行后,其結果是否為0.如果結果為0.那么Z = 1.如果結果不為0,那么Z = 0.
???對于Z的值,我們可以這樣來看,Z標記相關指令的計算結果是否為0,如果為0,則Z要記錄下"是0"這樣的肯定信息.在計算機中1表示邏輯真,表示肯定.所以當結果為0的時候Z = 1,表示"結果是0".如果結果不為0,則Z要記錄下"不是0"這樣的否定信息.在計算機中0表示邏輯假,表示否定,所以當結果不為0的時候Z = 0,表示"結果不為0"。
C(Carry)標志
CPSR的第29位是C,進位標志位。一般情況下,進行無符號數的運算。
加法運算:當運算結果產生了進位時(無符號數溢出),C=1,否則C=0。
減法運算(包括CMP):當運算時產生了借位時(無符號數溢出),C=0,否則C=1。
???對于位數為N的無符號數來說,其對應的二進制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相對于最高有效位的更高位。如下圖所示:
進位
???我們知道,當兩個數據相加的時候,有可能產生從最高有效位想更高位的進位。比如兩個32位數據:0xaaaaaaaa + 0xaaaaaaaa,將產生進位。由于這個進位值在32位中無法保存,我們就只是簡單的說這個進位值丟失了。其實CPU在運算的時候,并不丟棄這個進位制,而是記錄在一個特殊的寄存器的某一位上。ARM下就用C位來記錄這個進位值。比如,下面的指令
mov w0,#0xaaaaaaaa;0xa 的二進制是 1010
adds w0,w0,w0; 執行后 相當于 1010 << 1 進位1(無符號溢出) 所以C標記 為 1
adds w0,w0,w0; 執行后 相當于 0101 << 1 進位0(無符號沒溢出) 所以C標記 為 0
adds w0,w0,w0; 重復上面操作
adds w0,w0,w0
借位
???當兩個數據做減法的時候,有可能向更高位借位。再比如,兩個32位數據:0x00000000 - 0x000000ff,將產生借位,借位后,相當于計算0x100000000 - 0x000000ff。得到0xffffff01 這個值。由于借了一位,所以C位 用來標記借位。C = 0.比如下面指令:
mov w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff
V(Overflow)溢出標志
CPSR的第28位是V,溢出標志位。在進行有符號數運算的時候,如果超過了機器所能標識的范圍,稱為溢出。
- 正數 + 正數 為負數 溢出
- 負數 + 負數 為正數 溢出
- 正數 + 負數 不可能溢出