天天看點

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

前言

建議閱讀本文之前,你對ARM元件的有個基本了解,本文會先為你介紹32位Linux環境中程序的記憶體布局,然後再介紹堆棧和堆相關記憶體損壞的基本原理以及調試方法。

本文中使用的示例是在ARMv6 32位處理器上編譯的,如果你無法通路ARM裝置,可以點選這裡https://azeria-labs.com/emulate-raspberry-pi-with-qemu/建立自己的實驗環境并在虛拟機中模拟Raspberry Pi發行版。這裡使用的調試器是GDB(GDB增強功能)。如果你不熟悉這些工具,可以點選這裡https://azeria-labs.com/debugging-with-gdb-introduction/,檢視如何使用GDB和GEF進行調試。

程序的記憶體布局

每次啟動程式時,都會保留該程式的記憶體區域,然後再将該區域分割成多個區域。是以我感興趣的部分是:

1.程式映像

2.堆

3.棧

在下圖中,我可以看到這些部分是如何在程序記憶體中被布置的。用于指定記憶體區域的位址會根據環境的不同而不同,特别是在使用ASLR時,我在本文中也僅僅是舉一個例子進行說明:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

程式映像區基本上都是儲存加載到記憶體中的程式可執行檔案,這個記憶體區域可以分為多個段:.plt,.text,.got,.data,.bss等,這些是最相關的。例如,.text包含程式的可執行部分,其中包含所有的彙編指令.data和.bss儲存應用程式中使用的變量或指針,.plt和.got存儲各種導入函數的特定指針,用于共享庫。從安全的角度來說,如果攻擊者進行了.text部分的完整性重寫,就可以執行任意代碼。同樣,過程連結表(.plt)和全局偏移表(.got)的損壞也可能在特定情況下導緻執行任意代碼。

應用程式使用棧和堆區域來存儲和操作在執行程式期間使用的臨時資料或變量,這些區域通常被攻擊者利用,因為棧和堆區域中的資料通常可以通過使用者的輸入修改,如果不能正确處理,可能會導緻記憶體損壞,我将在本文後面說明這種情況。

除了記憶體映射之外,我還需要了解與不同記憶體區域相關聯的屬性。存儲區域的屬性可以是以下屬性之一,也可以是它們之間的随意組合:Read, Write, eXecute。

Read屬性允許程式從特定區域讀取資料,同樣,Write屬性允許程式将資料寫入特定的存儲器區域,并執行該存儲區域中的指令。我可以看到GEF中的程序記憶體區域(GDB強烈推薦的擴充名)如下所示:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...
Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

vmmap指令輸出中的堆區(Heap section)僅在使用了一些堆相關功能後才會出現,這樣我就看到了malloc函數用于在堆區域中建立的一個緩沖區。是以如果你想嘗試這個,你需要調試一個使malloc調用的程式。

另外,在Linux中,我可以通過通路程序特定的檔案來檢查程序的記憶體布局:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

大多數程式的編譯方式是使用共享庫,這些庫不是程式映像的一部分(即使可以通過靜态連結來包含它們),是以必須動态地引用。我看到在程序的記憶體布局中加載的庫(libc,ld等)。大緻來說,共享庫被加載到記憶體中的某個位置(在程序控制之外),由于為了節省記憶體,我的程式隻是為該記憶體區域建立虛拟的“連結”,而無需在程式的每個執行個體中加載相同的庫。

引入記憶體損壞

記憶體損壞是軟體錯誤的一種形式,允許以程式員不想要的方式修改記憶體。在大多數情況下,可以利用此條件執行任意代碼,禁用安全機制等。這是通過制作和注入改變正在運作的程式的某些記憶體部分的有效載荷來完成的。以下清單包含最常見的記憶體損壞類型或漏洞:

1.  緩沖區溢出

1.1 棧溢出

1.2 堆溢出

2. 懸垂指針

3. 格式化字元串

