Egg Hunter Shellcode – SLAE Exam Assignment #3

Assignment #3 for the SLAE exam is to write an egg hunter shellcode.

Egg Hunter Shellcode - Introduction

First, the requirements for assignment #3 were as follows.

Study about the Egg Hunter shellcode

Create a working demo of the Egghunter

Should be configurable for different payloads

Other than that, let's jump right in!

Egg Hunter Introduction

I cannot cover egg hunters as well as the original paper or other sources. That said, I wanted to touch on what they are, and what they might be used for.

An egg hunter is a small stager that searches a program's memory for a series of bytes (the "egg"). Once it finds this egg, it jumps to that location.

This is useful when the space for your first payload is smaller than your final payload. In this case, you can store the desired payload in memory, and then use the egg hunter to search for it. This egg + payload combination could live on the stack, heap, or somewhere else entirely. You do not need to know where, as the egg hunter can search it all.

I also used the following references to brush up on Linux program memory:

Egg Hunter Shellcode - Initial Version

I copied my first version from the Safely Searching Process Virtual Address Space white paper. I highly recommend reading through this, as it will do a far better job than me at explaining egg hunters.

Also, for more information on very small egg hunters, I recommend the following paper.

First, I took the shellcode from my Hello World example and added it to my shellcode wrapper. Additionally, I added in the egg hunter code. Finally, I changed the call to the egg hunter shellcode, and used memcpy to put my shellcode somewhere on the stack.

In this case, the egg hunter shellcode will execute, search for my shellcode, and then jump to it.


unsigned char egghunter[] = \

unsigned char code[] = \
"\x90\x50\x90\x50\x90\x50\x90\x50" // EGG - [NOP, PUSH eax] x4

	char *buffer;
	buffer = malloc(strlen(code));
	memcpy(buffer, code, strlen(code));

	printf("\nEgg Hunter Length: %d\n", strlen(egghunter));
	printf("Shellcode Length: %d\n\n", strlen(code));

	int (*ret)() = (int(*)())egghunter;



If you do not feel like disassembling it, you can find the original egg hunter assembly below.

or cx,0xfff
inc ecx
push 0x43
pop eax
int 0x80
cmp al, 0xf2
je 0x804a040 
mov eax, 0x50905090
mov edi, ecx
jne 0x804a045 
jne 0x804a045 
jmp edi

Initial Execution

With version 1 of my egg hunter complete, it was time to compile and execute it.

doyler@slae:~/slae/_exam/egghunter$ gcc -o shellcode -z execstack -fno-stack-protector shellcode.c 
doyler@slae:~/slae/_exam/egghunter$ ./shellcode 

Egg Hunter Length: 30
Shellcode Length: 51

Hello World!

As you can see, everything worked perfectly, and I was left with a 30 byte egg hunter!

Egg Hunter Shellcode - Optimizations and Modifications

While my demo technically meets all 3 requirements, I don't feel like I've done enough yet.

In this case, I'm going to make some changes to the original egg hunter. While I do not think I can improve on the length or speed of the code, I will improve the robustness.

My goal is to create an egg hunter shellcode that still works, uses the sigaction system call, has some integrity checks, and is still under 60 bytes.

As specified by the original author, the sigaction egg hunter has the following con.

  • If the direction flag is set, the payload will likely fail. This problem can be averted by adding a cld instruction should the case arise that it is needed, but more than likely it will not be necessary.

While this is a rare occurrence, it is still more likely than the other 2 potential issues.

In this case, adding one more byte shouldn't make a huge difference.

Next, I want to use a different egg. While this isn't a major issue, there is the minuscule chance that my egg could exist in a binary that I'm exploiting. For example, the author could just add those instructions as a defense mechanism to defeat copy and paste egg hunters. Additionally, if executed, this egg hunter could cause some minor stack corruption.

With this in mind, I decided to go with the following "egg":


50                      push   eax
58                      pop    eax
53                      push   ebx
5b                      pop    ebx

Finally, I also wanted to add an integrity check. For this, I used dijital1's simple egg hunter checksum routine.

Checksum Calculation

I will also be using a checksum byte in my shellcode. I'll cover where this comes from in the next section, but I wanted to share the code to calculate it now.

The code below is pretty straight forward, and will calculate the checksum byte for a provided payload. I calculate this by adding all the bytes together, and then performing an AND operation with FF. This will get the last 2 bytes of the total as a "checksum" byte.

While there is a 1/255 potential for collision, it is still better than nothing at all.

payload = "\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x31\xd2\x52\x52\x6a\x0a\x68\x72\x6c\x64\x21\x68\x6f\x20\x57\x6f\x68\x48\x65\x6c\x6c\x89\xe1\xb2\x0d\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80"

chksum = 0

for byte in bytearray(payload):
    chksum += byte

print "Checksum byte = " + "\\x%0.2X" % (chksum & 0xff)

As you can see, it calculated a checksum byte of 0x66 for my execve payload.

doyler@slae:~/slae/_exam/egghunter$ python 
Checksum byte = \x66

Egg Hunter Shellcode - My Version

