空指標漏洞防護技術(初級篇)

綠盟發表於2015-09-15

安全歷史上由於空指標所帶來的漏洞及攻擊數不勝數,但由於其對利用者的程式設計能力有要求,對分析及防護者來說有更高的要求,所以國內對空指標漏洞及相關技術的討論不是很多。今天這篇《空指標漏洞防護技術》,由綠盟科技威脅響應安全專家坐堂講解,大家可以從中瞭解空指標漏洞的基礎知識,並結合Windows 8的記憶體防護機制例項,動手實踐空指標漏洞的防護技術。

1 背景

指標對於絕大部分的程式設計人員來說都不陌生,說起C/C++中指標的使用既帶來了程式設計方面的方便;同時對程式設計人員來說,也是對個人程式設計能力的一種考驗,不正確的使用指標會直接導致程式崩潰,而如果是核心程式碼中對指標的錯誤使用,會導致系統崩潰,後果也是相當嚴重。

一般情況下我們使用指標時,錯誤用法集中在三個方面:

  1. 由指標指向的一塊動態記憶體,在利用完後,沒有釋放記憶體,導致記憶體洩露
  2. 野指標(懸浮指標)的使用,在指標指向的記憶體空間使用完釋放後,指標指向的記憶體空間已經歸還給了作業系統,此時的指標成為野指標,在沒有對野指標做處理的情況下,有可能對該指標再次利用導致指標引用錯誤而程式崩潰。
  3. Null Pointer空指標的引用,對於空指標的錯誤引用往往是由於在引用之前沒有對空指標做判斷,就直接使用空指標,還有可能把空指標作為一個物件來使用,間接使用物件中的屬性或是方法,而引起程式崩潰,空指標的錯誤使用常見於系統、服務、軟體漏洞方面。

對於第一和第二種情況,我們可以通過一些程式碼審計工具在釋出之前就能確定導致記憶體洩露或是野指標存在的地方。比如常見的工具有fority,valgrind等及時發現指標錯誤引用導致的問題。

對於第三種情況,空指標(Null Pointer)引用導致的錯誤,依靠程式碼審計工具很難發現其中的錯誤,因為空指標的引用一般不會發生在出現空指標然後直接使用空指標情況。往往是由於程式碼邏輯比較複雜空指標引用的位置會比較遠,不容易發現;並且在正常情況下不會觸發,只有在特定輸入條件下才會引發空指標引用。對於排查此類錯誤也就更加困難。

本文不會重點討論記憶體洩露和野指標的內容,而是通過一些現有的漏洞和例項來分析一下Null Pointer 空指標。從NULL Pointer概念、本質結合靜態逆向及核心動態除錯技術來了解在win7 32位和win8 32位下系統對Null Pointer處理情況有什麼不同;Win8 32位針對Null Pointer新增了哪些防護機制。

本文從淺入深,循序漸進的講述了NULL Pointer,結合靜態逆向分析和核心級動態除錯技術深入剖析win8系統對零頁記憶體的保護機制,其中還涉及到了一些核心除錯的技巧,對於對NULL Pointer的概念、使用比較模糊的人員值得一讀,對於從事安全研究和學習的人員也是鞏固、加深、擴充的好素材。

2 空指標由Null Pointer引發的漏洞

首先來看看近年來由於空指標的錯誤使用導致的系統、服務漏洞:

  • Microsoft windows kernel ‘win32k.sys’本地許可權提升漏洞(CVE-2015-1721)(MS15-061)

該漏洞影響了windows Server 2003 SP2和R2 SP2,Windows Vista SP2, Windows Server 2008 SP2 和R2 SP1,Windows7 SP1,Windows8 ,Windows8.1,Windows Server2012 Gold和R2,Windows RT Gold and 8.1 。利用核心驅動程式 win32k.sys漏洞可以提升許可權或是引起拒絕訪問服務,主要原因是空指標的錯誤引用導致。

  • PHP空指標引用限制繞過漏洞(CVE-2015-3411)

PHP存在安全漏洞,由於程式多個擴充套件中缺少路徑或某些函式的路徑引數的空位元組檢查,允許遠端攻擊者利用漏洞可繞過目標檔案系統訪問限制,訪問任意檔案。

  • 0rg空指標引用拒絕訪問漏洞(CVE-2008-0153 )

