Linux 堆溢出漏洞利用之 unlink

0 前言

以前咱們深刻了解了glibc malloc的運行機制(文章連接請看文末▼),下面就讓咱們開始真正的堆溢出漏洞利用學習吧。說實話,寫這類文章,我是比較慫的,由於我當前從事的工做跟漏洞挖掘徹底無關,學習這部分知識也純粹是我的愛好,於週末無聊時打發下時間,甚至我最初的目標也僅僅是能快速看懂、復現各類漏洞利用POC而已…鑑於此,後續的文章大體會由兩種內容構成:1)各類相關文章的總結,再提煉;2)某些好文章的翻譯及拓展。本文二者皆有,主要參考文獻見這裏linux

1 背景介紹

首先,存在漏洞的程序以下:shell

圖片描述

在代碼[3]中存在一個堆溢出漏洞:若是用戶輸入的argv[1]的大小比first變量的666字節更大的話,那麼輸入的數據就有可能覆蓋掉下一個chunk的chunk header——這能夠致使任意代碼執行。而攻擊的核心思路就是利用glibc malloc的unlink機制。安全

上述程序的內存圖以下所示:數據結構

圖片描述

2 unlink技術原理

2.1 基本知識介紹

unlink攻擊技術就是利用」glibc malloc」的內存回收機制,將上圖中的second chunk給unlink掉,而且,在unlink的過程當中使用shellcode地址覆蓋掉free函數(或其餘函數也行)的GOT表項。這樣當程序後續調用free函數的時候(如上面代碼[5]),就轉而執行咱們的shellcode了。顯然,核心就是理解glibc malloc的free機制。wordpress

在正常狀況下,free的執行流程以下文所述:函數

PS:鑑於篇幅,這裏主要介紹非mmaped的chunks的回收機制,回想一下在哪些狀況下使用mmap分配新的chunk,哪些狀況下不用mmap?學習

一旦涉及到free內存,那麼就意味着有新的chunk由allocated狀態變成了free狀態,此時glibc malloc就須要進行合併操做——向前以及(或)向後合併。這裏所謂向前向後的概念以下:將previous free chunk合併到當前free chunk,叫作向後合併;將後面的free chunk合併到當前free chunk,叫作向前合併。spa

1、向後合併翻譯

相關代碼以下:指針

圖片描述

圖片描述

首先檢測前一個chunk是否爲free,這能夠經過檢測當前free chunk的PREV_INUSE(P)比特位知曉。在本例中,當前chunk(first chunk)的前一個chunk是allocated的,由於在默認狀況下,堆內存中的第一個chunk老是被設置爲allocated的,即便它根本就不存在。

若是爲free的話,那麼就進行向後合併:

  1. 將前一個chunk佔用的內存合併到當前chunk;

  2. 修改指向當前chunk的指針,改成指向前一個chunk。

  3. 使用unlink宏,將前一個free chunk從雙向循環鏈表中移除(這裏最好本身畫圖理解,學過數據結構的應該都沒問題)。

在本例中因爲前一個chunk是allocated的,因此並不會進行向後合併操做。

2、向前合併操做

首先檢測next chunk是否爲free。那麼如何檢測呢?很簡單,查詢next chunk以後的chunk的PREV_INUSE (P)便可。相關代碼以下:

圖片描述

整個操做與」向後合併「操做相似,再經過上述代碼結合註釋應該很容易理解free chunk的向前結合操做。在本例中當前chunk爲first,它的下一個chunk爲second,再下一個chunk爲top chunk,此時top chunk的 PREV_INUSE位是設置爲1的(表示top chunk的前一個chunk,即second chunk,已經使用),所以first的下一個chunk不會被「向前合併「掉。

介紹完向前、向後合併操做,下面就須要瞭解合併後(或由於不知足合併條件而沒合併)的chunk該如何進一步處理了。在glibc malloc中,會將合併後的chunk放到unsorted bin中(還記得unsorted bin的含義麼?)。相關代碼以下:

圖片描述

圖片描述

上述代碼完成的整個過程簡要歸納以下:將當前chunk插入到unsorted bin的第一個chunk(第一個chunk是鏈表的頭結點,爲空)與第二個chunk之間(真正意義上的第一個可用chunk);而後經過設置本身的size字段將前一個chunk標記爲已使用;再更改後一個chunk的prev_size字段,將其設置爲當前chunk的size。

注意:上一段中描述的」前一個「與」後一個「chunk,是指的由chunk的prev_size與size字段隱式鏈接的chunk,即它們在內存中是連續、相鄰的!而不是經過chunk中的fd與bk字段組成的bin(雙向鏈表)中的前一個與後一個chunk,切記!

在本例中,只是將first chunk添加到unsorted bin中。

2.2 開始攻擊

如今咱們再來分析若是一個攻擊者在代碼[3]中精心構造輸入數據並經過strcpy覆蓋了second chunk的chunk header後會發生什麼狀況。

假設被覆蓋後的chunk header相關數據以下:

  1. prev_size = 一個偶數,這樣其PREV_INUSE位就是0了,即表示前一個chunk爲free。

  2. size = -4

  3. fd = free函數的got表地址address – 12;(後文統一簡稱爲「free addr – 12」)

  4. bk = shellcode的地址

那麼當程序在[4]處調用free(first)後會發生什麼呢?咱們一步一步分析。

