/* __________________________________________________________________________________________ | _______ _____ __ _ _______ _______ _______ | | | | | | | | \ | |______ | |_____| | | | | | |_____| | \_| |______ | | | | |________________________________________________________________________________________| | Moneta ~ Usermode memory scanner & malware hunter | |----------------------------------------------------------------------------------------| | https://www.forrest-orr.net/post/malicious-memory-artifacts-part-ii-bypassing-scanners | |----------------------------------------------------------------------------------------| | Author: Forrest Orr - 2020 | |----------------------------------------------------------------------------------------| | Contact: forrest.orr@protonmail.com | |----------------------------------------------------------------------------------------| | Licensed under GNU GPLv3 | |________________________________________________________________________________________| | ## Features | | | | ~ Query the memory attributes of any accessible process(es). | | ~ Identify private, mapped and image memory. | | ~ Correlate regions of memory to their underlying file on disks. | | ~ Identify PE headers and sections corresponding to image memory. | | ~ Identify modified regions of mapped image memory. | | ~ Identify abnormal memory attributes indicative of malware. | | ~ Create memory dumps of user-specified memory ranges | | ~ Calculate memory permission/type statistics | |________________________________________________________________________________________| */ #include "StdAfx.h" #include "FileIo.hpp" #include "PeFile.hpp" #include "Processes.hpp" #include "Memory.hpp" #include "Interface.hpp" #include "MemDump.hpp" #include "Suspicions.hpp" #include "Signing.h" #include "PEB.h" using namespace std; using namespace Memory; using namespace Processes; Process::~Process() { if (this->Handle != nullptr) { CloseHandle(this->Handle); } for (vector::const_iterator Itr = this->Threads.begin(); Itr != this->Threads.end(); ++Itr) { delete* Itr; } for (map::const_iterator Itr = this->Entities.begin(); Itr != this->Entities.end(); ++Itr) { if (Itr->second->GetType() == Entity::Type::PE_FILE) { delete dynamic_cast(Itr->second); // This will call the destructors for PE, mapped file and entity all to be called in inheritted order. } else if (Itr->second->GetType() == Entity::Type::MAPPED_FILE) { delete dynamic_cast(Itr->second); } else { delete dynamic_cast(Itr->second);; } } } Process::Process(uint32_t dwPid) : Pid(dwPid) { this->Handle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, dwPid); if (this->Handle != nullptr) { wchar_t ImageName[MAX_PATH + 1] = { 0 }, DevFilePath[MAX_PATH + 1] = { 0 }; if (GetModuleBaseNameW(this->Handle, nullptr, ImageName, MAX_PATH + 1) && GetProcessImageFileNameW(this->Handle, DevFilePath, sizeof(DevFilePath))) { wchar_t ImageFilePath[MAX_PATH + 1] = { 0 }; if (FileBase::TranslateDevicePath(DevFilePath, ImageFilePath)) { this->Name = wstring(ImageName); this->ImageFilePath = wstring(ImageFilePath); Interface::Log(VerbosityLevel::Debug, "... mapping address space of PID %d [%ws]\r\n", this->Pid, this->Name.c_str()); static IsWow64Process_t IsWow64Process = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"Kernel32.dll"), "IsWow64Process")); if (IsWow64Process != nullptr) { BOOL bSelfWow64 = FALSE; if (IsWow64Process(GetCurrentProcess(), static_cast(&bSelfWow64))) { if (IsWow64Process(this->Handle, static_cast(&this->Wow64))) { if (this->IsWow64()) { Interface::Log(VerbosityLevel::Debug, "... PID %d is Wow64\r\n", this->Pid); } else { if (bSelfWow64) { Interface::Log(VerbosityLevel::Debug, "... cannot scan non-Wow64 process from Wow64 Moneta instance\r\n"); throw 2; } } } } } } } /* HEAPLIST32 hl; HANDLE hHeapSnap = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, this->Pid); hl.dwSize = sizeof(HEAPLIST32); if (hHeapSnap == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot failed (%d)\n", GetLastError()); } if (Heap32ListFirst(hHeapSnap, &hl)) { do { HEAPENTRY32 he; ZeroMemory(&he, sizeof(HEAPENTRY32)); he.dwSize = sizeof(HEAPENTRY32); if (Heap32First(&he, this->Pid, hl.th32HeapID)) { printf("\nHeap ID: 0x%p\n", hl.th32HeapID); do { printf("Sub-heap 0x%p : Block size: %d\n", he.dwAddress, he.dwBlockSize); this->Heaps.push_back(reinterpret_cast(he.dwAddress)); he.dwSize = sizeof(HEAPENTRY32); } while (Heap32Next(&he)); } hl.dwSize = sizeof(HEAPLIST32); } while (Heap32ListNext(hHeapSnap, &hl)); } else printf("Cannot list first heap (%d)\n", GetLastError());*/ // // CreateToolhelp32Snapshot doesn't work work cross-arhitecture heap enumeration - use the PEB to walk the heaps. Note that it was confirmed private +RWX entries in .NET process (other than executable primary heaps) are not sub-heaps which can be enumerated with Heap32First/Next // static NtQueryInformationProcess_t NtQueryInformationProcess = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"Ntdll.dll"), "NtQueryInformationProcess")); NTSTATUS NtStatus; void* RemotePeb = nullptr; uint32_t dwPebSize = 0; PROCESS_BASIC_INFORMATION Pbi = { 0 }; if (this->IsWow64()) { NtStatus = NtQueryInformationProcess(this->Handle, ProcessWow64Information, &RemotePeb, sizeof(RemotePeb), nullptr); } else { NtStatus = NtQueryInformationProcess(this->Handle, ProcessBasicInformation, &Pbi, sizeof(Pbi), nullptr); if (NT_SUCCESS(NtStatus)) { RemotePeb = Pbi.PebBaseAddress; } } if (RemotePeb != nullptr) { Interface::Log(VerbosityLevel::Debug, "... PEB of 0x%p\r\n", RemotePeb); if (this->IsWow64()) { PEB32* LocalPeb = new PEB32; dwPebSize = sizeof(PEB32); if (ReadProcessMemory(this->Handle, RemotePeb, LocalPeb, dwPebSize, nullptr)) { uint32_t dwNumberOfHeaps = LocalPeb->NumberOfHeaps; uint32_t dwHeapsSize = dwNumberOfHeaps * sizeof(uint32_t); uint32_t* Heaps = new uint32_t [dwNumberOfHeaps]; Interface::Log(VerbosityLevel::Debug, "... successfully read remote PEB to local memory (%d heaps)\r\n", dwNumberOfHeaps); if (ReadProcessMemory(this->Handle, reinterpret_cast(LocalPeb->ProcessHeaps), Heaps, dwHeapsSize, nullptr)) { Interface::Log(VerbosityLevel::Debug, "... successfully read remote heaps to local memory.\r\n"); for (uint32_t dwX = 0; dwX < dwNumberOfHeaps; dwX++) { Interface::Log(VerbosityLevel::Debug, "... 0x%08x\r\n", Heaps[dwX]); this->Heaps.push_back(reinterpret_cast(Heaps[dwX])); } } } delete LocalPeb; } else { PEB64* LocalPeb = new PEB64; dwPebSize = sizeof(PEB64); if (ReadProcessMemory(this->Handle, RemotePeb, LocalPeb, dwPebSize, nullptr)) { uint32_t dwNumberOfHeaps = LocalPeb->NumberOfHeaps; uint32_t dwHeapsSize = dwNumberOfHeaps * sizeof(void*); void** Heaps = new void* [dwNumberOfHeaps]; Interface::Log(VerbosityLevel::Debug, "... successfully read remote PEB to local memory.\r\n"); if (ReadProcessMemory(this->Handle, reinterpret_cast(LocalPeb->ProcessHeaps), Heaps, dwHeapsSize, nullptr)) { Interface::Log(VerbosityLevel::Debug, "... successfully read remote heaps to local memory.\r\n"); for (uint32_t dwX = 0; dwX < dwNumberOfHeaps; dwX++) { Interface::Log(VerbosityLevel::Debug, "... 0x%p\r\n", Heaps[dwX]); this->Heaps.push_back(Heaps[dwX]); } } } delete LocalPeb; } } HANDLE hThreadSnap = INVALID_HANDLE_VALUE; THREADENTRY32 ThreadEntry; if ((hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)) != INVALID_HANDLE_VALUE) { ThreadEntry.dwSize = sizeof(THREADENTRY32); if (Thread32First(hThreadSnap, &ThreadEntry)) { do { if (ThreadEntry.th32OwnerProcessID == this->Pid) { try { this->Threads.push_back(new Thread(ThreadEntry.th32ThreadID, *this)); } catch (int32_t nError) { Interface::Log(VerbosityLevel::Surface, "... failed to query thread information for TID %d in PID %d: cancelling scan of process.\r\n", ThreadEntry.th32ThreadID, this->Pid); throw 2; } } } while (Thread32Next(hThreadSnap, &ThreadEntry)); } CloseHandle(hThreadSnap); Interface::Log(VerbosityLevel::Debug, "... associated a total of %d threads with the current process.\r\n", this->Threads.size()); } SIZE_T cbRegionSize = 0; vector Subregions; vector::iterator Region; for (uint8_t* pBaseAddr = nullptr;; pBaseAddr += cbRegionSize) { MEMORY_BASIC_INFORMATION* Mbi = new MEMORY_BASIC_INFORMATION; if (VirtualQueryEx(this->Handle, pBaseAddr, Mbi, sizeof(MEMORY_BASIC_INFORMATION)) == sizeof(MEMORY_BASIC_INFORMATION)) { cbRegionSize = Mbi->RegionSize; if (!Subregions.empty()) { // If the subregion list is empty then there is no region base for comparison if (Mbi->AllocationBase != (*Region)->GetBasic()->AllocationBase) { this->Entities.insert(make_pair(static_cast((*Region)->GetBasic()->AllocationBase), Entity::Create(*this, Subregions))); Subregions.clear(); } } Subregions.push_back(new Subregion(*this, Mbi)); Region = Subregions.begin(); // This DOES fix a bug. } else { if (!Subregions.empty()) { // Edge case: new ablock not yet found but finished enumerating sblocks. this->Entities.insert(make_pair(static_cast((*Region)->GetBasic()->AllocationBase), Entity::Create(*this, Subregions))); } delete Mbi; break; } } } else { Interface::Log(VerbosityLevel::Debug, "... failed to open handle to PID %d\r\n", this->Pid); throw 1; } } PeVm::Body* Process::GetLoadedModule(wstring Name) const { wstring SanitizedName = Name; transform(SanitizedName.begin(), SanitizedName.end(), SanitizedName.begin(), ::toupper); for (map::const_iterator Itr = this->Entities.begin(); Itr != this->Entities.end(); ++Itr) { if (Itr->second->GetType() == Entity::Type::PE_FILE) { PeVm::Body* PeEntity = dynamic_cast(Itr->second); wstring CurrentName = PeEntity->GetPebModule().GetName(); transform(CurrentName.begin(), CurrentName.end(), CurrentName.begin(), ::toupper); if (CurrentName == SanitizedName) { return PeEntity; } } } return nullptr; } void AlignName(const wchar_t* pOriginalName, wchar_t* pAlignedName, int32_t nAlignTo) { // Make generic and move to interface? assert(nAlignTo >= 1); assert(wcslen(pOriginalName) <= nAlignTo); if (wcslen(pOriginalName)) { wcsncpy_s(pAlignedName, (nAlignTo + 1), pOriginalName, nAlignTo); for (int32_t nX = wcslen(pAlignedName); nX < nAlignTo; nX++) { wcscat_s(pAlignedName, (nAlignTo + 1), L" "); } } else { wcscpy_s(pAlignedName, (nAlignTo + 1), L" "); for (int32_t nX = 1; nX < nAlignTo; nX++) { wcscat_s(pAlignedName, (nAlignTo + 1), L" "); } } } void EnumerateThreads(const wstring Indent, vector Threads) { for (vector::iterator ThItr = Threads.begin(); ThItr != Threads.end(); ++ThItr) { Interface::Log("%wsThread 0x%p [TID 0x%08x]\r\n", Indent.c_str(), (*ThItr)->GetEntryPoint(), (*ThItr)->GetTid()); } } int32_t FilterSuspicions(map >>&SuspicionsMap) { bool bReWalkMap = false; do { if (bReWalkMap) { bReWalkMap = false; // The re-walk boolean is only set when a suspicion was filtered. Reset it each time this happens. } /* Concept ~ Walk the map and search through the suspicion list corresponding to each sblock. When a suspicion is filtered, remove it from the list, and remove the sblock map entry if it was the only suspicion in its list. If the ablock map only had the one sblock map entry, then remove the ablock map entry as well. Walk the list again now that the map has updated, and repeat the process until there are no filterable suspicions remaining */ for (map >>::const_iterator AbMapItr = SuspicionsMap.begin(); !bReWalkMap && AbMapItr != SuspicionsMap.end(); ++AbMapItr) { /* Region map -> Key [Allocation base] -> Suspicions map -> Key [Subregion address] -> Suspicions list */ map < uint8_t*, list>& RefSbMap = SuspicionsMap.at(AbMapItr->first); int32_t nSbIndex = 0; for (map>::const_iterator SbMapItr = AbMapItr->second.begin(); !bReWalkMap && SbMapItr != AbMapItr->second.end(); ++SbMapItr, nSbIndex++) { list& RefSuspList = RefSbMap.at(SbMapItr->first); list::const_iterator SuspItr = SbMapItr->second.begin(); for (int32_t nSuspIndex = 0; !bReWalkMap && SuspItr != SbMapItr->second.end(); ++SuspItr, nSuspIndex++) { switch ((*SuspItr)->GetType()) { case Suspicion::Type::XPRV: { if (((*SuspItr)->GetSubregion()->GetFlags() & MEMORY_SUBREGION_FLAG_HEAP&&false)) { bReWalkMap = true; RefSuspList.erase(SuspItr); if (!RefSuspList.size()) { // // Erase the suspicion list from the sblock map and then erase the sblock map from the ablock map. Finalize by removing the ablock map from the suspicion map itself. // RefSbMap.erase(SbMapItr); if (!RefSbMap.size()) { SuspicionsMap.erase(AbMapItr); // Will this cause a bug if multiple suspicions are erased in one call to this function? } } } else { // // Check if the owner process of this suspicion has clr.dll loaded - if it does, scan its .data for references to this region // PeVm::Body* PeEntity; if ((PeEntity = (*SuspItr)->GetProcess()->GetLoadedModule(L"clr.dll")) != nullptr) { Interface::Log(VerbosityLevel::Surface, "... found private +x region at 0x%p within process with clr.dll loaded.\r\n", (*SuspItr)->GetSubregion()->GetBasic()->BaseAddress); PeVm::Section *DataSect = PeEntity->GetSection(".data"); if (DataSect != nullptr) { Interface::Log(VerbosityLevel::Surface, "... clr.dll .data section located at 0x%p\r\n", DataSect->GetStartVa()); uint8_t* Buf = new uint8_t[DataSect->GetEntitySize()]; if (ReadProcessMemory((*SuspItr)->GetProcess()->GetHandle(), DataSect->GetStartVa(), Buf, DataSect->GetEntitySize(), nullptr)) { uint32_t dwChunkSize = 4; uint32_t dwChunkCount = (DataSect->GetEntitySize() / dwChunkSize); Interface::Log(VerbosityLevel::Surface, "... successfully read %d bytes of .data section memory\r\n", DataSect->GetEntitySize()); for (uint32_t dwX = 0; dwX < DataSect->GetEntitySize(); dwX += dwChunkSize) { if (*(uint32_t*)&Buf[dwX] == reinterpret_cast((*SuspItr)->GetSubregion()->GetBasic()->BaseAddress)) { Interface::Log(VerbosityLevel::Surface, "... found private executable region address 0x%p at 0x%p (offset 0x%08x) in clr.dll .data section\r\n", (*SuspItr)->GetSubregion()->GetBasic()->BaseAddress, static_cast(const_cast(DataSect->GetStartVa())) + dwX, dwX); } } } delete[] Buf; //system("pause"); } } } break; } case Suspicion::Type::MISSING_PEB_MODULE: { /* Filter cases for missing PEB modules: ~ Signed metadata PEs. These appear in the C:\Windows\System32\WinMetadata folder with the .winmd extension. They've also been noted to appear in WindpwsApps, SystemApps and others. 0x000000000F3E0000:0x0009e000 | Executable image | C:\Windows\System32\WinMetadata\Windows.UI.winmd | Missing PEB module 0x000000000F3E0000:0x0009e000 | R | Header | 0x00000000 0x000000000F3E0000:0x0009e000 | R | .text | 0x00000000 0x000000000F3E0000:0x0009e000 | R | .rsrc | 0x00000000 */ const PeVm::Body* PeEntity = dynamic_cast((*SuspItr)->GetParentObject()); if (PeEntity->IsSigned()) { static const wchar_t* pWinmbExt = L".winmd"; if (_wcsicmp(PeEntity->GetFileBase()->GetPath().c_str() + PeEntity->GetFileBase()->GetPath().length() - wcslen(pWinmbExt), pWinmbExt) == 0) { if (PeEntity->GetPeFile() != nullptr && PeEntity->GetPeFile()->GetEntryPoint() == 0) { bReWalkMap = true; RefSuspList.erase(SuspItr); if (!RefSuspList.size()) { // // Erase the suspicion list from the sblock map and then erase the sblock map from the ablock map. Finalize by removing the ablock map from the suspicion map itself. // RefSbMap.erase(SbMapItr); if (!RefSbMap.size()) { SuspicionsMap.erase(AbMapItr); // Will this cause a bug if multiple suspicions are erased in one call to this function? } } } } } break; } } } } } } while (bReWalkMap); return 0; } int32_t AppendOverlapSuspicion(map>* Suspicions, uint8_t *pSbAddress, bool bEntityTop) { int32_t nCount = 0; if (Suspicions != nullptr && Suspicions->count(pSbAddress)) { list& SuspicionsList = Suspicions->at(pSbAddress); for (list::const_iterator SuspItr = SuspicionsList.begin(); SuspItr != SuspicionsList.end(); ++SuspItr) { if (bEntityTop == (*SuspItr)->IsFullEntitySuspicion()) { Interface::Log(" | "); Interface::Log(ConsoleColor::Red, "%ws", (*SuspItr)->GetDescription().c_str()); nCount++; } } } return nCount; } int32_t AppendSubregionAttributes(Subregion *Sbr) { int32_t nCount = 0; if ((Sbr->GetFlags() & MEMORY_SUBREGION_FLAG_HEAP)) { Interface::Log(" | "); Interface::Log(ConsoleColor::Yellow, "Heap"); nCount++; } if ((Sbr->GetFlags() & MEMORY_SUBREGION_FLAG_TEB)) { Interface::Log(" | "); Interface::Log(ConsoleColor::Yellow, "TEB"); nCount++; } if ((Sbr->GetFlags() & MEMORY_SUBREGION_FLAG_STACK)) { Interface::Log(" | "); Interface::Log(ConsoleColor::Yellow, "Stack"); nCount++; } return nCount; } int32_t SubEntitySuspCount(map>* Suspicions, uint8_t* pSbAddress) { int32_t nCount = 0; if (Suspicions != nullptr && Suspicions->count(pSbAddress)) { list& SuspicionsList = Suspicions->at(pSbAddress); for (list::const_iterator SuspItr = SuspicionsList.begin(); SuspItr != SuspicionsList.end(); ++SuspItr) { if (!(*SuspItr)->IsFullEntitySuspicion()) { nCount++; } } } return nCount; } bool Process::DumpBlock(MemDump &DmpCtx, const MEMORY_BASIC_INFORMATION *Mbi, wstring Indent) { wchar_t DumFilePath[MAX_PATH + 1] = { 0 }; if (Mbi->State == MEM_COMMIT) { if (DmpCtx.Create(Mbi, DumFilePath, MAX_PATH + 1)) { Interface::Log("%ws~ Memory dumped to %ws\r\n", Indent.c_str(), DumFilePath); return true; } else { Interface::Log("%ws~ Memory dump failed.\r\n", Indent.c_str()); return false; } } } /* Process memory enumeration 1. Loop entities to build suspicions list 2. Filter suspicions 3. Loop entities for enumeration if: mselect == process mselect == sblock and this eneity contains the sblock mselect == suspicious and there is 1 or more suspicions 4. Show the process if it has not been shown before 5. Display entity info (exe image, private, mapped + total size) ALWAYS (criteria already applied going into loop) along with suspicions (if any) 5. For PEs, loop sblocks/sections. Enum if: mselect == process mselect == sblock && sblock == current, or the "from base" option is set or mselect == suspicious and the current sblock has a suspicion or the "from base" option is set 6. Dump the current sblock based on the same criteria as above but ONLY if the "from base" option is not set. 7. Dump the entire PE entity if it met the initial enum criteria and "from base" option is set 8. For private/mapped loop sblocks and enum if: mselect == process mselect == sblock && sblock == current, or the "from base" option is set or mselect == suspicious and the current sblock has a suspicion or the "from base" option is set 9. Dump the current sblock based on the same criteria as above but ONLY if the "from base" option is not set. 10. Dump the entire entity if it met the initial enum criteria and "from base" option is set */ vector Process::Enumerate(uint64_t qwOptFlags, MemorySelection_t MemSelectType, const uint8_t *pSelectAddress) { bool bShownProc = false; MemDump DmpCtx(this->Handle, this->Pid); wstring_convert> UnicodeConverter; map >> SuspicionsMap; // More efficient to only filter this map once. Currently filtering it for every single entity vector SelectedSbrs; // // Build suspicions list for following memory selection and apply filters to it. // for (map::const_iterator Itr = this->Entities.begin(); Itr != this->Entities.end(); ++Itr) { Suspicion::InspectEntity(*this, *Itr->second, SuspicionsMap); } if (SuspicionsMap.size()) { FilterSuspicions(SuspicionsMap); } // // Display information on each selected sblock and/or entity within the process address space // for (map::const_iterator Itr = this->Entities.begin(); Itr != this->Entities.end(); ++Itr) { auto AbMapItr = SuspicionsMap.find(static_cast(const_cast(Itr->second->GetStartVa()))); // An iterator into the main ablock map which points to the entry for the sb map. map>* SbrMap = nullptr; if (AbMapItr != SuspicionsMap.end()) { SbrMap = &SuspicionsMap.at(static_cast(const_cast(Itr->second->GetStartVa()))); } if (MemSelectType == MemorySelection_t::All || (MemSelectType == MemorySelection_t::Block && ((pSelectAddress >= Itr->second->GetStartVa()) && (pSelectAddress < Itr->second->GetEndVa()))) || (MemSelectType == MemorySelection_t::Suspicious && AbMapItr != SuspicionsMap.end())) { // // Display process and/or entity information: the criteria has already been met for this to be done without further checks // if (!bShownProc) { Interface::Log("\r\n"); Interface::Log(ConsoleColor::Turquoise, "%ws", this->Name.c_str()); Interface::Log(" : "); Interface::Log(ConsoleColor::Turquoise, "%d", this->GetPid()); Interface::Log(" : "); Interface::Log(ConsoleColor::Turquoise, "%ws", this->IsWow64() ? L"Wow64" : L"x64"); Interface::Log(" : "); Interface::Log(ConsoleColor::Turquoise, "%ws\r\n", this->ImageFilePath.c_str()); bShownProc = true; } if (Itr->second->GetSubregions().front()->GetBasic()->State != MEM_FREE) { Interface::Log(" 0x%p:0x%08x ", Itr->second->GetStartVa(), Itr->second->GetEntitySize()); } if (Itr->second->GetType() == Entity::Type::PE_FILE) { PeVm::Body* PeEntity = dynamic_cast(Itr->second); wchar_t ImgType[22], AlignedImgType[22]; // Length of 20, + 1 for prefix, +1 for null Interface::Log("|"); wcscpy_s(ImgType, 22, L" "); if (PeEntity->GetPeFile()->IsDotNet()) { wcscat_s(ImgType, 22, L".NET "); } if (PeEntity->GetPeFile()->IsExe()) { wcscat_s(ImgType, 22, L"EXE "); } else if (PeEntity->GetPeFile()->IsDll()) { wcscat_s(ImgType, 22, L"DLL "); } if (PeEntity->IsNonExecutableImage()) { wcscat_s(ImgType, 22, L"NX-Image"); } else { wcscat_s(ImgType, 22, L"Image"); } AlignName(ImgType, AlignedImgType, 21); Interface::Log(ConsoleColor::Gold, "%ws", AlignedImgType); Interface::Log("| %ws", PeEntity->GetFileBase()->GetPath().c_str()); } else if (Itr->second->GetType() == Entity::Type::MAPPED_FILE) { Interface::Log("| "); Interface::Log(ConsoleColor::Gold, "Mapped"); Interface::Log(" | %ws", dynamic_cast(Itr->second)->GetFileBase()->GetPath().c_str()); } else { if (Itr->second->GetSubregions().front()->GetBasic()->Type == MEM_PRIVATE) { Interface::Log("| "); Interface::Log(ConsoleColor::Gold, "Private"); } else { continue; } } // // Display suspicions associated with the entity, if the current entity has any suspicions associated with it // //AppendSubregionAttributes(Itr->second->GetSubregions().front()); AppendOverlapSuspicion(SbrMap, static_cast(const_cast(Itr->second->GetStartVa())), true); Interface::Log("\r\n"); if (Interface::GetVerbosity() == VerbosityLevel::Detail) { if (Itr->second->GetType() == Entity::Type::PE_FILE) { PeVm::Body* PeEntity = dynamic_cast(Itr->second); Interface::Log(" |__ Mapped file base: 0x%p\r\n", PeEntity->GetStartVa()); Interface::Log(" | Mapped file size: %d\r\n", PeEntity->GetEntitySize()); Interface::Log(" | Mapped file path: %ws\r\n", PeEntity->GetFileBase()->GetPath().c_str()); Interface::Log(" | Size of image: %d\r\n", PeEntity->GetImageSize()); Interface::Log(" | PE type: %ws%ws\r\n", PeEntity->GetPeFile()->IsDotNet() ? L".NET " : L"", PeEntity->GetPeFile()->IsDll() ? L"DLL" : L"EXE"); Interface::Log(" | Non-executable: %ws\r\n", PeEntity->IsNonExecutableImage() ? L"yes" : L"no"); Interface::Log(" | Partially mapped: %ws\r\n", PeEntity->IsPartiallyMapped() ? L"yes" : L"no"); Interface::Log(" | Signed: %ws [%ws]\r\n", PeEntity->IsSigned() ? L"yes" : L"no", TranslateSigningType(PeEntity->GetSisningType())); Interface::Log(" | Signing level: %ws\r\n", TranslateSigningLevel(PeEntity->GetSigningLevel())); Interface::Log(" |__ PEB module"); if (PeEntity->GetPebModule().Exists()) { Interface::Log("\r\n"); Interface::Log(" | Name: %ws\r\n", PeEntity->GetPebModule().GetName().c_str()); Interface::Log(" | Image base: 0x%p\r\n", PeEntity->GetPebModule().GetBase()); Interface::Log(" | Image size: %d\r\n", PeEntity->GetPebModule().GetSize()); Interface::Log(" | Entry point: 0x%p\r\n", PeEntity->GetPebModule().GetEntryPoint()); Interface::Log(" | Image file path: %ws\r\n", PeEntity->GetPebModule().GetPath().c_str()); } else { Interface::Log(" (missing)\r\n"); } } else if (Itr->second->GetType() == Entity::Type::MAPPED_FILE) { Interface::Log(" |__ Mapped file base: 0x%p\r\n", Itr->second->GetStartVa()); Interface::Log(" | Mapped file size: %d\r\n", Itr->second->GetEntitySize()); Interface::Log(" | Mapped file path: %ws\r\n", dynamic_cast(Itr->second)->GetFileBase()->GetPath().c_str()); } /* if (Itr->second->GetRegionInfo() != nullptr) { // Due to flag inconsistency between architectures and different Windows version MEMORY_REGION_INFORMATION has been excluded } */ } // // Display the section/sblock information associated with this entity provided it meets the selection criteria // vector Subregions = Itr->second->GetSubregions(); for (vector::iterator SbrItr = Subregions.begin(); SbrItr != Subregions.end(); ++SbrItr) { if (MemSelectType == MemorySelection_t::All || (MemSelectType == MemorySelection_t::Block && (pSelectAddress == (*SbrItr)->GetBasic()->BaseAddress || (qwOptFlags & PROCESS_ENUM_FLAG_FROM_BASE))) || (MemSelectType == MemorySelection_t::Suspicious && ((qwOptFlags & PROCESS_ENUM_FLAG_FROM_BASE) || (SbrMap != nullptr && SbrMap->count(static_cast((*SbrItr)->GetBasic()->BaseAddress))) && SubEntitySuspCount(SbrMap, static_cast((*SbrItr)->GetBasic()->BaseAddress)) > 0))) { wchar_t AlignedAttribDesc[9] = { 0 }; AlignName(Subregion::AttribDesc((*SbrItr)->GetBasic()), AlignedAttribDesc, 8); if (Itr->second->GetType() == Entity::Type::PE_FILE && !dynamic_cast(Itr->second)->GetFileBase()->IsPhantom()) { // // Generate a list of all sections overlapping with this sblock and display them all. A typical example is a +r sblock at the end of the PE which encompasses all consecutive readonly sections ie. .rdata, .rsrc, .reloc // vector OverlapSections = dynamic_cast(Itr->second)->FindOverlapSect(*(*SbrItr)); if (OverlapSections.empty()) { Interface::Log(" 0x%p:0x%08x | %ws | ? | 0x%08x", (*SbrItr)->GetBasic()->BaseAddress, (*SbrItr)->GetBasic()->RegionSize, AlignedAttribDesc, (*SbrItr)->GetPrivateSize()); AppendSubregionAttributes(*SbrItr); AppendOverlapSuspicion(SbrMap, static_cast((*SbrItr)->GetBasic()->BaseAddress), false); Interface::Log("\r\n"); } else{ for (vector::const_iterator SectItr = OverlapSections.begin(); SectItr != OverlapSections.end(); ++SectItr) { wchar_t AlignedSectName[9] = { 0 }; char AnsiSectName[9]; strncpy_s(AnsiSectName, 9, (char*)(*SectItr)->GetHeader()->Name, 8); wstring UnicodeSectName = UnicodeConverter.from_bytes(AnsiSectName); AlignName(static_cast(UnicodeSectName.c_str()), AlignedSectName, 8); Interface::Log(" 0x%p:0x%08x | %ws | %ws | 0x%08x", (*SbrItr)->GetBasic()->BaseAddress, (*SbrItr)->GetBasic()->RegionSize, AlignedAttribDesc, AlignedSectName, (*SbrItr)->GetPrivateSize()); AppendSubregionAttributes(*SbrItr); AppendOverlapSuspicion(SbrMap, static_cast((*SbrItr)->GetBasic()->BaseAddress), false); Interface::Log("\r\n"); } } } else { Interface::Log(" 0x%p:0x%08x | %ws | 0x%08x", (*SbrItr)->GetBasic()->BaseAddress, (*SbrItr)->GetBasic()->RegionSize, AlignedAttribDesc, (*SbrItr)->GetPrivateSize()); AppendSubregionAttributes(*SbrItr); AppendOverlapSuspicion(SbrMap, static_cast((*SbrItr)->GetBasic()->BaseAddress), false); Interface::Log("\r\n"); } if (Interface::GetVerbosity() == VerbosityLevel::Detail) { Interface::Log(" |__ Base address: 0x%p\r\n", (*SbrItr)->GetBasic()->BaseAddress); Interface::Log(" | Size: %d\r\n", (*SbrItr)->GetBasic()->RegionSize); Interface::Log(" | Permissions: %ws\r\n", Subregion::ProtectSymbol((*SbrItr)->GetBasic()->Protect)); Interface::Log(" | Type: %ws\r\n", Subregion::TypeSymbol((*SbrItr)->GetBasic()->Type)); Interface::Log(" | State: %ws\r\n", Subregion::StateSymbol((*SbrItr)->GetBasic()->State)); Interface::Log(" | Allocation base: 0x%p\r\n", (*SbrItr)->GetBasic()->AllocationBase); Interface::Log(" | Allocation permissions: %ws\r\n", Subregion::ProtectSymbol((*SbrItr)->GetBasic()->AllocationProtect)); Interface::Log(" | Private size: %d [%d pages]\r\n", (*SbrItr)->GetPrivateSize(), (*SbrItr)->GetPrivateSize() / 0x1000); } EnumerateThreads(L" ", (*SbrItr)->GetThreads()); if ((qwOptFlags & PROCESS_ENUM_FLAG_MEMDUMP)) { if (!(qwOptFlags & PROCESS_ENUM_FLAG_FROM_BASE)) { this->DumpBlock(DmpCtx, (*SbrItr)->GetBasic(), L" "); } } SelectedSbrs.push_back(*SbrItr); } } if ((qwOptFlags & PROCESS_ENUM_FLAG_MEMDUMP)) { if ((qwOptFlags & PROCESS_ENUM_FLAG_FROM_BASE)) { if (Itr->second->Dump(DmpCtx)) { Interface::Log(" ~ Generated full region dump at 0x%p\r\n", Itr->second->GetStartVa()); } else { Interface::Log(" ~ Failed to generate full region dump at 0x%p\r\n", Itr->second->GetStartVa()); } } } } } return SelectedSbrs; }