内存五大区,实际是指虚拟内存,而不是真实物理内存,它们是在逻辑上划分的
iOS逆向实战--004:常量-%26-全局变量 - 图1

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

案例:

编译器如何存储常量?

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

```

import “ViewController.h”

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad]; printf(“haha”); }

@end

  1. > 真机运行项目,来到`viewDidLoad`方法<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-6da996e0d7bbfc52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `adrp x0, 1``adrp`指令包含三个操作<br />
  4. 将右侧的常数左移`12位`,得到偏移后的地址<br />
  5. `pc`寄存器的`低12位`清零<br />
  6. 将清零后的地址和偏移后的地址相加,写入`x0`寄存器
  7. > - `add x0, x0, #0x654`:将`x0`寄存器的值和偏移地址,写入`x0`寄存器
  8. > - `bl 0x100af65b8`:调用`printf`函数
  9. > 按照函数调用的原则,`printf`函数的参数应该使用`x0`传递。打印`x0`寄存器:<br />
  10. ![](https://upload-images.jianshu.io/upload_images/9297953-24df363c1ffec564.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  11. > - 由此可见,上面的一系列运算,最终目的还是获取常量的地址,写入到`x0`寄存器
  12. > 拆解指令
  13. > `adrp x0, 1`指令:
  14. > - 右侧的`1`为常数,将`1`左移`12位`,即:`1 << 12 = 4096``16进制``0x1000`
  15. > - 执行到`adrp`指令时的`pc`寄存器为`0x100af5fa0`,将其`低12位`清零,即:`0x100af5000`
  16. > - 将清零后的地址和偏移后的地址相加,即:`0x100af5000 + 0x1000 = 0x100af6000`
  17. > `add x0, x0, #0x654`指令:
  18. > - `x0`寄存器的值和偏移地址相加,即:`0x100af6000 + 0x654 = 0x100af6654`
  19. > `lldb`中,通过`x 0x100af6654`命令,按`16进制`格式输出<br />
  20. ![](https://upload-images.jianshu.io/upload_images/9297953-717e95086bbfc063.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  21. > - 存储的常量:`haha`
  22. > - `16进制``68``10进制``104`,即:`h``ASCII`
  23. > - `16进制``61``10进制``91`,即:`a``ASCII`
  24. > 原理解析
  25. > `adrp`指令计算后,得到一个内存结果,尾数为`000`。从`0x000 ~ 0xfff`刚好是`4096`,而`macOS`系统中,内存分页大小刚好是`4KB`,所以这里得到一个大小为`4KB`的页的基址<br />
  26. ![](https://upload-images.jianshu.io/upload_images/9297953-f756d3cd686d3dee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  27. > - `iOS`系统中,内存分页大小为`16KB`,但也是`4`的倍数,所以没有任何影响
  28. > - `adrp`指令中的常数(页码),由当前`pc`寄存器地址作为参照,和常量所在地址进行计算,所得到差值
  29. > 通过`add`指令,在此页的基址上加上偏移地址,从而拿到具体数据
  30. #####全局变量
  31. > 和常量一样,先计算数据在全局区所在页的基址,再加上偏移地址,从而拿到具体数据
  32. > 静态分析时,通过汇编代码和指令,无法区分当前数据是全局变量还是常量,只能通过内存的情况做理性判断,也可以通过数据所在地址减去`ASLR`偏移地址,然后在`Moch-O`中进行定位
  33. > 案例:
  34. > 编译器如何存储全局变量?
  35. > 打开`ViewController.m`文件,写入以下代码:
  36. >

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

  1. > 真机运行项目,来到`func`方法<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-b24869e830b59d9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `adrp x9, 8``x9 = 0x104b91000`
  4. > - `add x9, x9, #0x490``x9 = 0x104b91490`
  5. > 使用`View Memory`查看内存数据<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-dd3522d716e6f377.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > - 地址`0x104b91490`存储的`0xc`,也就是全局变量`g`的值:`12`
  8. > 继续执行代码,`x9`是偏移后的地址,读取`x9`地址的值,写入`w10`<br />
  9. ![](https://upload-images.jianshu.io/upload_images/9297953-7dddde159fe24638.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  10. #####还原高级代码
  11. > `Hopper Disassembler`是一款二进制反编译软件,它不仅拥有拆开任何二进制软件的强大功能,还可以提供所有的软件编码内容。如导入符号或控制流程的实用化信息,在允许您命名所有需要对象的基础上,能够轻松的将汇编语言转换为更容易理解的伪代码,甚至可以使用`GDB`来调试程序从而有效的达到反汇编的功能操作
  12. > 案例:
  13. > 借助`Hopper`进行高级代码的还原
  14. > 使用真机编译`Demo`项目,找到编译后的`Demo.app`文件<br />
  15. ![](https://upload-images.jianshu.io/upload_images/9297953-26d23e0c5c38626f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  16. > 右键显示包内容,找到里面的`Mach-O`文件(可执行文件)<br />
  17. ![](https://upload-images.jianshu.io/upload_images/9297953-0e74bbbfce1f388c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  18. > `Mach-O`文件拖到`Hopper`中,点击`OK`<br />
  19. ![](https://upload-images.jianshu.io/upload_images/9297953-5936ed963b5733dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  20. > 找到`viewDidLoad`方法
  21. >
  1. -[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

  1. > - 前五句代码,开辟栈空间,现场保护,无需代码还原
  2. > - `movz w0, #0xa` ~ `movz w1, #0x14``w0``w1`用于给函数传递参数,推断这里应该有两个参数需要传递给`func`函数
  3. > - `bl _func`:调用`func`函数
  4. > - 最后三句代码,还原`x29``x30`的值,恢复栈平衡并返回,无需代码还原
  5. > 根据上述汇编代码的分析,还原`viewDidLoad`方法
  6. >
  • (void)viewDidLoad { int w0 = 10; int w1 = 20; func(w0, w1); } ```

找到func函数

  1. _func:
  2. 0000000100005f38 sub sp, sp, #0x20 ; CODE XREF=-[ViewController viewDidLoad]+28
  3. 0000000100005f3c stp x29, x30, [sp, #0x10]
  4. 0000000100005f40 add x29, sp, #0x10
  5. 0000000100005f44 stur w0, [x29, #-0x4]
  6. 0000000100005f48 str w1, [sp, #0x8]
  7. 0000000100005f4c adrp x0, #0x100006000 ; argument #1 for method imp___stubs__printf
  8. 0000000100005f50 add x0, x0, #0x654 ; "haha"
  9. 0000000100005f54 bl imp___stubs__printf
  10. 0000000100005f58 ldur w8, [x29, #-0x4]
  11. 0000000100005f5c adrp x9, #0x10000d000
  12. 0000000100005f60 add x9, x9, #0x498 ; _g
  13. 0000000100005f64 ldr w10, x9
  14. 0000000100005f68 add w8, w8, w10
  15. 0000000100005f6c ldr w10, [sp, #0x8]
  16. 0000000100005f70 add w8, w8, w10
  17. 0000000100005f74 str w8, [sp, #0x4]
  18. 0000000100005f78 ldr w8, [sp, #0x4]
  19. 0000000100005f7c mov x0, x8
  20. 0000000100005f80 ldp x29, x30, [sp, #0x10]
  21. 0000000100005f84 add sp, sp, #0x20
  22. 0000000100005f88 ret
  23. ; endp
  • 前五句代码,开辟栈空间,现场保护,参数入栈,无需代码还原
  • adrp x0, #0x100006000:这里体现Hopper的强大之处,已经将运算后的地址准备好了
  • add x0, x0, #0x6540x100006000 + 0x654 = 0x100006654
  • Hopper给出的地址未经过ASLR偏移,可在Moch-O中直接查找。将Mach-O文件拖到MachOView中,0x100006654地址对应常量区字符串:haha
    iOS逆向实战--004:常量-%26-全局变量 - 图2
  • 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
    iOS逆向实战--004:常量-%26-全局变量 - 图3
  • 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; }

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

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文件,找到源码进行对比:
iOS逆向实战--004:常量-%26-全局变量 - 图4

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

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

总结

常量 & 全局变量

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

adrp x0,1

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

add x0, x0, #0x654

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

还原高级代码

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