Introduction
A while back I introduced the EggSandwich in my tutorial on Egghunting as a means to implement some basic integrity checks into the traditional Egghunter and overcome the problem of fragmented / corrupted shellcode. I recently took the opportunity to update my implementation so it could accomodate shellcode of any size. The code and a brief explanation follows.
What is the EggSandwich?
I ran into a situation when developing an exploit for an application in which my shellcode was corrupted in multiple locations but left intact in one occurrence. Unfortunately, the corrupted versions of the shellcode appeared first in memory and the Egghunter never reached the valid payload.
It was a situation that definitely called for an Egghunter (as I could not reliably jump to the valid copy of my shellcode) but I needed to figure out a way for that Egghunter to be smart enough to know when it reached the valid payload and skip all of the rest.
When I wrote my tutorial on Egghunting one of the things I covered was dealing with corrupted shellcode. There are a few of different methods you can use, which include 1) changing the shellcode offset, 2) changing the starting memory page, and 3) using the Omelette Egghunter (breaking up your shellcode into smaller chunks. I won’t go over these again (feel free to check out that tutorial for more information) but changing the shellcode offset didn’t fix the problem and I didn’t feel like experimenting with smaller shellcode chunks due to the sheer number of copies the application put in memory.
Instead, I decided that a better approach would be to “sandwich” my shellcode between two egg tags. Along with those egg tags I included an additional byte that represented the total length of the shellcode. The Egghunter would use that byte to jump ahead and verify the presence of both tags before executing the shellcode (thereby avoiding shellcode fragments of invalid lengths). If the shellcode failed this integrity check, the Egghunter would move on to the next copy in memory (and so on) until an intact version was located.
My initial POC was limited in that it only used a single byte for the shellcode length, limiting it to less than 255 total bytes in length — not very accommodating for most payloads!
I decided to update that POC to accomodate shellcode of any length and I added an additional check to verify the order of tag discovery (to prevent a false positive should the second egg be found first). This updated version of the EggSandwich Egghunter is described in the following sections.
How Does It Work?
Let’s assume you have a shellcode payload of length 510 bytes and you’re using an egg tag of PWND. Your payload would look as follows:
Let’s quickly walk through each of the eggs so you understand their purpose.
Egg 1
Here I prepend the shellcode with the first egg tag (PWNDPWND) as well as a series of bytes that represents the size of the shellcode or the offset to egg 2 (also taking into account the five bytes that make up the offset tags). For example, in a scenario where your shellcode is 510 bytes in size, the offset bytes would be 0xff + 0xff + 0x05 (or 255 + 255 + 5 = 515). These bytes are sandwiched between two tag identifiers (0x1) so the EggSandwich Egghunter knows when to start and stop processing them.
When the Egghunter finds the first tag, it checks the byte immediately following. If it’s 0x1 it knows this is the first tag and the bytes that follow represent the size of shellcode, until it reaches another 0x1. For each offset length byte that it processes, it adds that value to a register holding the current address and increments an “integrity” counter (in register ECX). Assuming the shellcode is intact, once all of the offset bytes are processed, the register holding the shellcode location will now point to the second egg tag (PWNDPWND).
Egg 2
The Egghunter checks the register holding the updated address for the presence of the second egg and if it finds the correct tag (PWNDPWND), it checks the next byte (0x2) to verify its found the second egg and not the first. The byte that follows (0x3) indicates the total number of offset bytes from tag 1 (0xff, 0xff, 0x5). Since the Eggsandwich code from Egg #1 was incrementing a counter in ECX for each offset byte it encountered, if ECX matches the value, it can be sure that it is processing the eggs in order, at which point it executes the shellcode. If the Egghunter does not find the second egg immediately following the first, it clears ECX.
It is this latter check that provides an extra bit of integrity verification that it necessary if your shellcode becomes fragmented. For example, let’s say the Egghunter processes the first egg attached to a corrupted form of your shellcode and jumps to the updated address looking for the second egg. Because the shellcode is corrupted, it would not find the tag (PWNDPWND) and the next loop would clear the ECX register used for the integrity check. Now let’s say the next egg the Egghunter comes across in memory is the second one (due to another corrupted copy of shellcode). It would check the integrity byte against the value in ECX and fail the check, at which point it would move on to the next egg and skip the corrupted payload altogether.
Here is a generalized flow diagram so you get a high-level idea of how it works before diving into the code:
The Code
Here’s a look at the assembly:
entry: loop_inc_page: or dx,0x0fff ; add PAGE_SIZE - 1 to EDX to get the last address in the page loop_inc_one: inc edx ; increment EDX by 1 to get current address check_memory: push edx ; save current address to stack push 0x02 ; push Syscall for NtAccessCheckAndAuditAlarm to stack pop eax ; pop syscall parameter into EAX for syscall int 0x2e ; issue interrupt to make syscall cmp al,0x5 ; compare low order byte of eax to 0x5 (indicates access violation) pop edx ; restore EDX from the stack je loop_inc_page ; if zf flag = 1, access violation, jump back to loop_inc_page xor ecx,ecx ; clear counter register for check egg function mov edi, edx ; move current address into edi for use in scasd instruction check_egg: mov eax,0x444e5750 ; valid address, move egg value (PWND) into EAX for comparison scasd ; compare value in EAX to dword value addressed by EDI ; increments EDI by 4 if DF flag is 0 or decrements if 1 jnz loop_inc_one ; egg not found, jump back to loop_inc_one scasd ; first half of egg found, compare next half jnz loop_inc_one ; only first half found, jump back to loop_inc_one found_egg: mov esi,edi ; first egg found, move start address of shellcode to ESI for LODSB xor eax, eax ; clear EAX for add/sub instructions lodsb ; loads egg number (1 or 2) into AL cmp al,0x1 ; determine if this is the first egg or second egg lodsb ; this will either load the offset (first egg) or chunk count (second egg) into AL jz first_egg ; if first egg, go to first_egg jmp second_egg ; second egg found, go to second_egg first_egg: inc ecx ; increment egg counter to represent first egg add edi, eax ; increment EDX by size of shellcode to point to 2nd egg for next check_egg lodsb ; loads egg number check into AL cmp al,0x1 ; determine if this is the end of the first egg jnz first_egg ; still more offsets left in first egg; jump back to beginning of first_egg mov ebx,esi ; move start of shellcode into EBX for second_egg jmp check_egg ; end of first egg, jump back to check_egg to search for second egg second_egg: cmp al,cl ; check if egg2 chunk count matches cl counter jnz check_egg ; if not, egg2 likely found first, indicating shellcode fragment jmp ebx ; otherwise, execute shellcode
Here’s a copy of some example C code you can compile so you can see exactly how this works in your debugger.
Also, here is a perl script to generate the EggSandwich shellcode to use in your own exploits:
The Drawbacks
There are a few drawbacks to using the EggSandwich, making it unnecessary if you don’t require some form of integrity check in your Egghunter-based exploit.
Size
The first and most obvious drawback of using the EggSandwich is its size. At 62 bytes, it is almost twice the size of Matt Miller’s original 32-byte Egghunter. There are also the additional bytes appended and prepended to the shellcode, which will add around 14 additional bytes to your payload, depending on its size. That said, 62 bytes is still relatively small and if you have a buffer large enough (but still too small for a 500+ byte exploit payload) and need to implement some form of integrity check, you may find this useful.
Assumptions
Second, the integrity check is not foolproof. As you will notice in the Assembly presented in the previous section, several of the integrity checks make assumptions in the interest of keeping the EggSandwich as small as possible. For example, if the first byte after the Egg tag is 0x1, it knows it has found the first egg otherwise it assumes it has found the second. There is no check to validate that that byte value is 0x2, just that it’s not 0x1. The integrity byte will help in this situation, though it is also assumed that if it matches ECX, the shellcode is intact. If that integrity check byte becomes corrupted and changed to 0x0, it may lead to a false positive detection. If you really needed to address this issue, you could modify the second egg check as follows:
second_egg: cmp al,cl ; check if egg2 chunk count matches cl counter jnz check_egg ; if not, egg2 likely found first, indicating shellcode fragment xor eax, eax ; clear eax cmp cl,al ; check if egg2 chunk count is 0 jz check_egg ; 0 is an invalid value, jump back to check_egg jmp ebx ; otherwise, execute shellcode
Of course this adds to the length of the Egghunter so you would only need to make adjustments if its absolutely necessary.
Length Not Content
Third, this additional integrity check only validates original shellcode length, not its content. If bad characters are altered by an application in a one-to-one swap (vs character expansion), then the shellcode size will remain the same yet your exploit will not run. This only provides protection against shellcode that becomes split or partially saved in memory thereby altering its size in some way.
Conclusion
While not without drawbacks, if you need to implement some basic integrity checks into your Egghunter to prevent it from executing fragmented shellcode you may find the EggSandwich useful. Feel free to modify or make improvements to suit your needs!
Until next time,
Mike