在本文中,我将嘗試使用熟悉的緩沖區溢出記憶體損壞漏洞的基礎知識。在我将要介紹的例子中,記憶體損壞漏洞的主要原因是不正确的使用者輸入驗證,有時它會與邏輯缺陷相結合。程式輸入或惡意有效載荷可能以使用者名,要打開的檔案,網絡資料包等形式出現,并且通常可能受到使用者的影響。如果程式員沒有對潛在有害的使用者輸入采取安全措施,那麼目标程式通常會遇到與記憶體有關的漏洞。

緩沖區溢出

緩沖區溢出是一種非常普遍、非常危險的漏洞,在各種作業系統、應用軟體中廣泛存在。利用緩沖區溢出攻擊,可以導緻程式運作失敗、系統當機、重新啟動等後果。更為嚴重的是,可以利用它執行非授權指令,甚至可以取得系統特權,進而進行各種非法操作。

緩沖區溢出通常是由程式設計錯誤引起的,允許使用者提供比可用的目标變量更多的資料。例如,當使用易受攻破的函數(如gets,strcpy,memcpy或其他)以及使用者提供的資料時,就會發生這種情況。這些函數不但不會檢查使用者資料的長度,還可能導緻寫入過去配置設定的緩沖區。為了更好地了解,我的研究将基于棧和堆的緩沖區溢出。

棧溢出

棧溢出,顧名思義,是影響堆棧的記憶體損壞。雖然在大多數情況下,堆棧的任意破壞很可能會導緻程式崩潰,精心制作的棧緩沖區溢出可能會導緻任意代碼執行。下圖顯示了Stack如何破壞圖解:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

如上圖所示,棧架構(專用于特定函數的一小部分棧)可以具有各種元件:使用者資料,前棧幀指針(previous frame pointer),前連結寄存器(previous Link Register)等。如果使用者也提供了受控變量的大部分資料,FP和LR字段可能會被覆寫。這會打破程式的執行,因為使用者在目前函數完成後會破壞應用程式傳回或跳轉的位址。

要檢查它在實踐中的運作,我可以使用以下這個例子:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

我的示例程式使用的是長度為8個字元的變量緩沖區,使用者輸入的函數“gets”,它将變量緩沖區的值設定為使用者提供的任何輸入值,該程式的反彙編代碼如下所示:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

這裡我懷疑記憶體損壞可能會在函數擷取完成之後發生,為了驗證這一點,我在調用擷取函數的一個指令之後放置了一個中斷點,位址為0x0001043c。為了減少幹擾,我配置了GEF的布局,隻顯示代碼和棧(見下圖中的指令)。一旦設定了斷點,我将繼續執行程式,并以7 A作為使用者的輸入指令。之是以我使用7 A,是因為空位元組将被函數“gets”自動附加:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

當我驗證我示例的棧後,我看到棧架構并沒有被損壞。這是因為使用者提供的輸入符合預期的8位元組緩沖區,并且棧架構中的前FP和LR值不會被破壞。現在讓我試着輸入16 A,看看會發生什麼。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

在第二個例子中,可以看到,當我為函數“gets”提供太多的資料時,它不會停止在目标緩沖區的邊界,并且保持寫入“down the Stack”,這導緻我以前的FP和LR值被破壞。當我繼續運作程式時,會發生程式崩潰,因為在目前函數的結尾處,FP和LR的先前值會從堆棧“P”“R”和PC寄存器強制程式跳轉到位址0x41414140(由于切換到Thumb模式,最後一個位元組自動轉換為0x40),這就是非法位址。下圖顯示了崩潰時寄存器的值(看看$pc)。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

堆溢出

首先,堆是一個更複雜的記憶體位置,主要是因為它的管理方式與棧不同。為了讓說明變得簡,我要先聲明一個事實:放置在堆存儲部分中的每個對象都被打包成具有兩部分的“chunk”:頭和使用者資料(有時被使用者完全控制)。在堆的情況下,隻有當使用者能夠寫出比預期更多的資料時,才會發生記憶體損壞。在這種情況下,損壞可能發生在 塊的邊界内或超出兩個(或更多) 塊的邊界。比如下面的例子。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

