# ASLR引入
# ASLR
## 未使用ASLR
## 使用了ASLR
## 符號(hào)在可執(zhí)行文件、虛擬地址空間中的地址計(jì)算
### 符號(hào)內(nèi)存、可執(zhí)行文件地址關(guān)系
### ASLR Offset的獲取
### Symbol Address符號(hào)化
# Mach-O文件的進(jìn)程地址空間分布
# 小結(jié)
# 參考鏈接
# ASLR引入
進(jìn)程在自己私有的虛擬地址空間中啟動(dòng)。按照傳統(tǒng)方式,進(jìn)程每一次啟動(dòng)時(shí)采用的都是固定的可預(yù)見的方式。然而,這意味著某個(gè)給定程序在某個(gè)給定架構(gòu)上的進(jìn)程初始虛擬內(nèi)存鏡像都是基本一致的。而且更嚴(yán)重的問(wèn)題在于,即使是在進(jìn)程正常運(yùn)行的生命周期中,大部分內(nèi)存分配的操作都是按照同樣的方式進(jìn)行的,因此使得內(nèi)存中的地址分布具有非常強(qiáng)的可預(yù)測(cè)性。
盡管這有助于調(diào)試,但是也給黑客提供了更大的施展空間。黑客主要采用的方法是代碼注入:通過(guò)重寫內(nèi)存中的函數(shù)指針,黑客就可以將程序的執(zhí)行路徑轉(zhuǎn)到自己的代碼,將程序的輸入轉(zhuǎn)變?yōu)樽约旱妮斎?。重寫?nèi)存最常用的方法是采用緩沖區(qū)溢出(即利用未經(jīng)保護(hù)的內(nèi)存復(fù)制操作越過(guò)上數(shù)組的邊界),可參考緩沖區(qū)溢出攻擊,將函數(shù)的返回地址重寫為自己的指針。不僅如此,黑客還有更具創(chuàng)意的技術(shù),例如破壞printf()格式化字符串以及基于堆的緩沖區(qū)溢出。此外,任何用戶指針甚至結(jié)構(gòu)化的異常處理程序都可以導(dǎo)致代碼注入。這里的關(guān)鍵問(wèn)題在于判斷重寫哪些指針,也就是說(shuō),可靠地判斷注入的代碼應(yīng)該在內(nèi)存中的什么位置。
不論被破解程序的薄弱環(huán)節(jié)在哪里:緩沖區(qū)溢出、格式化字符串攻擊或其他方式,黑客都可以花大力氣破解一個(gè)不安全的程序,找到這個(gè)程序的地址空間布局,然后精心設(shè)計(jì)一種方法,這種方法可以可靠地重現(xiàn)程序中的薄弱環(huán)節(jié),并且可以在類似的系統(tǒng)上暴露出一樣的薄弱環(huán)節(jié)。
現(xiàn)在大部分操作系統(tǒng)中都采用了一種稱為地址空間布局隨機(jī)化(ASLR) 的技術(shù),這是一種避免攻擊的有效保護(hù)。進(jìn)程每一次啟動(dòng)時(shí),地址空間都會(huì)被簡(jiǎn)單地隨機(jī)化:只是偏移,而不是攪亂?;镜牟季?程序文本、數(shù)據(jù)和庫(kù))仍然是一樣的。然而,這些部分具體的地址都不同了——區(qū)別足夠大,可以阻擋黑客對(duì)地址的猜測(cè)。實(shí)現(xiàn)方法是通過(guò)內(nèi)核將Mach-O的段“平移”某個(gè)隨機(jī)系數(shù)。
# ASLR
地址空間布局隨機(jī)化(Address Space Layout Randomization,ASLR)是一種針對(duì)緩沖區(qū)溢出的安全保護(hù)技術(shù),通過(guò)對(duì)堆、棧、共享庫(kù)映射等線性區(qū)布局的隨機(jī)化,通過(guò)增加攻擊者預(yù)測(cè)目的地址的難度,防止攻擊者直接定位攻擊代碼位置,達(dá)到阻止溢出攻擊的目的的一種技術(shù)。iOS4.3開始引入了ASLR技術(shù)。
下面分別來(lái)看一下,未使用ASLR、使用了ASLR下,進(jìn)程虛擬地址空間內(nèi)的分布。下圖中左側(cè)是mach-O可執(zhí)行文件,右側(cè)是鏈接之后的虛擬地址空間,如果對(duì)__TEXT、__DATA等Segment概念不清楚的地方,可以看一些第二篇關(guān)于Mach-O文件結(jié)構(gòu)的介紹。
## 未使用ASLR
- 函數(shù)代碼存放在__TEXT段中
- 全局變量存放在__DATA段中
- 可執(zhí)行文件的內(nèi)存地址是0x0
- 代碼段(__TEXT)的內(nèi)存地址就是LC_SEGMENT(__TEXT)中的VM Address:arm64設(shè)備下,為
0x100000000;非arm64下為0x4000 - 可以使用
size -l -m -x來(lái)查看Mach-O的內(nèi)存分布
## 使用了ASLR
- LC_SEGMENT(__TEXT)的VM Address為
0x100000000 - ASLR隨機(jī)產(chǎn)生的Offset(偏移)為
0x5000
## 函數(shù)(變量)符號(hào)的內(nèi)存地址、可執(zhí)行文件地址計(jì)算
### 函數(shù)內(nèi)存地址計(jì)算
- File Offset: 在當(dāng)前架構(gòu)(MachO)文件中的偏移量。
-
VM Address: 編譯鏈接后,射到虛擬地址中的內(nèi)存起始地址。
VM Address = File Offset + __PAGEZERO Size(__PAGEZERO段在MachO文件中沒有實(shí)際大小,在VM中展開) -
Load Address: 在運(yùn)行時(shí)加載到虛擬內(nèi)存的起始位置。Slide是加載到內(nèi)存的偏移,這個(gè)偏移值是一個(gè)隨機(jī)值,每次運(yùn)行都不相同。
Load Address = VM Address + Slide(ASLR Offset)
由于dsym符號(hào)表是編譯時(shí)生成的地址,crash堆棧的地址是運(yùn)行時(shí)地址,這個(gè)時(shí)候需要經(jīng)過(guò)轉(zhuǎn)換才能正確的符號(hào)化。crash日志里的符號(hào)地址被稱為Stack Address,而編譯后的符號(hào)地址被稱為Symbol Address,他們之間的關(guān)系如下:Stack Address = Symbol Address + Slide
符號(hào)化就是通過(guò)Stack Address到dsym文件中尋找對(duì)應(yīng)符號(hào)信息的過(guò)程。
Hopper、IDA圖形化工具中的地址都是未使用ASLR前的VM Address
### ASLR Offset的獲取
ASLR Offset有的地方也叫做slide,獲取方法:
- 在運(yùn)行時(shí)由API
dyld_get_image_vmaddr_slide(),來(lái)獲取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);
}
-
通過(guò)
lldb命令image list -o -f進(jìn)行獲?。ū镜?、遠(yuǎn)程debugserver調(diào)試都可以),如下圖:
根據(jù)運(yùn)行時(shí)crash中的
binary image信息 和 ELF 文件的load command計(jì)算的到。比如下例:
//下面是crash信息,其中包括了拋出異常的線程的函數(shù)調(diào)用棧信息,日志下方有binary image信息,都只摘取了部分:
/*
第一列,調(diào)用順序
第二列,對(duì)應(yīng)函數(shù)所屬的 binary image
第三列,stack address
第四列,地址的符號(hào)+偏移的表示法,運(yùn)算結(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 第一行可以看出進(jìn)程空間的 0x10003c000 - 0x100f7bfff 這個(gè)區(qū)域在運(yùn)行時(shí)被映射為 CrashDemo 內(nèi)的內(nèi)容,也就是我們的 ELF 文件(區(qū)域起始地址為0x10003c000)。
而在 Load Command 中看到的__TEXT的段起始地址卻是 0x0000000100000000
顯而易見:slide = 0x10003c000(Load Address) - 0x100000000(VM Address) = 0x3c000;之后,就可以通過(guò)公式symbol address = stack address - slide; 來(lái)計(jì)算stack address 在crash log 中已經(jīng)找到了。
### Symbol Address符號(hào)化
-
利用
dwarfdump可以從dsym文件中得到symbol Address對(duì)應(yīng)的內(nèi)容:- 拿到crash日志后,我們要先確定dsym文件是否匹配??梢允褂孟旅婷畈榭磀sym文件所有架構(gòu)的UUID:
dwarfdump --uuid CrashDemo.app.dSYM,然后跟crash日志中Binary Images中的UUID相比對(duì)。 - 用得到的Symbol Address去 dsym 文件中查詢,命令如下:
dwarfdump --arch arm64 --lookup [Symbol Address] CrashDemo.app.dSYM,就可以定位下來(lái)具體的代碼、函數(shù)名、所處的文件、行等信息了
- 拿到crash日志后,我們要先確定dsym文件是否匹配??梢允褂孟旅婷畈榭磀sym文件所有架構(gòu)的UUID:
-
如果只是簡(jiǎn)單的獲取符號(hào)名,可以用
atos來(lái)符號(hào)化:
atos -o [dsym file path] -l [Load Address] -arch [arch type] [Stack Address]- 不需要指定Symbol Address,只需要Load Address、Stack Address即可。
# 進(jìn)程地址空間
由于ASLR的作用,進(jìn)程的地址空間變得流動(dòng)性非常大。但是盡管具體的地址會(huì)隨機(jī)“滑動(dòng)”某個(gè)小的偏移量,但整體布局保持不變。
內(nèi)存空間分為以下幾個(gè)段:
- __PAGEZERO:在32位的系統(tǒng)中,這是內(nèi)存中單獨(dú)的一個(gè)頁(yè)面(4KB),而且這個(gè)頁(yè)面所有的訪問(wèn)權(quán)限都被撤消了。在 64 位系統(tǒng)上,這個(gè)段對(duì)應(yīng)了一個(gè)完整的32位地址空間(即前4GB)。這個(gè)段有助于捕捉空指針引用(因?yàn)榭罩羔槍?shí)際上就是 0),或捕捉將整數(shù)當(dāng)做指針引用(因?yàn)?2位平臺(tái)下的 4095 以下的值,以及64位平臺(tái)下4GB以下的值都在這個(gè)范圍內(nèi))。由于這個(gè)范圍內(nèi)所有訪問(wèn)權(quán)限(讀、寫和執(zhí)行)都被撤消了,所以在這個(gè)范圍內(nèi)的任何解引用操作都會(huì)引發(fā)來(lái)自 MMU 的硬件頁(yè)錯(cuò)誤, 進(jìn)而產(chǎn)生一個(gè)內(nèi)核可以捕捉的陷阱。內(nèi)核將這個(gè)陷阱轉(zhuǎn)換為C++異?;虮硎究偩€錯(cuò)誤的POSIX信號(hào)(SIGBUS) 。
PAGEZERO不是設(shè)計(jì)給進(jìn)程使用的,但是多少成為了惡意代碼的溫床。想要通過(guò)“額外”代碼感染Mach-O的攻擊者往往發(fā)現(xiàn)可以很方便地通過(guò)PAGEZERO實(shí)現(xiàn)這個(gè)目的。PAGEZERO通常不屬于目標(biāo)文件的一部分(其對(duì)應(yīng)的加載指令LC_SEGMENT將filesize指定為0),但是對(duì)此并沒有嚴(yán)格的要求.
- __TEXT:這個(gè)段存放的是程序代碼。和其他所有操作系統(tǒng)一樣,文本段被設(shè)置為r-x,即只讀且可執(zhí)行。這不僅可以防止二進(jìn)制代碼在內(nèi)存中被修改,還可以通過(guò)共享這個(gè)只讀段優(yōu)化內(nèi)存的使用。通過(guò)這種方式,同一個(gè)程序的多個(gè)實(shí)例可以僅使用一份TEXT副本。文本段通常包含多個(gè)區(qū),實(shí)際的代碼在_text區(qū)中。文本段還可以包含其他只讀數(shù)據(jù),例如常量和硬編碼的字符串。
- __LINKEDIT:由dyld使用,這個(gè)區(qū)包含了字符串表、符號(hào)表以及其他數(shù)據(jù)。
- __IMPORT:用于 i386 的二進(jìn)制文件的導(dǎo)入表。
- __DATA:用于可讀/可寫的數(shù)據(jù)。
- __MALLOC_TINY:用于小于一個(gè)頁(yè)面大小的內(nèi)存分配。
- __MALLOC_SMALL:用于幾個(gè)頁(yè)面大小的內(nèi)存分配。
下面是使用vmmap(1)輸出的一個(gè)實(shí)例程序a在32位硬件設(shè)備上運(yùn)行的進(jìn)程地址空間,顯示了區(qū)域的名稱、地址范圍、權(quán)限(當(dāng)前權(quán)限和最高權(quán)限)以及映射的名稱(通常對(duì)應(yīng)的是Mach-O目標(biāo)文件,如果有的話)。

# 小結(jié)
應(yīng)該注意的是,盡管ASLR是很顯著的改進(jìn),但也不是萬(wàn)能藥。黑客仍然能找到聰明的方法破解程序。事實(shí)上,目前臭名昭著的“Star 3.0”漏洞就攻破了ASLR,這個(gè)漏洞越獄了 iPad 2 上的iOS 4.3。這種破解使用了Retum-Oriented Programming(ROP)攻擊技術(shù),通過(guò)緩沖區(qū)溢出破壞棧,以設(shè)置完整的棧幀, 模擬對(duì)libSystem的調(diào)用。同樣的技術(shù)也用在iOS 5.0.1的“corona”漏洞中,這個(gè)漏洞成功地攻入了所有的蘋果設(shè)備,包括當(dāng)時(shí)最新的iPhone 4S。
預(yù)防攻擊的唯一之道就是編寫更加安全的代碼,并且采用嚴(yán)格的代碼審查,既要包含自動(dòng)的技術(shù),也要有人工的介入。
# 參考鏈接
- 《深入解析Mac OS X & iOS 操作系統(tǒng)》
- 動(dòng)態(tài)調(diào)試之ASLR
- iOS crash log 解析
