美文网首页iOS 逆向工程 app安全 网络安全iOS逆向工程女程序猿
iOS 汇编基础(三)还原高级代码之循环和判断

iOS 汇编基础(三)还原高级代码之循环和判断

作者: meryin | 来源:发表于2018-05-02 15:44 被阅读33次

    一 if 和 if else

    1. if
    int g = 30;
    void func(int a,int b){
       if (a >b) {
         g = a;
       }
     }
    
    int main(int argc, char * argv[]) {
        func(10, 20);
       return 0;
    }
    

    上面func的汇编代码如下:

    067A0 _func                                   ; CODE XREF: _main+28↓p
    
     var_8           = -8
     var_4           = -4
     SUB             SP, SP, #0x10 //拉伸栈空间
    STR             W0, [SP,#0x10+var_4] //把w0写入内存 int var_4=w0
    STR             W1, [SP,#0x10+var_8] //把w1写入内存 int var_8 =w1
    LDR             W0, [SP,#0x10+var_4] //把w0从内存var_4中读出来 int w0= var_4
    LDR             W1, [SP,#0x10+var_8] //把w1从内存var_8中读出来 int w1= var_8
     CMP             W0, W1  //比较w0和w1
    B.LE            loc_1000067CC  //如果w0<=w1那么跳转到loc_1000067CC处执行
    ADRP            X8, #_g@PAGE 
    ADD             X8, X8, #_g@PAGEOFF  //以上两句获取全局变量g的储存地址
    LDR             W9, [SP,#0x10+var_4]  //int w9=var_4
    STR             W9, [X8]  // 把w9读到x8的内存地址中去,即 *x8=w9
    
     loc_1000067CC                          
                    ADD             SP, SP, #0x10  //恢复栈 栈平衡
                    RET    //返回的时候没有任何操作以及没有对x0进行操作,所以没有返回值
    

    main函数的汇编如下:

     _main
    __text:00000001000068F8
    __text:00000001000068F8 var_10          = -0x10
    __text:00000001000068F8 var_8           = -8
    __text:00000001000068F8 var_4           = -4
    __text:00000001000068F8 var_s0          =  0
    __text:00000001000068F8
    __text:00000001000068F8                 SUB             SP, SP, #0x20
    __text:00000001000068FC                 STP             X29, X30, [SP,#0x10+var_s0]
    __text:0000000100006900                 ADD             X29, SP, #0x10
    __text:0000000100006904                 MOV             W8, #0xA
    __text:0000000100006908                 MOV             W9, #0x14
    __text:000000010000690C                 STUR            WZR, [X29,#var_4]
    __text:0000000100006910                 STR             W0, [SP,#0x10+var_8]
    __text:0000000100006914                 STR             X1, [SP,#0x10+var_10]
    __text:0000000100006918                 MOV             X0, X8
    __text:000000010000691C                 MOV             X1, X9
    __text:0000000100006920                 BL              _func
    __text:0000000100006924                 MOV             W8, #0
    __text:0000000100006928                 MOV             X0, X8
    __text:000000010000692C                 LDP             X29, X30, [SP,#0x10+var_s0]
    __text:0000000100006930                 ADD             SP, SP, #0x20
    __text:0000000100006934                 RET
    __text:0000000100006934 ; End of function _main
    
    • 因为在main中调用func之前MOV X0, X8 和 MOV X1, X9 x0-x7保存参数,w是x的低32位,所以func有两个参数。
    • 在main中,因为MOV W8, #0xAMOV W9, #0x14所以func的两个参数分别是#0xA和#0x14换算成10进制为10和20,以上得到void func(int a, int b)main中调用func(10,20)
    • 在func的汇编中SP, SP, #0x10这是拉伸栈空间,拉伸#0x10即16个字节。拉伸多少栈空间和局部变量参数以及是否保护x29,x30有关。
    • 根据func中的注释的分析,得到:
    void func(int a, int b)
    {
      int var_4=w0;
      int var_8 =w1;
      int w0= var_4;
      int w1= var_8;
      if (w0 <= w1}
      {
          return;
      }
      g = *x8;
      int w9=var_4;
      *x8 = w9;
    }  由此获得 void func(int a,intb){ if (a>b){g = a} }
    
    2. if else
    int  func(){
        int a=10;
        int b=20;
        if (a >b) {
            g = a;
        }
        else{
             g=b;
        }
        return g;
    }
    

    汇编如下:

     var_8           = -8
    var_4           = -4
    SUB             SP, SP, #0x10   //拉伸16字节栈空间
    MOV             W8, #0x14   //int w8 = 0x14即int w8=20;
    MOV             W9, #0xA   //int w9 = 10;
    STR             W9, [SP,#0x10+var_4]    
    STR             W8, [SP,#0x10+var_8]
    LDR             W8, [SP,#0x10+var_4]
    LDR             W9, [SP,#0x10+var_8]   //以上为w8,w9进行内存保护
    CMP             W8, W9  // if(w8 <= w9)执行loc_1000068E0处代码
    B.LE            loc_1000068E0
    ADRP            X8, #_g@PAGE
    ADD             X8, X8, #_g@PAGEOFF  //获取全局变量g
    LDR             W9, [SP,#0x10+var_4]
    STR             W9, [X8]    //x8地址的内容为var_4的内容
    B               loc_1000068F0  //跳转到loc_1000068F0处执行
    ---------------------------------------------------------------------------
     loc_1000068E0                         
    ADRP            X8, #_g@PAGE
    ADD             X8, X8, #_g@PAGEOFF //获取全局变量g储存到x8的地址中
    LDR             W9, [SP,#0x10+var_8]
     STR             W9, [X8]  //x8的内容为var_8的内容
    
     loc_1000068F0                          
    ADRP            X8, #_g@PAGE
    ADD             X8, X8, #_g@PAGEOFF //获取全局变量g储存到x8的地址中
    LDR             W0, [X8] //x8的内容为x0的内容
    ADD             SP, SP, #0x10 //栈平衡
    RET
    
    
    • 在func中,没有对x0-x7进行保护,main中x0-x7也没有入栈,那么func没有参数
    • func中,RET之前对w0进行了操作,所以有返回值。函数返回值一般为x0(w0是x0的低32位.返回值为x8,x8=g,那么返回值为g
    • 还可以看出g进行了赋值操作,赋值给var_4和var_8,那么g的类型为var_4和var_8的类型。
    • 还可得出var_4和var_8就是局部变量 W8和W9,w8=20;w9 = 10;
      以上还原成高级代码为:
    int func()
    {
      int w8 = 20;
      int w9=10;
      if(w8 > w9)
      {
        g=w8;
       }
      else{
        g = w9;
      }
      *x8 = g;
      int w0=*x8;
      return w0;
    }
    
    总结:CMP 和B.LE(小于等于)再加上B组成if else,只有cmp和B组成if等判断语句

    二 常见的cmp(Compare)比较指令

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

    • BL 标号:跳转到标号处执行
    • BL 标号:比较结果是小于等于,执行标号,否则不跳转
    • B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转
    • B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
    • B.EQ 标号:比较结果是等于,执行标号,否则不跳转
    • B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

    三 循环

    1. do while
    void funa(int a)
    {
        do{
            a++;
        }while (a<=g);
        /*
     var_4           = -4
     SUB             SP, SP, #0x10
     STR             W0, [SP,#0x10+var_4]  //参数w0入栈 int var_4 = w0;
    loc_1000068D4 
     LDR             W8, [SP,#0x10+var_4]  //int w8= var_4;
    ADD             W8, W8, #1     //int w8=w8+1;
    STR             W8, [SP,#0x10+var_4]  //int var_4 = w8;
     ADRP            X8, #_g@PAGE
    ADD             X8, X8, #_g@PAGEOFF  // *x8 = g
     LDR             W9, [SP,#0x10+var_4]  //int w9= var_4;
    LDR             W10, [X8]  //w10=*x8;
    CMP             W9, W10  
     B.LE            loc_1000068D4 // if(w9 <=w10)返回去执行loc_1000068D4
    ADD             SP, SP, #0x10 //栈平衡
     RET
         */
        
    }
    
    • main的汇编中由MOV W8, #1 ;MOV X0, X8 ; BL _funa可以看出funa的参数为int x0=1。
    • 先执行loc_1000068D4,再判断CMP和 B.LE,符合条件就再回去执行loc_1000068D4,由此可以得出为do while循环
    2. while
    void funb(int b){
        while (b<g) {
            b++;
        }
        /*
     var_4           = -4
     SUB             SP, SP, #0x10
    STR             W0, [SP,#0x10+var_4]
     loc_1000068D0                           
     ADRP            X8, #_g@PAGE
     ADD             X8, X8, #_g@PAGEOFF
     LDR             W9, [SP,#0x10+var_4]
     LDR             W10, [X8]
     CMP             W9, W10
    B.GE            loc_1000068F8
     LDR             W8, [SP,#0x10+var_4]
    ADD             W8, W8, #1
     STR             W8, [SP,#0x10+var_4]
    B               loc_1000068D0
     ---------------------------------------------------------------------------
     loc_1000068F8                      
     ADD             SP, SP, #0x10
    RET
         */
    }
    
    • 大于等于的时候执行loc_1000068F8 恢复栈平衡,然后RET,当不符合条件小于的时候执行loc_1000068D0,然后循环。可以判断出是while(<){...}
    3. for循环
    void func(int a){
        int sum = 0;
        for (int i=0;i<a; i++) {
            sum +=i;
        }
    }
    
    int main(int argc, char * argv[]) {
        func(3);
        return 0;
    }
    

    func的汇编代码是:

     var_C           = -0xC
     var_8           = -8
     var_4           = -4
    
    SUB             SP, SP, #0x10
    STR             W0, [SP,#0x10+var_4]
    STR             WZR, [SP,#0x10+var_8]
     STR             WZR, [SP,#0x10+var_C]
    
     loc_1000068C8                          
    LDR             W8, [SP,#0x10+var_C]
     LDR             W9, [SP,#0x10+var_4]
    CMP             W8, W9
     B.GE            loc_1000068F8
     LDR             W8, [SP,#0x10+var_C]
    LDR             W9, [SP,#0x10+var_8]
    ADD             W8, W9, W8
    STR             W8, [SP,#0x10+var_8]
    LDR             W8, [SP,#0x10+var_C]
    ADD             W8, W8, #1
    STR             W8, [SP,#0x10+var_C]
    B               loc_1000068C8
     ---------------------------------------------------------------------------
    
     loc_1000068F8                          
    ADD             SP, SP, #0x10
    RET
    
    • for循环和while的汇编代码基本是一样的,for和while的效率是一样的

    四 Swicth

    1. case选择条件连续且分支小于等于3时

    代码如下:

    
    void funA(int a){
        switch (a) {
            case 1:
                printf("1");
                break;
            case 2:
                printf("2");
                break;
            case 3:
                printf("3");
                break;
            default:
                printf("default");
                break;
        }
    }
    
    int main(int argc, char * argv[]) {
        funA(2);
        return 0;
    }
    

    xcode动态汇编分析如下:


    • 首先参数-1,最小的分支,看等不等于,等于就打印,然后结束函数;如果不等于就继续参数-2,减去第二个分支,看等不等于,以此类推,直到函数结束。
    • 相当于if-else直到函数结束
    2. case选择条件连续且分支大于3时

    代码如下:

    void funA(int a){
        switch (a) {
            case 1:
                printf("1");
                break;
            case 2:
                printf("2");
                break;
            case 3:
                printf("3");
                break;
            case 4:
                printf("4");
                break;
            default:
                printf("default");
                break;
        }
    }
    
    1.png
    • 先减去最大值,判断是否是default
    • 再差表,偏移量,在表中查到相应地址,如下:


      2.png
    • 获取x8地址0x100a2a844 <+44>: adrp x8, 0
      x8=x8(0x100a2a844)去掉低12位 ->x8=0x100a2a000,然后0左移3位,x8=0x100a2a00; 0x100a2a848 <+48>: add x8, x8, #0x8c8 ; =0x8c8 x8=0x100a2a000+0x8c8 =0x100a2a8c8
    • ldur x9, [x29, #-0x10] x9=x8,参数为2,那么x9=1
    • ldrsw x10, [x8, x9, lsl #2] 因为x9, lsl #2x9左移2位得到偏移量=0x100即4个字节,那么x10=[x8+4]
    • memory read 0x100a2a8c8得到0x100a2a8c8: 94 ff ff ff a8 ff ff ff bc ff ff ff d0 ff ff ff 那么x10=0xffffa8,从右往左读,一个字节一个字节的读,可以把断点达到ldrsw x10, [x8, x9, lsl #2] 这一行,然后register read x10验证,验证得到:x10 = 0xffffffffffffffa8
    • 因为x10为负数,负数算法:取反加一(0xff-0xa8+1)得到所以add x8, x10, x8x8=x8-0x58= 0x100a2a8c8-0x58= 0x100a2a870
    • 0x100f6e858 <+64>: br x8会跳转到0x100f6e870 <+88>: adrp x0, 1继续执行,可以xcode断点到0x100f6e858 <+64>: br x8这一行,然后单步走一个ni进行验证;
    switch总结:

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

    五 查看全局或者常量

    int sum(int a, int b){
        printf("string");
        return a + 3*g;
    }
    
    • 常量printf("string");找到string
    0x100aee7d0 <+20>: adrp   x0, 1
    0x100aee7d4 <+24>: add    x0, x0, #0xf18            ; =0xf18 
    0x100aee7d8 <+28>: bl     0x100aeebe8               ; symbol stub for: printf
    

    1、x0=0x100aee7d0去掉低12位 0x=0x100aee000;1左移3位得到0x1000,0x=0x100aef000
    2、0x=0x+0xf18即0x=0x100aeff18
    3、用p (char*)0x100aeff18查看得到(char *) $1 = 0x0000000100aeff18 "string"

    • 全局变量也一样

    六 编译器优化

    • 选择优化策略:build setting -> optimization level ->debug ->fastest smallest然后编译
    int sum(int a, int b){
        printf("string");
        return a +b;
    }
    int main(int argc, char * argv[]) {
        int c = sum(1, 2);
        return 0;
    }
    
     0x10050ab44 <+0>:  stp    x29, x30, [sp, #-0x10]!
        0x10050ab48 <+4>:  mov    x29, sp
        0x10050ab4c <+8>:  adr    x0, #0x13cc               ; "string"
        0x10050ab50 <+12>: nop    
        0x10050ab54 <+16>: bl     0x10050abd0               ; symbol stub for: printf
    ->  0x10050ab58 <+20>: mov    w0, #0x0
        0x10050ab5c <+24>: ldp    x29, x30, [sp], #0x10
        0x10050ab60 <+28>: ret    
    
    • 由于int c的值没有用到,编译器直接去掉了垃圾代码
    int main(int argc, char * argv[]) {
        int c = sum(1, 2);
        NSLog(@"%d",c);
        return 0;
    }
    

    就算用到了int c,编译器会直接把结果赋给c:

      0x1001e2b10 <+0>:  sub    sp, sp, #0x20             ; =0x20 
        0x1001e2b14 <+4>:  stp    x29, x30, [sp, #0x10]
        0x1001e2b18 <+8>:  add    x29, sp, #0x10            ; =0x10 
        0x1001e2b1c <+12>: adr    x0, #0x13fc               ; "string"
        0x1001e2b20 <+16>: nop    
        0x1001e2b24 <+20>: bl     0x1001e2bc4               ; symbol stub for: printf
    ->  0x1001e2b28 <+24>: orr    w8, wzr, #0x3
        0x1001e2b2c <+28>: str    x8, [sp]
        0x1001e2b30 <+32>: adr    x0, #0x1508               ; @"%d"
        0x1001e2b34 <+36>: nop    
        0x1001e2b38 <+40>: bl     0x1001e2ba0               ; symbol stub for: NSLog
        0x1001e2b3c <+44>: mov    w0, #0x0
        0x1001e2b40 <+48>: ldp    x29, x30, [sp, #0x10]
        0x1001e2b44 <+52>: add    sp, sp, #0x20             ; =0x20 
        0x1001e2b48 <+56>: ret    
    
    

    六 多线程之间共用寄存器,会对当前寄存器进行保护,操作系统会对内存进行保护,以防止资源抢夺。

    相关文章

      网友评论

        本文标题:iOS 汇编基础(三)还原高级代码之循环和判断

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