Detours是一个软件包,用于在应用程序下重新路由(拦截)Win32 API。[Detours]
我们首先需要编译指定的库,如果我们需要拦截32位程序下的函数,就编译x86版本,如果用在64位下,则编译x64版本。这个编译就不说明了。
主要使用到如下的接口:
1、DetourAttach&DetourDetach
LONG WINAPI DetourAttach(_Inout_ PVOID *ppPointer,
_In_ PVOID pDetour);
LONG WINAPI DetourDetach(_Inout_ PVOID *ppPointer,
_In_ PVOID pDetour);
前一个接口用于将拦截函数附加到目标函数,后一个接口用于取消目标函数的路由。
参数说明:
ppPointer :指向将附加到的目标指针的指针(地址)。
pDetour:指向拦截函数的指针(地址)。
返回值:
如果成功,则返回NO_ERROR;否则,返回错误代码。
在使用这两个接口前,需要调用下面的前置接口和提交接口
2、DetourTransactionBegin
开始事务
3、 DetourUpdateThread
更新线程
4、DetourTransactionCommit
提交事务
//附加拦截函数
void StartHook() {
//开始事务
DetourTransactionBegin();
//更新线程信息
DetourUpdateThread(GetCurrentThread());
//将拦截函数附加到原函数的地址上
DetourAttach(&(PVOID&)pfnOld, pfnNew);
//结束事务
DetourTransactionCommit();
}
//解除拦截函数
void EndHook() {
//开始事务
DetourTransactionBegin();
//更新线程信息
DetourUpdateThread(GetCurrentThread());
//将拦截函数从原函数的地址上解除
DetourDetach(&(PVOID&)pfnOld, pfnNew);
//结束事务
DetourTransactionCommit();
}
Detours可以拦截我们想要拦截的函数,并获取或者修改函数调用信息,一般我们会编写一个dll,在dllmain中attach和detach我们的拦截函数,从而在目标进程加载我们dll时,拦截目标函数,在卸载dll时,恢复目标函数。那么远程进程注入,是一个不错的选择,可以在目标进程运行时,注入调试库,不影响目标进程的运行。DLL注入不是本章重点,所以,此处使用最简单的注入方式,也就是远程线程注入方式(仅学习使用,请勿用于非法用途,且这种方式也是最容易被发现的注入方式)。
我写了一个简单的dll注入+Detours拦截指定函数的源码(vs2022),有需要的可以下载参考下
GitHub - Prophecy2015/DLLInject
其中DLLInject是一个MFC编写的dll注入的工具,功能仅仅是向目标进程注入dll用的,需要使用管理员权限运行,不过,由于是注入工具,会被系统报病毒威胁。
TestAddDLL就是使用Detours编写的拦截Add方法的调试库
TestApp是持续调用Add方法的控制台程序,使用DLLInject向该程序注入TestAddDLL,即可打印拦截函数的内容。
要通过远程注入,拦截指定函数,首先就需要找到指定函数在指定进程加载后的虚拟内存空间地址,这样才能正确调用DetourAttach函数,毕竟该函数第一个参数就是待拦截的函数的地址。寻找函数地址的方法有很多,上述GitHub源码,使用了两种途径获取函数地址
这种方式,适用于被导出的函数接口,这种函数接口,可以在PE文件的导出表中,找到对应的RVA,从而计算出VA地址,无需依赖dbghelp.dll接口就可以实现。
可以参考GetExportFunctionsVa函数的实现,这里涉及到对PE文件格式的解析,有兴趣的可以参考下我学习pe格式时,写的一个PETest工程(https://gitee.com/gaojunhuiwww/PETest.git),纯学习性质,大神勿喷
PVOID CMisc::GetExportFunctionsVa(const char* szModuleName, const char* szFuncName)
{
HMODULE hMod = ::GetModuleHandleA(szModuleName);
if (hMod == NULL)
{
DLL_TRACE(_T("Can not find %s!"), szModuleName);
return NULL;
}
DWORD dwFuncRva = GetExportFunctionsRva(hMod, szFuncName);
if (dwFuncRva == 0)
{
DLL_TRACE(_T("Can not find %s in %s!"), szFuncName, szModuleName);
return NULL;
}
DLL_TRACE(_T("%s!%s : %llX!"), szModuleName, szFuncName, (PVOID)((PBYTE)hMod + dwFuncRva));
return (PVOID)((PBYTE)hMod + dwFuncRva);
}
DWORD CMisc::GetExportFunctionsRva(HMODULE hModule, const char* strFuncName)
{
// 获取ExportsTableRva
DWORD dwExportTableRva = 0;
PBYTE pByte = (PBYTE)hModule;
PBYTE pTmp = pByte;
if (IMAGE_DOS_SIGNATURE == *(WORD*)pTmp)
{
//DOS头
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pTmp;
pTmp += pDosHeader->e_lfanew;
}
if (IMAGE_NT_SIGNATURE != *(DWORD*)(PBYTE)pTmp)
{
return 0;
}
pTmp += sizeof(DWORD);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)pTmp;
pTmp += sizeof(IMAGE_FILE_HEADER);
// opt tou
if (IMAGE_NT_OPTIONAL_HDR32_MAGIC == *(WORD*)(PBYTE)pTmp)
{
// 32位头
dwExportTableRva = ((PIMAGE_OPTIONAL_HEADER32)pTmp)->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
else if (IMAGE_NT_OPTIONAL_HDR64_MAGIC == *(WORD*)(PBYTE)pTmp)
{
// 32位头
dwExportTableRva = ((PIMAGE_OPTIONAL_HEADER64)pTmp)->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
//
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pByte + dwExportTableRva);
for (int i = 0; i < pExportDir->NumberOfNames; i++)
{
std::string strName = (char*)((PBYTE)pByte + *(DWORD*)((PBYTE)pByte + pExportDir->AddressOfNames + i * sizeof(DWORD)));
if (strName == strFuncName)
{
WORD wOrdinal = *(WORD*)((PBYTE)pByte + pExportDir->AddressOfNameOrdinals + i * sizeof(WORD));
if (wOrdinal < pExportDir->NumberOfFunctions)
{
return *(DWORD*)((PBYTE)pByte + pExportDir->AddressOfFunctions + wOrdinal * sizeof(DWORD));
}
}
}
return 0;
}
这种方式,只要你有指定模块的pdb文件,你就可以找到该模块的大部分函数的地址,inline和被优化的函数除外,也包括了上诉的导出表函数。不过需要依赖dbghelp.dll。
可以参考GetFunctionsVaFromSymbols函数的实现
PVOID CMisc::GetFunctionsVaFromSymbols(PCTSTR szModuleName, PCTSTR szFunctionName, PCTSTR szSymPath/* = nullptr*/)
{
HMODULE hMod = 0;
HANDLE hProcess = 0;
DWORD64 BaseOfDll = 0;
PIMAGEHLP_SYMBOL pSymbol = NULL;
PVOID pRet = NULL;
DWORD Options = SymGetOptions();
Options = Options | SYMOPT_DEBUG;
SymSetOptions(Options);
if (szModuleName)
{
hMod = GetModuleHandle(szModuleName);
}
if (hMod == 0)
{
DLL_TRACE(_T("Cannot find module %s"), szModuleName);
return NULL;
}
do
{
hProcess = GetCurrentProcess();
BOOL bRet = SymInitialize(hProcess, 0, TRUE);
if (FALSE == bRet)
{
DLL_TRACE(_T("SymInitialize error ..."));
break;
}
TCHAR SymbolPath[256];
GetCurrentDirectory(sizeof(SymbolPath) / sizeof(TCHAR), SymbolPath);
if (nullptr != szSymPath)
{
_tcscat_s(SymbolPath, _T(";"));
_tcscat_s(SymbolPath, szSymPath);
}
_tcscat_s(SymbolPath, _T(";"));
_tcscat_s(SymbolPath, g_szPDBPath);
SymSetSearchPath(hProcess, SymbolPath);
TCHAR FileName[256];
GetCurrentDirectory(sizeof(FileName) / sizeof(TCHAR), FileName);
_tcscat_s(FileName, _T("\\"));
_tcscat_s(FileName, szModuleName);
BaseOfDll = SymLoadModuleEx(hProcess, NULL, FileName, NULL, (DWORD64)hMod, 0, NULL, 0);
if (BaseOfDll == 0)
{
DLL_TRACE(_T("SymLoadModule %s error code:%d"), FileName, GetLastError());
break;
}
ULONG64 buffer[(sizeof(SYMBOL_INFO) +
MAX_SYM_NAME * sizeof(TCHAR) +
sizeof(ULONG64) - 1) /
sizeof(ULONG64)];
PSYMBOL_INFO pSym = (PSYMBOL_INFO)buffer;
pSym->SizeOfStruct = sizeof(SYMBOL_INFO);
pSym->MaxNameLen = MAX_SYM_NAME;
if (TRUE == SymFromName(hProcess, szFunctionName, pSym))
{
pRet = (PVOID)pSym->Address;
DLL_TRACE(_T("%s!%s: %llX"), szModuleName, szFunctionName, pRet);
}
else
{
DLL_TRACE(_T("Can not find symbol %s!%s"), szModuleName, szFunctionName);
}
} while (false);
if (BaseOfDll != 0)
{
BOOL bRet = SymUnloadModule(hProcess, BaseOfDll);
if (bRet == FALSE)
{
DLL_TRACE(_T("SymUnloadModule Failed! err:%d"), GetLastError());
}
BaseOfDll = 0;
}
SymCleanup(hProcess);
return pRet;
}