汇编-循环、选择、判断

2021-04-01  本文已影响0人  HotPotCat

内存分区

逻辑上划分(编译器划分)

全局变量和常量

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]));
}

对应的汇编:

image.png
可以看到printf函数的参数来源为:
0x104492140 <+20>: adrp   x0, 1
0x104492144 <+24>: add    x0, x0, #0xf81            ; =0xf81 

X0存储的是一个地址,为字符串常量区。那么以上两条指令是怎么获得0x0000000104493f81这个地址的?

这样就得到了常量区字符串的地址了。

0x104493000尾数为000意味着000~fff -> 0~4095大小为4096也就是4k。也就是定位到某一页数据的开始。macpagesize 4k iOSpagesize 16k。这里是兼容的4k * 4 = 16k

这里以pc寄存器为参照,也就是当前代码段地址为参照。

继续执行:

image.png
通过上面的规则可以计算出x9最终的值为0x1044955d8也就是全局变量g的值。

⚠️全局变量和常量都是通过一个 基地址 + 偏移 获取。

汇编还原高级代码

先编译要还原的工程,进入.app找到macho文件并拖入Hopper

image.png

Hopper分析完成后搜索要分析的函数

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字节参数2个
//0000000100006138         stur       w0, [x29, var_4]
//000000010000613c         str        w1, [sp, #0x10 + var_8]
    
    //这里已经算好了adrp的结果 等价于 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;
}

⚠️:从上往下还原,代码执行流程不关心,只关心结果和原始的一样。

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
 <= 跳转 loc_1000061c0,大于直接往下执行不跳转
 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跳转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
    //两个参数
//     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
//     <= 跳转 loc_1000061c0,大于直接往下执行不跳转
//     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字节
        //     00000001000061b8         str        w8, x9
        global = w8;
        //     这里b跳转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 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志(cpsr)。
一般cmp做完判断后会进行跳转,后面通常会跟上b指令!

b 跳转指令

b本身代表跳转,后面跟标号会有其他操作:

⚠️:cmp后跟的标号条件是else

循环

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 

    //参数入栈 2
    0x102c220c0 <+12>:  stur   w0, [x29, #-0x4]
    //参数存入w8
->  0x102c220c4 <+16>:  ldur   w8, [x29, #-0x4]
    //参数和最小case相减 这里是减1 给到 w8(这里相减有可能溢出)
    0x102c220c8 <+20>:  subs   w8, w8, #0x1              ; =0x1 
    //x8的值存入x9
    0x102c220cc <+24>:  mov    x9, x8
    //从0~31取出来给x9,目的是把高32位清零。相当于拿到低32位数据。
    0x102c220d0 <+28>:  ubfx   x9, x9, #0, #32
    //比较 x9 和 0x3 的值。这里 0x3是 最大 case 和 最小 case 的差值。相当于 (参数 - 最小case - (最大case - 最小case))
    0x102c220d4 <+32>:  cmp    x9, #0x3                  ; =0x3 
    //x8低32位入栈
    0x102c220d8 <+36>:  str    x9, [sp]
    //比较结果无符号大于 跳转default。相当于不在差值区间也就是匹配不了case的情况下直接走default。
    0x102c220dc <+40>:  b.hi   0x102c22138               ; <+132> at main.m

    //在区间中的情况下
    //x8的值为 0x102c22150,对应的内存中值为  fffffffa8  -88
    0x102c220e0 <+44>:  adrp   x8, 0
    0x102c220e4 <+48>:  add    x8, x8, #0x150            ; =0x150 
    //这时候从栈中取数据给x11,栈中目前是x9。x9为(参数 - 最小case = 1)
    0x102c220e8 <+52>:  ldr    x11, [sp]
    //x8 + (x11 << 2)的值给到 x10。[x8内存地址 + 4] = 移到下一个位置(0x102c22154) 这里对应的值为-72。⚠️ret的地址为0x102c2214c + 4 = 0x102c22150,0x102c22150 + 4 = 0x102c22154
    //这里计算完x10 = -72
    0x102c220ec <+56>:  ldrsw  x10, [x8, x11, lsl #2]
    //x8 + 偏移找到的负数 给到 x9  0x102c22150 - 72(0x48) = 0x102C22108
    0x102c220f0 <+60>:  add    x9, x8, x10
    //跳转寄存器的地址 0x102C22108 对应 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 

以上语句执行后x8的值:

x8的值
明显能够看出来是一个负数0xfffffffa8 ,在指向的地址中有一连串的负数。
(lldb) p *(int *)0x102c22150
(int) $5 = -88
(lldb) p (int)0xffffffa8
(int) $6 = -88
(lldb) 
Hopper解析switch(case > 3 )汇编代码

核心逻辑:

计算过程

⚠️:表中为什么不直接存地址? 1.地址过长 2.有ASLR的存在

  1. switch语句的分支比较少的时候(< 3的时候没有意义)没有必要使用表结构,相当于if

  2. 各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍,这个时候编译器还是会编译成类似于if-else的结构。
    比如:100、200、300、400这种case还是和if-else相同,10、20、30、40会生成一张表。所以在写switch逻辑的时候最好使用连续的值。至于具体逻辑编译器会根据case差值进行优化选择。case越多,差值越小,数值越连贯 编译器会生成跳转表,否则还是if-else

  3. 在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)。

  4. 跳转表中数量为最大case - 最小case + 1为一共有多少种可能性。

  5. switch分支的代码是连续的。由于case是连贯的用空间换时间。

总结

总结
上一篇下一篇

猜你喜欢

热点阅读