內存五大區,實際是指
虛擬內存
,而不是真實物理內存,它們是在邏輯
上劃分的
- 棧區:存放參數、局部變量、臨時數據。可讀,可寫
- 堆區:向系統申請區域,并指明大小。可讀,可寫
- 全局區:存放全局變量和靜態變量。可讀,可寫
- 常量區:存放常量,整個程序運行期不能被改變。只讀
- 代碼區:存放代碼。可讀,可執行
常量
案例:
編譯器如何存儲常量?
打開
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
指令,在此頁的基址上加上偏移地址,從而拿到具體數據還原高級代碼
- 不關心代碼語法和執行流程,只關心執行后的結果,和預期結果一致即可