1. 程式人生 > >大臉貓講逆向之ARM匯編中PC寄存器詳解

大臉貓講逆向之ARM匯編中PC寄存器詳解

nbsp 限制 得到 目標 進行 查找 i春秋 偏移量 .html

i春秋作家:v4ever

近日,在研究一些開源native層hook方案的實現方式,並據此對ARM匯編層中容易出問題的一些地方做了整理,以便後來人能有從中有所收獲並應用於現實問題中。當然,文中許多介紹參考了許多零散的文章,本文重點工作在於對相關概念的整理收集,並按相對合理順序引出後文中對hook技術中的一些難點的解讀。

Android平臺大多采用了ARM架構的CPU,而ARM屬RISC,與X86架構的處理器有不同的特征,本文講介紹ARM中不容易理解的PC寄存器各種問題,包括ARM流水線、PC寄存器指向問題、ARM和Thumb指令的區分及相關概念在native層hook中的應用問題。

1.ARM寄存器介紹

ARM微處理器共有37個32位寄存器,其中31個為通用寄存器,6個為狀態寄存器。但是這些寄存器不能被同時訪問,具體哪些寄存器是可以訪問的,取決ARM處理器的工作狀態及具體的運行模式。但在任何時候,通用寄存器R14~R0、程序計數器PC、一個狀態寄存器都是可訪問的。

簡言之,在用戶模式下ARM可見的寄存器有16個32位的寄存器(R0到R15)和一個當前程序狀態寄存器CPSR,其中R15是程序計數器PC,R14用於存儲子程序的返回地址LR,R13用於存儲堆棧棧頂SP。

本文我們將著重介紹一下R15寄存器,即PC寄存器。理論上,PC寄存器應指向即將執行的下一條指令的地址,然而在實際應用中卻發現PC寄存器總不是如此。經過翻看資料發現,該問題是由於ARM存在的指令流水線導致的。

2.ARM流水線介紹
流水線技術通過多個功能部件並行工作來縮短程序執行時間,提高處理器核的效率和吞吐率,從而成為微處理器設計中最為重要的技術之一。也就是說,通過劃分指令執行過程中的不同階段,並通過並行執行,從而提高指令的執行效率。ARM7處理器采用了三級流水線結構,包括取指(fetch)、譯碼(decode)、執行(execute)三級。

技術分享圖片

技術分享圖片

3.PC寄存器的指向問題
如上圖所示,在執行add r0, r1, #5指令時,第二條指令正在譯碼階段,而第三條指令正在取指階段。在執行第一條指令時,PC寄存器應指向第三條指令。也即,當處理器為三級流水線結構時,PC寄存器總是指向隨後的第三條指令。

  • 當處理器處於ARM狀態時,每條ARM指令為4個字節,所以PC寄存器的值為當前指令地址 + 8字節
  • 當處理器處於Thumb狀態時,每條Thumb指令為2字節,所以PC寄存器的值為當前指令地址 + 4字節

此外,在ARM9中,采用了五級流水線結構,是在ARM7的三級流水線結構後面添加了兩個新的過程。因此,指令的執行過程和取指過程還是相隔一個譯碼過程,因而PC還是指向當前指令隨後的第三條指令。

另外,關於PC寄存器需要註意的一點是:當使用指令STR或STM對R15進行保存時,保存的可能是當前指令地址加8或當前指令地址加12。具體是加8還是加12,取決於具體的處理器設計。但是,同一個芯片只能是其中一種的方案,即只能是加8或加12中的一種。

可以通過如下的代碼確定處理器采用的那種方式:

SUB R1,PC, #4 ;R1中存放STR指令地址

STR PC,[R0] ;用STR指令將PC保存到R0指向的地址單元中,PC=STR指令地址+偏移量(偏移量為8或者12)。

LDR R0,[R0] ;讀取STR指令地址+偏移量的值

SUB R0,R0,R1 ; STR指令地址+偏移量的值減去STR指令的地址,得到偏移量值(8或者12)。

4. ARM/Thumb指令的區分
眾所周知,ARM體系結構分為ARM狀態和Thumb狀態及Thumb-2狀態。在ARM狀態時執行32位長度的字對齊的ARM指令,Thumb狀態時執行16位長度的半字對齊的Thumb指令。

Thumb指令集與 ARM 指令的區別一般有如下幾點:

  • 跳轉指令

程序相對轉移,特別是條件跳轉與 ARM 代碼下的跳轉相比,在範圍上有更多的限制,轉向子程序是無條件的轉移.

  • 數據處理指令

