解讀ARM三級流水線概念和ARM編碼編譯為二進制的全過程
前言
交互式反匯編器,簡稱為IDA。是目前最棒的一個靜態反編譯軟件,為眾多0day世界的成員和ShellCode安全分析人士不可缺少的利器!此章節讓我們熟悉通過IDA修改參數、函數、返回值,同時詳細解讀標志位的概念,熟悉堆棧及詳細解讀ARM三級流水線概念和ARM編碼編譯為二進制的全過程。
一、環境配置
首先配置IDA與安卓聯動
IDA動態調用手機apk,請參考:安卓逆向-從環境搭建到動態調試apk IDA部分https://www.freebuf.com/articles/mobile/285861.html
1)加載server

2)端口轉發+執行app(javandk1這個測試app)
adb install E:\IDA7.0\test\javandk1.apk #安裝appadb shell am start -D -n com.example.javandk1/.MainActivity #啟動app


adb forward tcp:23946 tcp:23946 #端口轉發

3)打開啟動DDMS
DDMS


4)打開IDA 32并調試ndk運行

5)并勾選三項調試

6)F9啟動+執行jdb調用DDMS
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600

此時可以看到加載的不是so,而是/arm/base.odex文件,那么此時怎么加載我們需要的so庫,分析JNI_onload呢?
問題:解決方法無libjavandk1.so庫
1)在Modules查詢java:

2)選擇libjavacore.so庫-在搜索JNI_load選擇

那么此時就進入了libjavacore.so的JNI_onload了
3)接下來下一個斷點:點擊或F2

4)F9運行后跳轉到斷點截斷處,此時回到Modules繼續搜索java:

此時就出現需要調試的so庫文件:libjavandk1.so庫文件
5)那么繼續選擇進入搜索JNI即可

此時問題就解決了
6)繼續來到JNI_onload下斷點,開始分析判斷傳參

二、參數分析
1、第一種方法
這里BLX傳入了幾個參數?
這里BLX中R3需要跳轉,那么R3也是有規定給地址的,所以我們這里的有4個參數R0-R3
R0-R3:4個寄存器->參數寄存器
R0-R3:用于函數參數及返回值的傳遞R4-R6,R8,R10-R11:普通的通用寄存器R7:棧幀指針(Frame Pointer),指向前一個保存的棧幀(stack frame)和鏈接寄存器(link register,lr)在棧上的地址。R9:操作系統保留R12:IP(intra-procedure scratch)R13:SP(stack pointer) 是棧頂指針R14:LR(link register) 存放函數的返回地址R15:PC (program counter),指向當前指令地址
如果R3作為一個地址的存放,當你把一個函數地址存放在R3里面,根據規定R3已經作為地址存放了,如果這個函數要傳參,只能從R0-R2,這個三個寄存器里面進行傳參。
注:如果想判斷有幾個參數,看這個BLX(指令)后面的值(是不是寄存器,如果是,Rn小于4,參數個數就是當前減1;大于等于4,參數寄存器就是R0-R3)
此時回到圖中,BLX R3;三個參數,用了R0-R2后,只會依次使用下一個寄存器存放跳轉的地址;
BLX R4(三個參數:R0,R1,R2,R3)
現在打開堆棧看看


在BLX R3處打樁,F9運行到下一個斷點處

記住此時的棧頂是00000001,這時候SP:FF9ABBE0指向棧頂
這時候單步F7

這時候查看,SP的值還是FF9ABBE0是沒有變化的
2、第二種方法
靜態調試SO庫,按F5查看偽代碼


可以查看到偽c代碼(int a1,int a2)就是查看參數個數
二、修改寄存器-返回值
我們隨便找的幾個BLX指令的地方。
BLX R3此時傳入幾個參數 ?三個

那么BLX R12傳入幾個參數?四個

按F5進入偽C代碼


靜態注冊參數怎么修改呢?選擇你要修改的參數,按Y

如何修改寄存器的值呢


