Porting Metasploit Payloads to Ronin Payloads
This guide explains how to convert a Metasploit payloads into a Ronin payloads.
Table of Contents
- Class Definition
- Included Modules
- Metadata
- Options vs. Params
generate
vs.validate
/build
/prelaunch
/postlaunch
/cleanup
methods- Printing
- Packing
Class Definition
Metasploit
Every Metasploit payload is defined as a module named MetasploitModule
.
module MetasploitModule
# ...
end
Ronin
Ronin payloads are defined as classes that must:
- Require any classes or modules used.
- Be defined in the
Ronin::Payloads
namespace. - Have a unique
CamelCase
name similar to the file name. - Inherit from Ronin::Payloads::Payload or one of it’s sub-classes.
- Call
register
with the same name as it’s file name.
# payloads/my_payload.rb
require 'ronin/payloads/payload'
module Ronin
module Payloads
class MyPayload < Payload
register 'my_payload'
# ...
end
end
end
Included Modules
Metasploit
Since Metasploit payloads are defined as modules, they cannot inherit from
another base class. Instead, they must include in other Msf::Payload::
modules.
module MetasploitModule
include Msf::Payload::Adapter
include Msf::Exploit::Powershell
# ...
end
Ronin
Since Ronin payloads are defined as classes, they can inherit from specific Ronin::Payloads::Payload base classes which define different types of payloads.
require 'ronin/payloads/powershell_payload'
module Ronin
module Payloads
class MyPayload < PowerShellPayload
# ...
end
end
end
Additionally, ronin-payloads provides a set of modules that can be included to add additional functionality, such as working with binary data, using a build directory, bind shell or reverse shell functionality.
Cheat Sheet
Metasploit | Ronin |
---|---|
include Msf::Payload::Java |
class MyPayload < JavaPayload |
include Msf::Payload::JSP |
class MyPayload < JSPPayload |
include Msf::Payload::NodeJS |
class MyPayload < NodeJSPayload |
include Msf::Payload::Php |
class MyPayload < PHPPayload |
include Msf::Payload::Python |
class MyPayload < PythonPayload |
include Msf::Payload::Ruby |
class MyPayload < RubyPayload |
include Msf::Payload::Windows::Powershell |
class MyPayload < PowerShellPayload |
include Msf::Exploit::Powershell |
class MyPayload < PowerShellPayload |
Metadata
Metasploit
Metasploit payloads define their metadata in the initialize
method by
passing a large Hash of Strings:
module MetasploitModule
# ...
def initialize(info = {})
super(merge_info(info,
'Name' => 'Linux Command Shell, Reverse TCP Inline',
'Description' => 'Connect back to attacker and spawn a command shell',
'Author' => 'ricky',
'License' => MSF_LICENSE,
'Platform' => 'linux',
'Arch' => ARCH_X64,
# ...
))
end
end
Ronin
Ronin payloads define their metadata in the class body by calling class methods:
require 'ronin/payloads/payload'
module Ronin
module Payloads
class MyPayload < Payload
register 'my_payload'
summary 'A single sentence summary of the payload'
description <<~EOS
A longer multi-line or multi-paragraph description of the payload.
Bla bla bla bla.
EOS
author 'Author1'
author 'Author2', website: 'https://example.com',
email: 'author1@example2.com',
github: 'author2',
mastodon: 'https://example.com/@author2',
twitter: '@author2',
discord: '...'
references [
'https://example.com/url1',
'https://example.com/url2',
# ...
]
# ...
end
end
end
Additional metadata modules can be included to define additional metadata, such as Metadata::Arch or Metadata::OS. If the exploit class inherits from the ASMPayload or ShellcodePayload class, then those modules are already included by default.
require 'ronin/payloads/shellcode_payload'
module Ronin
module Payloads
class MyShellcode < ShellcodePayload
# ...
arch :x86_64
os :linux
# ...
end
end
end
See the arch, os, and os_version method documentation for further details.
Cheat Sheet
Metasploit | Ronin |
Name' => '...', |
summary '...' |
'Description' => %q{ ... }, |
description <<~DESC ... DESC |
'License' => MSF_LICENSE, |
N/A |
'Author' => [ 'John Doe', # discoverer ... ], |
author 'John Doe' ... |
'References' => [ [ 'URL', 'https://example.com/...' ], # ... ], |
references [ 'https://example.com/...', # ... ] |
'Platform' => 'Linux', |
include Metadata::OS # ... os :linux |
'Arch' => ARCH_X64, |
include Metadata::Arch # ... arch :x86_64 |
Options vs. Params
Metasploit
Metasploit payloads defines options using the register_options
within the
initialize
method. The register_options
method accepts an Array of one or
more options. The options are initialized using OptString
, OptBool
, etc,
option classes. The first argument is the option name in all uppercase.
The second argument is the option description. The first argument is the
optional default value for the option.
module MetasploitModule
# ...
def initialize(info = {})
# ...
# Register command execution options
register_options(
[
OptString.new('SHELL', [ true, "The shell to execute.", "/bin/sh" ]),
])
end
end
Ronin
Ronin payloads defines options using the param
method which can be called multiple times. The first argument is the
param name, in lowercase or snake_case
. The second argument is the
param type class. Additional keyword arguments
may define whether the param is required, it’s default value, and it’s
description.
require 'ronin/payloads/payload'
module Ronin
module Payloads
class MyPayload < Payload
# ...
param :shell, String, required: true,
default: '/bin/sh',
desc: 'The shell command to execute'
# ...
end
end
end
Note: if the payload class includes the
Mixins::BindShell or
Mixins::ReverseShell modules, then it
will automatically have host
and port
params.
Cheat Sheet
Metasploit | Ronin |
---|---|
OptString.new('FOO', ...) |
param :foo, String, ... |
OptInt.new('FOO', ...) |
param :foo, Integer, ... |
OptBool.new('FOO', ...) |
param :foo, Boolean, ... |
OptEnum.new('FOO', [true, '...', ['value1', 'value2', ...]) |
param :foo, Enum[:value1, :value2, ...] |
generate
vs. build
/prelaunch
/postlaunch
/cleanup
methods
Metasploit
Most Metasploit payloads will define their payload building logic in a
generate
method:
module MetasploitModule
# ...
def generate(opts = {})
opts[:arch] ||= module_info['AdaptedArch']
payload = super
cmd_psh_payload(payload, ARCH_X86, remove_comspec: true)
end
end
Note: some Metasploit payloads define their entire payload within the
metadata section in the initialize
method:
module MetasploitModule
# ...
def initialize(info = {})
super(merge_info(info,
# ...
'Payload' =>
{
'Offsets' =>
{
'LHOST' => [ 20, 'ADDR' ],
'LPORT' => [ 18, 'n' ],
},
'Payload' =>
"\x6a\x29" + # pushq $0x29
"\x58" + # pop %rax
"\x99" + # cltd
"\x6a\x02" + # pushq $0x2
# ...
}
))
end
end
Ronin
Ronin payloads define the payload’s logic in five main methods.
validate
- Perform any additional validations before building the payload.build
- Builds any payload string or file. Must set the@payload
instance variable with the built payload String.prelaunch
- Additional logic to execute before the exploit which is using the payload is launched.postlaunch
- Additional logic to execute after the exploit which is using the payload is launched.cleanup
- Perform any extra cleanup tasks after the payload is done being used.
require 'ronin/payloads/payload'
module Ronin
module Payloads
class MyPayload < Payload
# ...
def build
@payload = "..."
end
def cleanup
# ...
end
def prelaunch
# ...
end
def postlaunch
# ...
end
end
end
end
Note: the Mixins::BindShell or
Mixins::ReverseShell modules define
their own prelaunch
and postlaunch
logic to connect to the remote shell.
Metasploit | Ronin |
def generate(opts = {}) # ... end |
def build @payload = "..." end |
def initialize(info = {}) super(merge_info(info, # ... 'Payload' => { 'Payload' => "..." } )) end |
def build @payload = "..." end |
Printing
Metasploit
Metasploit provides a set of methods for printing status messages from an exploit:
print_status "Running the Javascript shell..."
# ...
print_warning("Unable to retrieve token")
# ...
print_error('Connection failed.')
# ...
Ronin
Ronin also provides a similar set of methods for printing status messages from a payloads:
- print_debug
- print_error
- print_info /
print_status
- print_negative /
print_failure
/print_bad
- print_positive /
print_success
/print_good
- print_warning
print_status "Sending request to /path ..."
response = http_get('/path')
if response.code == '200'
if response.body.include?('Success')
print_success "response indicates success! Proceeding ..."
else
print_failure "request failed! Trying again ..."
print_debug response.body
end
else
print_error "Did not receive a 200 response: #{response.code}"
end
Cheat Sheet
Metasploit | Ronin |
print_status("Message here") vprint_status("Message here")
|
print_info "Message here" /print_status "Message here"
|
print_good("Message here") vprint_good("Message here")
|
print_good "Message here" /print_success "Message here" /print_positive "Message here"
|
print_bad("Message here") vprint_bad("Message here")
|
print_bad "Message here" /print_failure "Message here" /print_negative "Message here"
|
print_warning("Message here") vprint_warning("Message here")
|
print_warning "Message here"
|
print_error("Message here") print_bad("Message here") vprint_error("Message here") vprint_bad("Message here")
|
print_error "Message here"
|
Packing
Metasploit
Metasploit payloads use Ruby’s built-in Array#pack method, which packs the contents of an Array using a binary format String:
pop_r3_ret = [0x00013cd0].pack('V')
Ronin
Ronin adds a it’s own pack
method to Array,
Integer, and Float
which can also accept a binary type Symbol name instead of a pack String:
pop_r3_ret = 0x00013cd0.pack(:uint32_le)
If the Ronin payload includes Mixins::Binary, then a pack method will be added, which packs a single value based on the payload’s defined arch and os.
require 'ronin/payloads/payload'
require 'ronin/payloads/metadata/arch'
require 'ronin/payloads/metadata/os'
require 'ronin/payloads/mixins/binary'
module Ronin
module Payloads
class MyPayload < Payload
include Metadata::Arch
include Metadata::OS
include Mixins::Binary
arch :x86_64
os :linux
def build
# ...
@payload = "..."
@payload << pack(:uint32, 0x12345678)
# ...
end
end
end
end
Cheat Sheet
Metasploit | Ronin |
---|---|
[i].pack('C') |
i.pack(:uchar) |
[i].pack('S') |
i.pack(:uint16) |
[i].pack('L') |
i.pack(:uint32) |
[i].pack('Q') |
i.pack(:uint64) |
[i].pack('c') |
i.pack(:char) |
[i].pack('s') |
i.pack(:int16) |
[i].pack('l') |
i.pack(:int32) |
[i].pack('q') |
i.pack(:int64) |
[i].pack('S<') |
i.pack(:uint16_le) |
[i].pack('L<') |
i.pack(:uint32_le) |
[i].pack('Q<') |
i.pack(:uint64_le) |
[i].pack('s<') |
i.pack(:int16_le) |
[i].pack('l<') |
i.pack(:int32_le) |
[i].pack('q<') |
i.pack(:int64_le) |
[i].pack('S>') |
i.pack(:uint16_be) |
[i].pack('L>') |
i.pack(:uint32_be) |
[i].pack('Q>') |
i.pack(:uint64_be) |
[i].pack('s>') |
i.pack(:int16_be) |
[i].pack('l>') |
i.pack(:int32_be) |
[i].pack('q>') |
i.pack(:int64_be) |
[f].pack('F') / [f].pack('f') |
f.pack(:float) / f.pack(:float32) |
[f].pack('D') / [f].pack('d') |
f.pack(:double) / f.pack(:float64) |
[f].pack('e') |
f.pack(:float_le) / f.pack(:float32_le) |
[f].pack('E') |
f.pack(:double_le) / f.pack(:float64_le) |
[f].pack('g') |
f.pack(:float_be) / f.pack(:float32_be) |
[f].pack('G') |
f.pack(:double_be) / f.pack(:float64_be) |
[i].pack('n') |
i.pack(:uint16_le) |
[i].pack('N') |
i.pack(:uint32_le) |
[i].pack('v') |
i.pack(:uint16_be) |
[i].pack('V') |
i.pack(:uint32_be) |
[...].pack('VVV') |
[...].pack([:uint32_le, 3]) |
[...].pack('V*') |
[...].pack(:uint32_le..) |
[...].pack('VS<C') |
[...].pack(:uint32_le, :uint16_le, :uchar) |