/SLAE32: Assignment 4
# IntroductionFor assignment #4 I had to create a custom shellcode encoding sheme (encoder and decoder). I wrote my encoder script in Python3 and decoder of course in NASM.
# Encoding SchemeThe scheme I decided upon is fairly simple. I thought it would be cool to convert all bytes into lowercase alphabetic characters (a-z) and so that's what I did. In the ASCII table
0x7a (122)so there are definitely not enough to represent all 256 unique bytes. To solve this my scheme will encode each byte two letters: the first letter encodes the bytes first bit, and the second letter the second bit to give well over 256 possible combinations.
The exact steps are:
- XOR the byte with a set key. This is to provide a bit of extra 'randomness' so that a pattern isn't too obvious
- Split the byte into bits
0x61to each bit. This is not a random number, it is the hex value for a lowercase 'a' character. Since bits are from
0x61+bitwill never fall out of the range of the alphabet and so the total will always be the hexcode of some lowercase alphabetic character.
- These two sums (bytes now) are combined into a word and represent the encoded byte.
I created a flowchart to visualize the encoding process a bit better:
As I mentioned above I created an encoding script in Python3 which basically just calls this function on each byte and prints it out nicely:
The script spits out three lines: "LENGTH", "KEY" and "SHELLCODE". These are all lines which you (the user) need to copy and paste into
def encode_byte(b): b = b ^ key l = (b & 0xf0) >> 4 r = b & 0x0f l_enc = l + 97 r_enc = r + 97 return chr(l_enc) + chr(r_enc)
# DecoderThe decoder itself is fairly short, partly because I spent a bunch of time trying to find ways to combine commands to save space. The first thing that happens is a Jump-Call-Pop to figure out where the shellcode is in memory.
Call. The shellcode here is defined to be referenced later on.
; A4 - Custom Encoding Scheme ; William Moody ; PA-25640 ; 28.12.2021 global _start section .text _start: jmp short call_decoder
And Pop. I stored the pointer to the shellcode variable in
call_decoder: call decoder ; === PASTE SHELLCODE FROM ENCODER HERE === shellcode: db "haibpbcjbbcjgocdcadccjgocdcicpmikchaibbbmikdbcmikapbekimmb" ; =========================================
ECXacts as an offset from the beginning of the shellcode to read and write locations. I used one register for both purposes in order to shorten the decoder stub. I chose to use the full
ECXregister as opposed to just
CXso that the decoder can work for longer shellcodes.
Right after this, the decoder jumps into the main loop. The decoding process is just the reverse of the encoding, so we need to
decoder: pop esi ; ESI points to shellcode xor ecx, ecx ; ECX is the offset to write location, ; ECX*2 is the offset to read location
- Subtract 0x61 from both bytes (letters)
- Combine the two bits back into one byte
- XOR with the key to retrieve the original byte
Once the byte is decoded, it is written into the memory space of the shellcode variable. The offset variable
decode_loop: mov ax, word [esi+ecx*2] ; ah = shellcode[i], al = shellcode[i+1] sub ax, 0x6161 ; ah -= 97, ah -= 97 shl al, 0x4 ; al = 0xA -> 0xA0 add al, ah ; ah = 0xB -> al = 0xAB ; === PASTE KEY FROM ENCODER HERE === xor al, 0x41 ; al = 0xAB ^ key ; ===================================
ECXis compared with the length of the shellcode (
0x1din this case) to see if it has decoded all bytes yet or still needs to continue.
JNAEwill jump if
ECXis not above or equal to
0x1d. If all bytes are decoded, the decoder jumps into the shellcode variable and hands over control.
mov [esi+ecx], byte al ; shellcode[j] = al inc ecx ; Increment offset(s) to read/write locations ; === PASTE LENGTH FROM ENCODER HERE === cmp ecx, 0x1d ; ====================================== jnae decode_loop ; Loop if i < len(shellcode) jmp short shellcode ; Hand over control to decoded shellcode
# CompilingTo test it all out, I threw together a bash / Python3 script which:
- Compiles / Links the
- Dumps the shellcode of
- Pastes this shellcode into a shellcode runner c file
- Compiles the shellcode runner c file
objdumpdoes not work for some reason (for key values which are smaller than the length of the shellcode). One byte is missing from the output and it caused me a lot of confusion because it ends up decoding incorrectly and I assumed my decoder was incorrect and not the shellcode itself. So to remedy this I wrote a Python3 script which dumps shellcode and included it in the
# SLAE32 Exam StatementThis blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: PA-25640
All my code for the exam is available in my SLAE32 exam Github repository.