美文网首页iOS逆向工程
逆向学习笔记4——汇编的循环&选择

逆向学习笔记4——汇编的循环&选择

作者: 危险地带_浅笑 | 来源:发表于2018-05-02 10:55 被阅读23次

    常用指令

    • cmp(Compare)比较指令 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志。一般CMP做完判断后会进行跳转,后面通常会跟上B指令!

    • BL 标号:跳转到标号处执行

    • B.LE 标号:比较结果是小于等于(less than or equal),执行标号,否则不跳转

    • B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转

    • B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转

    • B.EQ 标号:比较结果是等于,执行标号,否则不跳转

    • B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

    • ldrsw:[X8,X9,LSL#2]` 表示以 X8 为基地址,X9的二进制左移2位,再相加得到一个新的地址值。
      例如:A表示(X8 为基地址),B 表示(X9的二进制左移2位)
      那么 X10 = [A B];

    if

    void func(int a, int b){
        if(a > b){
            printf("a大于b");
        }else{
            printf("a不大于b");
        }
    }
    

    上面代码的汇编如下:

    demo1`func:
        0x100f926a0 <+0>:  sub    sp, sp, #0x20             ; =0x20 //拉升栈空间
        0x100f926a4 <+4>:  stp    x29, x30, [sp, #0x10]
        0x100f926a8 <+8>:  add    x29, sp, #0x10            ; =0x10 
        0x100f926ac <+12>: stur   w0, [x29, #-0x4]
        0x100f926b0 <+16>: str    w1, [sp, #0x8]
        0x100f926b4 <+20>: ldur   w0, [x29, #-0x4]
        0x100f926b8 <+24>: ldr    w1, [sp, #0x8]
       //上面这些代码不用管,全是函数对局部变量的操作
    
        //比较w0和w1的值
        0x100f926bc <+28>: cmp    w0, w1
        //如果w0<=w1 跳转到地址为0x100f926d8的指令
        0x100f926c0 <+32>: b.le   0x100f926d8               ; <+56> at main.m:17 
    
        //下面两句代码得到的x0对应的常量值为0x100f9365c
        0x100f926c4 <+36>: adrp   x0, 1
        0x100f926c8 <+40>: add    x0, x0, #0x65c            ; =0x65c 
    
        //打印
        0x100f926cc <+44>: bl     0x100f92a68               ; symbol stub for: printf
        0x100f926d0 <+48>: str    w0, [sp, #0x4]
    
        //跳转地址为0x100f926e8指令-->相当于高级语音中的goto
        0x100f926d4 <+52>: b      0x100f926e8               ; <+72> at main.m:19
    
        //下面两句代码得到的x0对应的常量值为0x100f93665
        0x100f926d8 <+56>: adrp   x0, 1
        0x100f926dc <+60>: add    x0, x0, #0x665            ; =0x665 
        //打印
        0x100f926e0 <+64>: bl     0x100f92a68               ; symbol stub for: printf
        0x100f926e4 <+68>: str    w0, [sp]
    
        //栈平衡
        0x100f926e8 <+72>: ldp    x29, x30, [sp, #0x10]
        0x100f926ec <+76>: add    sp, sp, #0x20             ; =0x20 
    
        //返回
        0x100f926f0 <+80>: ret
    

    while

    先补充两个指令
    Zero Register: 在大多数情况下,作为源寄存器使用时, r31读出来的值 是0; 作为目标寄存器使用时, 丢弃结果。 WZR(word zero rigiser)或者XZR(64位)

    Stack Register: 当 用作load/store 的base register时, 或者 一些算术指令中, r31提供当前的stack pointer WSP或者 SP

    void func(int a, int b){
        while(a < b){
            printf("a大于b");
        }
    }
    

    上面代码的汇编如下:

        0x1009ca700 <+0>:  sub    sp, sp, #0x10             ; =0x10 
        //将0寄存器的值写进内存
        0x1009ca704 <+4>:  str    wzr, [sp, #0xc]
        //将刚刚存的值取出来并放在w8寄存器中
        0x1009ca708 <+8>:  ldr    w8, [sp, #0xc]
        //比较w8 和 10的大小
        0x1009ca70c <+12>: cmp    w8, #0xa                  ; =0xa 
        如果w8 >= 10,跳转0x1009ca724
        0x1009ca710 <+16>: b.ge   0x1009ca724               ; <+36> at main.m:18
        //从内存拿出值赋值w8
        0x1009ca714 <+20>: ldr    w8, [sp, #0xc]
        //w8 += 1
        0x1009ca718 <+24>: add    w8, w8, #0x1              ; =0x1 
        //将w8值写进内存
        0x1009ca71c <+28>: str    w8, [sp, #0xc]
        //跳转到0x1009ca708
        0x1009ca720 <+32>: b      0x1009ca708               ; <+8> at main.m:15
        //栈平衡
        0x1009ca724 <+36>: add    sp, sp, #0x10             ; =0x10 
        0x1009ca728 <+40>: ret     
    

    for / do-while基本和while类似,此处就不再赘述

    Switch

    • 1、假设switch语句的分支比较少的时候(例如3,少于4的时候没有意义)没有必要使用此结构,相当于if。
    • 2、各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍,这个时候编译器还是会编译成类似于if,else的结构。
    • 3、在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)。

    CBZ

    比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)

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

    上面代码的汇编代码如下

    demo1`func:
        0x1005ae6a0 <+0>:   sub    sp, sp, #0x30             ; =0x30 
        0x1005ae6a4 <+4>:   stp    x29, x30, [sp, #0x20]
        0x1005ae6a8 <+8>:   add    x29, sp, #0x20            ; =0x20
        0x1005ae6ac <+12>:  stur   w0, [x29, #-0x4]
        0x1005ae6b0 <+16>:  ldur   w0, [x29, #-0x4]
        0x1005ae6b4 <+20>:  mov    x8, x0
        0x1005ae6b8 <+24>:  stur   w8, [x29, #-0x8]
       //上面全是栈操作和其他无关紧要操作
    
       //如果传进来的w0的值为0,就跳转地址0x1005ae6d8的指令,否则继续往下执行
        0x1005ae6bc <+28>:  cbz    w0, 0x1005ae6d8           ; <+56> at main.m:17
        
         //跳转地址为0x1005ae6c4的指令
        0x1005ae6c0 <+32>:  b      0x1005ae6c4               ; <+36> at main.m
        //拿到w8=x0的值
        0x1005ae6c4 <+36>:  ldur   w8, [x29, #-0x8]
        //w9=w8 - 1
        0x1005ae6c8 <+40>:  subs   w9, w8, #0x1              ; =0x1 
        //将w9的值存入内存
        0x1005ae6cc <+44>:  stur   w9, [x29, #-0xc]
        //w9=0,就跳转地址为0x1005ae6ec的指令,否则不跳转
        0x1005ae6d0 <+48>:  b.eq   0x1005ae6ec               ; <+76> at main.m:22
         //跳转地址为0x1005ae700指令
        0x1005ae6d4 <+52>:  b      0x1005ae700               ; <+96> at main.m:28
    
        //下面三句代码是一个打印,printf("0");
        0x1005ae6d8 <+56>:  adrp   x0, 1
        0x1005ae6dc <+60>:  add    x0, x0, #0x670            ; =0x670 
        0x1005ae6e0 <+64>:  bl     0x1005aea7c               ; symbol stub for: printf
        0x1005ae6e4 <+68>:  str    w0, [sp, #0x10]
    
      //跳转汇编为0x1005ae704的指令
        0x1005ae6e8 <+72>:  b      0x1005ae704               ; <+100> at main.m:31
       //下面三句是打印
        0x1005ae6ec <+76>:  adrp   x0, 1
        0x1005ae6f0 <+80>:  add    x0, x0, #0x672            ; =0x672 
        0x1005ae6f4 <+84>:  bl     0x1005aea7c               ; symbol stub for: printf
       
        0x1005ae6f8 <+88>:  str    w0, [sp, #0xc]
    
          //跳转地址为0x1005ae704指令
        0x1005ae6fc <+92>:  b      0x1005ae704               ; <+100> at main.m:31
        0x1005ae700 <+96>:  b      0x1005ae704               ; <+100> at main.m:31
        0x1005ae704 <+100>: ldp    x29, x30, [sp, #0x20]
        0x1005ae708 <+104>: add    sp, sp, #0x30             ; =0x30 
        0x1005ae70c <+108>: ret    
    
    
    void func(int a){
        switch (a) {
            case 0:
            {
                printf("0");
            }
                break;
            case 1:
            {
                printf("1");
            }
                break;
            case 2:
            {
                printf("2");
            }
                break;
            case 3:
            {
                printf("3");
            }
                break;
                
            default:
                break;
        }
        
    }
    

    上面代码的汇编如下

        0x1002e2658 <+0>:   sub    sp, sp, #0x40             ; =0x40 
        0x1002e265c <+4>:   stp    x29, x30, [sp, #0x30]
        0x1002e2660 <+8>:   add    x29, sp, #0x30            ; =0x30 
        0x1002e2664 <+12>:  stur   w0, [x29, #-0x4]
        0x1002e2668 <+16>:  ldur   w0, [x29, #-0x4]
        0x1002e266c <+20>:  mov    x8, x0
        0x1002e2670 <+24>:  mov    x0, x8
       //以上代码全是栈操作这里就不做分析了
    
       //w0 = w0 - 3;
        0x1002e2674 <+28>:  subs   w0, w0, #0x3              ; =0x3 
        //将x8的值写入内存
        0x1002e2678 <+32>:  stur   x8, [x29, #-0x10]
        //将w0写入内存
        0x1002e267c <+36>:  stur   w0, [x29, #-0x14]
        //如果w0>0就跳转到地址为0x1002e26ec的指令,否则不跳转
        0x1002e2680 <+40>:  b.hi   0x1002e26ec               ; <+148> at main.m:37
        //将常量0的地址赋值给x8  x8 = 0
        0x1002e2684 <+44>:  adrp   x8, 0
        0x1002e2688 <+48>:  add    x8, x8, #0x6fc            ; =0x6fc 
    
       //将上面0x1002e2678存入内存的值取出来
        0x1002e268c <+52>:  ldur   x9, [x29, #-0x10]
    
        //这个是x10 = x8 + x9<<2,得出系统给我们存的switch的那个表值得地址
        0x1002e2690 <+56>:  ldrsw  x10, [x8, x9, lsl #2]
        //x8 = x10 + x8
        0x1002e2694 <+60>:  add    x8, x10, x8
       //计算得到x8的值后,跳转到x8指向的地址值
        0x1002e2698 <+64>:  br     x8
    
       //下面3句打印
        0x1002e269c <+68>:  adrp   x0, 1
        0x1002e26a0 <+72>:  add    x0, x0, #0x66c            ; =0x66c 
        0x1002e26a4 <+76>:  bl     0x1002e2a78               ; symbol stub for: printf
        //保护w0
        0x1002e26a8 <+80>:  str    w0, [sp, #0x18]
       //跳转地址为0x1002e26f0的指令
        0x1002e26ac <+84>:  b      0x1002e26f0               ; <+152> at main.m:40
       
       //下面3句打印
        0x1002e26b0 <+88>:  adrp   x0, 1
        0x1002e26b4 <+92>:  add    x0, x0, #0x66e            ; =0x66e 
        0x1002e26b8 <+96>:  bl     0x1002e2a78               ; symbol stub for: printf
        0x1002e26bc <+100>: str    w0, [sp, #0x14]
        0x1002e26c0 <+104>: b      0x1002e26f0               ; <+152> at main.m:40
    
        //下面3句打印
        0x1002e26c4 <+108>: adrp   x0, 1
        0x1002e26c8 <+112>: add    x0, x0, #0x670            ; =0x670 
        0x1002e26cc <+116>: bl     0x1002e2a78               ; symbol stub for: printf
        0x1002e26d0 <+120>: str    w0, [sp, #0x10]
        0x1002e26d4 <+124>: b      0x1002e26f0               ; <+152> at main.m:40
    
        //下面3句打印
        0x1002e26d8 <+128>: adrp   x0, 1
        0x1002e26dc <+132>: add    x0, x0, #0x672            ; =0x672 
        0x1002e26e0 <+136>: bl     0x1002e2a78               ; symbol stub for: printf
        0x1002e26e4 <+140>: str    w0, [sp, #0xc]
        0x1002e26e8 <+144>: b      0x1002e26f0               ; <+152> at main.m:40
        0x1002e26ec <+148>: b      0x1002e26f0               ; <+152> at main.m:40
        //栈平衡
        0x1002e26f0 <+152>: ldp    x29, x30, [sp, #0x30]
        0x1002e26f4 <+156>: add    sp, sp, #0x40             ; =0x40 
        0x1002e26f8 <+160>: ret 
    

    执行过程

      1. switch 内分支比较多的时候(超过4个),在编译的时候会生成一个表(跳转表每个地址四个字节);
      1. 先判断是否是 default 分支;
      1. 根据相应的操作计算出要跳转的地址;
        这个是x10 = x8 + x9<<2,得出系统给我们存的switch的那个表值得地址
        0x1002e2690 <+56>: ldrsw x10, [x8, x9, lsl #2]
        通过计算得到跳转的指令的地址
        0x1002e2694 <+60>: add x8, x10, x8
      1. 跳转到X8的地址;
        0x1002e2698 <+64>: br x8

    注意

    虽然说正常分支超过4个,编译器就会生成一张表;但是也有不生成表的,比如你一定要写出case1,case 1000,case 200,编译器存储是连续的地址,会根据1,2,3...1000,如果还按照表的形式会浪费大量的空间,编译器会在效率和存储空间做出最合理的选择,将他们做成if,else的形式表现出来。

    总结

    switch语句 和 if...else语句执行效率问题,通过上面汇编代码得出结论:
    当 switch 分支和 if...else的条件判断小于4的时候,执行效率是一样的;
    当 switch 分支和 if...else的条件判断大于等于4的时候,switch 执行效率更高。(你的case是连续常量)

    相关文章

      网友评论

        本文标题:逆向学习笔记4——汇编的循环&选择

        本文链接:https://www.haomeiwen.com/subject/aviwlftx.html