With those changes in mind, including implementing the checksum similarly to Metasploit, I wound up with the following assembly.

; Filename: egghunter.nasm
; Author: Ray Doyle (@doylersec)
; Website:
; Purpose: SLAE Exam Assignment #3 - Egg Hunter Shellcode (Linux/x86)

global _start            

section .text
    ;cld  ; Clear the direction flag, for when it is set

    or cx,0xfff  ; Perform page alignment

    inc ecx  ; Increment address

    push 0x43  ; sys_sigaction
    pop eax  ; Load syscall into EAX
    int 0x80  ; Execute sigaction()

    cmp al, 0xf2  ; Compare return value to EFAULT
    jz page_align  ; If EFAULT occured, go to the next page (re-align)

    mov eax, 0x5058535b  ; Egg value to compare to
    mov edi, ecx  ; Save address pointer in EDI
    scasd  ; Compare EAX (egg) to EDI (first 4 bytes)
    jnz page_inc  ; If no egg, go to the next page
    scasd  ; If first egg found, perform compare to EDI (second 4 bytes)
    jnz page_inc  ; If no second egg, go to next page

    push ecx  ; Save the current value of ECX (not included in original)
    xor ecx, ecx  ; Zero out the counter
    xor eax, eax  ; Zero out the accumlator

    add al, byte [edi+ecx]  ; Add the byte to running total
    inc ecx  ; Increment the counter
    ; Payload length = 43 (0x2b)
    cmp cl, 0x2b  ; cmp counter to egg_size  --  ECX if length >= 256
    jnz calc_chksum_loop    ;if it's not equal repeat
    cmp al, byte [edi+ecx]  ; cmp eax with 1 byte checksum
    pop ecx
    jnz page_inc  ; Increment page to continue searching (failed checksum)

    jmp edi

I then compiled and linked my new binary.

doyler@slae:~/slae/_exam/egghunter$ ./ egghunter[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!

First, I grabbed the shellcode string from my binary.

doyler@slae:~/slae/_exam/egghunter$ objdump -d ./egghunter|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

Next, I modified my original shellcode wrapper. In addition to the new egg hunter, I also put a few mangled versions of my shellcode on the heap. This would show the integrity check and only execute the proper shellcode.

I also added all the different shellcodes to the heap, and printed out the address. This would prove that the egg hunter works just fine, even with ASLR enabled.


// EGG - PUSH eax, POP eax, PUSH ebx, POP ebx
#define EGG "\x5b\x53\x58\x50"

// Egg Hunter w/ integrity checking (51 bytes, 50 without first instruction (cld))
unsigned char egghunter[] = \

// Proper shellcode with improper checksum byte
unsigned char badcode1[] = \

// Improper shellcode (bytes missing from middle) with proper checksum byte
unsigned char badcode2[] = \

// Hello World shellcode with prepended eggs
unsigned char code[] = \

// Only one egg
unsigned char badcode3[] = \

    char *heap;
    heap = malloc(1000);

    // Idea from the RCEsecurity post -
    printf("\nMemory location of heap: %p\n", heap);

    memcpy(heap, badcode1, sizeof(badcode1));
    memcpy(heap + sizeof(badcode1), badcode2, sizeof(badcode2));
    memcpy(heap + sizeof(badcode1) + sizeof(badcode2), code, sizeof(code));
    memcpy(heap + sizeof(badcode1) + sizeof(badcode2) + sizeof(code), badcode3, sizeof(badcode3));

    printf("\nEgg Hunter Length: %d\n", strlen(egghunter));
    printf("Shellcode Length: %d\n\n", strlen(code));

    int (*ret)() = (int(*)())egghunter;



Since I was already familiar with how this operated, I didn't trace the execution in GDB.

That said, as you can see below. the payload works each time. This is in spite of the mangled shellcode(s) being loaded on the heap, as well as ASLR being enabled.

doyler@slae:~/slae/_exam/egghunter$ ./shellcode

Memory location of heap: 0x57a19160

Egg Hunter Length: 51
Shellcode Length: 52

Hello World!
doyler@slae:~/slae/_exam/egghunter$ ./shellcode

Memory location of heap: 0x56f59160

Egg Hunter Length: 51
Shellcode Length: 52

Hello World!
doyler@slae:~/slae/_exam/egghunter$ ./shellcode

Memory location of heap: 0x581a5160

Egg Hunter Length: 51
Shellcode Length: 52

Hello World!

Egg Hunter Shellcode - Execution

Also, in the end, I decided to remove my 'cld' instruction. I never ran into any scenarios where this was relevant, and it saved me one byte. This brought my shellcode down to the nice round number of 50 bytes.

Egg Hunter Shellcode - Conclusion

I learned a ton from this assignment, and it was fairly easy building off of Skape's original work.

Up next is a custom encoder/decoder, and I am looking forward to that as well.

I'm almost halfway through with this exam, but I'm looking forward to the end!

Finally, you can find the code and updates in my GitHub repository.

SLAE Exam Requirement

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:

Student-ID: SLAE-1212

doyler on Githubdoyler on Twitter
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Principal Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.