匯編-循環(huán)、選擇、判斷

內(nèi)存分區(qū)

邏輯上劃分(編譯器劃分)

  • 代碼區(qū):存放代碼,可讀可執(zhí)行
  • 棧區(qū):參數(shù)、局部變量、臨時數(shù)據(jù)??啥炭蓪?/li>
  • 堆區(qū):動態(tài)申請??勺x可寫
  • 全局變量:可讀可寫
  • 常量:只讀

全局變量和常量

int g = 12;

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

int main(int argc, char * argv[]) {
    func(1, 2);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

對應(yīng)的匯編:

image.png

可以看到printf函數(shù)的參數(shù)來源為:

0x104492140 <+20>: adrp   x0, 1
0x104492144 <+24>: add    x0, x0, #0xf81            ; =0xf81 

X0存儲的是一個地址,為字符串常量區(qū)。那么以上兩條指令是怎么獲得0x0000000104493f81這個地址的?

  • adrp:Address Page 內(nèi)存地址以頁尋址。
  • 0x104492140 <+20>: adrp x0, 1:定位到某一頁數(shù)據(jù)的開始(文件起始位置)
    1.將1的值左移12位變成0x1000
    2.當(dāng)前pc的值低12位清零。0x104492140 -> 0x104492000
    3.0x104492000 + 0x1000得到0x104493000。相當(dāng)于pc后3位置為0,第4位加上x0后跟的值。
  • 0x104492144 <+24>: add x0, x0, #0xf81:偏移地址(當(dāng)前代碼偏移)
    0x104493000 + 0xf81得到0x104493f81。

這樣就得到了常量區(qū)字符串的地址了。

0x104493000尾數(shù)為000意味著000~fff -> 0~4095大小為4096也就是4k。也就是定位到某一頁數(shù)據(jù)的開始。macpagesize 4k iOSpagesize 16k。這里是兼容的4k * 4 = 16k。

這里以pc寄存器為參照,也就是當(dāng)前代碼段地址為參照。

繼續(xù)執(zhí)行:

image.png

通過上面的規(guī)則可以計算出x9最終的值為0x1044955d8也就是全局變量g的值。

??全局變量和常量都是通過一個 基地址 + 偏移 獲取。

匯編還原高級代碼

先編譯要還原的工程,進入.app找到macho文件并拖入Hopper

image.png

Hopper分析完成后搜索要分析的函數(shù)

image.png

通過匯編我們可以還原高級語言的行為。
比如剛才func分析出來匯編代碼為:


                     _func:
000000010000612c         sub        sp, sp, #0x20                               ; CODE XREF=_main+32
0000000100006130         stp        x29, x30, [sp, #0x10]
0000000100006134         add        x29, sp, #0x10
0000000100006138         stur       w0, [x29, var_4]
000000010000613c         str        w1, [sp, #0x10 + var_8]
0000000100006140         adrp       x0, #0x100007000                            ; 0x100007f81@PAGE, argument "format" for method imp___stubs__printf
0000000100006144         add        x0, x0, #0xf81                              ; 0x100007f81@PAGEOFF, "test"
0000000100006148         bl         imp___stubs__printf                         ; printf
000000010000614c         ldur       w8, [x29, var_4]
0000000100006150         adrp       x9, #0x100009000                            ; 0x1000095d8@PAGE
0000000100006154         add        x9, x9, #0x5d8                              ; 0x1000095d8@PAGEOFF, _g
0000000100006158         ldr        w10, [x9]                                   ; _g
000000010000615c         add        w8, w8, w10
0000000100006160         ldr        w10, [sp, #0x10 + var_8]
0000000100006164         add        w8, w8, w10
0000000100006168         str        w8, [sp, #0x10 + var_C]
000000010000616c         ldr        w8, [sp, #0x10 + var_C]
0000000100006170         mov        x0, x8
0000000100006174         ldp        x29, x30, [sp, #0x10]
0000000100006178         add        sp, sp, #0x20
000000010000617c         ret
                        ; endp

可以通過MachOView查找0000000100007f81

image.png

00000001000095d8:

image.png

分析完代碼如下:

int global_g = 12;

int func2(int a, int b) {
    
//_func:
//000000010000612c         sub        sp, sp, #0x20                               ; CODE XREF=_main+32
//0000000100006130         stp        x29, x30, [sp, #0x10]
//0000000100006134         add        x29, sp, #0x10
    
    //4字節(jié)參數(shù)2個
//0000000100006138         stur       w0, [x29, var_4]
//000000010000613c         str        w1, [sp, #0x10 + var_8]
    
    //這里已經(jīng)算好了adrp的結(jié)果 等價于 0000000100006140   adrp  x0, #0x1
//0000000100006140         adrp       x0, #0x100007000                            ; 0x100007f81@PAGE, argument "format" for method imp___stubs__printf
//0000000100006144         add        x0, x0, #0xf81                              ; 0x100007f81@PAGEOFF, "test"
    //這里可以算出地址為 0000000100007f81,在 MachOView 查找為 test
//0000000100006148         bl         imp___stubs__printf                         ; printf
    printf("test");
//000000010000614c         ldur       w8, [x29, var_4]
    int w8 = a;
//0000000100006150         adrp       x9, #0x100009000                            ; 0x1000095d8@PAGE
//0000000100006154         add        x9, x9, #0x5d8                              ; 0x1000095d8@PAGEOFF, _g
    //00000001000095d8 在 MachOView 中 為 0xC 也就是 12,定義全局變量 global_g = 12
//0000000100006158         ldr        w10, [x9]                                   ; _g
    int w10 = global_g;
//000000010000615c         add        w8, w8, w10
    w8 += w10;
//0000000100006160         ldr        w10, [sp, #0x10 + var_8]
    w10 = b;
//0000000100006164         add        w8, w8, w10
    w8 += w10;
//0000000100006168         str        w8, [sp, #0x10 + var_C]
//000000010000616c         ldr        w8, [sp, #0x10 + var_C]
//0000000100006170         mov        x0, x8
    
//0000000100006174         ldp        x29, x30, [sp, #0x10]
//0000000100006178         add        sp, sp, #0x20
//000000010000617c         ret
//   ; endp
    return w8;
}

精簡后:

int global_g = 12;

int func2(int a, int b) {
    printf("test");
    return a + global_g + b;
}

??:從上往下還原,代碼執(zhí)行流程不關(guān)心,只關(guān)心結(jié)果和原始的一樣。

if識別

int  g = 12;

void func(int a, int b) {
    if (a > b) {
        g = a;
    } else {
        g = b;
    }
}
Hopper解析出來的匯編
 0000000100006190         sub        sp, sp, #0x10                               ; CODE XREF=_main+32
 0000000100006194         str        w0, [sp, #0xc]
 0000000100006198         str        w1, [sp, #0x8]
 000000010000619c         ldr        w8, [sp, #0xc]
 00000001000061a0         ldr        w9, [sp, #0x8]
 
 a和b比較大小,cmp不影響 w8 和 w9 的值。做減法只影響標記寄存器的值。
 00000001000061a4         cmp        w8, w9
 <= 跳轉(zhuǎn) loc_1000061c0,大于直接往下執(zhí)行不跳轉(zhuǎn)
 00000001000061a8         b.le       loc_1000061c0

 //代碼塊1
 00000001000061ac         ldr        w8, [sp, #0xc]
 00000001000061b0         adrp       x9, #0x100009000
 00000001000061b4         add        x9, x9, #0x5d0                              ; _g
 00000001000061b8         str        w8, x9
 這里b跳轉(zhuǎn)loc_1000061d0,跳過了else的代碼
 00000001000061bc         b          loc_1000061d0
 //代碼塊2
                      loc_1000061c0:
 00000001000061c0         ldr        w8, [sp, #0x8]                              ; CODE XREF=_func+24
 00000001000061c4         adrp       x9, #0x100009000
 00000001000061c8         add        x9, x9, #0x5d0                              ; _g
 00000001000061cc         str        w8, x9

                      loc_1000061d0:
 00000001000061d0         add        sp, sp, #0x10                               ; CODE XREF=_func+44
 00000001000061d4         ret

還原代碼

int global = 12;

void func2(int a, int b) {
//     0000000100006190         sub        sp, sp, #0x10                               ; CODE XREF=_main+32
    //兩個參數(shù)
//     0000000100006194         str        w0, [sp, #0xc]
//     0000000100006198         str        w1, [sp, #0x8]
//     000000010000619c         ldr        w8, [sp, #0xc]
//     00000001000061a0         ldr        w9, [sp, #0x8]
    int w8 = a, w9 = b;
//     a和b比較大小,cmp不影響 w8 和 w9 的值。做減法只影響標記寄存器的值。
//     00000001000061a4         cmp        w8, w9
//     <= 跳轉(zhuǎn) loc_1000061c0,大于直接往下執(zhí)行不跳轉(zhuǎn)
//     00000001000061a8         b.le       loc_1000061c0
    if (w8 > w9) {
        //     //代碼塊1
        //     00000001000061ac         ldr        w8, [sp, #0xc]
        w8 = a;
        //     00000001000061b0         adrp       x9, #0x100009000
        //     00000001000061b4         add        x9, x9, #0x5d0                              ; _g
        //MachOView中查到 00000001000095d0 為  0xC 4字節(jié)
        //     00000001000061b8         str        w8, x9
        global = w8;
        //     這里b跳轉(zhuǎn)loc_1000061d0,跳過了else的代碼
        //     00000001000061bc         b          loc_1000061d0

    } else {
        //     //代碼塊2
        //                          loc_1000061c0:
        //     00000001000061c0         ldr        w8, [sp, #0x8]                              ; CODE XREF=_func+24
        w8 = b;
        //     00000001000061c4         adrp       x9, #0x100009000
        //     00000001000061c8         add        x9, x9, #0x5d0                              ; _g
        //     00000001000061cc         str        w8, x9
        global = w8;
    }
//                          loc_1000061d0:
//     00000001000061d0         add        sp, sp, #0x10                               ; CODE XREF=_func+44
     //ret前沒有x8和x0的操作,返回void
//     00000001000061d4         ret
}

精簡后:

int global = 12;

void func2(int a, int b) {
    if (a > b) {
        global = a;
    } else {
        global = b;
    }
}

cmp(Compare)比較指令

cmp 把一個寄存器的內(nèi)容和另一個寄存器的內(nèi)容或立即數(shù)進行比較。但不存儲結(jié)果,只是正確的更改標志(cpsr)。
一般cmp做完判斷后會進行跳轉(zhuǎn),后面通常會跟上b指令!

b 跳轉(zhuǎn)指令

b本身代表跳轉(zhuǎn),后面跟標號會有其他操作:

  • bl 標號:跳轉(zhuǎn)到標號處執(zhí)行,并且影響lr寄存器的值。用于函數(shù)返回。
  • br 寄存器:根據(jù)寄存器中的值跳轉(zhuǎn)。
  • b.gt 標號:比較結(jié)果是 大于(greater than) 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.ge 標號:比較結(jié)果是 大于等于(greater than or equal to) 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.lt 標號 :比較結(jié)果是 小于(less than) 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.le 標號:比較結(jié)果是 小于等于(less than or equal to) 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.eq 標號 標號:比較結(jié)果是 等于(equal) 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.ne 標號 標號:比較結(jié)果是 不等于(not equal) 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.hi 標號 標號:比較結(jié)果是 無符號大于 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.hs 標號:比較結(jié)果是 無符號大于等于 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.lo 標號:比較結(jié)果是 無符號小于 執(zhí)行標號,否則不跳轉(zhuǎn)。
  • b.ls 標號:比較結(jié)果是 無符號小于等于 執(zhí)行標號,否則不跳轉(zhuǎn)。

??:cmp后跟的標號條件是else。

循環(huán)

do-while

void func() {
    int nSum = 0;
    int i = 0;
    do {
        nSum = nSum + 1;
        i++;
    } while (i < 100);
}
Hopper解析do-while匯編代碼

while

void func() {
    int nSum = 0;
    int i = 0;
    while (i < 100) {
        nSum = nSum + 1;
        i++;
    }
}
Hopper解析while匯編代碼

for

void func() {
    int nSum = 0;
    for (int i = 0; i < 100; i++) {
        nSum = nSum + 1;
    }
}

Hopper解析for匯編代碼

??forwhile的匯編代碼相同。

Switch 選擇指令

void func(int a) {
    switch (a) {
        case 1:
            printf("case 1");
            break;
        case 2:
            printf("case 2");
            break;
        case 3:
            printf("case 3");
            break;
        default:
            printf("case default");
            break;
    }
}
Hopper解析switch(case <=3 )匯編代碼

case < 3的情況下底層匯編就是if-else。

修改代碼讓case多于3個:

void func(int a) {
    switch (a) {
        case 1:
            printf("case 1");
            break;
        case 2:
            printf("case 2");
            break;
        case 3:
            printf("case 3");
            break;
        case 4:
            printf("case 4");
            break;
        default:
            printf("case default");
            break;
    }
}

int main(int argc, char * argv[]) {
    func(2);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

分析匯編代碼:

TestDemo`func:
    0x102c220b4 <+0>:   sub    sp, sp, #0x20             ; =0x20 
    0x102c220b8 <+4>:   stp    x29, x30, [sp, #0x10]
    0x102c220bc <+8>:   add    x29, sp, #0x10            ; =0x10 

    //參數(shù)入棧 2
    0x102c220c0 <+12>:  stur   w0, [x29, #-0x4]
    //參數(shù)存入w8
->  0x102c220c4 <+16>:  ldur   w8, [x29, #-0x4]
    //參數(shù)和最小case相減 這里是減1 給到 w8(這里相減有可能溢出)
    0x102c220c8 <+20>:  subs   w8, w8, #0x1              ; =0x1 
    //x8的值存入x9
    0x102c220cc <+24>:  mov    x9, x8
    //從0~31取出來給x9,目的是把高32位清零。相當(dāng)于拿到低32位數(shù)據(jù)。
    0x102c220d0 <+28>:  ubfx   x9, x9, #0, #32
    //比較 x9 和 0x3 的值。這里 0x3是 最大 case 和 最小 case 的差值。相當(dāng)于 (參數(shù) - 最小case - (最大case - 最小case))
    0x102c220d4 <+32>:  cmp    x9, #0x3                  ; =0x3 
    //x8低32位入棧
    0x102c220d8 <+36>:  str    x9, [sp]
    //比較結(jié)果無符號大于 跳轉(zhuǎn)default。相當(dāng)于不在差值區(qū)間也就是匹配不了case的情況下直接走default。
    0x102c220dc <+40>:  b.hi   0x102c22138               ; <+132> at main.m

    //在區(qū)間中的情況下
    //x8的值為 0x102c22150,對應(yīng)的內(nèi)存中值為  fffffffa8  -88
    0x102c220e0 <+44>:  adrp   x8, 0
    0x102c220e4 <+48>:  add    x8, x8, #0x150            ; =0x150 
    //這時候從棧中取數(shù)據(jù)給x11,棧中目前是x9。x9為(參數(shù) - 最小case = 1)
    0x102c220e8 <+52>:  ldr    x11, [sp]
    //x8 + (x11 << 2)的值給到 x10。[x8內(nèi)存地址 + 4] = 移到下一個位置(0x102c22154) 這里對應(yīng)的值為-72。??ret的地址為0x102c2214c + 4 = 0x102c22150,0x102c22150 + 4 = 0x102c22154
    //這里計算完x10 = -72
    0x102c220ec <+56>:  ldrsw  x10, [x8, x11, lsl #2]
    //x8 + 偏移找到的負數(shù) 給到 x9  0x102c22150 - 72(0x48) = 0x102C22108
    0x102c220f0 <+60>:  add    x9, x8, x10
    //跳轉(zhuǎn)寄存器的地址 0x102C22108 對應(yīng) case 2 的地址
    0x102c220f4 <+64>:  br     x9

    //case中語句
    0x102c220f8 <+68>:  adrp   x0, 1
    0x102c220fc <+72>:  add    x0, x0, #0xf5d            ; =0xf5d 
    0x102c22100 <+76>:  bl     0x102c22588               ; symbol stub for: printf
    0x102c22104 <+80>:  b      0x102c22144               ; <+144> at main.m:48:1
    0x102c22108 <+84>:  adrp   x0, 1
    0x102c2210c <+88>:  add    x0, x0, #0xf64            ; =0xf64 
    0x102c22110 <+92>:  bl     0x102c22588               ; symbol stub for: printf
    0x102c22114 <+96>:  b      0x102c22144               ; <+144> at main.m:48:1
    0x102c22118 <+100>: adrp   x0, 1
    0x102c2211c <+104>: add    x0, x0, #0xf6b            ; =0xf6b 
    0x102c22120 <+108>: bl     0x102c22588               ; symbol stub for: printf
    0x102c22124 <+112>: b      0x102c22144               ; <+144> at main.m:48:1
    0x102c22128 <+116>: adrp   x0, 1
    0x102c2212c <+120>: add    x0, x0, #0xf72            ; =0xf72 
    0x102c22130 <+124>: bl     0x102c22588               ; symbol stub for: printf
    0x102c22134 <+128>: b      0x102c22144               ; <+144> at main.m:48:1
    0x102c22138 <+132>: adrp   x0, 1
    0x102c2213c <+136>: add    x0, x0, #0xf79            ; =0xf79 
    0x102c22140 <+140>: bl     0x102c22588               ; symbol stub for: printf

    0x102c22144 <+144>: ldp    x29, x30, [sp, #0x10]
    0x102c22148 <+148>: add    sp, sp, #0x20             ; =0x20 
    0x102c2214c <+152>: ret    
0x102c220e0 <+44>:  adrp   x8, 0
0x102c220e4 <+48>:  add    x8, x8, #0x150            ; =0x150 

以上語句執(zhí)行后x8的值:

x8的值

明顯能夠看出來是一個負數(shù)0xfffffffa8 ,在指向的地址中有一連串的負數(shù)。

(lldb) p *(int *)0x102c22150
(int) $5 = -88
(lldb) p (int)0xffffffa8
(int) $6 = -88
(lldb) 
Hopper解析switch(case > 3 )匯編代碼

核心邏輯:

  • subs w8, w8, #0x1:參數(shù)-最小case
  • ubfx x9, x9, #0, #32:從0~31取出來給x9,目的是把高32位清零。相當(dāng)于拿到低32位數(shù)據(jù)。
  • cmp x9, #0x3:0x3最大case - 最小case
  • b.hi 0x102c22138: 大于為了判斷(參數(shù) - 最小case)是否在(最大case - 最小case)區(qū)間。無符號為了處理負數(shù),否則負數(shù)永遠小于,用無符號就永遠大于直接排除掉了。
  • 0x102c220e0 <+44>: adrp x8, 0
    0x102c220e4 <+48>: add x8, x8, #0x150 ; =0x150:
    找到跳轉(zhuǎn)表(jump table)地址,這張?zhí)D(zhuǎn)表在編譯的時候生成是連續(xù)的,永遠在程序結(jié)束的后面。
  • ldrsw x10, [x8, x11, lsl #2]:[跳轉(zhuǎn)表地址 +(參數(shù)-最小case)<< 2] 得到跳轉(zhuǎn)表中的偏移值。<<2是因為表中數(shù)據(jù)是4個字節(jié)。這里為 差值 * 4
  • add x9, x8, x10:上面的到的偏移值加上跳轉(zhuǎn)表地址就得到了case執(zhí)行的地址。

計算過程

  • 參數(shù) - 最小case 得到表中index
  • index 與 (最大case - 最小case)無符號比較判斷是否在區(qū)間內(nèi)。
  • 不在區(qū)間內(nèi)直接跳轉(zhuǎn)defalult。
  • 在區(qū)間內(nèi) 表頭地址 + index << 2獲取偏移地址(為負數(shù))。
  • 跳轉(zhuǎn)表頭地址 + 偏移地址執(zhí)行對應(yīng)case邏輯。

??:表中為什么不直接存地址? 1.地址過長 2.有ASLR的存在

  1. switch語句的分支比較少的時候(< 3的時候沒有意義)沒有必要使用表結(jié)構(gòu),相當(dāng)于if

  2. 各個分支常量的差值較大的時候,編譯器會在效率還是內(nèi)存進行取舍,這個時候編譯器還是會編譯成類似于if-else的結(jié)構(gòu)。
    比如:100、200、300、400這種case還是和if-else相同,10、20、30、40會生成一張表。所以在寫switch邏輯的時候最好使用連續(xù)的值。至于具體邏輯編譯器會根據(jù)case差值進行優(yōu)化選擇。case越多,差值越小,數(shù)值越連貫 編譯器會生成跳轉(zhuǎn)表,否則還是if-else。

  3. 在分支比較多的時候:在編譯的時候會生成一個表(跳轉(zhuǎn)表每個地址四個字節(jié))。

  4. 跳轉(zhuǎn)表中數(shù)量為最大case - 最小case + 1為一共有多少種可能性。

  5. switch分支的代碼是連續(xù)的。由于case是連貫的用空間換時間。

總結(jié)

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

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

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