天天看點

ARM exploit編寫四

我們先了解記憶體布局有關知識。

每次我們啟動一個程式,都會為該程式保留一個記憶體區域。然後這個區域被分成多個區域。然後這些區域被分成更多的區域(segment),但是我們将堅持總體概述。是以,我們感興趣的部分是:程式鏡像,堆,棧。

在下圖中,我們可以看到這些部分在程序記憶體中是如何布局的一般表示。用于指定存儲區域的位址隻是為了舉例,因為它們會因環境而異,尤其是在使用ASLR時。

ARM exploit編寫四

程式鏡像區域基本上儲存程式的可執行檔案,該檔案被加載到記憶體中。 此記憶體區域可以拆分為各種段:.plt,.text,.got,.data,.bss等。 這些是最相關的。 例如,.text包含程式的可執行部分,包含所有程式集指令,.data和.bss包含變量或應用程式中使用的指向變量的指針,.plt和.got存儲指向各種導入函數的特定指針,用于共享庫等。 從安全角度來看,如果攻擊者可能影響.text部分的完整性(重寫),他可以執行任意代碼。類似地,過程連結表(.plt)和全局偏移表(.got)的損壞可能在特定情況下導緻執行任意代碼。

應用程式使用棧和堆區域來存儲和操作在程式執行期間使用的臨時資料(變量)。 這些區域通常被攻擊者利用,因為堆棧和堆區域中的資料通常可以通過使用者的輸入進行修改,如果處理不當,可能會導緻記憶體損壞。

除了記憶體映射之外,我們還需要了解與不同記憶體區域相關的屬性。 記憶體區域可以具有以下屬性中的一個或組合:Read,Write,execute。 Read屬性允許程式從特定區域讀取資料。 類似地,Write允許程式将資料寫入特定的存儲區域,并execute - 執行該存儲區域中的指令。

接下來了解下記憶體損壞有關知識。

記憶體損壞是一種軟體錯誤類型,允許以程式員不想要的方式修改記憶體。 在大多數情況下,可以利用這種情況來執行任意代碼,禁用安全機制等。這可以通過制作和注入payload來完成,該payload會改變正在運作的程式的某些記憶體部分。 最常見的記憶體損壞類型/漏洞包括: 緩沖區溢出:棧溢出,堆溢出;懸空指針(釋放後重利用);格式化字元串

在本實驗中,我們将嘗試熟悉緩沖區溢出記憶體損壞漏洞的基礎知識。 在我們即将讨論的示例中,我們将看到記憶體損壞漏洞的主要原因是使用者輸入驗證不正确,有時會與邏輯缺陷相結合。 對于程式,輸入(或payload)可以以使用者名,要打開的檔案,網絡包等形式出現,并且通常可以受到使用者的影響。 如果程式員沒有為可能有害的使用者輸入設定安全措施,那麼目标程式通常會遇到某種與記憶體相關的問題。

先來看看緩沖區溢出。

緩沖區溢出是最常見的記憶體損壞類之一,通常是由程式設計錯誤引起的,該錯誤允許使用者提供的資料多于目标變量(緩沖區)可用的資料。 例如,當易受攻擊的函數(如gets,strcpy,memcpy或其他函數)與使用者提供的資料一起使用時,就會發生這種情況。 這些函數不檢查使用者資料的長度,這可能導緻寫入過去(溢出)配置設定的緩沖區。 為了更好地了解,我們将研究基于堆棧和堆的緩沖區溢出的基礎知識。

這次我們要學習的是棧溢出。

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

ARM exploit編寫四

可以看到:棧幀(整個棧的一小部分專用于特定函數)可以具有各種元件:使用者資料,先前的幀指針,先前的連結寄存器等。如果使用者也提供了對于受控變量的大部分資料,FP和LR字段可能會被覆寫。 這會中斷程式的執行,因為使用者會破壞目前函數完成後應用程式傳回/跳轉的位址。

實驗用到的代碼如下:

ARM exploit編寫四

在stack.c

我們的示例程式使用變量“buffer”,長度為8個字元,以及一個函數“gets”用于使用者的輸入,它隻是将變量“buffer”的值設定為使用者提供的任何輸入。

生成可執行檔案後使用gdb調試

ARM exploit編寫四

檢視反彙編代碼

ARM exploit編寫四

在這裡,我們懷疑在“gets”函數完成後可能會發生記憶體損壞。為了研究這個問題,我們在調用“gets”函數的分支指令之後下一個斷點 。 在我們的例子中,位址為0x0001043c。

ARM exploit編寫四

為了降低其他無關資訊的幹擾,我們配置GEF的布局,隻向我們展示代碼和堆棧。

ARM exploit編寫四

一旦設定了斷點,我們繼續執行程式并提供7 個A作為使用者的輸入

ARM exploit編寫四

當我們研究我們的示例的棧時,我們看到(上圖)棧幀沒有被破壞。 這是因為使用者提供的輸入符合預期的8位元組緩沖區,并且棧幀中的先前FP和LR值不會被破壞。 現在讓我們輸入16個A,看看會發生什麼。

