在XDebug的源码学习中看到的命令行操作,将主要源码摘录出来做了个获取和更改进程命令行的Demo.
0x01 获取命令行
这里获取命令行的方式并不是通过调用GetCommandLine函数。
而是借由ntdll中的NtQueryInformationProcess函数查询ProcessBasicInformation,得到ProcessBasicInformation中的PebBaseAddress字段。得到进程PEB地址后,再通过PEB地址的获取ProcessParameter字段地址,

最后通过ProcessParameter字段获取到ProcessParameter的 CommandLine 字段,得到了命令行的存储地址。

主要源码:
void* GetPEBLocation(HANDLE hProcess)
{
ULONG RequiredLen = 0;
void* PebAddress = 0;
PROCESS_BASIC_INFORMATION myProcessBasicInformation[5] = { 0 };
if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, sizeof(PROCESS_BASIC_INFORMATION), &RequiredLen) == STATUS_SUCCESS)
{
PebAddress = (void*)myProcessBasicInformation->PebBaseAddress;
}
else
{
if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, RequiredLen, &RequiredLen) == STATUS_SUCCESS)
{
PebAddress = (void*)myProcessBasicInformation->PebBaseAddress;
}
}
return PebAddress;
}
BOOL Getcommandlineaddr(duint *CommandLineAddressdr)
{
duint PEBAddress;
duint pprocess_parameters;
duint ProcessParametersAddress;
duint ReturnLength;
PEBAddress = (duint)GetPEBLocation(__ProcessHandle);
ProcessParametersAddress = (duint) & (((PPEB)PEBAddress)->ProcessParameters);
ReadProcessMemory(__ProcessHandle, reinterpret_cast<LPVOID>(ProcessParametersAddress), &pprocess_parameters,
sizeof(pprocess_parameters), reinterpret_cast<SIZE_T*>(&ReturnLength));
*CommandLineAddressdr = (duint) & (((RTL_USER_PROCESS_PARAMETERS*)pprocess_parameters)->CommandLine);
return TRUE;
}
0x02 修改命令行
修改命令行的关键就是操作内存。
XDebug首先获取GetCommandLineA或GetCommandLineW函数的地址。
if (!valfromstring("kernelBase:GetCommandLineA", &getcommandline))
{
if (!valfromstring("kernel32:GetCommandLineA", &getcommandline))
{
return FALSE;
}
}
bool valfromstring(const char* string, duint* value, bool silent, bool baseonly, int* value_size, bool* isvar, bool* hexonly, bool allowassign)
{
duint result;
if (!Calculate(string, result, false /*valuesignedcalc()*/, allowassign, silent, baseonly, value_size, isvar, hexonly))
return false;
*value = result;
return true;
}
bool Calculate(const char* string, duint & value, bool signedcalc, bool allowassign, bool silent, bool baseonly, int* value_size, bool* isvar, bool* hexonly)
{
return DoEvaluate(string, value, silent, baseonly, value_size, isvar, hexonly);
}
bool DoEvaluate(const char* string, duint & result, bool silent, bool baseonly, int* value_size, bool* isvar, bool* hexonly)
{
return valfromstring_noexpr(string, (duint*)&result, silent, baseonly, value_size, isvar, hexonly);
}
bool valfromstring_noexpr(const char* string, duint* value, bool silent, bool baseonly, int* value_size, bool* isvar, bool* hexonly)
{
if (!value || !string || !*string)
return false;
if (valapifromstring(string, value, value_size, true, silent, hexonly)) //then come APIs
return true;
return false;
}
bool valapifromstring(const char* name, duint* value, int* value_size, bool printall, bool silent, bool* hexonly)
{
//explicit API handling
const char* apiname = strchr(name, ':'); //the ':' character cannot be in a path: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#naming_conventions
bool noexports = false;
if (!apiname) //not found
{
apiname = strstr(name, "..") ? strchr(name, '.') : strrchr(name, '.'); //kernel32.GetProcAddress support
if (!apiname) //not found
{
apiname = strchr(name, '?'); //the '?' character cannot be in a path either
noexports = true;
}
}
if (apiname)
{
char modname[MAX_MODULE_SIZE] = "";
strncpy_s(modname, name, _TRUNCATE);
modname[apiname - name] = 0;
apiname++;
if (!strlen(apiname))
return false;
duint modbase = 0;//ModBaseFromName(modname);从MAP表里查基地址
char szModPath[MAX_PATH] = "kernel32.dll";
if (!1/*ModPathFromAddr(modbase, szModPath, _countof(szModPath))*/)//查表得到完整路径
{
//if (!silent)
//dprintf(QT_TRANSLATE_NOOP("DBG", "Could not get filename of module %p
"), modbase);
}
else
{
HMODULE mod = LoadLibraryExW(Utf8ToUtf16(szModPath).c_str(), 0, DONT_RESOLVE_DLL_REFERENCES);
if (!mod)
{
//if (!silent)
// dprintf(QT_TRANSLATE_NOOP("DBG", "Unable to load library %s
"), szModPath);
}
else
{
duint addr = noexports ? 0 : SafeGetProcAddress(mod, apiname);
//if (addr) //found exported function
// addr = modbase + (addr - (duint)mod); //correct for loaded base
//else //not found
{
}
FreeLibrary(mod);
if (addr) //found!
{
if (value_size)
*value_size = sizeof(duint);
if (hexonly)
*hexonly = true;
*value = addr;
return true;
}
}
}
return false;
}
return true;
}
获取到这个所谓的“函数地址后并没有结束,这里需要设计到重定向表的重定向问题,在这个地址的基础上还需要两次跳转才能得到真正的功能函数地址。这个地址上的第一次跳转,是跳向重定向表,然后重定向表再给出地址,跳向库中真正的功能函数地址。
对应的源码操作(图中getcommandline即为前文valfromstring函数获取的GetCommandLineA或GetCommandLineW函数的地址。)

调试反汇编查看获取到的getcommandline地址:

越过FF 25两字节,获取到跳转地址76b311fch:
ReadProcessMemory(__ProcessHandle, reinterpret_cast<LPVOID>(getcommandline + 2), data, 100, reinterpret_cast<SIZE_T*>(&ReturnLength));
内存窗口查看76b311fch:处内容,得到重定向表给出的功能函数地址:0x7406e210

ReadProcessMemory(__ProcessHandle, reinterpret_cast<LPVOID>(*(int*)data), data, 100, reinterpret_cast<SIZE_T*>(&ReturnLength));
反汇编窗口中查看0x7406e210中的内容:

现在就可以通过字符串匹配来得到命令行的存储地址:
/*
750FE9CA | A1 CC DB 1A 75 | mov eax,dword ptr ds:[751ADBCC] |
750FE9CF | C3 | ret |
*/
if (data[0] != 0xA1 || data[5] != 0xC3)
{
return FALSE;
}
command_line_stored = *((duint*)& data[1]);
找到地址后最后一步写入地址:
WriteProcessMemory(__ProcessHandle, reinterpret_cast<LPVOID>(command_line_stored), &new_command_line, sizeof(new_command_line), reinterpret_cast<SIZE_T*>(&ReturnLength));
//update the pointer in the debuggee