iOS逆向工程(十):ARM64匯編

前言

我們使用Hopper Disassembler等反編譯工具查看Mach-O可執(zhí)行文件時(shí),看到的都是匯編代碼,所以只有我們學(xué)會(huì)了匯編后,才能更好的去調(diào)試,去分析App的邏輯
學(xué)會(huì)了匯編之后,甚至可以直接修改匯編代碼,導(dǎo)出可執(zhí)行文件,再經(jīng)過(guò)簽名安裝使用了,都不用通過(guò)之前的tweak方式hook了,當(dāng)然,這需要對(duì)匯編掌握的及其深入,并且經(jīng)過(guò)調(diào)試分析清楚了App整體邏輯之后了。
一、匯編
    1. CPU的內(nèi)部有寄存器、運(yùn)算器和控制器,它們之間由總線連接。其中運(yùn)算器負(fù)責(zé)信息處理,由CPU硬編碼指令完成;控制器負(fù)責(zé)協(xié)調(diào)控制計(jì)算機(jī)的其他器件進(jìn)行工作;寄存器進(jìn)行數(shù)據(jù)的臨時(shí)存儲(chǔ),程序員只需關(guān)心寄存器的數(shù)據(jù)存取即可改變運(yùn)行CPU運(yùn)行結(jié)果。
    1. 在iOS領(lǐng)域,根據(jù)CPU架構(gòu)不同,匯編主要分為兩種:模擬器上的x86匯編、真機(jī)上的arm64匯編,這里我們重點(diǎn)介紹真機(jī)上的arm64匯編
    1. 想要學(xué)好arm64匯編,主要學(xué)三個(gè)方面:寄存器、指令、堆棧
二、寄存器
寄存器就是CPU內(nèi)部臨時(shí)存儲(chǔ)數(shù)據(jù)的地方,分為有很多種,如下圖所示,我們下面依次講解:
寄存器
    1. 我們把x0~x28稱為通用寄存器,通用寄存器通常用來(lái)存放一般性的數(shù)據(jù),通用寄存器分為64位和32位

      • 64位(8字節(jié))的通用寄存器有:x0 ~ x28,29個(gè)寄存器,8個(gè)字節(jié)代表這個(gè)寄存器最多能放8個(gè)字節(jié)的數(shù)據(jù)

      • 32位(4字節(jié))的通用寄存器有:w0 ~ w28,這29個(gè)4字節(jié)的寄存器其實(shí)是8字節(jié)寄存器x0 ~ x28的低32位

      • 通用寄存器的x0與w0之間的關(guān)系,如下圖所示,其他通用寄存器也是如此


        x0寄存器與w0寄存器的關(guān)系
      • 通過(guò)LLDB命令,我們也可以驗(yàn)證出他們的關(guān)系,如下圖所示:


        讀取x0與w0寄存器.png

        寫(xiě)入再讀取x0與w0寄存器.png
      • x0~x7寄存器,一般會(huì)存儲(chǔ)函數(shù)的參數(shù),大于8個(gè)的會(huì)通過(guò)堆棧傳參

      • x0:一般會(huì)存儲(chǔ)函數(shù)的返回值

    1. pc寄存器,也叫做程序計(jì)數(shù)器,記錄著當(dāng)前CPU執(zhí)行的是哪一條指令,存儲(chǔ)著當(dāng)前CPU正在執(zhí)行指令的地址,類似于8086匯編的ip寄存器
    1. 堆棧寄存器,有兩個(gè),分別是:棧頂指針寄存器sp棧低指針寄存器fp,棧低指針寄存器fp也叫做x29寄存器,堆棧寄存器是用來(lái)控制函數(shù)分配棧空間的
    1. 鏈接寄存器lr,也叫做x30寄存器存儲(chǔ)著函數(shù)的返回地址
    1. 程序狀態(tài)寄存器,有兩種,分別是cpsr(current Program Status Register)spsr(Saved Program Status Register),cpsr是當(dāng)前程序的運(yùn)行狀態(tài),spsr是在異常狀態(tài)下使用的,CPSR寄存器和其他寄存器不一樣(其他寄存器是用來(lái)存放數(shù)據(jù)的,都是整個(gè)寄存器具有一個(gè)含義),是按起作用的,也就是說(shuō),它的每一位都有專門(mén)的含義,記錄特定的信息,如下圖所示:
    spsr的各位
      - CPSR的低8位(包括I、F、T和M[4~0])稱為控制位,程序無(wú)法修改,除非CPU運(yùn)行于特權(quán)模式下,程序才能修改控制位!
      - N、Z、C、V均為條件碼標(biāo)志位。它們的內(nèi)容可被算術(shù)或邏輯運(yùn)算的結(jié)果所改變,并且可以決定某條指令是否被執(zhí)行,意義重大!
    
    1. xzr零寄存器,表示zero register,里面存儲(chǔ)的值都是0,xzr代表8字節(jié),wzr代表4字節(jié)
    1. 總結(jié):
      • x0 ~ x28通用寄存器,有8個(gè)字節(jié)的空間,其中較低的4個(gè)字節(jié)的空間,是w0~w28寄存器,x代表8個(gè)字節(jié),w代表4個(gè)字節(jié)

      • x29就是fp寄存器,也就是棧底寄存器,代表著函數(shù)的棧底

      • x30就是lr寄存器,也就是鏈接寄存器,存儲(chǔ)著函數(shù)的返回地址,使用bl指令跳轉(zhuǎn)時(shí),會(huì)把bl的下面的一條指令的地址,存放到lr寄存器中,如果bl跳轉(zhuǎn)之后,遇到了ret指令ret指令會(huì)把lr寄存器的值給pc寄存器,這樣CPU就會(huì)執(zhí)行lr寄存器存放的指令了,就相當(dāng)于函數(shù)返回了