這時候就可以修改寄存器的值了
例如:cmp R0,0,那么就執行BEN,意思就是修改了條件為0后,就不執行改條件,反調試會更深入演示
Y鍵修改C代碼(退出C代碼按ESC)
R0-R3:用于函數參數及返回值的傳遞R4-R6,R8,R10-R11:普通的通用寄存器R7:棧幀指針(Frame Pointer),指向前一個保存的棧幀(stack frame)和鏈接寄存器(link register,lr)在棧上的地址。R9:操作系統保留R12:IP(intra-procedure scratch)R13:SP(stack pointer) 是棧頂指針R14:LR(link register) 存放函數的返回地址R15:PC (program counter),指向當前指令地址
三、修改寄存器-修改函數
1、方法1-修改Hex
如果我要改這條指令

根據三級流水線,需要在前三個代碼斷點
不想讓程序執行怎么辦?直接同步下PC寄存器

現在你想在HEX處找的PC的指令,當鼠標放在PC指令處,hex自動選擇

然后在Hex View-1處快捷鍵F2操作,修改,

修改為00 00 00 00后,然后在快捷鍵F2保存下

這時候指令就沒了
2、方法2-修改General
或者直接設置PC(只適合調試時候測試使用)
例如此刻需求是,跳過大紅框內容,直接執行MOV R0, #0x10004這條指令,此刻PC在F42AA08C

只需要在General registers處修改PC值為MOV處的指令值即可,那么此時MOV出指令值為:

F42AA0A4
開始修改,雙擊General registers-PC處,修改為MOV的值:

直接跳轉mov處

四、IDA配置堆棧信息
在PC窗口處配置出堆棧指針和Hex View對應的十六進制的值


五、IDA標志位詳解
CPSR標志位詳解
一邊情況下標志位情況

斷點后的情況

用最簡單的理解,這些到底有什么用

通過圖很好了解,例如BLX進行運算是正數還是負數,經過運算后值是不是為零,可以理解為條件標志位,幫我們記錄一些狀態!
標志位的結果內容可被算數或邏輯運算的結果所改變,并且可以決定某條指令是否被執行。
最重要的是N、Z、C、V、Q、T,那么T是什么意思?
T標志位︰該為反應處理器的運行狀態。當該位為1時,程序運行于THUMB狀態,否則運行于ARM狀態。該信號反映在外部引腳TBIT上。在程序中不得修改CPSR中的TBIT位,否則處理器工作狀態不能確定。
ARM狀態
arm處理器工作于32位指令的狀態,所有指令均為32位
thumb狀態
arm執行16位指令的狀態,即16位狀態
六、ARM匯編三級流水線詳解
1、什么是三級流水線
前綴
ARM7處理器采用3級流水線來增加處理器指令流的速度,能提供0.9MIPS/MHz的指令處理速度。MIPS(Million Instruction Per Second)表示每秒多少百萬條指令。比如0.9MIPS,表示每秒九十萬條指令。MIPS/MHz表示CPU在每MHz的運行速度下可以執行多少個MIPS,如0.9MIPS/MHz則表示如果CPU運行在1MHz的頻率下,每秒可執行90萬條指令。
三級流水線使用3個階段,因此指令分為3個階段執行
1)取指從存儲器裝載一條指令 2)譯碼識別將要被執行的指令 3)執行處理指令并將結果寫會寄存器
但是處理實際是這樣的:ARM正在執行第一條指令的同時對第二條指令進行譯碼,并將第三條指令從存儲器中取出
所以,ARM7流水線只能在取第4條指令時,第1條指令才算完成執行
無論處理器處于何種狀態,程序計數器R15(PC)總是指向”正在取指“的指令,而不是指向”正在執行“的指令或者正在”譯碼“的指令。
人們一邊會習慣性的將正在執行的指令作為參考點,即當前第一條指令,所以,pc總是指向第三條指令
或者說PC總是指向當前正在執行的指令在加2條指令的地址
2、三級流水線詳解
處理器處于ARM狀態是,每條指令為4個字節,所以PC指令為正在執行的指令地址加8個字節,即是:
PC值=當前程序執行位置+8字節
處理器處于Thumb狀態時,每條指令為2字節,所以PC值為正在執行的指令地址加4字節,即是:
PC值=當前程序執行位置+4字節
下面一個列子很好的說明了這個問題
libjavandk1.so:F42AA090 010 00 C0 90 E5 LDR R12, [R0] #正在被執行的指令libjavandk1.so:F42AA094 010 5C C3 9C E5 LDR R12, [R12,#0x35C] #正在被編碼的指令libjavandk1.so:F42AA098 010 3C FF 2F E1 BLX R12 #正在被取指的指令 PC=F42AA098libjavandk1.so:F42AA09C 010 00 00 50 E3 CMP R0, #0 #PC+4=F42AA09C
另外補充說明就是根據以上描述,流水線只有被指令填滿時才能發揮最大的效能,既每時鐘周期完成一條指令的指向(僅單周期指令)
如果程序發送跳轉,流水線會被清空,這將需要幾個時鐘才能使流水線被再次填滿。因此,盡量地少使用跳轉指令可以提高程序的指令效率
PC代表程序計數器,流水線使用三個階段,因此指令為分為三個階段執行:
1、取指(從存儲器裝載一條指令) 2、譯碼(識別將要被執行的指令) 3、執行(處理指令并將結果寫回寄存器)
而R15(PC)總是指向”正在取指“的指令,而不是指向”正在執行“的指令或者正在”譯碼“的指令。一般來說,人習慣性約定將”正在執行“的指令作為參考點,稱之為第一條指令,因此PC總是指向第三條指令,當ARM狀態時,每條指令為4字節長,所以PC始終指向改指令地址加8字節的地址,既:PC值=當前程序執行位置+8;
ARM指令是三級流水線,取指、譯指、執行是同時執行的,現在PC指向的是正在取值的地址,那么CPU正在譯指的指令地址是PC-4(假設ARM狀態下,一個指令占4個字節),cpu正在執行的指令地址是cpu-8,也就是說PC所指向的地址和現在所執行的指令地址相差8。
當突然發生中斷的時候,保存的是PC的地址
這樣你就知道了,如果返回的時候返回PC,那么中間就有一個指令沒有執行,所以用SUB pc lr-irq#4。
這個需要參考《ARM指令》來學習

