def loadPeIntoWorkspace(vw, pe, filename=None, baseaddr=None): mach = pe.IMAGE_NT_HEADERS.FileHeader.Machine arch = arch_names.get(mach) if arch is None: raise Exception("Machine %.4x is not supported for PE!" % mach) vw.setMeta('Architecture', arch) vw.setMeta('Format', 'pe') platform = 'windows' # Drivers are platform "winkern" so impapi etc works subsys = pe.IMAGE_NT_HEADERS.OptionalHeader.Subsystem if subsys == PE.IMAGE_SUBSYSTEM_NATIVE: platform = 'winkern' vw.setMeta('Platform', platform) vw.setMeta('DefaultCall', defcalls.get(arch, 'unknown')) # Set ourselves up for extended windows binary analysis if baseaddr is None: baseaddr = pe.IMAGE_NT_HEADERS.OptionalHeader.ImageBase entry = pe.IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint + baseaddr entryrva = entry - baseaddr codebase = pe.IMAGE_NT_HEADERS.OptionalHeader.BaseOfCode codesize = pe.IMAGE_NT_HEADERS.OptionalHeader.SizeOfCode codervamax = codebase + codesize fvivname = filename # This will help linkers with files that are re-named dllname = pe.getDllName() if dllname is not None: fvivname = dllname if fvivname is None: fvivname = "pe_%.8x" % baseaddr # grab the file bytes for hashing pe.fd.seek(0) bytez = pe.fd.read() fhash = v_parsers.md5Bytes(bytez) sha256 = v_parsers.sha256Bytes(bytez) # create the file and store md5 and sha256 hashes fname = vw.addFile(fvivname.lower(), baseaddr, fhash) vw.setFileMeta(fname, 'sha256', sha256) symhash = e_symcache.symCacheHashFromPe(pe) vw.setFileMeta(fname, 'SymbolCacheHash', symhash) # Add file version info if VS_VERSIONINFO has it try: vs = pe.getVS_VERSIONINFO() except Exception as e: vs = None vw.vprint('Failed to load version info resource due to %s' % (repr(e), )) if vs is not None: vsver = vs.getVersionValue('FileVersion') if vsver is not None and len(vsver): # add check to split seeing samples with spaces and nothing else.. parts = vsver.split() if len(parts): vsver = vsver.split()[0] vw.setFileMeta(fname, 'Version', vsver) # Setup some va sets used by windows analysis modules vw.addVaSet("Library Loads", (("Address", VASET_ADDRESS), ("Library", VASET_STRING))) vw.addVaSet('pe:ordinals', (('Address', VASET_ADDRESS), ('Ordinal', VASET_INTEGER))) # SizeOfHeaders spoofable... curr_offset = pe.IMAGE_DOS_HEADER.e_lfanew + len(pe.IMAGE_NT_HEADERS) secsize = len(vstruct.getStructure("pe.IMAGE_SECTION_HEADER")) sec_offset = pe.IMAGE_DOS_HEADER.e_lfanew + 4 + len( pe.IMAGE_NT_HEADERS.FileHeader ) + pe.IMAGE_NT_HEADERS.FileHeader.SizeOfOptionalHeader if sec_offset != curr_offset: header_size = sec_offset + pe.IMAGE_NT_HEADERS.FileHeader.NumberOfSections * secsize else: header_size = pe.IMAGE_DOS_HEADER.e_lfanew + len( pe.IMAGE_NT_HEADERS ) + pe.IMAGE_NT_HEADERS.FileHeader.NumberOfSections * secsize # Add the first page mapped in from the PE header. header = pe.readAtOffset(0, header_size) secalign = pe.IMAGE_NT_HEADERS.OptionalHeader.SectionAlignment subsys_majver = pe.IMAGE_NT_HEADERS.OptionalHeader.MajorSubsystemVersion subsys_minver = pe.IMAGE_NT_HEADERS.OptionalHeader.MinorSubsystemVersion secrem = len(header) % secalign if secrem != 0: header += "\x00" * (secalign - secrem) vw.addMemoryMap(baseaddr, e_mem.MM_READ, fname, header) vw.addSegment(baseaddr, len(header), "PE_Header", fname) hstruct = vw.makeStructure(baseaddr, "pe.IMAGE_DOS_HEADER") magicaddr = hstruct.e_lfanew if vw.readMemory(baseaddr + magicaddr, 2) != "PE": raise Exception("We only support PE exe's") if not vw.isLocation(baseaddr + magicaddr): padloc = vw.makePad(baseaddr + magicaddr, 4) ifhdr_va = baseaddr + magicaddr + 4 ifstruct = vw.makeStructure(ifhdr_va, "pe.IMAGE_FILE_HEADER") ohstruct = vw.makeStructure(ifhdr_va + len(ifstruct), "pe.IMAGE_OPTIONAL_HEADER") nxcompat = ohstruct.DllCharacteristics & PE.IMAGE_DLLCHARACTERISTICS_NX_COMPAT # get resource data directory ddir = pe.getDataDirectory(PE.IMAGE_DIRECTORY_ENTRY_RESOURCE) loadrsrc = vw.config.viv.parsers.pe.loadresources carvepes = vw.config.viv.parsers.pe.carvepes deaddirs = [ PE.IMAGE_DIRECTORY_ENTRY_EXPORT, PE.IMAGE_DIRECTORY_ENTRY_IMPORT, PE.IMAGE_DIRECTORY_ENTRY_RESOURCE, PE.IMAGE_DIRECTORY_ENTRY_EXCEPTION, PE.IMAGE_DIRECTORY_ENTRY_SECURITY, PE.IMAGE_DIRECTORY_ENTRY_BASERELOC, PE.IMAGE_DIRECTORY_ENTRY_DEBUG, PE.IMAGE_DIRECTORY_ENTRY_COPYRIGHT, PE.IMAGE_DIRECTORY_ENTRY_ARCHITECTURE, PE.IMAGE_DIRECTORY_ENTRY_GLOBALPTR, PE.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, PE.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, PE.IMAGE_DIRECTORY_ENTRY_IAT, PE.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, PE.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR ] deadvas = [ddir.VirtualAddress] for datadir in deaddirs: d = pe.getDataDirectory(datadir) if d.VirtualAddress: deadvas.append(d.VirtualAddress) for idx, sec in enumerate(pe.sections): mapflags = 0 chars = sec.Characteristics if chars & PE.IMAGE_SCN_MEM_READ: mapflags |= e_mem.MM_READ isrsrc = (sec.VirtualAddress == ddir.VirtualAddress) if isrsrc and not loadrsrc: continue # If it's for an older system, just about anything # is executable... # However, there is the DLLCHARACTERISTICS NXCOMPAT flag to take into account, # which works with the OS to prevent certain pages of memory from achieving # execution unless they're marked with the execute bit # so we can't just blindly mark these as executable quite yet. if not nxcompat: if not vw.config.viv.parsers.pe.nx and subsys_majver < 6 and not isrsrc: mapflags |= e_mem.MM_EXEC if chars & PE.IMAGE_SCN_MEM_READ: mapflags |= e_mem.MM_READ if chars & PE.IMAGE_SCN_MEM_WRITE: mapflags |= e_mem.MM_WRITE if chars & PE.IMAGE_SCN_MEM_EXECUTE: mapflags |= e_mem.MM_EXEC if chars & PE.IMAGE_SCN_CNT_CODE: mapflags |= e_mem.MM_EXEC secrva = sec.VirtualAddress secvsize = sec.VirtualSize secfsize = sec.SizeOfRawData secbase = secrva + baseaddr secname = sec.Name.strip("\x00") secrvamax = secrva + secvsize # If the section is part of BaseOfCode->SizeOfCode # force execute perms... if secrva >= codebase and secrva < codervamax: mapflags |= e_mem.MM_EXEC # If the entry point is in this section, force execute # permissions. if secrva <= entryrva and entryrva < secrvamax: mapflags |= e_mem.MM_EXEC if not nxcompat: if not vw.config.viv.parsers.pe.nx and subsys_majver < 6 and mapflags & e_mem.MM_READ: mapflags |= e_mem.MM_EXEC if sec.VirtualSize == 0 or sec.SizeOfRawData == 0: if idx + 1 >= len(pe.sections): continue # fill the gap with null bytes.. nsec = pe.sections[idx + 1] nbase = nsec.VirtualAddress + baseaddr plen = nbase - secbase readsize = sec.SizeOfRawData if sec.SizeOfRawData < sec.VirtualSize else sec.VirtualSize secoff = pe.rvaToOffset(secrva) secbytes = pe.readAtOffset(secoff, readsize) secbytes += "\x00" * plen vw.addMemoryMap(secbase, mapflags, fname, secbytes) vw.addSegment(secbase, len(secbytes), secname, fname) # Mark dead data on resource and import data directories if sec.VirtualAddress in deadvas: vw.markDeadData(secbase, secbase + len(secbytes)) #FIXME create a mask for this if not (chars & PE.IMAGE_SCN_CNT_CODE) and not ( chars & PE.IMAGE_SCN_MEM_EXECUTE) and not ( chars & PE.IMAGE_SCN_MEM_WRITE): vw.markDeadData(secbase, secbase + len(secbytes)) continue # if SizeOfRawData is greater than VirtualSize we'll end up using VS in our read.. if sec.SizeOfRawData < sec.VirtualSize: if sec.SizeOfRawData > pe.filesize: continue plen = sec.VirtualSize - sec.SizeOfRawData try: # According to http://code.google.com/p/corkami/wiki/PE#section_table if SizeOfRawData is larger than VirtualSize, VS is used.. readsize = sec.SizeOfRawData if sec.SizeOfRawData < sec.VirtualSize else sec.VirtualSize secoff = pe.rvaToOffset(secrva) secbytes = pe.readAtOffset(secoff, readsize) secbytes += "\x00" * plen vw.addMemoryMap(secbase, mapflags, fname, secbytes) vw.addSegment(secbase, len(secbytes), secname, fname) # Mark dead data on resource and import data directories if sec.VirtualAddress in deadvas: vw.markDeadData(secbase, secbase + len(secbytes)) #FIXME create a mask for this if not (chars & PE.IMAGE_SCN_CNT_CODE) and not ( chars & PE.IMAGE_SCN_MEM_EXECUTE) and not ( chars & PE.IMAGE_SCN_MEM_WRITE): vw.markDeadData(secbase, secbase + len(secbytes)) except Exception as e: logger.warning( "Error Loading Section (%s size:%d rva:%.8x offset: %d): %s", secname, secfsize, secrva, secoff, e) vw.addExport(entry, EXP_FUNCTION, '__entry', fname) vw.addEntryPoint(entry) # store the actual reloc section virtual address reloc_va = pe.getDataDirectory( PE.IMAGE_DIRECTORY_ENTRY_BASERELOC).VirtualAddress if reloc_va: reloc_va += baseaddr vw.setFileMeta(fname, "reloc_va", reloc_va) for rva, rtype in pe.getRelocations(): # map PE reloc to VIV reloc ( or dont... ) vtype = relmap.get(rtype) if vtype is None: logger.info('Skipping PE Relocation type: %d at %d (no handler)', rtype, rva) continue mapoffset = vw.readMemoryPtr(rva + baseaddr) - baseaddr vw.addRelocation(rva + baseaddr, vtype, mapoffset) for rva, lname, iname in pe.getImports(): if vw.probeMemory(rva + baseaddr, 4, e_mem.MM_READ): vw.makeImport(rva + baseaddr, lname, iname) # Tell vivisect about ntdll functions that don't exit... vw.addNoReturnApi("ntdll.RtlExitUserThread") vw.addNoReturnApi("kernel32.ExitProcess") vw.addNoReturnApi("kernel32.ExitThread") vw.addNoReturnApi("kernel32.FatalExit") vw.addNoReturnApiRegex("^msvcr.*\._CxxThrowException$") vw.addNoReturnApiRegex("^msvcr.*\.abort$") vw.addNoReturnApiRegex("^msvcr.*\.exit$") vw.addNoReturnApiRegex("^msvcr.*\._exit$") vw.addNoReturnApiRegex("^msvcr.*\.quick_exit$") # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/invalid-parameter-functions?view=vs-2019 # TODO: Again, there's a couple in there that have conditional termination that we should check for #vw.addNoReturnApiRegex("vcruntime140.__std_terminate") vw.addNoReturnApiRegex( "^api_ms_win_crt_runtime_.*\._invalid_parameter_noinfo_noreturn$") vw.addNoReturnApiRegex("^api_ms_win_crt_runtime_.*\.exit$") vw.addNoReturnApiRegex("^api_ms_win_crt_runtime_.*\._exit$") # TODO: we should add abort and terminate on the conditions that there are no signal handlers # registered # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/cexit-c-exit?view=vs-2019 # vw.addNoReturnApiRegex("^msvcr.*\._cexit$") # vw.addNoReturnApiRegex("^msvcr.*\._c_exit$") vw.addNoReturnApi("ntoskrnl.KeBugCheckEx") exports = pe.getExports() for rva, ord, name in exports: eva = rva + baseaddr # Functions exported by ordinal only have no name if not name: name = "Ordinal_" + str(ord) try: vw.setVaSetRow('pe:ordinals', (eva, ord)) vw.addExport(eva, EXP_UNTYPED, name, fname) if vw.probeMemory(eva, 1, e_mem.MM_EXEC): vw.addEntryPoint(eva) except Exception as e: vw.vprint('addExport Failed: %s.%s (0x%.8x): %s' % (fname, name, eva, e)) # Save off the ordinals... vw.setFileMeta(fname, 'ordinals', exports) fwds = pe.getForwarders() for rva, name, forwardname in fwds: vw.makeName(rva + baseaddr, "forwarder_%s.%s" % (fname, name)) vw.makeString(rva + baseaddr) vw.setFileMeta(fname, 'forwarders', fwds) # Check For SafeSEH list... if pe.IMAGE_LOAD_CONFIG is not None: vw.setFileMeta(fname, "SafeSEH", True) va = pe.IMAGE_LOAD_CONFIG.SEHandlerTable if va != 0: vw.makeName(va, "%s.SEHandlerTable" % fname) count = pe.IMAGE_LOAD_CONFIG.SEHandlerCount # RP BUG FIX - sanity check the count if count * 4 < pe.filesize and vw.isValidPointer(va): # XXX - CHEAP HACK for some reason we have binaries still thorwing issues.. try: # Just cheat and use the workspace with memory maps in it already for h in vw.readMemoryFormat(va, "<%dP" % count): sehva = baseaddr + h vw.addEntryPoint(sehva) #vw.hintFunction(sehva, meta={'SafeSEH':True}) except: vw.vprint("SEHandlerTable parse error") # Last but not least, see if we have symbol support and use it if we do if vt_win32.dbghelp: s = vt_win32.Win32SymbolParser(-1, filename, baseaddr) # We don't want exports or whatever because we already have them s.symopts |= vt_win32.SYMOPT_EXACT_SYMBOLS s.parse() # Add names for any symbols which are missing them for symname, symva, size, flags in s.symbols: if not vw.isValidPointer(symva): continue try: if vw.getName(symva) is None: vw.makeName(symva, symname, filelocal=True) except Exception as e: vw.vprint("Symbol Load Error: %s" % e) # Also, lets set the locals/args name hints if we found any vw.setFileMeta(fname, 'PELocalHints', s._sym_locals) # if it has an EXCEPTION directory parse if it has the pdata edir = pe.getDataDirectory(PE.IMAGE_DIRECTORY_ENTRY_EXCEPTION) if edir.VirtualAddress and arch == 'amd64': va = edir.VirtualAddress + baseaddr vamax = va + edir.Size while va < vamax: f = vw.makeStructure(va, 'pe.IMAGE_RUNTIME_FUNCTION_ENTRY') if not vw.isValidPointer(baseaddr + f.UnwindInfoAddress): break # FIXME UNWIND_INFO *requires* DWORD alignment, how is it enforced? fva = f.BeginAddress + baseaddr uiva = baseaddr + f.UnwindInfoAddress # Possible method 1... #uiva = baseaddr + (f.UnwindInfoAddress & 0xfffffffc ) # Possible method 2... #uirem = f.UnwindInfoAddress % 4 #if uirem: #uiva += ( 4 - uirem ) uinfo = vw.getStructure(uiva, 'pe.UNWIND_INFO') ver = uinfo.VerFlags & 0x7 if ver != 1: vw.vprint('Unwind Info Version: %d (bailing on .pdata)' % ver) break flags = uinfo.VerFlags >> 3 # Check if it's a function *block* rather than a function *entry* if not (flags & PE.UNW_FLAG_CHAININFO): vw.addEntryPoint(fva) va += len(f) # auto-mark embedded PEs as "dead data" to prevent code flow... if carvepes: pe.fd.seek(0) fbytes = pe.fd.read() for offset, i in pe_carve.carve(fbytes, 1): # Found a sub-pe! subpe = pe_carve.CarvedPE(fbytes, offset, chr(i)) pebytes = subpe.readAtOffset(0, subpe.getFileSize()) rva = pe.offsetToRva(offset) + baseaddr vw.markDeadData(rva, rva + len(pebytes)) return fname
# Possible method 2... #uirem = f.UnwindInfoAddress % 4 #if uirem: #uiva += ( 4 - uirem ) uinfo = vw.getStructure(uiva, 'pe.UNWIND_INFO') ver = uinfo.VerFlags & 0x7 if ver != 1: vw.vprint('Unwind Info Version: %d (bailing on .pdata)' % ver) break flags = uinfo.VerFlags >> 3 # Check if it's a function *block* rather than a function *entry* if not (flags & PE.UNW_FLAG_CHAININFO): vw.addEntryPoint(fva) va += len(f) # auto-mark embedded PEs as "dead data" to prevent code flow... if carvepes: pe.fd.seek(0) fbytes = pe.fd.read() for offset, i in pe_carve.carve(fbytes, 1): # Found a sub-pe! subpe = pe_carve.CarvedPE(fbytes, offset, chr(i)) pebytes = subpe.readAtOffset(0, subpe.getFileSize()) rva = pe.offsetToRva(offset) vw.markDeadData(rva, rva + len(pebytes)) return fname