iOS逆向 03:循環選擇指針(上)

iOS 底層原理 + 逆向 文章匯總

本文的主要目的是理解匯編中全局變量、常量的存儲,以及如何將if、while等匯編代碼還原成高級代碼

全局變量

在這之前首先需要了解內存的分區,對這塊不是特別清晰的,建議看看這篇文章iOS-底層原理 24:內存五大區,下面進行一個簡單的匯總說明

  • 代碼區:存放代碼,可讀、可執行

  • 棧區:存放參數、局部變量、臨時數據,可讀寫

  • 堆區:開發人員動態申請,大小可變,可讀寫

  • 全局變量:可讀可寫

  • 常量:只讀

案例分析

在main.m中定義一個函數和一個全局變量

int g = 12;

int func(int a, int b){
    printf("haha");
    int c = a + g;
    return c;
}


int main(int argc, char * argv[]) {
    
    func(1, 2);
}
  • func函數斷點運行,以下是main函數的匯編代碼


    image
  • 查看func的匯編代碼,分析如下


    image
    • 查看x0是否為“haha”,通過調試得以驗證,x0存的是haha的地址
      image
    • 查看其地址:x 0x000000010098bf9f,屬于字符串的常量區(即左邊是右邊字符串的ASCII碼)
      image

其中重點分析adrp x0,1add x0,x0,#0xf9f兩句

  • adrp指令(address page 按頁尋址):
    • 將1的值左移12位,此時的1是二進制
    • 加上pc寄存器的值(先需要將pc的低12位清零)
<!--(按頁尋址)-->
<!--adrp-->
0x10098a824 <+20>: adrp   x0, 1
- 1)1左移12位:0x1000
- 2)pc寄存器低12位清零:0x10098a000
- 3)加上pc寄存器的值:0x10098a000 + 0x1000 = 0x10098b000
===> 得到x0地址就是某一頁數據的起始位置(即首地址)

<!--add-->
0x10098a828 <+24>: add    x0, x0, #0xf9f            ; =0xf9f 
- adrp得到的地址加上偏移:0x10098b000 + 0xf9f = 0x10098bf9f
===> 此時的x0就是某一頁中某段代碼的地址,即當前代碼段的地址

通過這個計算結果可知與上面調試的x0地址是一致的