如上圖所示,當使用者有能力向u_data_1提供更多資料并跨越u_data_1和u_data_2之間的邊界時,就會發生塊内堆溢出。這樣,目前對象的字段或屬性被破壞。如果使用者提供的資料比目前堆可容納的還要多,則就會從塊間溢出并導緻相鄰塊的損壞。

塊内堆溢出(Intra-chunk Heap overflow)

為了說明塊内堆棧溢出在實踐中如何運作,我可以使用下面的例子,并用“-O”(優化标志)來編譯一個較小的二進制程式,以友善大家檢視:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

上述程式會執行以下操作:

1.定義具有兩個字段的資料結構(u_data)

2.建立一個類型為u_data的對象(在堆記憶體區域)

3.為對象的數字字段配置設定一個靜态值

4.提示使用者為該對象的名稱字段提供一個值

5.根據數字字段的值列印字元串

是以在這5種情況下,我也懷疑在函數“gets”之後可能會發生損壞,于是我反彙編目标程式的主要函數來擷取斷點的位址:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

這樣,我就在函數”gets”完成之後設定位址0x00010498的斷點。由于我配置的GEF僅向我顯示代碼,是以我運作該程式并提供7A作為使用者輸入:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

一旦找到突破點,我就會快速查找程式的記憶體布局,以便找到其中的堆。我使用vmmap指令,看到我的堆從位址0x00021000開始。鑒于我的對象(objA)是程式建立的第一個也是唯一的,我從一開始就開始分析堆:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

上圖顯示了我分析堆的一些細節,該塊用一個頭(8位元組)和使用者資料部分(12個位元組)存儲我的對象。我看到名稱字段正确地存儲了提供的7 A的字元串,并由一個空位元組終止。數字字段存儲0x4d2(十進制為1234)。我會輸入8A,重複這些步驟,。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

在輸入8A再檢查堆時,我看到數字的字段已經損壞(現在是0x400而不是0x4d2)。空位元組終止符覆寫了該字段的一部分(最後一個位元組)。這将導緻塊内堆記憶體損壞。不過,在這種情況下,這種損壞的影響并不是毀滅性的,而是可預測的。在邏輯上, else語句并不能達到代碼,因為數字的字段是靜态的。然而,我剛剛觀察到的記憶體損壞卻可以使得else語句達到該代碼。這可以通過下面的示例容易地确認:

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

塊間堆溢出(Inter-chunk Heap overflow)

為了說明一個塊之間的堆溢出在實踐中如何運作,在下面的例子,我可以不适用優化标志(optimization flag)來編譯。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

上圖的過程類似于以前的過程,即在函數”gets”之後設定一個斷點,運作程式,提供7 A,最後調查堆。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

一旦找到突破點,我就能檢查堆。在這種情況下,我有兩個塊,如下圖所示,some_string在它的邊界内,some_number等于0x4d2。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

現在,讓我來試試16 A,看看會發生什麼。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

你可能已經猜到,提供太多的輸入會導緻溢出并發生相鄰塊的損壞。 在這種情況下,确實,經過驗證,我看到我的使用者輸入損壞了頭部和some_number字段的第一個位元組。 被破壞後,我可以達到代碼部分的some_number,但是按着邏輯,不應該達到這個代碼段。

Linux如何釋放棧的記憶體,Linux環境中堆棧和堆相關記憶體損壞的基本原理和調試方法介紹 - 嘶吼 RoarTalk – 回歸最本質的資訊安全,網際網路安全新媒體,4hou.com...

總結

讀完本文,你應該會熟悉程序記憶體布局和堆棧相關記憶體損壞的基礎知識, 在下一篇中,我會繼續介紹其他記憶體損壞,比如懸垂指針和格式化字元串。