// Return the last modified time of a file, or zero if failure time_t MassStorage::GetLastModifiedTime(const char* directory, const char *fileName) const { String<MaxFilenameLength> location; CombineName(location.GetRef(), directory, fileName); FILINFO fil; if (f_stat(location.c_str(), &fil) == FR_OK) { return ConvertTimeStamp(fil.fdate, fil.ftime); } return 0; }
// Open a directory to read a file list. Returns true if it contains any files, false otherwise. // If this returns true then the file system mutex is owned. The caller must subsequently release the mutex either // by calling FindNext until it returns false, or by calling AbandonFindNext. bool MassStorage::FindFirst(const char *directory, FileInfo &file_info) { // Remove any trailing '/' from the directory name, it sometimes (but not always) confuses f_opendir String<MaxFilenameLength> loc; loc.copy(directory); const size_t len = loc.strlen(); if (len != 0 && (loc[len - 1] == '/' || loc[len - 1] == '\\')) { loc.Truncate(len - 1); } if (!dirMutex.Take(10000)) { return false; } FRESULT res = f_opendir(&findDir, loc.c_str()); if (res == FR_OK) { FILINFO entry; for (;;) { res = f_readdir(&findDir, &entry); if (res != FR_OK || entry.fname[0] == 0) break; if (!StringEqualsIgnoreCase(entry.fname, ".") && !StringEqualsIgnoreCase(entry.fname, "..")) { file_info.isDirectory = (entry.fattrib & AM_DIR); file_info.fileName.copy(entry.fname); file_info.size = entry.fsize; file_info.lastModified = ConvertTimeStamp(entry.fdate, entry.ftime); return true; } } f_closedir(&findDir); } dirMutex.Release(); return false; }
// Find the next file in a directory. Returns true if another file has been read. // If it returns false then it also releases the mutex. bool MassStorage::FindNext(FileInfo &file_info) { if (dirMutex.GetHolder() != RTOSIface::GetCurrentTask()) { return false; // error, we don't hold the mutex } FILINFO entry; if (f_readdir(&findDir, &entry) != FR_OK || entry.fname[0] == 0) { f_closedir(&findDir); dirMutex.Release(); return false; } file_info.isDirectory = (entry.fattrib & AM_DIR); file_info.size = entry.fsize; file_info.fileName.copy(entry.fname); file_info.lastModified = ConvertTimeStamp(entry.fdate, entry.ftime); return true; }
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); } } } }