三、指令
    1. mov指令,傳送指令,格式是mov {條件}{S}目標(biāo)寄存器 ,源操作數(shù) ,mov指令可以完成從另一個(gè)寄存器或者是將一個(gè)立即數(shù),加載到目的寄存器中,其中S選項(xiàng)決定執(zhí)行的操作是否影響CPSR中條件標(biāo)志位的值,當(dāng)沒(méi)有S時(shí)指令不更新CPSR中的條件標(biāo)志位的值。

      • mov指令實(shí)例如下:

      • mov x1,x0,意思是將寄存器x0的值傳送到寄存器x1

      • mov pc,x14,意思是將寄存器x14的值傳送到寄存器pc中,pc寄存器通常用來(lái)存放CPU正在執(zhí)行的指令的地址,所以常用于子程序返回

      • mov x1,x0,#0x3,意思是將寄存器x0的值加上0x3,然后傳送到寄存器x1

    1. ret指令,返回指令,作用是函數(shù)返回,本質(zhì)上是將lr(x30)寄存器的值賦值給pc寄存器,pc寄存器存儲(chǔ)CPU當(dāng)前執(zhí)行的指令的地址,lr寄存器存儲(chǔ)著函數(shù)的返回地址,將lr寄存器的值,給了pc寄存器,相當(dāng)于實(shí)現(xiàn)了函數(shù)返回
    1. add指令,加法指令,格式是add {條件}{S}目標(biāo)寄存器 ,操作數(shù)1,操作數(shù)2 ,add指令用于將 兩個(gè)操作數(shù)相加,并將結(jié)果存放到目的寄存器中,操作數(shù)1必須是一個(gè)寄存器,操作數(shù)2可以是寄存器,也可以是立即數(shù)

      • add指令實(shí)例如下:

      • add x0,x1,x3,意思是將寄存器x1的值加上寄存器x3的值,賦值給寄存器x0

      • add x0,x1,#0x77,意思是將寄存器x1的值加上0x77,然后賦值給寄存器x0

      • add x0,x1,x3,LSL0x1,意思是將寄存器x1的值加上寄存器x3左移0x1,然后賦值給寄存器x0,也就是:x0 = x1 + (x3 << 1)

    1. sub指令,減法指令,格式是sub {條件}{S}目標(biāo)寄存器 ,操作數(shù)1,操作數(shù)2 ,sub指令用于把操作數(shù)1減去操作數(shù)2,并將結(jié)果存放到目的寄存器中,操作數(shù)1必須是一個(gè)寄存器,操作數(shù)2可以是寄存器,也可以是立即數(shù)

      • sub指令實(shí)例如下:

      • sub x0,x1,x3,意思是將寄存器x1的值減去寄存器x3的值,賦值給寄存器x0

      • sub x0,x1,#0x88,意思是將寄存器x1的值減去0x88,然后賦值給寄存器x0

      • sub x0,x1,x3,LSL0x1,意思是將寄存器x1的值減去寄存器x3左移0x1,然后賦值給寄存器x0,也就是:x0 = x1 - (x3 << 1)

    1. cmp指令,比較指令,格式是cmp {條件}{S}目標(biāo)寄存器 ,操作數(shù)1,操作數(shù)2 ,cmp指令用于把一個(gè)寄存器的內(nèi)容和另一個(gè)寄存器的內(nèi)容或者立即數(shù),進(jìn)行比較,同時(shí)更新CPSR中的條件標(biāo)志位,cmp指令進(jìn)行的是一次減法運(yùn)算,不會(huì)存儲(chǔ)結(jié)果,只會(huì)更改條件標(biāo)志位。

      • cmp指令實(shí)例如下:

      • cmp x0,x1,意思是將寄存器x0的值與寄存器x1的值相減,并根據(jù)結(jié)果設(shè)置CPSR的標(biāo)志位

      • cmp x0,#0x88,意思是將寄存器x0的值與立即數(shù)0x88相減,并 根據(jù)結(jié)果設(shè)置CPSR的標(biāo)志位

    1. b指令,不帶返回的跳轉(zhuǎn)指令,常與cmp指令配合使用,格式是b {條件} 目的地址 ,一旦遇到b指令,ARM處理器將立即跳轉(zhuǎn)到目的地址,從那里繼續(xù)執(zhí)行,注意存儲(chǔ)在跳轉(zhuǎn)指令中的是相對(duì)于當(dāng)前pc值的一個(gè)偏移量,而不是一個(gè)絕對(duì)地址,偏移量是由匯編器來(lái)計(jì)算的。
    1. bl 指令,帶返回的跳轉(zhuǎn)指令,常與cmp指令配合使用,格式是bl {條件} 目的地址,bl 指令的格式為:bl{條件} 目標(biāo)地址,bl是另一個(gè)跳轉(zhuǎn)指令,在跳轉(zhuǎn)之前,會(huì)把下一條指令的地址,存儲(chǔ)到lr寄存器中,等到子函數(shù)執(zhí)行完畢,執(zhí)行ret指令時(shí),ret指令會(huì)把lr寄存器的值給了pc寄存器,pc寄存器存儲(chǔ)的是CPU當(dāng)前執(zhí)行的指令的地址,這樣就實(shí)現(xiàn)了子函數(shù)返回。

      例如:cmp與b配合使用,如下所示

