#APP漏洞掃描用地址空間隨機化html
##前言android
咱們在前文《APP漏洞掃描器之本地拒絕服務檢測詳解》瞭解到阿里聚安全漏洞掃描器有一項靜態分析加動態模糊測試的方法來檢測的功能,並詳細的介紹了它在針對本地拒絕服務的檢測方法。程序員
同時,阿里聚漏洞掃描器有一個檢測項叫未使用地址空間隨機化技術, 該檢測項會分析APP中包含的ELF文件判斷它們是否使用了該項技術。若是APP中存在該項漏洞則會下降緩衝區溢出攻擊的門檻。安全
本文主要介紹該項技術的原理和掃描器的檢測方法。因爲PIE的實現細節較複雜,本文只是介紹了大體的原理。想深刻了解細節的同窗能夠參看潘愛民老師的書籍《程序員的自我修養》。dom
##PIE是什麼wordpress
PIE(position-independent executable)是一種生成地址無關可執行程序的技術。若是編譯器在生成可執行程序的過程當中使用了PIE,那麼當可執行程序被加載到內存中時其加載地址存在不可預知性。函數
PIE還有個孿生兄弟PIC(position-independent code)。其做用和PIE相同,都是使被編譯後的程序可以隨機的加載到某個內存地址。區別在於PIC是在生成動態連接庫時使用(Linux中的so),PIE是在生成可執行文件時使用。測試
##PIE的做用spa
####安全性.net
PIE能夠提升緩衝區溢出攻擊的門檻。它屬於ASLR(Address space layout randomization)的一部分。ASLR要求執行程序被加載到內存時,它其中的任意部分都是隨機的。包括 **Stack, Heap ,Libs and mmap, Executable, Linker, VDSO。**經過PIE咱們可以實現Executable 內存隨機化
####**節約內存使用空間 **
除了安全性,地址無關代碼還有一個重要的做用是提升內存使用效率。
一個共享庫能夠同時被多個進程裝載,若是不是地址無關代碼(代碼段中存在絕對地址引用),每一個進程必須結合其自生的內存地址調用動態連接庫。致使不得不將共享庫總體拷貝到進程中。若是系統中有100個進程調用這個庫,就會有100份該庫的拷貝在內存中,這會照成極大的空間浪費。
相反若是被加載的共享庫是地址無關代碼,100個進程調用該庫,則該庫只須要在內存中加載一次。這是由於PIE將共享庫中代碼段需要變換的內容分離到數據段。使得代碼段加載到內存時能作到地址無關。多個進程調用共享庫時只須要在本身的進程中加載共享庫的數據段,而代碼段則能夠共享。
##PIE工做原理簡介
咱們先從實際的例子出發,觀察PIE和NO-PIE在可執行程序表現形式上的區別。管中窺豹探索地址無關代碼的實現原理。
例子一
定義以下C代碼:
#include <stdio.h> int global; void main() { printf("global address = %x\n", &global); }
程序中定義了一個全局變量global並打印其地址。咱們先用普通的方式編譯程序。
gcc -o sample1 sample1.c
運行程序能夠觀察到global加載到內存的地址每次都同樣。
$./sample1 global address = 6008a8 $./sample1 global address = 6008a8 $./sample1 global address = 6008a8
接着用PIE方式編譯 sample1.c
gcc -o sample1_pie sample1.c -fpie -pie
運行程序觀察global的輸出結果:
./sample1_pie global address = 1ce72b38 ./sample1_pie global address = 4c0b38 ./sample1_pie global address = 766dcb38
每次運行地址都會發生變換,說明PIE使執行程序每次加載到內存的地址都是隨機的。
例子二
在代碼中聲明一個外部變量global。但這個變量的定義並未包含進編譯文件中。
#include <stdio.h> extern int global; void main() { printf("extern global address = %x\n", &global); }
首先使用普通方式編譯 extern_var.c。在編譯選項中故意不包含有global定義的源文件。
gcc -o extern_var extern_var.c
發現不能編譯經過, gcc提示:
/tmp/ccJYN5Ql.o: In function `main': extern_var.c:(.text+0xa): undefined reference to `global' collect2: ld returned 1 exit status
編譯器在連接階段有一步重要的動做叫符號解析與重定位。連接器會將全部中間文件的數據,代碼,符號分別合併到一塊兒,並計算出連接後的虛擬基地址。好比 「.text」段從 0x1000開始,」.data」段從0x2000開始。接着連接器會根據基址計算各個符號(global)的相對虛擬地址。
當編譯器發如今符號表中找不到global的地址時就會報出 undefined reference to `global`.
說明在靜態連接的過程當中編譯器必須在編譯連接階段完成對全部符號的連接。
若是使用PIE方式將extern_var.c編譯成一個share library會出現什麼狀況呢?
gcc -o extern_var.so extern_var.c -shared -fPIC
程序可以順利編譯經過生成extern_var.so。但運行時會報錯,由於裝載時找不到global符號目標地址。這說明-fPIC選項生成了地址無關代碼。將靜態連接時沒有找到的global符號的連接工做推遲到裝載階段。
那麼在編譯連接階段,連接器是如何將這個缺失的目標地址在代碼段中進行地址引用的呢?
連接器巧妙的用一張中間表GOT(Global Offset Table)來解決被引用符號缺失目標地址的問題。若是在連接階段(jing tai)發現一個不能肯定目標地址的符號。連接器會將該符號加到GOT表中,並將全部引用該符號的地方用該符號在GOT表中的地址替換。到裝載階段動態連接器會將GOT表中每一個符號對應的實際目標地址填上。
當程序執行到符號對應的代碼時,程序會先查GOT表中對應符號的位置,而後根據位置找到符號的實際的目標地址。
####地址無關代碼的生成方式
所謂地址無關代碼要求程序被加載到內存中的任意地址都可以正常執行。因此程序中對變量或函數的引用必須是相對的,不能包含絕對地址。
好比以下僞彙編代碼:
**PIE方式:**代碼能夠運行在地址100或1000的地方
100: COMPARE REG1, REG2 101: JUMP_IF_EQUAL CURRENT+10 ... 111: NOP
**Non-PIE: **代碼只能運行在地址100的地方
100: COMPARE REG1, REG2 101: JUMP_IF_EQUAL 111 ... 111: NOP
由於可執行程序的代碼段只有讀和執行屬性沒有寫屬性,而數據段具備讀寫屬性。要實現地址無關代碼,就要將代碼段中需要改變的絕對值分離到數據段中。在程序加載時能夠保持代碼段不變,經過改變數據段中的內容,實現地址無關代碼。
####PIE和Non-PIE程序在內存中映射方式
在Non-PIE時程序每次加載到內存中的位置都是同樣的。
執行程序會在固定的地址開始加載。系統的動態連接器庫ld.so會首先加載,接着ld.so會經過.dynamic段中類型爲DT_NEED的字段查找其餘須要加載的共享庫。並依次將它們加載到內存中。注意:由於是Non-PIE模式,這些動態連接庫每次加載的順序和位置都同樣。
而對於經過PIE方式生成的執行程序,由於沒有絕對地址引用因此每次加載的地址也不盡相同。
不只動態連接庫的加載地址不固定,就連執行程序每次加載的地址也不同。這就要求ld.so首先被加載後它不只要負責重定位其餘的共享庫,同時還要對可執行文件重定位。
##PIE與編譯器選項
GCC編譯器用於生成地址無關代碼的參數主要有-fPIC, -fPIE, -pie。
其中-fPIC, -fPIE屬於編譯時選項,分別用於生成共享庫和可執行文件。它們可以使編譯階段生成的中間代碼具備地址無關代碼的特性。但這並不表明最後生成的可執行文件是PIE的。還須要在連接時經過-pie選項告訴連接器生成地址無關代碼的可執行程序。
一個標準的PIE程序編譯設置以下:
gcc -o sample_pie sample.c -fPIE -pie
在gcc中使用編譯選項與是否生成PIE可執行文件對應關係以下:
Type爲DYN的程序支持PIE,EXEC類型不支持。DYN, EXEC與PIE對應關係詳見後文。
##ASLR在Android中的應用
PIE屬於ASLR的一部分,如上節提到ASLR包括對Stack, Heap, Libs and mmap, Executable, Linker, VDSO的隨機化。
而支持PIE只表示對Executable實現了ASLR。隨着Android的發展對ASLR的支持也逐漸加強。
**ASLR in Android 2.x **
Android對ASLR的支持是從Android 2.x開始的。2.x只支持對Stack的隨機化。
ASLR in Android 4.0
而在4.0,即其所謂的支持ASLR的版本上,其實ASLR也僅僅增長了對libc等一些shared libraries進行了隨機化,而對於heap, executable和linker仍是static的。
對於heap的隨機化來講,能夠經過
echo 2 > /proc/sys/kernel/randomize_va_space
來開啓。
而對於executable的隨機化,因爲大部分的binary沒有加GCC的-pie -fPIE選項,因此編譯出來的是EXEC,而不是DYN這種shared object file,所以不是PIE(Position Independent Executable),因此沒有辦法隨機化;
一樣的linker也沒有作到ASLR。
ASLR in Android 4.1
終於,在4.1 Jelly Bean中,Android支持了全部內存的ASLR。在Android 4.1中,基本上全部binary都被編譯和鏈接成了PIE模式(能夠經過readelf查看其Type)。因此,相比於4.0,4.1對Heap,executable和linker都提供了ASLR的支持。
ASLR in Android 5.0
5.0中Android拋棄了對non-PIE的支持,全部的進程均是ASLR的。若是程序沒有開啓PIE,在運行時會報錯並強制退出。
PIE程序運行在Android各版本
支持PIE的可執行程序只能運行在4.1+的版本上。 在4.1版本以前運行會出現crash。而Non-PIE的程序,在5.0以前的版本能正常運行,但在5.0上會crash。
##如何檢測是否開啓PIE
未開啓PIE的執行程序用readelf查看其文件類型應顯示EXEC(可執行文件),開啓PIE的可執行程序的文件類型爲DYN(共享目標文件)。另外代碼段的虛擬地址老是從0開始。
爲何檢測DYN就能夠判斷是否支持PIE?
DYN指的是這個文件的類型,即共享目標文件。那麼全部的共享目標文件必定是開啓了PIE的嗎?咱們能夠從源碼中尋找答案。查看glibc/glibc-2.16.0/elf/dl-load.c中的代碼。
從源碼可知,若是加載類型不爲ET_DYN時調用mmap加載文件時會傳入MAP_FIXED標誌。將程序映射到固定地址。
##阿里聚安全開發中建議
##Reference