數據處理指令是對通用寄存器進行操作,在大多數情況下,操作的結果須放入其中一個操作數寄存器中,而不是第 3 個寄存器中.數據處理操作比 ARM 狀態的更少,訪問寄存器 R8~R15 受到一定限制.除 MOV 和 ADD 指令訪問器 R8~R15 外,其它數據處理指令總是更新 CPSR 中的 ALU 狀態標誌.訪問寄存器 R8~R15 的 Thumb 數據處理指令不能更新 CPSR 中的 ALU 狀態標誌.

  • 單寄存器加載和存儲指令

在 Thumb 狀態下,單寄存器加載和存儲指令只能訪問寄存器 R0~R7

  • 批量寄存器加載和存儲指令

LDM 和 STM 指令可以將任何範圍為 R0~R7 的寄存器子集加載或存儲. PUSH 和 POP 指令使用堆棧指令 R13 作為基址實現滿遞減堆棧.除 R0~R7 外,PUSH 指令還可以存儲鏈接寄存器 R14,並且 POP 指令可以加載程序指令PC

而程序在執行過程中,是如何區分ARM狀態和Thumb狀態的呢?在逆向分析過程中,經常會看到許多函數調用過程為形如BX sub_84C0 + 1,即函數地址為奇數。在ARM運行過程中,函數調用的地址最後一位為1時,表示目標函數為Thumb指令;否則為ARM指令。然而,不管是ARM和Thumb狀態指令,均是偶數字節對齊的,即函數地址最後一位肯定為0。因此,可以用最後一位判斷目標函數是否為Thumb和ARM狀態。

綜上,程序狀態切換可以用如下方式實現:

  • 從ARM切換到Thumb:

LDR R0, =label + 1

BX R0

  • 從Thumb切換到ARM:

LDR R0, = label

BX R0

上文中,label為符號的地址,因為字節對齊緣故,最後一位肯定為0。

5.native層hook技術解析
以上 問題均是我在分析開源hook框架adbi源碼時遇到的問題的解答,下面我將介紹一下上述幾個問題在hook中的應用。

在adbi源文件中,hijack.c文件中的sc數組用於存儲在對目標函數前幾個字節的指令修改過程中的相關指令和寄存器值。

技術分享圖片

第一條指令為ldr r0, [pc, #64],即將pc + 64位置處的內存的4個字節讀到r0寄存器中。經過上面幾個章節的介紹,且此處看出指令均為4個字節即ARM指令,可知讀取的位置為當前位置 + 8 + 64 位置處的內容,即sc[18]處的內容,也即addr of libname的內容,即將函數調用時第一個參數R0設置為動態庫的字符串名。

第二條指令將r1寄存器,即函數的第二個參數設置為0。

第三條指令將pc的值賦值給lr寄存器,即將隨後函數調用後的返回地址設置為559行的那條指令,即第五條指令。

第四條指令將pc + 56位置處存儲的值賦值給pc寄存器,即當前位置 + 8 + 56位置處的值,也即sc[16]處的值,即被hook的dlopen函數的地址。

隨後函數調轉到目標函數執行,並在返回後執行559行的執行,從此開始恢復寄存器環境。也即通過這種方式完成了對目標函數的hook劫持過程。

在hook.c文件中,實現了對目標函數的查找及指令替換功能,詳情如下圖所示。圖中,通過對find_name函數的調用,得到了目標so庫中的函數funcname的地址並存儲於addr中。在代碼編譯過程中,編譯為Thumb指令的函數,通過函數名得到的目標函數地址最後一位為1,用於表示目標函數為Thumb指令集。在adbi代碼中,即根據該方法判斷需要hook的目標函數是Thumb指令集還是ARM指令集。ARM指令集由於是4字節對齊的,因此最後2位總是為0,據此判斷是否是ARM指令。根據不同的指令集實現不同的指令替換,並完成hook功能。

技術分享圖片

以上即是我在研究adbi源碼過程中碰到的需要深入了解的一些基本概念及其具體應用,了解這些能對hook的實現原理能有較為深刻的理解,並據此編寫自己的簡易的hook方案。

6.總結
在ARM處理器架構中,PC寄存器通常是指向當前指令後的第三條指令地址,即在ARM指令是+8,Thumb指令時+4。

ARM/Thumb狀態切換是根據目標函數地址最後一位是否為0來進行判斷,並用BX指令實現。

參考鏈接
https://blog.csdn.net/Sandeldeng/article/details/52954781

https://blog.csdn.net/zhi_yong_chen/article/details/51314377

http://www.eepw.com.cn/article/201611/318735.htm

有問題大家可以留言哦,也歡迎大家到春秋論壇中來耍一耍 >>>點擊跳轉

大臉貓講逆向之ARM匯編中PC寄存器詳解