(五) Mach-O 文件之進(jìn)程(虛擬)地址空間、ASLR

# 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ù)名、所處的文件、行等信息了
  • 如果只是簡(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í)例程序a32位硬件設(shè)備上運(yùn)行的進(jìn)程地址空間,顯示了區(qū)域的名稱、地址范圍、權(quán)限(當(dāng)前權(quán)限和最高權(quán)限)以及映射的名稱(通常對(duì)應(yīng)的是Mach-O目標(biāo)文件,如果有的話)。

32位進(jìn)程的虛擬地址空間布局

# 小結(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ù),也要有人工的介入。

# 參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容