why?:一個頁的大小是4096,而0xFFF4095,加上1就是0x1000(即4096),所以是1左移12位即可得到一個頁的首地址(注:macOS的pageSize是 4k(0x1000),而iPhone的pageSize是16k(0x4000),但是16仍是4的倍數,adrp兼容者mac和iPhone,所以此時定位的仍然是一頁數據)

  • 繼續分析bl printf以下的匯編代碼
    image
    • ldur w8, [x29, #-0x4] :拿出棧中的數據,即1
      image
    • adrp + add + ldr :拿出0x10098ce98內存地址的數據,將x9的數據給w10。這樣就拿到了全局變量g
      image

反匯編分析

示例代碼如下

int g = 12;

int func(int a, int b){
    printf("haha");
    int c = a + g + b;
    return c;
}


int main(int argc, char * argv[]) {
    func(10, 20);
}

通過hopper來進行反匯編分析

  • 首先將工程編譯:CMD+B

  • 進入App的包


    image

    image

    image
  • 將第5步中的可執行文件拖入hopper中進行分析


    image
  • 在hopper中搜索func


    image
  • 拷貝func的匯編代碼,將其還原成高級語言代碼(即反匯編)

<!--1、將匯編初步還原為高級語言代碼-->
int gl = 12;
int func2(int a, int b){
    /*
     //一個函數的開始
     0000000100006808         sub        sp, sp, #0x20
     000000010000680c         stp        x29, x30, [sp, #0x10]
     0000000100006810         add        x29, sp, #0x10
    */
    
    /*
     //調用bl printf
     0000000100006814         stur       w0, [x29, #-0x4]
     0000000100006818         str        w1, [sp, #0x8]
     //===>此時的獲取的0x100007f9f地址的數據 是沒有ASLR的值
     000000010000681c         adrp       x0, #0x100007000
     0000000100006820         add        x0, x0, #0xf9f                              ; "haha"
     0000000100006824         bl         imp___stubs__printf
     */
    printf("haha");
     
     /*
      0000000100006828         ldur       w8, [x29, #-0x4]
      */
    int w8 = a;
    /*
      //===>此時的獲取0x100008e98的數據
      000000010000682c         adrp       x9, #0x100008000
      0000000100006830         add        x9, x9, #0xe98                              ; _g
     */
//    int gl = 12;//(需要寫外面)
    /*
      0000000100006834         ldr        w10, x9
     */
    int w10 = gl;
    
    /*
      0000000100006838         add        w8, w8, w10
     */
    w8 += w10;
    /*
      000000010000683c         ldr        w10, [sp, #0x8]
     */
    w10 = b;
    /*
      0000000100006840         add        w8, w8, w10
     */
    w8 += w10;
    /*
      0000000100006844         str        w8, [sp, #0x4]
      0000000100006848         ldr        w8, [sp, #0x4]
      000000010000684c         mov        x0, x8
      */
    return w8;
     
    /*
     //一個函數的結束
     0000000100006850         ldp        x29, x30, [sp, #0x10]
     0000000100006854         add        sp, sp, #0x20
     0000000100006858         ret
     */
    
}

<!--2、去掉匯編-->
int gl = 12;
int func2(int a, int b){

    printf("haha");

    int w8 = a;

    int w10 = gl;

    w8 += w10;

    w10 = b;

    w8 += w10;

    return w8;
}

<!--3、簡化代碼-->
int gl = 12;
int func2(int a, int b){
    printf("haha");
    return a + b + gl;
}

簡化過程如下圖所示(注:是從下向上還原,而不是從上向下(業務邏輯是從上至下執行):

image

image

其中

//===>此時的獲取的0x100007f9f地址的數據 是沒有ASLR的值
 000000010000681c         adrp       x0, #0x100007000      
 0000000100006820         add        x0, x0, #0xf9f
  • hopper中按G,查找0x100007f9f對應的數據
    image

同理,獲取全局變量g也是同樣的原理

//===>此時的獲取0x100008e98的數據
000000010000682c         adrp       x9, #0x100008000
0000000100006830         add        x9, x9, #0xe98                              ; _g
0000000100006834         ldr        w10, x9
image

總結

  • 獲取全局變量和常量時,會出現adrpadd兩條指令獲得一個地址的情況

  • ADRP(Address Page)

    • adrp x0,1
      • PC寄存器的低12位清零

      • 將1的值,左移12位,16進制就是0x1000

      • 以上兩個結果相加放入x0寄存器

  • 通過ADD指令獲取這頁內存中的偏移值

條件

有如下代碼,查看其匯編

int g = 12;
void func(int a, int b){
    if (a > b) {
        g = a;
    }else{
        g = b;
    }
}

int main(int argc, char * argv[]) { 
    func(1, 2);
}

通過hopper查看其匯編,代碼如下

_func:
 ==>拉伸棧空間
 0000000100006828         sub        sp, sp, #0x10                               ; CODE XREF=_main+32
 ==>w0、w1數據入棧
 000000010000682c         str        w0, [sp, #0xc]
 0000000100006830         str        w1, [sp, #0x8]
 ==>從棧中讀取數據到w8、w9
 0000000100006834         ldr        w8, [sp, #0xc]
 0000000100006838         ldr        w9, [sp, #0x8]
 
 ==>比較w8、w9,即比較w0、w1(cmp是減法,但不影響目標寄存器w8、w9,只看減法結果,修改標記寄存器)
 000000010000683c         cmp        w8, w9
 //如果是小于等于,就跳到到 loc_100006858 執行,如果是大于,則直接往下執行
 0000000100006840         b.le       loc_100006858

 0000000100006844         ldr        w8, [sp, #0xc]
 0000000100006848         adrp       x9, #0x100008000
 000000010000684c         add        x9, x9, #0xe90                              ; _g
 0000000100006850         str        w8, x9
 //硬跳,規避小于等于的代碼,跳到loc_100006868
 0000000100006854         b          loc_100006868

                      loc_100006858:
 0000000100006858         ldr        w8, [sp, #0x8]                              ; CODE XREF=_func+24
 000000010000685c         adrp       x9, #0x100008000
 0000000100006860         add        x9, x9, #0xe90                              ; _g
 0000000100006864         str        w8, x9

                      loc_100006868:
 0000000100006868         add        sp, sp, #0x10                               ; CODE XREF=_func+44
 000000010000686c         ret

這是典型的if-else,通過hopper查看其匯編代碼如下

image

將上述匯編代碼進行還原

<!--1、還原-->
int cc = 12;
void func2(int a, int b){
//==>拉伸棧空間
//0000000100006828         sub        sp, sp, #0x10
//==>w0、w1數據入棧
//000000010000682c         str        w0, [sp, #0xc]
//0000000100006830         str        w1, [sp, #0x8]
//==>從棧中讀取數據到w8、w9
//0000000100006834         ldr        w8, [sp, #0xc]
//0000000100006838         ldr        w9, [sp, #0x8]
    int w8 = a;
    int w9 = b;

//==>比較w8、w9,即比較w0、w1(cmp是減法,但不影響目標寄存器w8、w9,只看減法結果,修改標記寄存器)
//000000010000683c         cmp        w8, w9
////如果是小于等于,就跳到到 loc_100006858 執行,如果是大于,則直接往下執行
//0000000100006840         b.le       loc_100006858
    if (w8 > w9 ) {//大于
        //0000000100006844         ldr        w8, [sp, #0xc]
        //0000000100006848         adrp       x9, #0x100008000
        //000000010000684c         add        x9, x9, #0xe90                              ; _g
        //0000000100006850         str        w8, x9
        cc = w8;//此時的w8是a
        ////硬跳,規避小于等于的代碼,跳到loc_100006868
        //0000000100006854         b          loc_100006868
        
    }else{//小于等于
        //                     loc_100006858:
        //0000000100006858         ldr        w8, [sp, #0x8]
        //000000010000685c         adrp       x9, #0x100008000
        //0000000100006860         add        x9, x9, #0xe90                              ; _g
        //0000000100006864         str        w8, x9
        cc = w8;//此時的w8是b
    }

//                     loc_100006868:
//0000000100006868         add        sp, sp, #0x10
//000000010000686c         ret
}


<!--2、簡化-->
int cc = 12;
void func2(int a, int b){
    if (a > b ) {//大于
        cc = a; 
    }else{//小于等于
        cc = b;
    }
}

cmp(Compare)比較指令

  • CMP把一個寄存器的內容和另一個寄存器的內容或立即數進行比較,但不存儲結果,只是正確的更改標志(CMP后面跟的是B.LE,即else的條件)
  • 一般CMP做完判斷后會進行跳轉,后面通常會跟上B指令
    • BL 標號:跳轉到標號處執行

    • B.LT 標號:比較結果是小于(less than ),執行標號,否則不跳轉

    • B.LE 標號:比較結果是小于等于(less than or equal to),執行標號,否則不跳轉

    • B.GT 標號:比較結果是大于(greater than),執行標號,否則不跳轉

    • B.GE 標號:比較結果是大于等于(greater than or equal to),執行標號,否則不跳轉

- `B.EQ 標號`:比較結果是`等于`,執行標號,否則不跳轉
- `B.NE 標號`:比較結果是不等于(not equal),執行標號,否則不跳轉

- `B.HI 標號`:比較結果是`無符號大于`,執行標號,否則不跳轉
- `B.HS 標號`:比較結果是`無符號大于等于`,執行標號,否則不跳轉 

循環

循環常用的主要有forwhiledo-while,下面來一一進行分析

do-while分析

分析以下do-while的代碼

int main(int argc, char * argv[]) {
    int sum = 0;
    int i = 0;
    do{
        sum += 1;
        i++;
    }while (i<100);
}
  • 通過hopper查看其匯編


    image
  • 匯編結束如下所示


    image

結論do-while循環:判斷條件在后面,滿足條件往外跳

while循環分析

int main(int argc, char * argv[]) {
    
    int sum = 0;
    int i = 0;
    while (i<100){
        sum += 1;
        i++;
    }
}

匯編如圖所示


image

結論while循環:判斷條件在里面,不滿足就往外跳

for循環分析

int main(int argc, char * argv[]) { 
    int sum = 0;
    for (int i = 0; i < 100; i++) {
        sum += 1;
    }
}

此時和while的匯編是一樣的


image

結論for循環很像:判斷條件在里面,不滿足就往外跳

總結

全局變量和常量

  • 獲取全局變量和常量時,會出現adrpadd兩條指令獲得一個地址的情況

  • ADRP(Address Page)

    • adrp x0,1
      • PC寄存器的低12位清零

      • 將1的值,左移12位

      • 以上兩個結果相加放入x0寄存器

  • 通過ADD指令獲取這頁內存中的偏移值

條件判斷

  • CMP把一個寄存器的內容和另一個寄存器的內容或立即數進行比較,但不存儲結果,只是正確的更改標志(CMP后面跟的是B.LE,即else的條件)
  • 一般CMP做完判斷后會進行跳轉,后面通常會跟上B指令
    • BL 標號:跳轉到標號處執行

    • B.LT 標號:比較結果是小于(less than ),執行標號,否則不跳轉

    • B.LE 標號:比較結果是小于等于(less than or equal to),執行標號,否則不跳轉

    • B.GT 標號:比較結果是大于(greater than),執行標號,否則不跳轉

    • B.GE 標號:比較結果是大于等于(greater than or equal to),執行標號,否則不跳轉

    • B.EQ 標號:比較結果是等于,執行標號,否則不跳轉

    • B.NE 標號:比較結果是不等于(not equal),執行標號,否則不跳轉

    • B.HI 標號:比較結果是無符號大于,執行標號,否則不跳轉

    • B.HS 標號:比較結果是無符號大于等于,執行標號,否則不跳轉

循環

  • do-while循環:判斷條件在后面,滿足條件往外跳

  • for循環和while循環很像:判斷條件在里面,不滿足就往外跳

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容