ARM exploit編寫四

在第二個例子中,我們看到(上圖)當我們為函數“gets”傳入太多資料時,它不會停留在目标緩沖區的邊界并繼續寫下去。 這會導緻我們之前的FP和LR值被破壞。 當我們繼續運作程式時,程式崩潰(導緻“segment fault”),因為在目前函數的結尾期間,FP和LR的先前值從堆棧“加載”到R11和PC寄存器,強制程式到跳轉到位址0x41414140(由于切換到Thumb模式,最後一個位元組自動轉換為0x40),在這種情況下是非法位址。 下圖顯示了崩潰時寄存器的值(注意到 pc此時為0x41414140)

ARM exploit編寫四

接下來我們看看堆溢出

首先,Heap是一個更複雜的記憶體位置,主要是因為它的管理方式。 為了簡單起見,我們要知道:放置在堆記憶體部分中的每個對象都“打包”成一個“塊”,它包含兩部分:header頭部和user data使用者資料(有時由使用者完全控制)。 在堆的情況下,當使用者能夠寫入比預期更多的資料時,會發生記憶體損壞。 在這種情況下,損壞可能發生在塊的邊界内(intra-chunk overflow),或跨越兩個(或更多)塊的邊界(inter-chunk overflow)。我們來看看下圖。

ARM exploit編寫四

如上圖所示,當使用者能夠向u_data_1提供更多資料并跨越u_data_1和u_data_2之間的邊界時,會發生intra-chunk overflow。 這樣,目前對象的字段/屬性就會被破壞。 如果使用者提供的資料多于目前堆塊可容納的資料,則溢出變為inter-chunk overflow并導緻相鄰塊的損壞。

先來看看intra-chunk 堆溢出

我們可以使用下面的示例。

ARM exploit編寫四

代碼在intra-chunk.c

程式主要做了:

  1. 定義一個具有兩個字段的資料結構(u_data)
  2. 在堆記憶體區域建立一個u_data類型的對象
  3. 為對象的number字段指定一個靜态值
  4. 提示使用者為對象的name字段輸入值
  5. 根據number字段的值列印字元串

    在這種情況下,我們還懷疑在函數“gets”之後可能發生損壞。

    我們先編譯

    ARM exploit編寫四
    我們反彙編目标程式的主函數來擷取斷點的位址。
    ARM exploit編寫四
    我們在位址0x10498處設定斷點—在gets函數完成之後,我們配置gef隻顯示代碼,然後運作程式并輸入7個A
    ARM exploit編寫四
    ARM exploit編寫四

一旦命中斷點,我們就查找程式的記憶體布局,找到堆的位置。我們使用vmmap指令,看到堆從位址0x00021000開始。鑒于我們的對象(objA)是程式建立的第一個也是唯一一個對象,我們從一開始就開始分析堆。

ARM exploit編寫四

下圖可以看到與我們的對象相關的Heap塊的詳細分解。第一個框是塊的header,第二個框是對象(u_data)

ARM exploit編寫四

塊有一個header(8個位元組)和user data(12個位元組)存儲我們的對象。 我們看到name字段正确存儲了提供的7個字元串,以空位元組結尾。Number字段存儲0x4d2(十進制1234)。讓我們重複這些步驟,但在這種情況下輸入8 A.

ARM exploit編寫四

然後檢查堆

ARM exploit編寫四

在檢查堆時,我們看到number的字段已損壞(現在它等于0x400而不是0x4d2)。空位元組終結符覆寫了數字字段的一部分(最後一個位元組)。 這會導緻塊内堆記憶體損壞。從邏輯上講,代碼中的else語句永遠不會到達,因為數字的字段是固定的。但是,我們剛剛觀察到的記憶體損壞使得可以到達代碼的else那一部分。如下所示

ARM exploit編寫四

接下來我們看看inter-chunk 堆溢出

我們使用下面的程式,在編譯時不用優化标志

ARM exploit編寫四

代碼在inier-chunk.c

ARM exploit編寫四

還是同樣的流程

ARM exploit編寫四

在gets函數後下斷點,run輸入7個A

ARM exploit編寫四
ARM exploit編寫四

命中斷點後,來觀察下堆

注意,在這個例子中我們有兩個塊。我們看到(下圖)它們的結構是完整的:some_string(輸入的7個A)在其邊界内,some_number(程式寫好的1234)為0x4d2

ARM exploit編寫四

現在我們輸入16個A看看

ARM exploit編寫四

在上圖可以看到,提供過多的輸入會導緻溢出,進而導緻相鄰塊的損壞。 在這種情況下,我們看到我們輸入的過長的A損壞了header和some_number字段的第一個位元組。 在這裡,通過破壞some_number,我們設法到達邏輯上永遠不應該到達的代碼部分,即:在這種情況下轉向了else部分,輸出了memory corrupted。我們試試就知道了

ARM exploit編寫四

繼續閱讀