1、向後合併

鑑於first的前一個chunk非free的,因此不會發生向後合併操做。

2、向前合併

先判斷後一個chunk是否爲free,前文已經介紹過,glibc malloc經過以下代碼判斷:

圖片描述

PS:在本例中next chunk即second chunk,爲了便於理解後文統一用next chunk。

從上面代碼能夠知道,它是經過將nextchunk + nextsize計算獲得指向下下一個chunk的指針,而後判斷下下個chunk的size的PREV_INUSE標記位。在本例中,此時nextsize被咱們設置爲了-4,這樣glibc malloc就會將next chunk的prev_size字段看作是next-next chunk的size字段,而咱們已經將next chunk的prev_size字段設置爲了一個偶數,所以此時經過inuse_bit_at_offset宏獲取到的nextinuse爲0,即next chunk爲free!既然next chunk爲free,那麼就須要進行向前合併,因此就會調用unlink(nextchunk, bck, fwd);函數。真正的重點就是這個unlink函數!

在前文2.1節中已經介紹過unlink函數的實現,這裏爲了便於說明攻擊思路和過程,再詳細分析一遍,unlink代碼以下:

圖片描述

此時 P = nextchunk, BK = bck, FD = fwd。

  1. 首先FD = nextchunk->fd = free地址– 12;

  2. 而後BK = nextchunk->bk = shellcode起始地址;

  3. 再將BK賦值給FD->bk,即(free地址– 12)->bk = shellcode起始地址;

  4. 最後將FD賦值給BK->fd,即(shellcode起始地址)->fd = free地址– 12。

前面兩步還好理解,主要是後面2步比較迷惑。咱們做圖理解:

圖片描述

結合上圖就很好理解第3,4步了。細心的朋友已經注意到,free addr -12和shellcode addr對應的prev_size等字段是用虛線標記的,爲何呢?由於事實上它們對應的內存並非chunk header,只是在咱們的攻擊中須要讓glibc malloc在進行unlink操做的時候將它們強制看做malloc_chunk結構體。這樣就很好理解爲何要用free addr – 12替換next chunk的fd了,由於(free addr -12)->bk恰好就是free addr,也就是說第3步操做的結果就是將free addr處的數據替換爲shellcode的起始地址。

因爲已經將free addr處的數據替換爲了shellcode的起始地址,因此當程序在代碼[5]處再次執行free的時候,就會轉而執行shellcode了。

至此,整個unlink攻擊的原理已經介紹完畢,剩下的工做就是根據上述原理,編寫shellcode了。只不過這裏須要注意一點,glibc malloc在unlink的過程當中會將shellcode + 8位置的4字節數據替換爲free addr – 12,因此咱們編寫的shellcode應該跳過前面的12字節。

3 對抗技術

當前,上述unlink技術已通過時了(但不表明全部的unlink技術都失效,詳情見後文),由於glibc malloc對相應的安全機制進行了增強,具體而言,就是添加了以下幾條安全檢測機制。

3.1 Double Free檢測

該機制不容許釋放一個已經處於free狀態的chunk。所以,當攻擊者將second chunk的size設置爲-4的時候,就意味着該size的PREV_INUSE位爲0,也就是說second chunk以前的first chunk(咱們須要free的chunk)已經處於free狀態,那麼這時候再free(first)的話,就會報出double free錯誤。相關代碼以下:

圖片描述

3.2 next size非法檢測

該機制檢測next size是否在8到當前arena的整個系統內存大小之間。所以當檢測到next size爲-4的時候,就會報出invalid next size錯誤。相關代碼以下:

圖片描述

3.3 雙鏈表衝突檢測

該機制會在執行unlink操做的時候檢測鏈表中前一個chunk的fd與後一個chunk的bk是否都指向當前須要unlink的chunk。這樣攻擊者就沒法替換second chunk的fd與fd了。相關代碼以下:

圖片描述

4 另外一種unlink攻擊技術

通過上述3層安全檢測,是否意味着全部unlink技術都失效了呢?答案是否認的,由於進行漏洞攻擊的人腦洞永遠比天大!以前恰好看到一篇好文][15,主講在Android4.4上利用unlink機制實現堆溢出攻擊。衆所周知,Android內核基於linux,且其堆內存管理也是使用的glibc malloc,雖然在一些細節上有些許不一樣,但核心原理相似。該文介紹的攻擊方式就成功繞過了上述三層檢測。

5 總結

本文詳細介紹了unlink攻擊技術的核心原理,雖然上述介紹的unlink漏洞利用技術已經失效,而其餘的unlink技術難度也愈來愈大,可是咱們仍是有必要認真學習,由於它一方面能夠進一步加深咱們對glibc malloc的堆棧管理機制的理解,另外一方面也爲後續的各類堆溢出攻擊技術提供了思路。

從上文的分析能夠看出,unlink主要仍是利用的glibc malloc中隱式鏈表機制,經過覆蓋相鄰chunk的數據實現攻擊,那麼咱們能不能在顯示鏈表中也找到攻擊點呢?請關注下一篇文章:基於fastbin的堆溢出漏洞利用介紹。

附:Linux技術分析系列文章

做者:走位@阿里聚安全,更多安全技術文章,請訪問阿里聚安全博客

相關文章
相關標籤/搜索