Please consider donating: https://www.corelan.be/index.php/donate/


15,307 views

Hack Notes : Ropping eggs for breakfast

Introduction

I think we all agree that bypassing DEP (and ASLR) is no longer a luxury today. As operating systems (such as Windows 7) continue to gain popularity, exploit developers are breakfast1forced to deal with increasingly more memory protection mechanisms, including DEP and ASLR. From a defense perspective, this is a good thing. But we all know there are ways around it when the stars and moon are properly aligned.

One of the most popular techniques used to bypass DEP is ROP (also called "code reuse"). As explained in one of my previous articles, the concept behind this technique is that you would chain a series of pointers together, each pointing to a series of already existing executable instructions that will lead to setting up the arguments of a function that would allow you to disable DEP or bypass it.

After the rop chain has set up the arguments and after the function gets called, you should be able to execute your shellcode.

The most important functions to do this on Windows 7 are :

  • VirtualProtect (change the protection of a given region)
  • VirtualAlloc + a function to copy or move shellcode into the newly allocated region. Depending on the type of payload, one of the following functions should allow you to do this : strcpy(), strncpy(), memcpy(), memmove(), etc

With a few exceptions, that’s about it.  NtSetInformationProcess() and SetProcessDEPPolicy() are no longer usable on Windows 7.

For the sake of this "hack note" post, I’ll assume that you know how to build your ROP chain, how to pick up a reliable pointer to those functions and how to execute your shellcode. If you don’t, check the tutorial or one of the "Corelan Live" training sessions at a con near you :)

Today, I will elaborate more on the use of an egghunter in a DEP bypass exploit.

What if you have to use an egghunter ?

As explained in the aforementioned tutorial, executing an egghunter is not different than executing other shellcode. However there is an additional issue we need to solve.

As you can see in the screenshot below, by default, immediately after the eggunter has located the egg (= your real payload) in memory, it will simply jump to that egg. (jmp edi)

image

With DEP enabled, this will most likely fail (unless you accidentally marked the egg as executable too, or if you are on an older OS & you are in the very fortunate position to be able to use NtSetInformationProcess() or SetProcessDEPPolicy())

Furthermore, it’s very likely that, since you have to use an egghunter, your payload size is limited too.  That means we have to solve the issue as efficient as possible (as small as possible).

In the tutorial, I explained how you can add a custom routine to the egghunter (before jumping to the payload), which would dynamically find a pointer to VirtualProtect() and eventually call it to mark the discovered egg as executable.  Although that code is generic and dynamic, it’s quite big.  It works, but it’s not efficient enough.

So I decided to take a shortcut and modified the egghunter implementation in metasploit, allowing you to mark a found egg as executable, based on the following idea :

  • Since you already had to use a technique to mark the egghunter itself as executable, that means that you have been able to pick up a pointer to the required windows functions (virtualprotect(), virtualalloc(), strncpy(), memcpy() etc).
  • Since you already have a pointer, we can simply re-use it and call it again to mark the egg as executable.

The only thing you will need to do, in order to make this idea work, is to put the pointer to either virtualprotect(), or to one of the copy/move functions into a register prior to getting the egghunter to run.

As soon as the hunter has discovered the egg, it will take that pointer (by default from ESI), set up stack parameters, call the function, and execute the payload.

Since the original egghunter code was null byte free, I decided to make the added code null byte free as well, which means it’s slightly bigger.  But with a total of 20 extra bytes for the entire routine, it won’t hurt that much…

Metasploit usage

First of all, update your metasploit repository and verify that you are running revision 12637 or above.

This his how it works :

General

Creating an egg hunter in metasploit requires you to do the following things

  • use the Egghunter mixin in your module
  • create a hash array with the egg options (the options you want to pass on to the egghunter creation routine in Metasploit)
  • call the egghunter creation routine
  • place the hunter and egg in your payload

Including the mixin is as easy as including this statement at the top of your code :

include Msf::Exploit::Remote::Egghunter


Next, set the options :

badchars=""
eggoptions =
{
   :checksum => false,
   :eggtag => "W00T"
}

The additional options that will allow you to manipulate the way the DEP bypass technique works are :

Mandatory

:depmethod => "function"

where "function" can be one of the following :

  • "virtualprotect"
  • "copy"
  • "copy_size"

"Virtualprotect" will (obviously) call virtualprotect on the found egg, marking it as executable.  By default, it will take twice the size of the payload as size parameter (so self-modifying/decoding/growing shellcode will still run fine). You can overrule this by using the depsize parameter (see below).

"copy" will set up the arguments for a function that will copy data from an address, to another address, until a null byte is found.  It doesn’t really matter which function you use, as long as it takes 2 arguments : destination and source, and stops when a null byte is found.

"copy_size"will set up the arguments for a function that will copy a given amount of bytes from an address, to another address.  Again, it does not matter which function you tell it to use, as long as it takes 3 arguments : destination, source and size (nr of bytes to copy).  By default, it will use the payload length as size parameter.

In all cases, the routine will assume the function pointer is in ESI at the time the egg hunter starts to run (unless you’ve overruled this by using the depreg option (see below)). EDI will point at the begin of the shellcode after it has been prepared for execution (which means that you can use EDI, just like before, as bufferregister for your payload)

Optional

:depreg => reg

where reg is a valid register. When not specified, the code will assume the API pointer is in ESI. If you specify a register, the egghunter will start by moving the dword in that register into ESI.

