天天看點

iOS開發逆向之循環選擇指針(上)

本文的主要目的是了解彙編中全局變量、常量的存儲,以及如何将if、while等彙編代碼還原成進階代碼

iOS開發逆向之循環選擇指針(上)

全局變量

在這之前首先需要了解記憶體的分區,對這塊不是特别清晰的,建議看看iOS-底層原理:記憶體五大區,下面進行一個簡單的彙總說明

  • 代碼區

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

    :存放參數、局部變量、臨時資料,可讀寫
  • 堆區

    :開發人員動态申請,大小可變,可讀寫
  • 全局變量

    :可讀可寫
  • 常量

    :隻讀

案例分析

首先作為一個開發者,有一個學習的氛圍跟一個交流圈子特别重要,這是一個我的iOS開發交流群:130595548,不管你是小白還是大牛都歡迎入駐 ,讓我們一起進步,共同發展!(群内會免費提供一些群主收藏的免費學習書籍資料以及整理好的幾百道面試題和答案文檔!)

在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函數的彙編代碼
    iOS開發逆向之循環選擇指針(上)
  • 檢視func的彙編代碼,分析如下
    iOS開發逆向之循環選擇指針(上)
    • 檢視x0是否為“haha”,通過調試得以驗證,

      x0存的是haha的位址

      iOS開發逆向之循環選擇指針(上)
    • 檢視其位址:

      x 0x000000010098bf9f

      ,屬于字元串的常量區(即左邊是右邊字元串的ASCII碼)
      iOS開發逆向之循環選擇指針(上)

其中重點分析

adrp x0,1

add 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

,而

0xFFF

4095

,加上1就是

0x1000

(即

4096

),是以是1左移12位即可得到一個頁的首位址(注:macOS的pageSize是 

4k

(0x1000),而iPhone的pageSize是

16k

(0x4000),但是16仍是4的倍數,adrp相容者mac和iPhone,是以此時定位的仍然是一頁資料)

  • 繼續分析

    bl printf

    以下的彙編代碼
    iOS開發逆向之循環選擇指針(上)
    • ldur w8, [x29, #-0x4]

       :拿出棧中的資料,即1
      iOS開發逆向之循環選擇指針(上)
    • adrp + add + ldr

       :拿出0x10098ce98記憶體位址的資料,将x9的資料給w10。這樣就拿到了全局變量g
      iOS開發逆向之循環選擇指針(上)

反彙編分析

示例代碼如下

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的包
    iOS開發逆向之循環選擇指針(上)
    iOS開發逆向之循環選擇指針(上)
    iOS開發逆向之循環選擇指針(上)
  • 将第5步中的可執行檔案拖入hopper中進行分析
    iOS開發逆向之循環選擇指針(上)
  • 在hopper中搜尋func
    iOS開發逆向之循環選擇指針(上)
  • 拷貝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;
}      

簡化過程如下圖所示(注:是

從下向上還原

,而不是從上向下(業務邏輯是從上至下執行):

iOS開發逆向之循環選擇指針(上)
iOS開發逆向之循環選擇指針(上)

其中

//===>此時的擷取的0x100007f9f位址的資料 是沒有ASLR的值
 000000010000681c         adrp       x0, #0x100007000      
 0000000100006820         add        x0, x0, #0xf9f      
  • hopper中按

    G

    ,查找

    0x100007f9f

    對應的資料
    iOS開發逆向之循環選擇指針(上)

同理,擷取

全局變量g

也是同樣的原理

//===>此時的擷取0x100008e98的資料000000010000682c         adrp       x9, #0x1000080000000000100006830         add        x9, x9, #0xe98                              ; _g0000000100006834         ldr        w10, x9      
iOS開發逆向之循環選擇指針(上)

總結

  • 擷取

    全局變量和常量

    時,會出現

    adrp

    add

    兩條指令獲得一個位址的情況
  • ADRP(Address Page)
    • PC

      寄存器的

      低12位清零

    • 将1的值,左移12位,16進制就是0x1000
    • 以上兩個結果相加放入

      x0

      寄存器
    • adrp x0,1

  • 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檢視其彙編代碼如下

iOS開發逆向之循環選擇指針(上)

将上述彙編代碼進行還原

<!--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 标号`:比較結果是`無符号大于等于`,執行标号,否則不跳轉      

循環

循環常用的主要有

for

while

do-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檢視其彙編
    iOS開發逆向之循環選擇指針(上)
  • 彙編結束如下所示
    iOS開發逆向之循環選擇指針(上)

結論:

do-while

循環:判斷條件在後面,滿足條件往外跳

while循環分析

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

彙編如圖所示

iOS開發逆向之循環選擇指針(上)

while

循環:判斷條件在裡面,不滿足就往外跳

for循環分析

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

此時和while的彙編是一樣的

iOS開發逆向之循環選擇指針(上)

for

循環很像:判斷條件在裡面,不滿足就往外跳

全局變量和常量

  • 全局變量和常量

    adrp

    add

    • PC

      低12位清零

    • 将1的值,左移12位
    • x0

    • adrp x0,1

  • ADD

    擷取這頁記憶體中的偏移值

條件判斷

  • CMP

    内容或立即數進行比較,但不存儲結果,隻是正确的更改标志

    B.LE

    • BL 标号

    • B.LT 标号

    • B.LE 标号

    • B.GT 标号

      大于(greater than),執行标号

    • B.GE 标号

      大于等于

    • B.EQ 标号

      等于

      ,執行标号,否則不跳轉
    • B.NE 标号

      :比較結果是不等于(not equal),執行标号,否則不跳轉
    • B.HI 标号

      無符号大于

    • B.HS 标号

      無符号大于等于