0x100432624 <+88>:  cmp    x1, #0x1                 ; =0x1 
0x100432628 <+92>:  b.le   0x100432630               ; 
<1>. cmp:  將寄存器 x1 的值與立即數(shù) 0x1 相減,并根據(jù)結(jié)果設(shè)置 CPSR 的標(biāo)志位
<2>. b.le 0x100432630:表示如果x1 <= 0x1那么就執(zhí)行0x100432630
    1. ldr 指令,從內(nèi)存加載數(shù)據(jù)到寄存器,格式是:ldr{條件} 目的寄存器,<存儲(chǔ)器地址>,ldr指令用于從存儲(chǔ)器中將讀取相應(yīng)大小的字節(jié)數(shù),傳送到目的寄存器中

      • ldr指令實(shí)例如下:

      • ldr x0,[x1],意思是從x1寄存器存儲(chǔ)的地址取出8個(gè)字節(jié),然后存到x0寄存器

      • ldr w0,[x1],意思是從x1寄存器存儲(chǔ)的地址取出4個(gè)字節(jié),然后存到w0寄存器

      • ldr x0,[x1,#0x888],意思是從x1寄存器存儲(chǔ)的值加上0x888得到地址處,取出8個(gè)字節(jié),然后存到x0寄存器

      • ldr x0,[x1,#0x888]!,意思是從x1寄存器存儲(chǔ)的值加上0x888得到地址處,取出8個(gè)字節(jié),然后存到x0寄存器中,并且將新地址x1+0x888,存入到x1寄存器

    1. str 指令,將寄存器的數(shù)據(jù)存儲(chǔ)到內(nèi)存中,格式是:str{條件} 源寄存器,<存儲(chǔ)器地址>

      • str指令實(shí)例如下:

      • str x0,[x1],意思是從x0寄存器存儲(chǔ)的地址取出8個(gè)字節(jié),然后存到x1寄存器的內(nèi)存中

      • ldr w0,[x1,#0x888],意思是從w0寄存器存儲(chǔ)的地址取出4個(gè)字節(jié),然后存到x1寄存器的值加上0x888的地址中

三、葉子函數(shù)的堆棧
    1. 函數(shù)分為:葉子函數(shù)和非葉子函數(shù),這兩種函數(shù)的堆棧分配情況略有不同,葉子函數(shù)就是像葉子一樣沒(méi)有分支了,函數(shù)內(nèi)部不會(huì)調(diào)用其他函數(shù)了;非葉子函數(shù),就是函數(shù)內(nèi)部會(huì)調(diào)用其他函數(shù)。
    1. 我們先來(lái)看一個(gè)葉子函數(shù),C代碼和匯編代碼如下:(w代表4個(gè)字節(jié),x代表8個(gè)字節(jié),;代表注釋)
C代碼:

void test(){
    int a = 2;
    int b = 3;
}

test的函數(shù)匯編代碼如下:

sub sp, sp, #16             ; =16          ;sp = sp - 16,開(kāi)辟16個(gè)字節(jié)的??臻g

orr w8, wzr, #0x2                          ;設(shè)置w8寄存器為2
str w8, [sp, #12]                          ;在sp地址偏移12個(gè)字節(jié)的地方開(kāi)始存儲(chǔ)4個(gè)字節(jié)的數(shù)據(jù),也就是w8寄存器的值,也就是存儲(chǔ)了2
orr w8, wzr, #0x3                          ;設(shè)置w8寄存器為3
str w8, [sp, #8]                           ;在sp指針+8的地址開(kāi)始,存儲(chǔ)4個(gè)字節(jié)的數(shù)據(jù),也就是將w8的值存儲(chǔ)到sp偏移8字節(jié)的位置

add sp, sp, #16             ; =16          ;sp = sp + 16,回收16個(gè)字節(jié)的空間,保持堆棧平衡
ret
    1. 葉子函數(shù)的堆棧開(kāi)辟,可以用下面一幅圖表示:


      image.png
四、非葉子函數(shù)的堆棧
    1. 我們?cè)賮?lái)看一個(gè)非葉子函數(shù),C代碼和匯編代碼如下:(w代表4個(gè)字節(jié),x代表8個(gè)字節(jié),;代表注釋)
C代碼如下,我們?cè)趃ood函數(shù)里,調(diào)用了test函數(shù)
void test(){
    int a = 2;
    int b = 3;
}

void good(){
    int a = 7;
    int b = 8;
    test();
}

good函數(shù)的匯編代碼如下:

sub sp, sp, #32             ; =32                     ;sp = sp-32,開(kāi)辟函數(shù)??臻g,也就是將sp指針往低字節(jié)移動(dòng)32字節(jié)
stp x29, x30, [sp, #16]     ; 16-byte Folded Spill    ; 在sp+16的地方,往高字節(jié)的方向,依次存放x29、x30的數(shù)據(jù),x29的數(shù)據(jù)占8個(gè)字節(jié),x30的數(shù)據(jù)也占8個(gè)字節(jié)
add x29, sp, #16            ; =16                     ;x29 = sp + 16,x29也就是fp棧底指針

orr w8, wzr, #0x7                                     ;將w8置為7
stur w8, [x29, #-4]                                   ;將w8的值存放到x29-4的位置
orr w8, wzr, #0x8                                     ;將w8置為8
str w8, [sp, #8]                                      ;將w8的值存放到sp+8的位置
bl  _test                                             ;調(diào)用test函數(shù),執(zhí)行test函數(shù)的匯編代碼

ldp x29, x30, [sp, #16]     ; 16-byte Folded Reload   ;調(diào)用完test,會(huì)返回到這里,繼續(xù)執(zhí)行,從sp+16的地址,加載8個(gè)字節(jié)到x29,然后再加載8個(gè)字節(jié)的數(shù)據(jù)到x30
add sp, sp, #32             ; =32                     ;sp = sp + 32,回收函數(shù)的??臻g,將sp指針復(fù)原
ret                                                   ;good函數(shù)調(diào)用完畢,將x30的值給了pc寄存器,CPU會(huì)執(zhí)行pc寄存器中存放的地址所對(duì)應(yīng)的指令的

    1. 我們可以用一副圖,清晰的表示good函數(shù)的調(diào)用過(guò)程,的如下所示:


      good函數(shù)的調(diào)用流程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 初識(shí)匯編 我們是逆向iOS系統(tǒng)上面的APP.那么我們知道,一個(gè)APP安裝在手機(jī)上面的可執(zhí)行文件本質(zhì)上是二進(jìn)制文件....
    looha閱讀 656評(píng)論 0 2
  • 搗鼓了一段時(shí)間的iOS逆向相關(guān)的東西,在動(dòng)態(tài)分析過(guò)程中會(huì)閱讀匯編代碼,分析代碼的執(zhí)行流程,在此記錄下閱讀匯編代碼過(guò)...
    傻傻木閱讀 3,256評(píng)論 0 14
  • 我們?cè)趯W(xué)習(xí)逆向開(kāi)發(fā)之前,我們要了解一個(gè)基本的逆向原理.首先我們是逆向iOS系統(tǒng)上面的APP.那么我們知道,一個(gè)AP...
    Colin_狂奔的螞蟻閱讀 1,921評(píng)論 4 20
  • 以arm64為例 xcode調(diào)試匯編 1. xcode 查看運(yùn)行時(shí)的匯編代碼 debug -> debug wor...
    meryin閱讀 2,664評(píng)論 1 6
  • 寄存器 內(nèi)部部件之間由總線連接 對(duì)程序員來(lái)說(shuō),CPU中最主要部件是寄存器,可以通過(guò)改變寄存器的內(nèi)容來(lái)實(shí)現(xiàn)對(duì)CPU的...
    成績(jī)是汗閱讀 2,174評(píng)論 0 3

友情鏈接更多精彩內(nèi)容