I am trying to modify command line arguments of my executable so that GetCommandLine() will return the string I set. Since I want to modify the command line value before anyone, I have changed my entry point to testme() function via /ENTRY switch and also set /NODEFAULTLIB option in order to exclude CRT. Using the following code why can I change the string buffer pointer by CommandLine but cannot allocate a completely new buffer?
The code:
#include <Windows.h>
#include <winternl.h>
typedef NTSTATUS (WINAPI *PFN_NtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength );
int testme()
{
// Get PEB block address
PROCESS_BASIC_INFORMATION pbi;
ULONG result;
PFN_NtQueryInformationProcess pfnQueryProcess =
(PFN_NtQueryInformationProcess) GetProcAddress(LoadLibrary("ntdll"),
"NtQueryInformationProcess");
pfnQueryProcess(GetCurrentProcessId(),
ProcessBasicInformation, &pbi, sizeof(pbi), &result);
// Modify ProcessParameters->CommandLine
// This works
pbi.PebBaseAddress->ProcessParameters->CommandLine.Buffer[0] = L'a';
pbi.PebBaseAddress->ProcessParameters->CommandLine.Buffer[1] = L' ';
pbi.PebBaseAddress->ProcessParameters->CommandLine.Buffer[2] = L'b';
pbi.PebBaseAddress->ProcessParameters->CommandLine.Buffer[3] = L'\0';
pbi.PebBaseAddress->ProcessParameters->CommandLine.Length = 6;
// This does not work
UNICODE_STRING cmdLine;
wchar_t wszNewCmdLine[] = L"x y\0";
cmdLine.Buffer = (wchar_t*)GlobalAlloc(GMEM_FIXED, sizeof(wchar_t)*pbi.PebBaseAddress->ProcessParameters->CommandLine.MaximumLength);
cmdLine.MaximumLength = pbi.PebBaseAddress->ProcessParameters->CommandLine.MaximumLength;
cmdLine.Length = sizeof(wszNewCmdLine) - sizeof(L'\0');
//Copy buffer
for(int i=0; i<cmdLine.Length; ++i)
cmdLine.Buffer[i] = wszNewCmdLine[i];
pbi.PebBaseAddress->ProcessParameters->CommandLine.Buffer = cmdLine.Buffer;
pbi.PebBaseAddress->ProcessParameters->CommandLine.Length = cmdLine.Length;
pbi.PebBaseAddress->ProcessParameters->CommandLine.MaximumLength = cmdLine.MaximumLength;
// Now testing, pCmdLine returned is "a b", not "x y".
wchar_t *pCmdLine = GetCommandLine();
return 0;
}
Unfortunately
GetCommandLineW
doesn't return the command line from the PEB. In theBaseDllInitialize
routine a copy is made of the PEB command line structure, and from then on this copy is used byGetCommandLineW
. You would need to locate this copy in memory in order to modify it, which seems quite difficult and also dangerous/unreliable.You could look at an API hook like Detours, but an easier solution might just be to launch your executable with your desired command line in the first place. You could test when it starts up if the command line is right and if not have it spawn another copy of itself with the desired command line.
After a bit of trial and error I come up with following. I wrote a C executable that only links with
kernel32.lib
and does not linkCRT
. In theexe
, I doEAT
patching ofGetCommandLineX
functions inkernel32.dll
. Then I load another of my dll (test.dll) which needsGetCommandLineX
method as part of its functions. Sincekernel32
is patched, loader fills the import table of the test.dll with the patched function pointers. In the end, methods in the test.dll calls my version of GetCommandLineX, which I can easily change their implementation.