Pwn入門筆記(二)a little棧基礎知識

Wust-Mo0n5ea丶發表於2020-11-23

規則為先進後出的一片連續記憶體,棧底為記憶體高地址,棧頂為低地址。這裡先說一個小知識,指標存的地址,32位程式中佔4個位元組,而在64位中佔8個位元組。
在ida中棧結構為
在這裡插入圖片描述注意左邊不是它的地址。輸入值時從上到下進行填充,
在這裡插入圖片描述在底部,s為棧底指標,也就是bp。r是返回地址,這裡會直接跳轉到上面存的地址,也就是rop的原理。
而當你要計算從b輸入多少位元組才能把a位置之前填充滿,公式為

 b-a = 填充值

舉個例子,上圖中輸入s的左值為0x74,下邊指標s的左值為0x00,s在32位程式中佔4個位元組(圖中也有寫),所以如果我們要將返回地址覆蓋為system_addr,就在輸入的地方構造

payload = 'a' * (0x74-0x00) + 'a' * 0x4 + p32(system_addr)

再解釋一下,填充0x74-0x00個a,即將指標s之前的空間都填充滿,0x4個a填充指標s的位置,下一個即是返回地址。

函式呼叫

彙編中執行一個函式時,會發現它call 了一個地址(地址藍色高亮)
在這裡插入圖片描述雙擊進去,
在這裡插入圖片描述
會發現到了一個左邊全是plt的地方,這一段就叫做plt表,接著看,它又jmp了一個地址,再雙擊進去
在這裡插入圖片描述這裡就是got表,最終才指向函式實際存在的地址。
也就是說函式呼叫時是call ->plt表->got表,got表裡存的才是函式的真實地址。所以當找不到函式真實地址時,只需通過plt表找到got表,再將上面存的地址利用漏洞列印出來就行。

引數,返回,棧幀上的函式,棧溢位ROP

引數

在彙編中可以看到,有引數的函式在call之前就將引數進行壓棧,多個引數時,如呼叫一個這樣定義的函式void func(int a,int b,int c)時,會將引數壓棧,ida中棧是正放,但實際理解中,一般進行倒放,也就是棧底畫上邊。結構如下圖,

棧底-高地址->c
b
a
棧頂-低地址->

可以看到,因為越先進棧的越靠近棧底,所以引數中第一個進棧的是c,然後是b,a。

返回

func函式呼叫完需要返回主函式,那麼在壓入引數後,函式就會將返回地址進行壓棧,在棧上結構如下圖所示:

棧底-高地址->c
b
a
main函式中呼叫func後一條指令的地址
棧頂-低地址->

棧幀上的函式

當main函式呼叫函式時,會給函式開一個棧幀出來,現將前面說的引數和返回地址進行壓棧後,接著會壓入一箇舊的ebp指標,再接著壓入函式的內容,結構變為下圖:

棧底-高地址->c
b
a
main函式中呼叫func後一條指令的地址
舊的ebp
函式
棧頂-低地址->

棧溢位ROP後的函式引數定址

就拿剛剛講的 棧 那欄中裡舉的栗子,結合上圖,在ida中,棧底下的“s”即為舊的ebp,“r”即為返回地址,

payload = 'a' * (0x74-0x00) + 'a' * 0x4 + p32(system_addr)

所以當過量輸入產生棧溢位後,會發現垃圾字元從函式中溢位,填充了舊的ebp,並將返回地址改為了system的地址。
但是當棧中出現函式時,一定會在函式的上面(靠近棧底方向)放一個壓棧指標ebp,所以實際情況是這樣:

棧底-高地址->c
b
a
ebp
system的plt表地址
aaaa
aaaaaaaaaaaaaaa…
棧頂-低地址->

當執行到system時,system函式是需要引數的,此時他會自動去往上兩格的位置尋找引數(越過ebp),也就是a的地方,所以我們要執行/bin/sh的話,就需要在a處放入這個字串或者他的地址。繼續構造為

payload = 'a' * (0x74-0x00) + 'a' * 0x4 + p32(system_addr) + 'a' * 0x4 +p32(binsh_addr)

即可獲取shell。

相關文章