:depsize => value

where value is the size (numeric value) to be used as size parameter for the function you want it to call. If you are using the "copy" depmethod, this option will be ignored. As explained earlier, if you omit this parameter it will use (payload.length * 2) when using the "virtualprotect" method, and payload.length when using the "copy_size" method.

:depdest => reg

where reg is a valid register.  This register indicates the location of the egghunter (so it can be used as the destination for a copy or move operation executed after the hunter has found the egg). This option only works with the "copy" or "copy_size" method and will prevent the egghunter from using a GetPC routine to find itself (= slightly shorter code)

 

VirtualProtect()

VirtualProtect() needs a pointer to virtualprotect in one of the registers (ESI by default).  The other parameters will be created at runtime.

badchars=""
eggoptions =
{
   :checksum => false,
   :eggtag => "W00T"
   :depmethod => "virtualprotect"
   :depreg => "esi"
}

When using the virtualprotect() function, this is what the produced egghunter will look like :

image

So, instead of jumping directly to edi, it will set up the stack arguments for virtualprotect and will call that function.  Since it already has a pointer to the shellcode in EDI, it can use that pointer for the lpAddress and returnTo parameters.

In order to make the code null byte free, I used a series of add instructions (doubling the value of the register every time). This, of course, makes the code somewhat longer. If null bytes are not an issue, you could edit the code and just push the desired value onto the stack.

 

Alloc + Copy_to_self()

This technique is based on the following concept :

Using a rop chain, you allocate executable memory and copy the egghunter to that location

the egghunter will locate the egg, copy the egg to self, thus overwriting itself, and the return to self (memory is executable already).

The code supports 2 types of copy/move operations : one that will copy until it sees a nullbyte, and one that will copy a given amount of bytes.

"copy"

If you allocated executable memory in your rop chain, transferred the egghunter to the new location and executed it, then you can re-use that memory. In the "copy" and "copy_size" modes, the discovered shellcode will be copied to the current location (overwriting the hunter itself). After the copy or move, the copy/move function will return to the copied/moved shellcode and execute it.

Let’s say we have a pointer to strcpy() in edi, and since we just copied the egghunter to our new (executable) memory location, we also have a pointer to that memory location in ebp.  The egghunter options will look like this :

badchars=""
eggoptions =
{
   :checksum => false,
   :eggtag => "W00T"
   :depmethod => "copy"
   :depreg => "edi",
   :depdest => "ebp"
}

The generated egghunter for this strcpy() will look like this :

image

First, the pointer to strcpy() is saved in ESI (so it can be called later on).  Until 100B001E we see the normal egghunter routine which will locate the double tag in memory. Next, the pointer to the egg (EDI) is pushed onto the stack.

We specified a depdest parameter (basically telling the hunter that it can copy the egg to the location referenced by that register. In our case this is EBP. The code pushes it on the stack twice (on time as "returnto" argument, one time as "destination" for the strcpy().  Finally, EBP is put in EDI too (so if your payload needs a bufferregister, you can still use EDI)

The copy/move will grab the shellcode and copy it, overwriting the hunter code (or any other location you told it to write to), and then jumps (returns) to it.

If you don’t specify the depdest option, the generated egghunter will contain a getPC routine in order to dynamically locate it’s own address (which will be the destination for the copy/move operation). The code will be slightly longer (7 bytes) and looks like this :

image

As explained earlier, the "copy" technique requires you to figure out a way to allocate RWX memory first and to copy the hunter into that region.  Make sure to allocate enough space so it would hold the egg as well.

 

"copy_size"

Very similar to the "copy" routine, this technique will prepare the arguments for a copy/move function which takes a source, a destination and the number of bytes to copy.

badchars=""
eggoptions =
{
   :checksum => false,
   :eggtag => "W00T"
   :depmethod => "copy_size"
   :depreg => "edi"
}

If no size is specified using the depsize option, the payload length is used. A null byte free routine is used to put the size in a register. If no depdest option is specified, the routine will obviously contain a getpc stub.

image

This routine will end up calling strncpy() with the following arguments :

image

The copy/move function will copy the egg on top of the hunter and finally execute it.

Note : make sure to allocate sufficient space to host the egg as well

That’s it !

 

Example

You can find an example on how to use the hunter here

Credits

Of course, Corelan Team, for giving me the inspiration and motiviation to go on, Lincoln for starting to write the msf module (see ‘Example’), and _sinn3r, for testing the modified hunter.

"Egg" image : digitalart / FreeDigitalPhotos.net


© 2011 – 2021, Peter Van Eeckhoutte (corelanc0d3r). All rights reserved.

One Response to Hack Notes : Ropping eggs for breakfast

Corelan Training

We have been teaching our win32 exploit dev classes at various security cons and private companies & organizations since 2011

Check out our schedules page here and sign up for one of our classes now!

Donate

Want to support the Corelan Team community ? Click here to go to our donations page.

Want to donate BTC to Corelan Team?



Your donation will help funding server hosting.

Corelan Team Merchandise

You can support Corelan Team by donating or purchasing items from the official Corelan Team merchandising store.

Protected by Copyscape Web Plagiarism Tool

Corelan on Slack

You can chat with us and our friends on our Slack workspace:

  • Go to our facebook page
  • Browse through the posts and find the invite to Slack
  • Use the invite to access our Slack workspace
  • Categories