汇编-循环、选择、判断

内存分区

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

  • 代码区:存放代码,可读可执行
  • 栈区:参数、局部变量、临时数据。可短可写
  • 堆区:动态申请。可读可写
  • 全局变量:可读可写
  • 常量:只读

全局变量和常量

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这个地址的?

  • adrp:Address Page 内存地址以页寻址。
  • 0x104492140 <+20>: adrp x0, 1:定位到某一页数据的开始(文件起始位置)
    1.将1的值左移12位变成0x1000
    2.当前pc的值低12位清零。0x104492140 -> 0x104492000
    3.0x104492000 + 0x1000得到0x104493000。相当于pc后3位置为0,第4位加上x0后跟的值。
  • 0x104492144 <+24>: add x0, x0, #0xf81:偏移地址(当前代码偏移)
    0x104493000 + 0xf81得到0x104493f81

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

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本身代表跳转,后面跟标号会有其他操作:

  • bl 标号:跳转到标号处执行,并且影响lr寄存器的值。用于函数返回。
  • br 寄存器:根据寄存器中的值跳转。
  • b.gt 标号:比较结果是 大于(greater than) 执行标号,否则不跳转。
  • b.ge 标号:比较结果是 大于等于(greater than or equal to) 执行标号,否则不跳转。
  • b.lt 标号 :比较结果是 小于(less than) 执行标号,否则不跳转。
  • b.le 标号:比较结果是 小于等于(less than or equal to) 执行标号,否则不跳转。
  • b.eq 标号 标号:比较结果是 等于(equal) 执行标号,否则不跳转。
  • b.ne 标号 标号:比较结果是 不等于(not equal) 执行标号,否则不跳转。
  • b.hi 标号 标号:比较结果是 无符号大于 执行标号,否则不跳转。
  • b.hs 标号:比较结果是 无符号大于等于 执行标号,否则不跳转。
  • b.lo 标号:比较结果是 无符号小于 执行标号,否则不跳转。
  • b.ls 标号:比较结果是 无符号小于等于 执行标号,否则不跳转。

⚠️: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 )汇编代码

核心逻辑:

  • subs w8, w8, #0x1:参数-最小case
  • ubfx x9, x9, #0, #32:从0~31取出来给x9,目的是把高32位清零。相当于拿到低32位数据。
  • cmp x9, #0x3:0x3最大case - 最小case
  • b.hi 0x102c22138: 大于为了判断(参数 - 最小case)是否在(最大case - 最小case)区间。无符号为了处理负数,否则负数永远小于,用无符号就永远大于直接排除掉了。
  • 0x102c220e0 <+44>: adrp x8, 0
    0x102c220e4 <+48>: add x8, x8, #0x150 ; =0x150:
    找到跳转表(jump table)地址,这张跳转表在编译的时候生成是连续的,永远在程序结束的后面。
  • ldrsw x10, [x8, x11, lsl #2]:[跳转表地址 +(参数-最小case)<< 2] 得到跳转表中的偏移值。<<2是因为表中数据是4个字节。这里为 差值 * 4
  • add x9, x8, x10:上面的到的偏移值加上跳转表地址就得到了case执行的地址。

计算过程

  • 参数 - 最小case 得到表中index
  • index 与 (最大case - 最小case)无符号比较判断是否在区间内。
  • 不在区间内直接跳转defalult
  • 在区间内 表头地址 + index << 2获取偏移地址(为负数)。
  • 跳转表头地址 + 偏移地址执行对应case逻辑。

⚠️:表中为什么不直接存地址? 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是连贯的用空间换时间。

总结

总结
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容