Photo by Kony Xyzx on Unsplash

Eggs? Hunting? What are we talking about?

In exploit development, an Egg is a full shellcode payload that usually has a nop sled at the beginning. The start of the payload (usually the nop sled) will have a particular signature that we will use to identify where our shellcode is placed in memory. Sometime you can’t know beforehand where is your shellcode going to be allocated in memory. So you can use the egg’s signature (usually the first 8 bytes of the payload) to find the exact place.

An Egg-Hunter is a piece of shellcode that searches for a particular signature in a process’ Virtual Address Space (a.k.a VAS) and jumps to the payload.

This is useful when dealing with a small input buffer. Imagine that we could place a large payload in a non-vulnerable buffer and then use the egg-hunter in a vulnerable buffer to search for the larger payload and execute it flawlessly.

Photo by Claudio Hirschberger on Unsplash

The general use-case would be:

1- We provide the shellcode with the actual payload we want to execute (a reverse shell, for example).

2- This shellcode will be placed in a random section of the VAS.

3- We then exploit a vulnerability and execute the egg-hunter shellcode.

4- The egg-hunter goes through the whole VAS searching for the larger payload. Once it finds the payload it jumps into it.

5- The larger and final payload is executed. Giving us the ability to use a large payload that would be impossible to use otherwise.

In this example, we are using a single egg-hunter but take into consideration this technique could be chained multiple times to execute really large staged payloads.

Anatomy of an egg-hunter

Photo by Specna Arms on Unsplash

An egg-hunter should have the following properties:

1- It should be robust

It should be able to step into invalid memory regions without crashing the application. Therefore, it should somehow manage the dereference of invalid addresses and the exceptions occurred when, for example, it tries to read from a privileged/critical memory region.

2- It should be small

The main purpose of an egg-hunter is to fit where no other payload could. If we could fit our regular shell payload, why would we need this for? So, naturally, size is one of the most important aspects of an egg-hunter.

3- It should be fast

Waiting for several minutes while the egg-hunter searches for the payload would be a pain. This is certainly not the most important property for an egg-hunter, as we (as a general rule) don’t want to detriment robustness or size in order to make it faster.

Anatomy of an egg

The egg can be any payload of which we will take the first 8 bytes as the signature. In general, the first 8 bytes are part of a nop sled (to improve robustness) so we’ll focus on that kind.

Photo by Annie Spratt on Unsplash

; Signature sample 1 (8 bytes long )

; String representation "\x90\x50\x90\x50\x90\x50\x90\x50"

; Hexadecimal 32-bit 0x90509050 0x90509050

; Hexadecimal 64-bit 0x9050905090509050

; Assembly code: 90 nop

50 push rax

90 nop

50 push rax

90 nop

50 push rax

90 nop

50 push rax -- -- -- -- -- ; Signature sample 2 (8 bytes long )

; String representation "\x99\x99\x90\x99\x99\x99\x90\x99"

; Hexadecimal 32-bit 0x99999099 0x99999099

; Hexadecimal 64-bit 0x9999909999999099

; Assembly code: 99 cdq

99 cdq

90 xchg rax, rax ; same as nop

99 cdq

99 cdq

99 cdq

90 xchg rax, rax ; same as nop

99 cdq

1- Size

An egg usually is 8 bytes long. We, of course, can tweak this making it larger or shorter depending on the circumstances.

2- Type

The egg’s signature is usually executable code. This derives from the fact that once the egg-hunter confirms the match it jumps straight into its location and executes the whole egg. Again, that’s the usual case but an egg-hunter that allows non-executable code as a signature can be implemented too.

Implementing an egg-hunter in Linux/x64

Photo by Eamonn Maguire on Unsplash

In Linux, when a syscall encounters an invalid memory address, most of them will return the EFAULT error code into the RAX register. This is crucial, as it will be our way to know we stepped into the wrong place and it will be our key mechanism for making the egg-hunter robust. You can find more info about the error codes here. Let’s confirm this and gather the value of EFAULT in the process. In order to do that we can use this small assembly snippet that calls the access syscall for the address 0x00 which will fail.

BITS 64 global _start section .text _start:

mov rax, 0x15 ; Syscall access number

mov rdi, 0x00 ; Address to check : 0x00

mov rsi, 0x00 ; Mode

syscall

After having compiled and linked this we proceed with the test:

So, EFAULT is 0xfffffffffffffff2 (-14)

Now, let’s write our egg-hunter. We need to check every address to see if it’s valid for our process, if not, we can skip the whole page since a page is the minimum allocable unit in memory. In order to get the page size, we can use the getconf command. In my case, I’m using an ubuntu64 VM that yields: 4096

> getconf PAGE_SIZE

> 4096 This can be easily converted to hex using radare2 utility rax2 > rax2 (getconf PAGE_SIZE)

> 0x1000

Our egg-hunter will use the access syscall to check if the memory region is valid. If the page is valid then it will look for the egg. If that’s not the case it will jump to the next page.

Photo by Nicolas Thomas on Unsplash

Let’s test this out!

In the following video, I go through the whole process of extracting the shellcode for both the egg-hunter and the payload. Then I use a test skeleton written in C to demonstrate the functionality.

The payload I’m using is a custom password-protected reverse shell I wrote. You can find the source code within this blog post. The payload will connect back to tcp://127.0.0.1:4444 and will give a shell after the password “LETMEIN!” is introduced.

The source code for the egg-hunter tester written in C can be found here.