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()
, whosefilename
pointer argument can be used for this test. It will reliably returnEFAULT
(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.
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.
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.
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
.
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.
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).
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.