X.Org是X.Org基金會運作的一個對X Window系統的官方參考實現,是開源的自由軟體。libXfont是一個用於伺服器和實用程式的X字型處理庫。 X.Org libXfont 1.4.9之前版本和1.5.1之前1.5.x版本的bitmap/bdfread.c檔案中的’bdfReadCharacters’函式存在安全漏洞,該漏洞源於程式未能正確處理不能讀取的字元點陣圖。遠端攻擊者可藉助特製的BDF字型檔案利用該漏洞造成拒絕服務(空指標逆向引用和崩潰),執行任意程式碼。

  • Paragma TelnetServer空指標引用拒絕服務漏洞(BID-27143)

Pragma TelnetServer是一款遠端訪問和控制Telnet伺服器。Pragma TelnetServer處理協議資料時存在漏洞,遠端攻擊者可能利用此漏洞導致伺服器不可用。TelnetServer伺服器對每個入站連線啟動一個telnetd.exe程式,該程式在處理TELOPT PRAGMA LOGON telnet選項(138號)期間存在空指標引用,導致程式終止。儘管終止單個程式不會影響其他程式,但終止某些程式會導致拒絕訪問伺服器。

  • OpenSSL SSLv2客戶端空指標引用拒絕服務漏洞(CVE-2006-4343)

OpenSSL是一種開放原始碼的SSL實現,用來實現網路通訊的高強度加密,現在被廣泛地用於各種網路應用程式中。OpenSSL的協議實現在處理連線請求時存在問題,遠端攻擊者可能利用此漏洞導致伺服器拒絕服務。SSLv2客戶端的get_server_hello()函式沒有正確地檢查空指標。使用OpenSSL的受影響客戶端如果建立了到惡意伺服器的SSLv2連線,就會導致崩潰。

  • Linux Kernel空指標間接引用本地拒絕服務漏洞(CVE-2014-2678)

Linux kernel 3.14版本內,net/rds/iw.c中的函式rds_iw_laddr_check在實現上存在本地拒絕服務漏洞,本地使用者通過盲系統呼叫沒有RDS傳輸的系統上的RDS套接字,利用此漏洞可造成空指標間接引用和系統崩潰。

  • ISC BIND named拒絕服務漏洞(CVE-2015-5477)

ISC BIND 9.9.7-P2之前版本、9.10.2-P3之前版本,named存在安全漏洞,遠端攻擊者通過TKEY查詢,利用此漏洞可造成拒絕服務(REQUIRE斷言失敗及程式退出,指標未初始化)。

此類漏洞還有很多,從給出的幾個漏洞來看,空指標漏洞主要是以拒絕服務訪問漏洞為主,空指標錯誤引用自然會到底程式出現錯誤,嚴重者會崩潰,從而引起拒絕服務訪問。

3 基礎篇:空指標驗證方式

由空指標的錯誤引用導致的漏洞,其原理本身很簡單:錯誤引用空指標,導致非法訪問記憶體地址。

那到底什麼是空指標漏洞呢?在計算機程式設計過程中使用指標時有兩個重要的概念:空指標和野指標。那麼在前面我們提到的空指標漏洞是不是就是我們在程式設計時所說的空指標呢?要弄清這個問題,首先需要知道程式設計領域中空指標和野指標分別是什麼,還要弄清楚什麼是空指標漏洞。

3.1 概念性驗證

