二進制學習系列-堆溢出

Pwnable-UAF

這道題主要考察的是虛函數的內存地址空間以及UAF的使用

所需知識:

1.虛函數的內存地址空間:

在C++中,如果類中有虛函數,那麼它就會有一個虛函數表的指針__vfptr,在類對象最開始的內存數據中。之後是類中的成員變量的內存數據。 對於子類,最開始的內存數據記錄着父類對象的拷貝(包括父類虛函數表指針和成員變量)。 之後是子類自己的成員變量數據。

單繼承,無虛函數重載:

單繼承,有虛函數重載:

總結

  • 如果一個類中有虛函數,那麼就會建立一張虛函數表vtable,子類繼承父類vtable,若,父類的vtable中私有(private)虛函數,則子類vtable中同樣有該私有(private)虛函數的地址。注意這並不是直接繼承了私有(private)虛函數
  • 當子類重載父類虛函數時,修改vtable同名函數地址,改爲指向子類的函數地址,若子類中有新的虛函數,在vtable尾部添加。
  • vptr每個對象都會有一個,而vptable是每個類有一個,vptr指向vtable,一個類中就算有多個虛函數,也只有一個vptr;做多重繼承的時候,繼承了多個父類,就會有多個vptr

詳情知識請移步:https://bbs.pediy.com/thread-224651.htm

2.Use-After-Free

Dangling pointer:

指向被釋放的內存的指針。

成因:釋放掉後沒有將指針重置爲NULL.簡單來說就是因爲分配的內存釋放後,指針沒有因爲內存釋放而變爲NULL,而是繼續指向已經釋放的內存.

UAF:

對上面所說的指針進行利用,引用到自己想引用的函數上等等。

3.SLUB:

SLUB:系統內存分配機制。

對對象類型沒有限制,兩個對象只要大小差不多就可以重用同一塊內存,而不在乎類型是否相同樣的話,同一個籠子既可以放雞,又可以放鴨。也就是說我們釋放掉sock對象A以後馬上再創建對象B,只要A和B大小相同(不在乎B的類型),那麼B就極有可能重用A的內存。SLAB差不多,只不過要求類型也要相同。

既然B可以爲任意對象類型,那我們當然希望選擇一個用起來順手的對象類型。至少要符合以下2個條件:

1.用戶可以控制該對象的大小

2.用戶空間可以對該對象寫入數據

Pwnable-UAF詳解:

源代碼:

快速瀏覽一遍過後我們可以觀察到主要的Human和Man、Woman三個類,Human是父類,其餘是繼承了的子類,並且兩個子類都重寫了父類中的introduce函數,我們還注意到了父類中的getshell私有函數,所以我們之後肯定會用到它。由前者的知識點我們可以明白,三個類中都有虛函數,所以每個類都有一個vtable表來存儲虛函數,並且兩個子類都繼承了父類的vtable表,並且也有父類私有虛函數的getshell虛函數。

所以我們可以想到利用子類的構造函數,來跟隨找出vtable,再利用getshell虛函數地址來繼續。

main函數中after那一段的作用是分配一段地址空間,我們可以利用已經被free的內存重新allocate一個可控的地址空間。

所以我們的思路是:

1.找到getshell虛表的地址

2.找到vtable的地址

3.重寫覆蓋vptr指針指向地址

4.free後再allocate得到可控地址

1.getshell虛表地址

由於我是本地自己重新把平臺上的cpp文件編譯了一遍,所以地址和平臺上環境地址會不一樣。(後來我才明白是因爲自己編譯cpp文件的時候所使用的參數不同的原因,比如gcc -g uaf.cpp -o uaf和不加-g是有區別的) 以上可以看見getshell虛函數在vtable中的地址爲0x4012ea,也可以在gdb中調試,來查看getshell地址。

2.vtable地址

找到man的構造函數

在0x401084處下斷點,用gdb調試

p /x $ebx的作用是打印出實例化man對象的地址,而後查看man對象的內存地址空間,因爲虛表指針在首部,所以我們找到了虛表的地址是0x401668

3.重寫覆蓋

我們首先得需要找到虛表指針引用introduce函數時候的偏移量:

我們可以大致推測出v12和v13是同一個vptr指針,偏移+8後剛好是getshell地址+8後的introduce函數地址,所以我們可以開始利用,把vtable表的地址-8,即把vptr指針指向的地址-8時,就可以在程序運行use段時引用introduce函數的時候實則引用的是getshell函數。

4.allocate

可以看到原來man對象分配到的空間是0x30,即48字節,所以當我們再次分配的時候也要分配48字節,保證自己拿到的是原先被free掉的地址空間。

利用:

由於先free掉的是m,所以當我們分配第一次的時候得到的是w所指向的空間,所以我們需要分配兩次得到m所指向的空間再來利用。 因爲這題是從文件中讀出內容覆蓋,所以我們可以使用python -c來寫入轉變成不可見字符(由於我試過直接在文檔裏面寫十六進制的地址沒法被讀取,所以才明白要轉變成不可見的字符)。

程序在case2中讀取數據的填充到data空間的時候,開始的八字節就是vtable。之後是類的數據。(因爲geshell表+8字節後就是introduce表,所以推測讀取的地址爲8字節一個段)

0x401668-0x8=0x401660 python -c ' print "\x60\x16\x40\x00\x00\x00\x00\x00" ' > /tmp/exp ./uaf 48 /tmp/exp

getshell

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章