iOS逆向开发-ARM64汇编简单介绍

527 阅读5分钟

导语:在学习iOS逆向开发里面,反汇编是一个非常重要的过程,拿到别人app的可执行文件,无论是通过ida hopper打开,都是汇编代码,虽然汇编分很多种类,iOS逆向分析使用的主要是ARM64,今天就来简单了解一下ARM64汇编。

ARM64汇编

  • 处理iOS开发主要了解三方面汇编知识

    • 寄存器

    • 指令

    • 堆栈

寄存器

  • 64bit通用寄存器

    x0 x1 ... x27 x28

    • x0~x7,通常是放函数参数,超出部分使用堆栈来传递

    • x0通常是函数返回值

    • 32bit通用寄存器(64bit向下兼容)

      w0 w1 ... w7 w28 (是64bit的低32bit)

    这里的32bit通用寄存器并非armv7等32bit里的寄存器

    如果是armv7的汇编,寄存器是 r1,r2,r3...

  • 程序计数器

    • pc (program Counter)
    • 记录CPU当前指令,储存着当前正在执行的指令地址
    • 类似于8086汇编的ip寄存器
  • 堆栈指针

    • sp (Stack Pointer)
    • fp (Frame Pointer),也就是x29
  • 链接寄存器

    • lr (link Register), 也就是x30

      是用于存放子程序的返回地址

  • 程序状态寄存器

    • cpsr (Current Program Status Register)

    • spsr (Saved Program Status Register),异常状态下使用

指令

  • mov

    mov w10, #0xa :意思就是将10放到w10寄存器中

  • ret

    函数返回

    将lr (x30)寄存器的值赋值给pc寄存器

  • add

    add w10, w10, w11 :w10的值 + w11值,结果保存到w10寄存器中

  • sub

    subs w10, w10, w11 : w10的值 - w 11的值,结果保存到w10中

  • cmp

    cmp w10, w11 : 比较两个寄存器里值得大小

    通过 w10 - w11 的结果得出结论

    相减结果会影响到cpsr(程序状态寄存器)寄存器的标志位

  • b

    跳转指令(如果和ret指令一起用,无法返回都正确位置)

    如果跟cmp配合使用,带条件跳转(如 eq(相等):beq相等就跳转)

  • bl

    • 带返回的跳转指令(调用函数,跟ret配合返回到调用函数指令的下一条指令)

      首先将下一条指令的地址储存到lr (即x30链接寄存器)

      然后根据地址跳转执行代码

  • 条件域

    • EQ: equal 相等

    • NE:not equal 不相等

    • GT:great than 大于

    • GE:great equal 大于等于

    • LT:less than 小于

    • LE:less equal 小于等于

  • 内存操作指令

    • load:从内存中读取数据

      • ldr

        ldr w10, [sp, #0x8] :从[sp, #0x8]地址中读取值保存到w10寄存器中

      • ldur

        ldur w10, [sp, #-0x8]:从[sp, #-0x8]读取数据保存到w10寄存器

      • ldp

        ldur w10, w11, [sp, #0x8] :把值分段储存在w10,w11寄存器中

      ldr与ldur不同的是一个是地址加,一个是地址减

    • store:向内存写入数据

      • str

        str w10, [sp, #0x8] :注意:是把w10寄存器的值写入[sp, #0x8]地址中

      • stur

        同上,不过地址是减

      • stp

        stp x10, x11 [sp, #0x20] :x10,x11寄存器的值分段写入对应内存地址

    • 零寄存器,里面储存的值是0

      • wzr (32bit, Word Zero Register)

      • xzr (64bit)

函数的堆栈平衡

  • 叶子函数

    //函数内没有其他函数
    void test1(){
        int a = 3;
        int b = 4;
    }
    
    //汇编
    ArmAssembly`test1:
        0x1047b20dc <+0>:  sub    sp, sp, #0x10             ; =0x10 
        0x1047b20e0 <+4>:  mov    w8, #0x3
        0x1047b20e4 <+8>:  str    w8, [sp, #0xc]
        0x1047b20e8 <+12>: mov    w8, #0x4
        0x1047b20ec <+16>: str    w8, [sp, #0x8]
        0x1047b20f0 <+20>: add    sp, sp, #0x10             ; =0x10 
        0x1047b20f4 <+24>: ret  
    

    sub sp, sp, #0x10:sp = sp - 0x10 sp指针向低地址偏移16个字节,相当于分配了16个字节空间

    mov w8, #0x3

    w8, [sp, #0xc] :这两句把3保存到内存空间的后四个字节

    mov w8, #0x4

    w8, [sp, #0x8] :把4保存到比3低地址的4个字节里,挨着3

    add sp, sp, #0x10 : 恢复sp指针位置,还原现场

    ret :返回

  • 非叶子函数

    //函数内部包括其他函数
    void test2(){
        int c = 5;
        int d = 6;
        test1();
    }
    
    //汇编
    ArmAssembly`test2:
        0x104dd20cc <+0>:  sub    sp, sp, #0x20             ; =0x20 
        0x104dd20d0 <+4>:  stp    x29, x30, [sp, #0x10]
        0x104dd20d4 <+8>:  add    x29, sp, #0x10            ; =0x10 
        0x104dd20d8 <+12>: mov    w8, #0x5
        0x104dd20dc <+16>: stur   w8, [x29, #-0x4]
        0x104dd20e0 <+20>: mov    w8, #0x6
        0x104dd20e4 <+24>: str    w8, [sp, #0x8]
        0x104dd20e8 <+28>: bl     0x1000060b0               ; test1 at main.m:11
        0x104dd20ec <+32>: ldp    x29, x30, [sp, #0x10]
        0x104dd20f0 <+36>: add    sp, sp, #0x20             ; =0x20 
        0x104dd20f4 <+40>: ret 
    

    sub sp, sp, #0x20 分配空间,sp指针偏移

    stp x29, x30, [sp, #0x10] :x29(fp),x30(lr) 把原来的fp,lr寄存器的数据保存到分配好的空间的后16个字节里面

    add x29, sp, #0x10: fp指针放到 sp+#0x10 的位置上

    。。。

    bl 0x1000060b0:1.保存下一条指令的地址到x30(lr)寄存器中 2.跳转 3. 等test1执行完之后根据lr跳转回来

    ldp x29, x30, [sp, #0x10] : 取出原来的数据,放回fp(x29),lr(x30)中,fp指针回到原来的位置

    add sp, sp, #0x20:sp指针归位,恢复现场

    在非叶子函数里面如果画一个内存图,可以发现,数据保存是在sp地址到fp地址之间,sp-fp决定数据保存位置,所以叫做堆栈指针(个人理解)


    汇编当然有很多知识点,不过不在今天的讨论之中,学习这些是为了分析反汇编代码,有时间精力的朋友当然可以深入研究一下。

    由于带有个人学习理解,不免有错漏,如发现请一定更正,谢谢。