这里写自定义目录标题
Detours Create Process & Load Dll API源码分析
Hook工作一般独立于宿主工程,甚至很多时候我们Hook的都不是自己的工程。那么如何注入就变成了一个严峻的问题。这部Detours API就是为了解决注入问题。
从withdll sample开始先看看疗效
D:\code\detour\Detours-main\bin.X64>withdll -d:simple64.dll sleep5.exe 2
withdll.exe: Starting: `sleep5.exe 2
withdll.exe: with `D:\code\detour\Detours-main\bin.X64\simple64.dll'
simple64.dll: Starting.
simple64.dll: Detoured SleepEx().
simple64.dll: Removed SleepEx() (result=0), slept 2016 ticks.
通过withdll加载simple64.dll 并启动 sleep5.exe,这样simple64 中Hook了sleep API在sleep中也生效。我们来看看如何实现的。
镇楼原理图
withdll sample 源码
为了更好的理解主线流程,删减掉来部分不核心的代码
int CDECL main(int argc, char **argv)
{
///
// 参数解析 ,略
///
// Dlls合法性检验,略
///
// 创建进程
// 参数拼接,略
STARTUPINFOA si;
PROCESS_INFORMATION pi;
DetourCreateProcessWithDllsA(szFullExe[0] ? szFullExe : NULL, szCommand,
NULL, NULL, TRUE, dwFlags, NULL, NULL,
&si, &pi, nDlls, rpszDllsOut, NULL);
// 唤醒主线程,直到运行结束,略
return 0;
}
核心是调用Detours API DetourCreateProcessWithDllsA 创建进程 。
DetourCreateProcessWithDllsA函数分析
BOOL WINAPI DetourCreateProcessWithDllsA(_In_opt_ LPCSTR lpApplicationName,
_Inout_opt_ LPSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOA lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation,
_In_ DWORD nDlls,
_In_reads_(nDlls) LPCSTR *rlpDlls,
_In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA)
{
if (pfCreateProcessA == NULL) {
pfCreateProcessA = CreateProcessA;
}
// 调用WindowsAPI创建进程并挂起
if (!pfCreateProcessA(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags | CREATE_SUSPENDED,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation)) {
return FALSE;
}
// 两种方式加载DLL
if (!DetourUpdateProcessWithDll(lpProcessInformation->hProcess, rlpDlls, nDlls) &&
!DetourProcessViaHelperDllsA(lpProcessInformation->dwProcessId,
nDlls,
rlpDlls,
pfCreateProcessA)) {
TerminateProcess(lpProcessInformation->hProcess, ~0u);
CloseHandle(lpProcessInformation->hProcess);
CloseHandle(lpProcessInformation->hThread);
return FALSE;
}
// 唤醒主线程
ResumeThread(lpProcessInformation->hThread);
CloseHandle(lpProcessInformation->hProcess);
CloseHandle(lpProcessInformation->hThread);
return TRUE;
}
先启动一个进程运行exe,在使用两种方式加载Dll。这里设置了启动后挂起,因此启动后的进程不会继续往下跑。
DetourUpdateProcessWithDll函数分析——加载方式1
BOOL WINAPI DetourUpdateProcessWithDll(_In_ HANDLE hProcess,
_In_reads_(nDlls) LPCSTR *rlpDlls,
_In_ DWORD nDlls)
{
// 查找已加载的PE image中最后一个hModule
HMODULE hModule = NULL;
HMODULE hLast = NULL;
for (;;) {
IMAGE_NT_HEADERS32 inh;
if ((hLast = EnumerateModulesInProcess(hProcess, hLast, &inh, NULL)) == NULL) {
break;
}
if ((inh.FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) {
hModule = hLast;
}
}
// 检查是不是64位系统,略
BOOL bIs64BitOS = TRUE;
// 检查是不是32为应用
BOOL bIs32BitProcess = FALSE;
// 调用xxxEX继续加载
return DetourUpdateProcessWithDllEx(hProcess,
hModule,
bIs32BitProcess,
rlpDlls,
nDlls);
}
检查系统是不是64位,exe是不是64,然后就继续调用DetourUpdateProcessWithDllEx了
DetourUpdateProcessWithDllEx函数分析
BOOL WINAPI DetourUpdateProcessWithDllEx(_In_ HANDLE hProcess,
_In_ HMODULE hModule,
_In_ BOOL bIs32BitProcess,
_In_reads_(nDlls) LPCSTR *rlpDlls,
_In_ DWORD nDlls)
{
// 确认PE块代码是不是32位,略
BOOL bIs32BitExe = FALSE;
// 修改之前(DetourRestoreAfterWith)先保存各种headers
DETOUR_EXE_RESTORE der;
if (!RecordExeRestore(hProcess, hModule, der)) {
return FALSE;
}
// exe是32位且进程是64位的处理逻辑
if (bIs32BitExe && !bIs32BitProcess) {
// 升级成64位
UpdateFrom32To64(hProcess, hModule,
IMAGE_FILE_MACHINE_AMD64,
der);
}
// 64位进程,64位PE结构,加载DLL
UpdateImports64(hProcess, hModule, rlpDlls, nDlls);
/// Update the CLR header.
// 更新CLR头:CLR(Common Language Runtime)头是.NET程序集的一部分
// .net可执行文件特殊处理,略
Save the undo data to the target process.
// 上保存的 DETOUR_EXE_RESTORE der 拷贝到目标进程里,做为 撤销(操作需要使用的)数据
DetourCopyPayloadToProcess(hProcess, DETOUR_EXE_RESTORE_GUID, &der, sizeof(der));
return TRUE;
}
保存环境,把运行环境和exe都改成64位,然后调用UpdateImports64 Load Dll
UpdateImports64函数分析
这个函数实在是太晦涩了,允许我先吐一会……
uimports.cpp就这一个300行风格古怪的函数,大概率是祖传代码。
实在受不了了!理解这个函数需要了解Dll的加载过程,我们单开一篇文章来讲解吧。
略略略略略略略略略略略略
略略略略略略略略略略略略
略略略略略略略略略略略略
DetourProcessViaHelperDllsA函数分析——加载方式2
BOOL WINAPI DetourProcessViaHelperDllsA(_In_ DWORD dwTargetPid,
_In_ DWORD nDlls,
_In_reads_(nDlls) LPCSTR *rlpDlls,
_In_ PDETOUR_CREATE_PROCESS_ROUTINEA pfCreateProcessA)
{
//把要加载的Dll都用AllocExeHelper处理一下,生成一个PDETOUR_EXE_HELPER结构
PDETOUR_EXE_HELPER helper = NULL;
AllocExeHelper(&helper, dwTargetPid, nDlls, rlpDlls);
// 拼接WINDIR路径
CHAR szExe[MAX_PATH];
GetEnvironmentVariableA("WINDIR", szExe, ARRAYSIZE(szExe));
// 拼出rundll32.exe的路径
StringCchCatA(szExe, ARRAYSIZE(szExe), "\\syswow64\\rundll32.exe");
CHAR szCommand[MAX_PATH];
StringCchPrintfA(szCommand, ARRAYSIZE(szCommand),
"rundll32.exe \"%s\",#1", &helper->rDlls[0]);
// 创建一个rundll32.exe的进程
PROCESS_INFORMATION pi;
STARTUPINFOA si;
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
pfCreateProcessA(szExe, szCommand, NULL, NULL, FALSE, CREATE_SUSPENDED,
NULL, NULL, &si, &pi);
// PDETOUR_EXE_HELPER结构的helper写到刚刚创建的,rundll32进程中去。
DetourCopyPayloadToProcess(pi.hProcess,
DETOUR_EXE_HELPER_GUID,
helper, helper->cb);
// 执行到结束
ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
// 回收资源
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
FreeExeHelper(&helper);
return TRUE;
}
有点迷糊了。这里又创建了一个rundll32.exe进程,且加载了dll。我们的预期是给DetourCreateProcessWithDllsA创建出的进程加载dll。是哪里理解错了吗?
我们实验一下吧。先把方式一的路堵上
观察启动rundll32.exe进程时的参数
szExe :C:\WINDOWS\syswow64\rundll32.exe
szCommandrundll32.exe “D:\code\detour\Detours-main\bin.X64\simple32.dll”,#1
语义是,使用rundll32.exe 打开 simple32.dll 的#1号导出函数。
Dependency瞅一眼,这是个啥?
最终在Makefile这里找到了结果
原来是这个函数!那我们调起来吧。
让主进程停在这里
这个时候rundll32.exe进程应该还在挂起状态,我们Attach上去看看。
这时停在ntdll里,因为创建进程以后就挂起了嘛,符合预期。
现在simple 的dllmain 和 DetourFinishHelperProcess 里都打上断线。看看到底怎么会回事。跑起来。
先进DllMain,符合预期。
原来是这里Load出了之前塞进rundll32.exe 里的 Helper。
和主进程里看到的完全一致。
再跑并没能顺利进入DetourFinishHelperProcess。但是看到的返回码确实符合预期。因为我们改了DetourUpdateProcessWithDll,这里必然失败。
至此,真相大白。我们也退出调试模式吧。
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
// 判断是不是加载了DetourHelper信息的进程,并且Load DetourHelper信息
if (DetourIsHelperProcess()) {
return TRUE;
}
}
rundllndll32进程,加载sample.dll时运行dllmain
BOOL WINAPI DetourIsHelperProcess(VOID)
{
// Load DetourHelper信息放在全家变量中。
pvData = DetourFindPayloadEx(DETOUR_EXE_HELPER_GUID, &cbData);
s_pHelper = (PDETOUR_EXE_HELPER)pvData;
return TRUE;
}
DetourHelper被加载在 s_pHelper中
VOID CALLBACK DetourFinishHelperProcess(_In_ HWND,
_In_ HINSTANCE,
_In_ LPSTR,
_In_ INT)
{
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, s_pHelper->pid);
rlpDlls = new NOTHROW LPCSTR [s_pHelper->nDlls];
DetourUpdateProcessWithDll(hProcess, rlpDlls, s_pHelper->nDlls);
ExitProcess(Result);
}
然后rundll32调用DetourFinishHelperProcess,于是又回来到方式一。
SO,假设方式一加载失败,Detours会调用rundll32起个进程,再跑一边加载dll的过程,相当于重试机制。