Detours的使用方法

一、Detours介绍

Detours是一个软件包,用于在应用程序下重新路由(拦截)Win32 API。[Detours]

二、Detours使用

我们首先需要编译指定的库,如果我们需要拦截32位程序下的函数,就编译x86版本,如果用在64位下,则编译x64版本。这个编译就不说明了。

2.1、主要接口说明

主要使用到如下的接口:

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

提交事务

2.2、代码示例

//附加拦截函数
void StartHook() {
	//开始事务
	DetourTransactionBegin();
	//更新线程信息
	DetourUpdateThread(GetCurrentThread());
	//将拦截函数附加到原函数的地址上
	DetourAttach(&(PVOID&)pfnOld, pfnNew);
	//结束事务
	DetourTransactionCommit();
}

//解除拦截函数
void EndHook() {
	//开始事务
	DetourTransactionBegin();
	//更新线程信息 
	DetourUpdateThread(GetCurrentThread());
	//将拦截函数从原函数的地址上解除
	DetourDetach(&(PVOID&)pfnOld, pfnNew);
	//结束事务
	DetourTransactionCommit();
}

三、配合DLL注入使用Detours

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,即可打印拦截函数的内容。

3.1、找到拦截函数地址

要通过远程注入,拦截指定函数,首先就需要找到指定函数在指定进程加载后的虚拟内存空间地址,这样才能正确调用DetourAttach函数,毕竟该函数第一个参数就是待拦截的函数的地址。寻找函数地址的方法有很多,上述GitHub源码,使用了两种途径获取函数地址

3.1.1、通过导出表,查找导出函数

这种方式,适用于被导出的函数接口,这种函数接口,可以在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;
}

3.1.2、通过dbghelp接口查询指定函数

这种方式,只要你有指定模块的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;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙语者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值