假如 char p,那麼p是一個指標變數,該變數還沒有指向任何記憶體空間,如果p = 0; p = 0L; p = ”; p = 3 – 3; p = 0 * 5; 中的任何一種賦值操作之後(對於 C 來說還可以是 p = (void)0;), p 都成為一個空指標,由系統保證空指標不指向任何實際的物件或者函式。反過來說,任何物件或者函式的地址都不可能是空指標。(比如這裡的(void)0就是一個空指標。當然除了上面的各種賦值方式之外,還可以用 p = NULL; 來使 p 成為一個空指標。因為在很多系統中#define NULL (void)0。

3.1.1 記憶體管理之空指標

空指標指向了記憶體的什麼地方呢?標準中並沒有對空指標指向記憶體中的什麼地方這一個問題作出規定,也就是說用哪個具體的地址值(0x0 地址還是某一特定地址)表示空指標取決於系統的實現。我們常見的 空指標一般指向 0 地址,即空指標的內部用全 0來表示 (zero null pointer,零空指標)。

對於NULL能表示空指標,是否還有其他值來表示空指標呢?在windows核心程式設計第五版的windows記憶體結構一章中,表13-1有提到NULL指標分配的分割槽,其範圍是從0x00000000到0x0000FFFF。這段空間是空閒的,對於空閒的空間而言,沒有相應的物理儲存器與之相對應,所以對這段空間來說,任何讀寫操作都是會引起異常的。

從圖中看出,對NULL指標分配的區域有0x10000之多,為什麼分配如此大的空間?在定義NULL的時候,只使用了 0x00000000這麼一個值,而在表13-1有提到NULL指標分配的分割槽包含了0x00000000-0x0000FFFF,是不是有點浪費空間了;這是和作業系統地址空間的分配粒度相關的,windows X86的預設分配粒度是64KB,為了達到對齊,空間地址需要從0x00010000開始分配,故空指標的區間範圍有那麼大。

3.1.2知識擴充之野指標

“野指標”又叫”懸浮指標”不是NULL指標,是指向”垃圾”記憶體的指標。

“野指標”的成因主要有三種:

1)指標變數沒有被初始化。任何指標變數剛被建立時不會自動成為NULL指標,它的預設值是隨機的,它會亂指一氣。所以,指標變數在建立的同時應當被初始化,要麼將指標設定為NULL,要麼讓它指向合法的記憶體。例如: char *p = NULL; char *str = (char *) malloc(100);

2)指標p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指標。

free和delete只是把指標所指的記憶體給釋放掉,但並沒有把指標本身幹掉。free以後其地址仍然不變(非NULL),只是該地址對應的記憶體變成垃圾記憶體,p成了”野指標”。如果此時不把p設定為NULL,會讓人誤以為p是個合法的指標。如果程式比較長,我們有時記不住p所指的記憶體是否已經被釋放,在繼續使用p之前,通常會用語句if (p != NULL)進行防錯處理。很遺憾,此時if語句起不到防錯作用,因為即便p不是NULL指標,它也不指向合法的記憶體塊。

用free或delete釋放了記憶體之後,就應立即將指標設定為NULL,防止產生”野指標”。記憶體被釋放了,並不表示指標會消亡或者成了NULL指標。

3)指標操作超越了變數的作用範圍。例如不要返回指向棧記憶體的指標或引用,因為棧記憶體在函式結束時會被釋放,這種情況讓人防不勝防,示例程式如下:

另外需要注意的是如果程式定義了一個指標,就必須要立即讓它指向一個我們設定的空間或者把它設為NULL,如果沒有這麼做,那麼這個指標裡的內容是不可預知的,即不知道它指向記憶體中的哪個空間(即野指標),它有可能指向的是一個空白的記憶體區域,可能指向的是已經受保護的區域,甚至可能指向系統的關鍵記憶體,如果是那樣就糟了,也許我們後面不小心對指標進行操作就有可能讓系統出現紊亂,當機了。所以必須設定一個空間讓指標指向它,或者把指標設為NULL。

3.2原始碼級驗證

接下來就結合兩個小的實驗例子看看空指標,及使用空指標的後果。

3.2.1指標使用前期對比

本例是要驗證指標變數在初始化前後的狀況,下圖是一段程式碼:

程式碼很簡單只是驗證指標變數在賦值與未賦值分別指向的空間。程式碼中要列印的值:

  1. 指標變數初始化之前的地址
  2. 指標變數初始化之前的值
  3. 指標變數初始化之後的地址
  4. 指標變數初始化之後的值

編譯之後,執行三次程式看執行的結果:

從三次執行結果來看:

  1. 每次列印指標變數的地址都不同,因為每次執行程式,P指標變數的地址都在變動。
  2. 同一次列印中指標變數在初始化之前與初始化之後的地址沒有變化。因為指標變數也是變數,變數在其生命週期中值可以變動,但是地址不會改變。
  3. 每次列印中指標變數在初始化之前的值發生了變化。指標變數的值本身也是指向一塊記憶體地址,在初始化之前該變數不指向任何記憶體地址,所以該變數的值就是一個隨機值(也就是指向隨機記憶體地址)。
  4. 每次列印中指標變數在初始化之後的值沒有發生變化,在指標變數初始化之後P=0,所以在記憶體初始化的指標變數指向了記憶體地址都為0x00000000的位置。在每次列印中指標變數在初始化之後的值就不會發生變化。

