void HandleLongOrUnprintableString(std::wstring const& name, std::wstring const& description, std::size_t tabs, WarningType warning_type, std::string value) { std::wostream& out = GetOutputStreamW(); // TODO: Fix perf for extremely long names. Instead of reading indefinitely // and then checking the size after the fact, we should perform a bounded // read. auto const unprintable = FindFirstUnprintableClassicLocale(value); std::size_t const kMaxNameLength = 1024; if (unprintable != std::string::npos) { WriteNormal(out, L"WARNING! Detected unprintable " + description + L". Truncating.", tabs); WarnForCurrentFile(warning_type); value.erase(unprintable); } else if (value.size() > kMaxNameLength) { WriteNormal(out, L"WARNING! Detected suspiciously long " + description + L". Truncating.", tabs); WarnForCurrentFile(warning_type); value.erase(kMaxNameLength); } WriteNamedNormal(out, name, value.c_str(), tabs); }
void HandleLongOrUnprintableString(std::wstring const& name, std::wstring const& description, std::size_t tabs, WarningType warning_type, std::string value) { std::wostream& out = std::wcout; auto const unprintable = FindFirstUnprintableClassicLocale(value); std::size_t const kMaxNameLength = 1024; if (unprintable != std::string::npos) { WriteNormal(out, L"WARNING! Detected unprintable " + description + L". Truncating.", tabs); WarnForCurrentFile(warning_type); value.erase(unprintable); } else if (value.size() > kMaxNameLength) { WriteNormal(out, L"WARNING! Detected suspiciously long " + description + L". Truncating.", tabs); WarnForCurrentFile(warning_type); value.erase(kMaxNameLength); } WriteNamedNormal(out, name, value.c_str(), tabs); }
void DumpBoundImports(hadesmem::Process const& process, hadesmem::PeFile const& pe_file, bool has_new_bound_imports_any) { std::wostream& out = GetOutputStreamW(); if (!HasBoundImportDir(process, pe_file)) { // Sample: dllmaxvals.dll (Corkami PE Corpus) if (has_new_bound_imports_any) { WriteNewline(out); WriteNormal( out, L"WARNING! No bound import directory on file with an import dir " L"indicating the presence of a bound import dir.", 1); WarnForCurrentFile(WarningType::kSuspicious); } return; } if (!has_new_bound_imports_any) { WriteNewline(out); WriteNormal( out, L"WARNING! Seemingly valid bound import directory on file with an " L"import dir indicating no new bound import dir.", 1); WarnForCurrentFile(WarningType::kSuspicious); return; } hadesmem::BoundImportDescriptorList const bound_import_descs(process, pe_file); if (std::begin(bound_import_descs) == std::end(bound_import_descs)) { WriteNewline(out); WriteNormal(out, L"WARNING! Empty or invalid bound import directory.", 1); WarnForCurrentFile(WarningType::kSuspicious); return; } WriteNewline(out); WriteNormal(out, L"Bound Import Descriptors:", 1); std::uint32_t num_descs = 0U; for (auto const& desc : bound_import_descs) { WriteNewline(out); if (num_descs++ == 1000) { WriteNormal(out, L"WARNING! Processed 1000 bound import descriptors. Stopping " L"early to avoid resource exhaustion attacks.", 2); WarnForCurrentFile(WarningType::kUnsupported); break; } DWORD const time_date_stamp = desc.GetTimeDateStamp(); std::wstring time_date_stamp_str; if (!ConvertTimeStamp(time_date_stamp, time_date_stamp_str)) { WriteNormal(out, L"WARNING! Invalid timestamp.", 2); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHexSuffix( out, L"TimeDateStamp", time_date_stamp, time_date_stamp_str, 2); WriteNamedHex(out, L"OffsetModuleName", desc.GetOffsetModuleName(), 2); WriteNamedNormal(out, L"ModuleName", desc.GetModuleName().c_str(), 2); WriteNamedHex(out, L"NumberOfModuleForwarderRefs", desc.GetNumberOfModuleForwarderRefs(), 2); hadesmem::BoundImportForwarderRefList const forwarder_refs( process, pe_file, desc); if (std::begin(forwarder_refs) != std::end(forwarder_refs)) { WriteNewline(out); WriteNormal(out, L"Module Forwarder Refs:", 2); } for (auto const& forwarder : forwarder_refs) { WriteNewline(out); DWORD const fwd_time_date_stamp = forwarder.GetTimeDateStamp(); std::wstring fwd_time_date_stamp_str; if (!ConvertTimeStamp(fwd_time_date_stamp, fwd_time_date_stamp_str)) { WriteNormal(out, L"WARNING! Invalid timestamp.", 3); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHexSuffix( out, L"TimeDateStamp", fwd_time_date_stamp, fwd_time_date_stamp_str, 3); WriteNamedHex( out, L"OffsetModuleName", forwarder.GetOffsetModuleName(), 3); WriteNamedNormal( out, L"ModuleName", forwarder.GetModuleName().c_str(), 3); WriteNamedHex(out, L"Reserved", forwarder.GetReserved(), 3); } } }
void DumpExports(hadesmem::Process const& process, hadesmem::PeFile const& pe_file) { std::unique_ptr<hadesmem::ExportDir const> export_dir; try { export_dir = std::make_unique<hadesmem::ExportDir const>(process, pe_file); } catch (std::exception const& /*e*/) { return; } std::wostream& out = std::wcout; WriteNewline(out); WriteNormal(out, L"Export Dir:", 1); WriteNewline(out); WriteNamedHex(out, L"Characteristics", export_dir->GetCharacteristics(), 2); DWORD const time_date_stamp = export_dir->GetTimeDateStamp(); std::wstring time_date_stamp_str; if (!ConvertTimeStamp(time_date_stamp, time_date_stamp_str)) { WriteNormal(out, L"WARNING! Invalid timestamp.", 2); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHexSuffix( out, L"TimeDateStamp", time_date_stamp, time_date_stamp_str, 2); WriteNamedHex(out, L"MajorVersion", export_dir->GetMajorVersion(), 2); WriteNamedHex(out, L"MinorVersion", export_dir->GetMinorVersion(), 2); WriteNamedHex(out, L"Name (Raw)", export_dir->GetNameRaw(), 2); // Name is not guaranteed to be valid. // Sample: dllord.dll (Corkami PE Corpus) try { auto name = export_dir->GetName(); HandleLongOrUnprintableString( L"Name", L"export module name", 2, WarningType::kSuspicious, name); } catch (std::exception const& /*e*/) { WriteNormal(out, L"WARNING! Failed to read export dir name.", 2); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHex(out, L"OrdinalBase", export_dir->GetOrdinalBase(), 2); WriteNamedHex( out, L"NumberOfFunctions", export_dir->GetNumberOfFunctions(), 2); WriteNamedHex(out, L"NumberOfNames", export_dir->GetNumberOfNames(), 2); WriteNamedHex( out, L"AddressOfFunctions", export_dir->GetAddressOfFunctions(), 2); WriteNamedHex(out, L"AddressOfNames", export_dir->GetAddressOfNames(), 2); WriteNamedHex( out, L"AddressOfNameOrdinals", export_dir->GetAddressOfNameOrdinals(), 2); std::set<std::string> export_names; hadesmem::ExportList const exports(process, pe_file); if (std::begin(exports) != std::end(exports)) { WriteNewline(out); WriteNormal(out, L"Exports:", 2); } else { WriteNewline(out); WriteNormal(out, L"WARNING! Empty or invalid export list.", 2); WarnForCurrentFile(WarningType::kSuspicious); } std::uint32_t num_exports = 0U; for (auto const& e : exports) { WriteNewline(out); // Some legitimate DLLs have well over 1000 exports (e.g. ntdll.dll). if (num_exports++ == 10000) { WriteNormal( out, L"WARNING! Processed 10000 exports. Stopping early to avoid resource " L"exhaustion attacks.", 2); WarnForCurrentFile(WarningType::kSuspicious); break; } if (e.ByName()) { auto const name = e.GetName(); // Sample: dllweirdexp.dll HandleLongOrUnprintableString( L"Name", L"export name", 3, WarningType::kSuspicious, name); // PE files can have duplicate exported function names (or even have them // all identical) because the import hint is used to check the name first // before performing a search. // Sample: None ("Import name hint" section of "Undocumented PECOFF" // whitepaper). if (!export_names.insert(name).second) { WriteNormal(out, L"WARNING! Detected duplicate export name.", 3); WarnForCurrentFile(WarningType::kSuspicious); } } WriteNamedHex(out, L"ProcedureNumber", e.GetProcedureNumber(), 3); WriteNamedHex(out, L"OrdinalNumber", e.GetOrdinalNumber(), 3); if (e.IsForwarded()) { WriteNamedNormal(out, L"Forwarder", e.GetForwarder().c_str(), 3); WriteNamedNormal( out, L"ForwarderModule", e.GetForwarderModule().c_str(), 3); WriteNamedNormal( out, L"ForwarderFunction", e.GetForwarderFunction().c_str(), 3); WriteNamedNormal( out, L"IsForwardedByOrdinal", e.IsForwardedByOrdinal(), 3); if (e.IsForwardedByOrdinal()) { try { WriteNamedHex(out, L"ForwarderOrdinal", e.GetForwarderOrdinal(), 3); } catch (std::exception const& /*e*/) { WriteNormal(out, L"WARNING! ForwarderOrdinal invalid.", 3); WarnForCurrentFile(WarningType::kSuspicious); } } } else { auto const ep_rva = e.GetRva(); WriteNamedHex(out, L"RVA", e.GetRva(), 3); auto const ep_va = e.GetVa(); WriteNamedHex(out, L"VA", reinterpret_cast<std::uintptr_t>(ep_va), 3); if (ep_va) { DisassembleEp(process, pe_file, ep_rva, ep_va, 4); } else { WriteNormal(out, L"WARNING! Export VA is invalid.", 3); WarnForCurrentFile(WarningType::kSuspicious); } } } }