3、案例-ARM指令轉換為機器碼
下面我們將一個ARM指令轉換為機器碼試試
00001BD0 BEQ lc04 BEQ lc04;跳轉指令,執行條件EQ,即相等跳轉到lc04
來計算這條指令
1)首先ARM指令是32位的,因為這里是BEQ的B的跳轉指令,32位可以拆分為格式如下

2)前四位:31-28就是cond,這里的意思就是條件碼,例如EQ、NE

0000
3)緊接著3位:27-25(這里由于B指令是101固定的)

0000 101
4)在往下24位:(1或者0,具體判斷:帶有連接的如果是BL指 令對應的二進制操作數就是1 無條件跳轉B指令就是0。)
0000 1010
5)0-23位:偏移地址:目標地址與該指令的相對偏移
偏移的計算公式:
(目標跳轉的地址-(當前這條指令的地址+8))/4 (1c04-(1bd0+8))/4=1011 1c04:要跳轉的地址 1bd0:此指令所在的地址 +8:arm指令有3級流水線的原因,如果執行1c04地址指令,需要加8 /4:因為要做對 其處理
我們拼接一下計算出來的二進制ARM指令機器碼,因為0-23位算出來的是1011,
1011對應0-3位置
4-23用0補齊
結果
0000 1010 0000 0000 0000 0000 0000 1011
轉換為16進制就是
0A 00 00 0B

因為是小端模式,需要換一下位置結果就是
0B 00 00 0A
我們在IDA中測試一下
將IDA中hex中隨便一個地方改為0B 00 00 0A

我們看到指令變為了BEQ loc_F42AA0D4
我們計算利用偏移公式計算F42AA0D4(目標地址)和F42AA0A0(當前地址)結果是不是1011
(A0D4-(A0A0+8))/4=1011
我們這里就簡單了解到了ARM指令如何計算為ARM指令機器碼
七、總結
此章節我們詳細學習了使用IDA在靜態注冊和動態注冊下分析和修改參數,學習修改返回值、函數,對IDA堆棧功能模塊和標志位功能模塊進行深入解析,同時了解了ARM中三級流水線并利用案例熟悉ARM指令轉換為機器碼的案例。
