内存五大区,实际是指
虚拟内存
,而不是真实物理内存,它们是在逻辑
上划分的
- 栈区:存放参数、局部变量、临时数据。可读,可写
- 堆区:向系统申请区域,并指明大小。可读,可写
- 全局区:存放全局变量和静态变量。可读,可写
- 常量区:存放常量,整个程序运行期不能被改变。只读
- 代码区:存放代码。可读,可执行
常量
案例:
编译器如何存储常量?
打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController - (void)viewDidLoad { // [super viewDidLoad]; printf("haha"); } @end
真机运行项目,来到
viewDidLoad
方法
adrp x0, 1
:adrp
指令包含三个操作
将右侧的常数左移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 = 4096
,16进制
为0x1000
- 执行到
adrp
指令时的pc
寄存器为0x100af5fa0
,将其低12位
清零,即:0x100af5000
- 将清零后的地址和偏移后的地址相加,即:
0x100af5000 + 0x1000 = 0x100af6000
add x0, x0, #0x654
指令:
- 将
x0
寄存器的值和偏移地址相加,即:0x100af6000 + 0x654 = 0x100af6654
在
lldb
中,通过x 0x100af6654
命令,按16进制
格式输出
- 存储的常量:
haha
16进制
的68
,10进制
为104
,即:h
的ASCII
值16进制
的61
,10进制
为91
,即:a
的ASCII
值
原理解析
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, 8
:x9 = 0x104b91000
add x9, x9, #0x490
:x9 = 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, #0x14
:w0
、w1
用于给函数传递参数,推断这里应该有两个参数需要传递给func
函数bl _func
:调用func
函数- 最后三句代码,还原
x29
、x30
的值,恢复栈平衡并返回,无需代码还原根据上述汇编代码的分析,还原
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, #0x654
:0x100006000 + 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, #0x498
:0x10000d000 + 0x498 = 0x10000d498
- 在
MachOView
中,0x10000d498
地址对应全局数据0xC
,10进制
为12
ldr w10, x9
:将x9
地址的值,写入w10
。此时w10
相当于全局变量add w8, w8, w10
:w8 += w10
,参数1 += 全局变量
ldr w10, [sp, #0x8]
:将sp + 0x8
地址的值,写入w10
,也就是参数2
的值add w8, w8, w10
:w8 += w10
,参数1 + 全局变量 + 参数2
str w8, [sp, #0x4]
~ldr w8, [sp, #0x4]
:将w8
入栈,然后读取,又写入到w8
,两句废话,无需代码还原mov x0, x8
:将x8
写入x0
,x0
寄存器用于函数的返回值- 最后三句代码,还原
x29
、x30
的值,恢复栈平衡并返回,无需代码还原根据上述汇编代码的分析,还原
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
文件,找到源码进行对比:
- 源码和还原后的代码,语法上并不完全一样,但逻辑上没有区别,执行结果更是毫无二致
还原高级代码,并不关心代码语法和执行流程,只关心执行后的结果,和预期结果一致即可
总结
常量 & 全局变量
- 获取常量和全局变量时,会出现
adrp
和add
两条指令获得一个地址的情况
adrp x0,1
adrp
:(Address Page
)内存分页寻址- 将
pc
寄存器的低12位
清零- 将
1
的值,左移12
位。16进制
就是0x1000
- 以上两个结果相加放入
x0
寄存器
add x0, x0, #0x654
- 通过
add
指令,在此页的基址上加上偏移地址,从而拿到具体数据还原高级代码
- 不关心代码语法和执行流程,只关心执行后的结果,和预期结果一致即可
网友评论