3.2.2指標使用後期對比

針對指標變數在使用完畢後,記憶體釋放之後的情況對比,其原始碼如下:

程式碼沒什麼複雜邏輯,只是想驗證一下在指標變數釋放後, P=NULL之前的野指標與P=NULL之後的指標狀況,列印的內容:

  1. 指標變數P開始的地址與值
  2. 指標變數P在分配記憶體空間之後的地址與值
  3. 指標變數P在釋放記憶體空間之後的地址與值
  4. 指標變數P在P=NULL之後的地址與值

下圖是在編譯之後執行的結果:

a. 指標變數在宣告之後一直到最後的程式結束,指標變數的地址一直沒有變動為0x35f778。 b. 定義指標變數時其值為0x00000000 c. 在申請新的記憶體之後,指標變數的值為申請記憶體的地址0x6e7a78 d. 在記憶體釋放後,P變成野指標,但是此時P的值仍然是申請的記憶體地址0x6e7a78,但是此時P指標已經不能再使用。 e. 在P=NULL,p重新指向了地址0x00000000處。

由此可見,我們在釋放指標變數後,指標變數會變成野指標,如果此時引用該指標會出現非法訪問,因此需要在釋放指標變數後將指標變數指向空。

3.3視覺化記憶體驗證

為了能夠更加直觀的檢視指標變數在記憶體中的變化,下面就結合動態除錯的技術看看指標變數在整個使用過程中的變化情況。下面是一段程式程式碼:

在這裡,我們使用OD動態偵錯程式,來看看呼叫printf函式的情況:

a. 第一條列印語句

在呼叫printf前其記憶體情況:

可知指標變數P的值為EAX=[EBP-4],P的地址ECX=EBP-4,此時的暫存器值:

呼叫printf之後的值剛好是這兩個值

b.第二條列印語句printf(“p intialized address :%p, value :%08x\n”, &p, p);在呼叫printf前其記憶體情況:

可知指標變數P的值為EDX=[EBP-4],P的地址EAX=EBP-4,此時的暫存器值:

image013

呼叫printf之後的值剛好是這兩個值:

c. 第三條列印語句printf(“p free address :%p, value :%08x\n”, &p, p);在呼叫printf前其記憶體情況:

可知指標變數P的值為EAX=[EBP-4],P的地址ECX=EBP-4,此時的暫存器值:

image016

呼叫printf之後的值剛好是這兩個值:

d. 第四條列印語句printf(“p ultimate address :%p, value :%08x\n”, &p, p);在呼叫printf前其記憶體情況:

可知指標變數P的值為EDX=[EBP-4],P的地址EAX=EBP-4,此時的暫存器值:

呼叫printf之後的值剛好是這兩個值:

3.4 概念總結之空指標漏洞

前面對什麼是空指標,什麼是野指標做了講解和驗證。

在程式設計領域的空指標是指向NULL的指標,也就是說指向零頁記憶體的指標叫空指標。對於未初始化的指標,釋放記憶體而未將指標置為NULL和指標指向超出範圍的情況稱為野指標。那麼在第二節中列舉的空指標漏洞及未列舉的空指標漏洞是不是都是由於引用零頁記憶體導致的呢(比如CVE-2014-2678)?其實不然,有些漏洞是由於引用未初始化的指標或是引用超出範圍的指標所導致,而這類漏洞應該說是由於錯誤的引用了野指標。比如最新的BIND漏洞(CVE-2015-5477):

但是到目前為止還沒用聽說過哪個漏洞命名為野指標漏洞,而更多的是空指標漏洞。也就是說在電腦保安領域中由空指標或是野指標導致的漏洞統一叫做空指標漏洞。

…未完待續 在下一次《空指標漏洞防護技術-高階篇》中,我們將介紹空指標利用及Windows 8中相應的防護機制。

相關文章