mirror of
https://github.com/forrest-orr/moneta
synced 2026-06-08 14:14:09 +00:00
166 lines
7.5 KiB
C++
166 lines
7.5 KiB
C++
/*
|
|
____________________________________________________________________________________________________
|
|
| _______ _____ __ _ _______ _______ _______ |
|
|
| | | | | | | \ | |______ | |_____| |
|
|
| | | | |_____| | \_| |______ | | | |
|
|
|__________________________________________________________________________________________________|
|
|
| Moneta ~ Usermode memory scanner & malware hunter |
|
|
|--------------------------------------------------------------------------------------------------|
|
|
| https://www.forrest-orr.net/post/masking-malicious-memory-artifacts-part-ii-insights-from-moneta |
|
|
|--------------------------------------------------------------------------------------------------|
|
|
| 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 "Memory.hpp"
|
|
#include "Interface.hpp"
|
|
#include "Processes.hpp"
|
|
|
|
using namespace std;
|
|
using namespace Memory;
|
|
using namespace Processes;
|
|
|
|
Subregion::Subregion(Processes::Process &OwnerProc, const MEMORY_BASIC_INFORMATION* Mbi) : ProcessHandle(OwnerProc.GetHandle()), Basic(Mbi) {
|
|
vector<Processes::Thread*> Threads = OwnerProc.GetThreads();
|
|
vector<void*> Heaps = OwnerProc.GetHeaps();
|
|
|
|
for (vector<Processes::Thread*>::const_iterator ThItr = Threads.begin(); ThItr != Threads.end(); ++ThItr) {
|
|
if ((*ThItr)->GetEntryPoint() >= this->Basic->BaseAddress && (*ThItr)->GetEntryPoint() < (static_cast<uint8_t *>(this->Basic->BaseAddress) + this->Basic->RegionSize)) {
|
|
this->Threads.push_back(new Processes::Thread((*ThItr)->GetTid(), OwnerProc));
|
|
}
|
|
|
|
if ((*ThItr)->GetStackAddress() >= this->Basic->BaseAddress && (*ThItr)->GetStackAddress() < (static_cast<uint8_t*>(this->Basic->BaseAddress) + this->Basic->RegionSize)) {
|
|
this->Flags |= MEMORY_SUBREGION_FLAG_STACK;
|
|
}
|
|
|
|
if ((*ThItr)->GetTebAddress() >= this->Basic->BaseAddress && (*ThItr)->GetTebAddress() < (static_cast<uint8_t*>(this->Basic->BaseAddress) + this->Basic->RegionSize)) {
|
|
this->Flags |= MEMORY_SUBREGION_FLAG_TEB;
|
|
}
|
|
}
|
|
|
|
if (OwnerProc.GetImageBase() >= this->Basic->BaseAddress && OwnerProc.GetImageBase() < (static_cast<uint8_t*>(this->Basic->BaseAddress) + this->Basic->RegionSize)) {
|
|
this->Flags |= MEMORY_SUBREGION_FLAG_BASE_IMAGE;
|
|
}
|
|
|
|
if (find(Heaps.begin(), Heaps.end(), Mbi->BaseAddress) != Heaps.end()) {
|
|
this->Flags |= MEMORY_SUBREGION_FLAG_HEAP;
|
|
}
|
|
|
|
if (Mbi->State == MEM_COMMIT && Mbi->Type != MEM_PRIVATE) {
|
|
this->PrivateSize = this->QueryPrivateSize(); // Querying the working set is one of the greatest performance drains in the tool and should be done sparingly
|
|
}
|
|
}
|
|
|
|
Subregion::~Subregion() {
|
|
if (this->Basic != nullptr) {
|
|
delete Basic;
|
|
}
|
|
|
|
for (vector<Processes::Thread*>::const_iterator Itr = this->Threads.begin(); Itr != this->Threads.end(); ++Itr) {
|
|
delete* Itr;
|
|
}
|
|
}
|
|
|
|
const wchar_t* Subregion::ProtectSymbol(uint32_t dwProtect) {
|
|
switch (dwProtect) {
|
|
case PAGE_READONLY: return L"R";
|
|
case PAGE_READWRITE: return L"RW";
|
|
case PAGE_EXECUTE_READ: return L"RX";
|
|
case PAGE_EXECUTE_READWRITE: return L"RWX";
|
|
case PAGE_EXECUTE_WRITECOPY: return L"RWXC";
|
|
case PAGE_EXECUTE: return L"X";
|
|
case PAGE_WRITECOPY: return L"WC";
|
|
case PAGE_NOACCESS: return L"NA";
|
|
case PAGE_WRITECOMBINE: return L"WCB";
|
|
case (PAGE_GUARD | PAGE_READWRITE): // Typically these flags are never combined: page guard is an exception
|
|
case PAGE_GUARD: return L"PG";
|
|
case PAGE_NOCACHE: return L"NC";
|
|
case 0: return L"-";
|
|
default: return L"?";
|
|
}
|
|
}
|
|
|
|
const wchar_t* Subregion::StateSymbol(uint32_t dwState) {
|
|
switch (dwState) {
|
|
case MEM_COMMIT: return L"Commit";
|
|
case MEM_FREE: return L"Free";
|
|
case MEM_RESERVE: return L"Reserve";
|
|
default: return L"?";
|
|
}
|
|
}
|
|
|
|
const wchar_t* Subregion::AttribDesc(const MEMORY_BASIC_INFORMATION* Mbi) {
|
|
switch (Mbi->State) {
|
|
case MEM_COMMIT: return ProtectSymbol(Mbi->Protect);
|
|
case MEM_FREE: return L"Free";
|
|
case MEM_RESERVE: return L"Reserve";
|
|
default: return L"?";
|
|
}
|
|
}
|
|
|
|
const wchar_t* Subregion::TypeSymbol(uint32_t dwType) {
|
|
switch (dwType) {
|
|
case MEM_IMAGE: return L"IMG";
|
|
case MEM_MAPPED: return L"MAP";
|
|
case MEM_PRIVATE: return L"PRV";
|
|
default: return L"?";
|
|
}
|
|
}
|
|
|
|
uint32_t Subregion::QueryPrivateSize() const {
|
|
static RtlGetVersion_t RtlGetVersion = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetVersion"));
|
|
static RTL_OSVERSIONINFOW osvi = { 0 };
|
|
uint32_t dwPrivateSize = 0;
|
|
|
|
if (RtlGetVersion && !osvi.dwBuildNumber) {
|
|
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
|
RtlGetVersion(&osvi);
|
|
}
|
|
|
|
if (this->Basic->State == MEM_COMMIT && this->Basic->Protect != PAGE_NOACCESS && this->Basic->Type == MEM_IMAGE) { // Optimize performance by skipping working set scan for non-image memory, as this data is not valuable for private and mapped types.
|
|
MEMORY_WORKING_SET_EX_INFORMATION WorkingSets = { 0 };
|
|
|
|
for (uint32_t dwPageOffset = 0; dwPageOffset < this->Basic->RegionSize; dwPageOffset += 0x1000) {
|
|
WorkingSets.VirtualAddress = (static_cast<uint8_t *>(this->Basic->BaseAddress) + dwPageOffset);
|
|
if (K32QueryWorkingSetEx(this->ProcessHandle, &WorkingSets, sizeof(MEMORY_WORKING_SET_EX_INFORMATION))) {
|
|
// Use SharedOriginal after RS3/1709
|
|
// https://windows-internals.com/understanding-a-new-mitigation-module-tampering-protection/
|
|
if (osvi.dwBuildNumber >= 16299) {
|
|
if (!WorkingSets.VirtualAttributes.SharedOriginal) {
|
|
dwPrivateSize += 0x1000;
|
|
}
|
|
} else {
|
|
if (!WorkingSets.VirtualAttributes.Shared) {
|
|
dwPrivateSize += 0x1000;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Interface::Log(Interface::VerbosityLevel::Debug, "... failed to query working set at 0x%p\r\n", WorkingSets.VirtualAddress);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dwPrivateSize;
|
|
}
|
|
|
|
bool Subregion::PageExecutable(uint32_t dwProtect) {
|
|
return (dwProtect == PAGE_EXECUTE || dwProtect == PAGE_EXECUTE_READ || dwProtect == PAGE_EXECUTE_READWRITE);
|
|
} |