美文网首页
iOS逆向实战--004:常量 & 全局变量

iOS逆向实战--004:常量 & 全局变量

作者: 帅驼驼 | 来源:发表于2021-04-04 09:53 被阅读0次

    内存五大区,实际是指虚拟内存,而不是真实物理内存,它们是在逻辑上划分的

    • 栈区:存放参数、局部变量、临时数据。可读,可写
    • 堆区:向系统申请区域,并指明大小。可读,可写
    • 全局区:存放全局变量和静态变量。可读,可写
    • 常量区:存放常量,整个程序运行期不能被改变。只读
    • 代码区:存放代码。可读,可执行
    常量

    案例:

    编译器如何存储常量?

    打开ViewController.m文件,写入以下代码:

    #import "ViewController.h"
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    //    [super viewDidLoad];
       printf("haha");
    }
    
    @end
    

    真机运行项目,来到viewDidLoad方法

    • adrp x0, 1adrp指令包含三个操作
      将右侧的常数左移12位,得到偏移后的地址
      pc寄存器的低12位清零
      将清零后的地址和偏移后的地址相加,写入x0寄存器
    • add x0, x0, #0x654:将x0寄存器的值和偏移地址,写入x0寄存器
    • bl 0x100af65b8:调用printf函数

    按照函数调用的原则,printf函数的参数应该使用x0传递。打印x0寄存器:

    • 由此可见,上面的一系列运算,最终目的还是获取常量的地址,写入到x0寄存器

    拆解指令

    adrp x0, 1指令:

    • 右侧的1为常数,将1左移12位,即:1 << 12 = 409616进制0x1000
    • 执行到adrp指令时的pc寄存器为0x100af5fa0,将其低12位清零,即:0x100af5000
    • 将清零后的地址和偏移后的地址相加,即:0x100af5000 + 0x1000 = 0x100af6000

    add x0, x0, #0x654指令:

    • x0寄存器的值和偏移地址相加,即:0x100af6000 + 0x654 = 0x100af6654

    lldb中,通过x 0x100af6654命令,按16进制格式输出

    • 存储的常量:haha
    • 16进制6810进制104,即:hASCII
    • 16进制6110进制91,即:aASCII

    原理解析

    adrp指令计算后,得到一个内存结果,尾数为000。从0x000 ~ 0xfff刚好是4096,而macOS系统中,内存分页大小刚好是4KB,所以这里得到一个大小为4KB的页的基址

    • iOS系统中,内存分页大小为16KB,但也是4的倍数,所以没有任何影响
    • adrp指令中的常数(页码),由当前pc寄存器地址作为参照,和常量所在地址进行计算,所得到差值

    通过add指令,在此页的基址上加上偏移地址,从而拿到具体数据

    全局变量

    和常量一样,先计算数据在全局区所在页的基址,再加上偏移地址,从而拿到具体数据

    静态分析时,通过汇编代码和指令,无法区分当前数据是全局变量还是常量,只能通过内存的情况做理性判断,也可以通过数据所在地址减去ASLR偏移地址,然后在Moch-O中进行定位

    案例:

    编译器如何存储全局变量?

    打开ViewController.m文件,写入以下代码:

    #import "ViewController.h"
    
    int g = 12;
    
    int func(int a,int b){
       int c = a + g + b;
       return c;
    }
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    //    [super viewDidLoad];
       func(10, 20);
    }
    
    @end
    

    真机运行项目,来到func方法

    • adrp x9, 8x9 = 0x104b91000
    • add x9, x9, #0x490x9 = 0x104b91490

    使用View Memory查看内存数据

    • 地址0x104b91490存储的0xc,也就是全局变量g的值:12

    继续执行代码,x9是偏移后的地址,读取x9地址的值,写入w10

    还原高级代码

    Hopper Disassembler是一款二进制反编译软件,它不仅拥有拆开任何二进制软件的强大功能,还可以提供所有的软件编码内容。如导入符号或控制流程的实用化信息,在允许您命名所有需要对象的基础上,能够轻松的将汇编语言转换为更容易理解的伪代码,甚至可以使用GDB来调试程序从而有效的达到反汇编的功能操作

    案例:

    借助Hopper进行高级代码的还原

    使用真机编译Demo项目,找到编译后的Demo.app文件

    右键显示包内容,找到里面的Mach-O文件(可执行文件)

    Mach-O文件拖到Hopper中,点击OK

    找到viewDidLoad方法

                        -[ViewController viewDidLoad]:
    0000000100005f8c         sub        sp, sp, #0x20            ; Objective C Implementation defined at 0x10000c0a0 (instance method), DATA XREF=0x10000c0a0
    0000000100005f90         stp        x29, x30, [sp, #0x10]
    0000000100005f94         add        x29, sp, #0x10
    0000000100005f98         str        x0, [sp, #0x8]
    0000000100005f9c         str        x1, sp
    0000000100005fa0         movz       w0, #0xa
    0000000100005fa4         movz       w1, #0x14
    0000000100005fa8         bl         _func
    0000000100005fac         ldp        x29, x30, [sp, #0x10]
    0000000100005fb0         add        sp, sp, #0x20
    0000000100005fb4         ret
                           ; endp
    
    • 前五句代码,开辟栈空间,现场保护,无需代码还原
    • movz w0, #0xa ~ movz w1, #0x14w0w1用于给函数传递参数,推断这里应该有两个参数需要传递给func函数
    • bl _func:调用func函数
    • 最后三句代码,还原x29x30的值,恢复栈平衡并返回,无需代码还原

    根据上述汇编代码的分析,还原viewDidLoad方法

    - (void)viewDidLoad {
       int w0 = 10;
       int w1 = 20;
       func(w0, w1);
    }
    

    找到func函数

                        _func:
    0000000100005f38         sub        sp, sp, #0x20             ; CODE XREF=-[ViewController viewDidLoad]+28
    0000000100005f3c         stp        x29, x30, [sp, #0x10]
    0000000100005f40         add        x29, sp, #0x10
    0000000100005f44         stur       w0, [x29, #-0x4]
    0000000100005f48         str        w1, [sp, #0x8]
    0000000100005f4c         adrp       x0, #0x100006000          ; argument #1 for method imp___stubs__printf
    0000000100005f50         add        x0, x0, #0x654            ; "haha"
    0000000100005f54         bl         imp___stubs__printf
    0000000100005f58         ldur       w8, [x29, #-0x4]
    0000000100005f5c         adrp       x9, #0x10000d000
    0000000100005f60         add        x9, x9, #0x498            ; _g
    0000000100005f64         ldr        w10, x9
    0000000100005f68         add        w8, w8, w10
    0000000100005f6c         ldr        w10, [sp, #0x8]
    0000000100005f70         add        w8, w8, w10
    0000000100005f74         str        w8, [sp, #0x4]
    0000000100005f78         ldr        w8, [sp, #0x4]
    0000000100005f7c         mov        x0, x8
    0000000100005f80         ldp        x29, x30, [sp, #0x10]
    0000000100005f84         add        sp, sp, #0x20
    0000000100005f88         ret
                           ; endp
    
    • 前五句代码,开辟栈空间,现场保护,参数入栈,无需代码还原
    • adrp x0, #0x100006000:这里体现Hopper的强大之处,已经将运算后的地址准备好了
    • add x0, x0, #0x6540x100006000 + 0x654 = 0x100006654
    • Hopper给出的地址未经过ASLR偏移,可在Moch-O中直接查找。将Mach-O文件拖到MachOView中,0x100006654地址对应常量区字符串:haha
    • bl imp___stubs__printf:调用printf函数,x0为参数
    • ldur w8, [x29, #-0x4]:将x29 - 0x4地址的值,写入w8,也就是参数1的值
    • adrp x9, #0x10000d000 ~ add x9, x9, #0x4980x10000d000 + 0x498 = 0x10000d498
    • MachOView中,0x10000d498地址对应全局数据0xC10进制12
    • ldr w10, x9:将x9地址的值,写入w10。此时w10相当于全局变量
    • add w8, w8, w10w8 += w10参数1 += 全局变量
    • ldr w10, [sp, #0x8]:将sp + 0x8地址的值,写入w10,也就是参数2的值
    • add w8, w8, w10w8 += w10参数1 + 全局变量 + 参数2
    • str w8, [sp, #0x4] ~ ldr w8, [sp, #0x4]:将w8入栈,然后读取,又写入到w8,两句废话,无需代码还原
    • mov x0, x8:将x8写入x0x0寄存器用于函数的返回值
    • 最后三句代码,还原x29x30的值,恢复栈平衡并返回,无需代码还原

    根据上述汇编代码的分析,还原func函数

    int x9 = 12;
    
    int func(int p1, int p2){
       
       const char *c_x0 = "haha";
       printf("%s", c_x0);
       
       int w8 = p1;
       
       int w10 = x9;
       w8 += w10;
       
       w10 = p2;
       w8 += w10;
       
       int x0 = w8;
       return x0;
    }
    

    将还原后的高级代码进行优化,去掉繁琐的中间步骤

    int x9 = 12;
    
    int func(int p1, int p2){
       const char *c = "haha";
       printf("%s", c);
       
       int w8 = p1 + x9 + p2;
       return w8;
    }
    
    - (void)viewDidLoad {
       func(10, 20);
    }
    

    打开ViewController.m文件,找到源码进行对比:

    • 源码和还原后的代码,语法上并不完全一样,但逻辑上没有区别,执行结果更是毫无二致

    还原高级代码,并不关心代码语法和执行流程,只关心执行后的结果,和预期结果一致即可

    总结

    常量 & 全局变量

    • 获取常量和全局变量时,会出现adrpadd两条指令获得一个地址的情况

    adrp x0,1

    • adrp:(Address Page)内存分页寻址
    • pc寄存器的低12位清零
    • 1的值,左移12位。16进制就是0x1000
    • 以上两个结果相加放入x0寄存器

    add x0, x0, #0x654

    • 通过add指令,在此页的基址上加上偏移地址,从而拿到具体数据

    还原高级代码

    • 不关心代码语法和执行流程,只关心执行后的结果,和预期结果一致即可

    相关文章

      网友评论

          本文标题:iOS逆向实战--004:常量 & 全局变量

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