# ASLR引入
# ASLR
## 未使用ASLR
## 使用了ASLR
## 符號在可執(zhí)行文件谤绳、虛擬地址空間中的地址計算
### 符號內(nèi)存占锯、可執(zhí)行文件地址關系
### ASLR Offset的獲取
### Symbol Address符號化
# Mach-O文件的進程地址空間分布
# 小結(jié)
# 參考鏈接
# ASLR引入
進程在自己私有的虛擬地址空間中啟動袒哥。按照傳統(tǒng)方式,進程每一次啟動時采用的都是固定的可預見的方式消略。然而堡称,這意味著某個給定程序在某個給定架構(gòu)上的進程初始虛擬內(nèi)存鏡像都是基本一致的。而且更嚴重的問題在于艺演,即使是在進程正常運行的生命周期中却紧,大部分內(nèi)存分配的操作都是按照同樣的方式進行的,因此使得內(nèi)存中的地址分布具有非常強的可預測性胎撤。
盡管這有助于調(diào)試晓殊,但是也給黑客提供了更大的施展空間。黑客主要采用的方法是代碼注入:通過重寫內(nèi)存中的函數(shù)指針伤提,黑客就可以將程序的執(zhí)行路徑轉(zhuǎn)到自己的代碼巫俺,將程序的輸入轉(zhuǎn)變?yōu)樽约旱妮斎搿V貙憙?nèi)存最常用的方法是采用緩沖區(qū)溢出(即利用未經(jīng)保護的內(nèi)存復制操作越過上數(shù)組的邊界)肿男,可參考緩沖區(qū)溢出攻擊介汹,將函數(shù)的返回地址重寫為自己的指針。不僅如此舶沛,黑客還有更具創(chuàng)意的技術(shù)嘹承,例如破壞printf()格式化字符串以及基于堆的緩沖區(qū)溢出。此外如庭,任何用戶指針甚至結(jié)構(gòu)化的異常處理程序都可以導致代碼注入叹卷。這里的關鍵問題在于判斷重寫哪些指針,也就是說坪它,可靠地判斷注入的代碼應該在內(nèi)存中的什么位置骤竹。
不論被破解程序的薄弱環(huán)節(jié)在哪里:緩沖區(qū)溢出、格式化字符串攻擊或其他方式哟楷,黑客都可以花大力氣破解一個不安全的程序瘤载,找到這個程序的地址空間布局,然后精心設計一種方法卖擅,這種方法可以可靠地重現(xiàn)程序中的薄弱環(huán)節(jié),并且可以在類似的系統(tǒng)上暴露出一樣的薄弱環(huán)節(jié)墨技。
現(xiàn)在大部分操作系統(tǒng)中都采用了一種稱為地址空間布局隨機化(ASLR) 的技術(shù)惩阶,這是一種避免攻擊的有效保護。進程每一次啟動時扣汪,地址空間都會被簡單地隨機化:只是偏移断楷,而不是攪亂≌副穑基本的布局(程序文本冬筒、數(shù)據(jù)和庫)仍然是一樣的恐锣。然而,這些部分具體的地址都不同了——區(qū)別足夠大舞痰,可以阻擋黑客對地址的猜測土榴。實現(xiàn)方法是通過內(nèi)核將Mach-O的段“平移”某個隨機系數(shù)。
# ASLR
地址空間布局隨機化(Address Space Layout Randomization响牛,ASLR)是一種針對緩沖區(qū)溢出的安全保護技術(shù)玷禽,通過對堆、棧呀打、共享庫映射等線性區(qū)布局的隨機化矢赁,通過增加攻擊者預測目的地址的難度,防止攻擊者直接定位攻擊代碼位置贬丛,達到阻止溢出攻擊的目的的一種技術(shù)撩银。iOS4.3開始引入了ASLR技術(shù)。
下面分別來看一下豺憔,未使用ASLR蜒蕾、使用了ASLR下,進程虛擬地址空間內(nèi)的分布焕阿。下圖中左側(cè)是mach-O可執(zhí)行文件咪啡,右側(cè)是鏈接之后的虛擬地址空間,如果對__TEXT
暮屡、__DATA
等Segment概念不清楚的地方撤摸,可以看一些第二篇關于Mach-O文件結(jié)構(gòu)的介紹。
## 未使用ASLR
- 函數(shù)代碼存放在__TEXT段中
- 全局變量存放在__DATA段中
- 可執(zhí)行文件的內(nèi)存地址是0x0
- 代碼段(__TEXT)的內(nèi)存地址就是LC_SEGMENT(__TEXT)中的VM Address:arm64設備下褒纲,為
0x100000000
准夷;非arm64下為0x4000
- 可以使用
size -l -m -x
來查看Mach-O的內(nèi)存分布
## 使用了ASLR
- LC_SEGMENT(__TEXT)的VM Address為
0x100000000
- ASLR隨機產(chǎn)生的Offset(偏移)為
0x5000
## 函數(shù)(變量)符號的內(nèi)存地址、可執(zhí)行文件地址計算
### 函數(shù)內(nèi)存地址計算
- File Offset: 在當前架構(gòu)(MachO)文件中的偏移量莺掠。
-
VM Address: 編譯鏈接后衫嵌,射到虛擬地址中的內(nèi)存起始地址。
VM Address = File Offset + __PAGEZERO Size
(__PAGEZERO段在MachO文件中沒有實際大小彻秆,在VM中展開) -
Load Address: 在運行時加載到虛擬內(nèi)存的起始位置楔绞。Slide是加載到內(nèi)存的偏移,這個偏移值是一個隨機值唇兑,每次運行都不相同酒朵。
Load Address = VM Address + Slide(ASLR Offset)
由于dsym符號表是編譯時生成的地址,crash堆棧的地址是運行時地址扎附,這個時候需要經(jīng)過轉(zhuǎn)換才能正確的符號化蔫耽。crash日志里的符號地址被稱為Stack Address,而編譯后的符號地址被稱為Symbol Address留夜,他們之間的關系如下:Stack Address = Symbol Address + Slide
符號化就是通過Stack Address到dsym文件中尋找對應符號信息的過程匙铡。
Hopper图甜、IDA圖形化工具中的地址都是未使用ASLR前的VM Address
### ASLR Offset的獲取
ASLR Offset有的地方也叫做slide
,獲取方法:
- 在運行時由API
dyld_get_image_vmaddr_slide()
鳖眼,來獲取image虛擬地址的偏移量黑毅。
//函數(shù)原型如下:
extern intptr_t _dyld_get_image_vmaddr_slide(uint32_t image_index);
//一般使用方法如下:
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
intptr_t index = _dyld_get_image_vmaddr_slide(i);
}
-
通過
lldb
命令image list -o -f
進行獲取(本地具帮、遠程debugserver
調(diào)試都可以)博肋,如下圖:
根據(jù)運行時crash中的
binary image
信息 和 ELF 文件的load command
計算的到。比如下例:
//下面是crash信息蜂厅,其中包括了拋出異常的線程的函數(shù)調(diào)用棧信息匪凡,日志下方有binary image信息,都只摘取了部分:
/*
第一列掘猿,調(diào)用順序
第二列病游,對應函數(shù)所屬的 binary image
第三列,stack address
第四列稠通,地址的符號+偏移的表示法衬衬,運算結(jié)果等于第三列
*/
Last Exception Backtrace:
0 CoreFoundation 0x189127100 __exceptionPreprocess + 132
1 libobjc.A.dylib 0x1959e01fc objc_exception_throw + 60
2 CoreFoundation 0x189127040 +[NSException raise:format:] + 128
3 CrashDemo 0x100a8666c 0x10003c000 + 10790508
4 libsystem_platform.dylib 0x19614bb0c _sigtramp + 56
5 CrashDemo 0x1006ef164 0x10003c000 + 7024996
6 CrashDemo 0x1006e8580 0x10003c000 + 6997376
7 CrashDemo 0x1006e8014 0x10003c000 + 6995988
8 CrashDemo 0x1006e7c94 0x10003c000 + 6995092
9 CrashDemo 0x1006f2460 0x10003c000 + 7038048
/*
第一列,虛擬地址空間區(qū)塊改橘;
第二列滋尉,映射文件名;
第三列:加載的image的UUID飞主;
第四列狮惜,映射文件路徑
*/
Binary Images:
0x10003c000 - 0x100f7bfff CrashDemo arm64 <b5ae3570a013386688c7007ee2e73978> /var/mobile/Applications/05C398CE-21E9-43C2-967F-26DD0A327932/CrashDemo.app/CrashDemo
0x12007c000 - 0x1200a3fff dyld arm64 <628da833271c3f9bb8d44c34060f55e0> /usr/lib/dyld
//下面是使用 otool 工具查看到的 MedicalRecordsFolder(我的程序)的 加載命令 。
$otool -l CrashDemo.app/CrashDemo
CrashDemo.app/CrashDemo:
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
fileoff 0
filesize 0
maxprot 0x00000000
initprot 0x00000000
nsects 0
flags 0x0
Load command 1
cmd LC_SEGMENT_64
cmdsize 792
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x000000000000c000
fileoff 0
filesize 49152
maxprot 0x00000005
initprot 0x00000005
nsects 9
flags 0x0
……
Load command 2
……
在 binary image 第一行可以看出進程空間的 0x10003c000 - 0x100f7bfff 這個區(qū)域在運行時被映射為 CrashDemo 內(nèi)的內(nèi)容碌识,也就是我們的 ELF 文件(區(qū)域起始地址為0x10003c000)碾篡。
而在 Load Command 中看到的__TEXT
的段起始地址卻是 0x0000000100000000
顯而易見:slide = 0x10003c000(Load Address) - 0x100000000(VM Address) = 0x3c000;之后筏餐,就可以通過公式symbol address = stack address - slide;
來計算stack address 在crash log 中已經(jīng)找到了开泽。
### Symbol Address符號化
-
利用
dwarfdump
可以從dsym文件中得到symbol Address對應的內(nèi)容:- 拿到crash日志后,我們要先確定dsym文件是否匹配魁瞪∧侣桑可以使用下面命令查看dsym文件所有架構(gòu)的UUID:
dwarfdump --uuid CrashDemo.app.dSYM
,然后跟crash日志中Binary Images中的UUID相比對佩番。 - 用得到的Symbol Address去 dsym 文件中查詢众旗,命令如下:
dwarfdump --arch arm64 --lookup [Symbol Address] CrashDemo.app.dSYM
,就可以定位下來具體的代碼趟畏、函數(shù)名、所處的文件滩租、行等信息了
- 拿到crash日志后,我們要先確定dsym文件是否匹配魁瞪∧侣桑可以使用下面命令查看dsym文件所有架構(gòu)的UUID:
-
如果只是簡單的獲取符號名赋秀,可以用
atos
來符號化:
atos -o [dsym file path] -l [Load Address] -arch [arch type] [Stack Address]
- 不需要指定Symbol Address利朵,只需要Load Address、Stack Address即可猎莲。
# 進程地址空間
由于ASLR的作用绍弟,進程的地址空間變得流動性非常大。但是盡管具體的地址會隨機“滑動”某個小的偏移量著洼,但整體布局保持不變樟遣。
內(nèi)存空間分為以下幾個段:
- __PAGEZERO:在32位的系統(tǒng)中,這是內(nèi)存中單獨的一個頁面(4KB)身笤,而且這個頁面所有的訪問權(quán)限都被撤消了豹悬。在 64 位系統(tǒng)上,這個段對應了一個完整的32位地址空間(即前4GB)液荸。這個段有助于捕捉空指針引用(因為空指針實際上就是 0)瞻佛,或捕捉將整數(shù)當做指針引用(因為32位平臺下的 4095 以下的值,以及64位平臺下4GB以下的值都在這個范圍內(nèi))娇钱。由于這個范圍內(nèi)所有訪問權(quán)限(讀伤柄、寫和執(zhí)行)都被撤消了,所以在這個范圍內(nèi)的任何解引用操作都會引發(fā)來自 MMU 的硬件頁錯誤文搂, 進而產(chǎn)生一個內(nèi)核可以捕捉的陷阱适刀。內(nèi)核將這個陷阱轉(zhuǎn)換為C++異常或表示總線錯誤的POSIX信號(SIGBUS) 煤蹭。
PAGEZERO不是設計給進程使用的笔喉,但是多少成為了惡意代碼的溫床。想要通過“額外”代碼感染Mach-O的攻擊者往往發(fā)現(xiàn)可以很方便地通過PAGEZERO實現(xiàn)這個目的疯兼。PAGEZERO通常不屬于目標文件的一部分(其對應的加載指令LC_SEGMENT將filesize指定為0)然遏,但是對此并沒有嚴格的要求.
- __TEXT:這個段存放的是程序代碼。和其他所有操作系統(tǒng)一樣吧彪,文本段被設置為r-x待侵,即只讀且可執(zhí)行。這不僅可以防止二進制代碼在內(nèi)存中被修改姨裸,還可以通過共享這個只讀段優(yōu)化內(nèi)存的使用秧倾。通過這種方式,同一個程序的多個實例可以僅使用一份TEXT副本傀缩。文本段通常包含多個區(qū)那先,實際的代碼在_text區(qū)中。文本段還可以包含其他只讀數(shù)據(jù)赡艰,例如常量和硬編碼的字符串售淡。
- __LINKEDIT:由dyld使用,這個區(qū)包含了字符串表、符號表以及其他數(shù)據(jù)揖闸。
- __IMPORT:用于 i386 的二進制文件的導入表揍堕。
- __DATA:用于可讀/可寫的數(shù)據(jù)。
- __MALLOC_TINY:用于小于一個頁面大小的內(nèi)存分配汤纸。
- __MALLOC_SMALL:用于幾個頁面大小的內(nèi)存分配衩茸。
下面是使用vmmap(1)
輸出的一個實例程序a
在32位
硬件設備上運行的進程地址空間,顯示了區(qū)域的名稱贮泞、地址范圍楞慈、權(quán)限(當前權(quán)限和最高權(quán)限)以及映射的名稱(通常對應的是Mach-O目標文件,如果有的話)啃擦。
# 小結(jié)
應該注意的是囊蓝,盡管ASLR是很顯著的改進,但也不是萬能藥议惰。黑客仍然能找到聰明的方法破解程序慎颗。事實上,目前臭名昭著的“Star 3.0”漏洞就攻破了ASLR言询,這個漏洞越獄了 iPad 2 上的iOS 4.3俯萎。這種破解使用了Retum-Oriented Programming(ROP)攻擊技術(shù),通過緩沖區(qū)溢出破壞棧运杭,以設置完整的棧幀夫啊, 模擬對libSystem的調(diào)用。同樣的技術(shù)也用在iOS 5.0.1的“corona”漏洞中辆憔,這個漏洞成功地攻入了所有的蘋果設備撇眯,包括當時最新的iPhone 4S。
預防攻擊的唯一之道就是編寫更加安全的代碼虱咧,并且采用嚴格的代碼審查熊榛,既要包含自動的技術(shù),也要有人工的介入腕巡。
# 參考鏈接
- 《深入解析Mac OS X & iOS 操作系統(tǒng)》
- 動態(tài)調(diào)試之ASLR
- iOS crash log 解析