Detours

什么是Detours 
简单地说,Detours是微软提供的一个开发库,使用它可以简单、高校、稳定地实现API HOOK的功能。

  Detours是一个可以在x86、x64和IA64平台上测试任意Win32函数的程序开发库。它可以通过为目标函数重写在内存中的代码而达到拦截Win32函数的目的。Detours还可以将任意的DLL或数据片段(称之为有效载荷)注入到任意Win32二进制文件中。Detours 被广泛应用在微软内部和其他行业中。 
你可以从微软官方网站下载到Detours的express版本,目前最新版本是2.1,它包含有下列的崭新特性:

* 完整的Detours API文档
* 附加和拆卸处理模块  
* 支持附加和拆卸Detours的时候更新对等线程
* 静态和动态注入单个API
* 支持监视注入后的进程  
* 改进后更加健壮的API可以将一个挂钩函数,通过一个进程附着到dll上
* 新的API通过复制将有效载荷注入到目标进程中
* 支持x64和IA64平台上的64-位源代码(仅专业版可用)  
* 支持Visual Studio 2005,Visual Studio .NET 2003, Visual Studio .NET(VC8)以及Visual Studio (VC7)开发工具

Detours的使用

Detours开发包被下载回来会不能直接使用,安装完毕后需要自己使用nmake生成相应的DLL以及lib文件

 

一. 介绍 
  Api hook包括两部分:api调用的截取和api函数的重定向。通过api hook可以修改函数的参数和返回值。关于原理的详细内容参见《windows核心编程》第19章和第22章。

 

二. Detours API hook 
"Detours is a library for intercepting arbitrary Win32 binary functions on x86 machines. Interception code is applied dynamically at runtime. Detours replaces the first few instructions of the target function with an unconditional jump to the user-provided detour function. Instructions from the target function are placed in a trampoline. The address of the trampoline is placed in a target pointer. The detour function can either replace the target function, or extend its semantics by invoking the target function as a subroutine through the target pointer to the trampoline." 在Detours库中,驱动detours执行的是函数 DetourAttach(…).

LONG DetourAttach(
     PVOID * ppPointer,
     PVOID pDetour     
);

这个函数的职责是挂接目标API,函数的第一个参数是一个指向将要被挂接函数地址的函数指针,第二个参数是指向实际运行的函数的指针,一般来说是我们定义的替代函数的地址。但是,在挂接开始之前,还有以下几件事需要完成: 需要对detours进行初始化.  需要更新进行detours的线程.  这些可以调用以下函数很容的做到:

DetourTransactionBegin()  
DetourUpdateThread(GetCurrentThread())

在这两件事做完以后,detour函数才是真正地附着到目标函数上。在此之后,调用DetourTransactionCommit()是detour函数起作用并检查函数的返回值判断是正确还是错误。

1.  hook DLL 中的函数 
在这个例子中,将要hook winsock中的函数 send(…)和recv(…).在这些函数中,我将会在真正调用send或者recv函数前,把真正说要发送或者接收的消息写到一个日志文件中去。注意:我们自定义的替代函式一定要与被hook的函数具有相同的参数和返回值。例如,send函数的定义是这样的.

int send( 
   __in  SOCKET s,
   __in  const char *buf,
   __in  int len,
   __in  int flags );

因此,指向这个函数的指针看起来应该是这样的:

int (WINAPI *pSend)(SOCKET, const char*, intint) = send;

把函数指针初始化成真正的函数地址是ok的;另外还有一种方式是把函数指针初始化为NULL,然后用函数DetourFindFunction(…) 指向真正的函式地址.把send(…) 和 recv(…)初始化:

int (WINAPI *pSend)(SOCKET s, const char* buf, int len, int flags) = send; 
int WINAPI MySend(SOCKET s, const char* buf, int len, int flags); 
int (WINAPI *pRecv)(SOCKET s, char* buf, int len, int flags) = recv; 
int WINAPI MyRecv(SOCKET s, char* buf, int len, int flags);

现在,需要hook的函数和重定向到的函数已经定义好了。这里使用 WINAPI 是因为这些函数是用 __stdcall 返回值的导出函数,现在开始hook:

INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)  {  
    switch(Reason)
    {  
        case DLL_PROCESS_ATTACH:  
            DisableThreadLibraryCalls(hDLL);  
            DetourTransactionBegin();  
            DetourUpdateThread(GetCurrentThread());  
            DetourAttach(&(PVOID&)pSend, MySend);  
            if(DetourTransactionCommit() == NO_ERROR)  
                OutputDebugString("send() detoured successfully");
             break;  
                }  }

它基本上是用上面介绍的步骤开始和结束 —— 初始化,更新detours线程,用DetourAttach(…)开始hook函数,最后调用DetourTransactionCommit() 函数, 当调用成功时返回 NO_ERROR, 失败是返回一些错误码.下面是我们的函数的实现,我发送和接收的信息写入到一个日志文件中:

int WINAPI MySend(SOCKET s, const char* buf, int len, int flags)  
{
    fopen_s(&pSendLogFile, "C:\\SendLog.txt""a+");  
    fprintf(pSendLogFile, "%s\n", buf);  
    fclose(pSendLogFile);  
    return pSend(s, buf, len, flags);  }

int WINAPI MyRecv(SOCKET s, char* buf, int len, int flags)  {  
    fopen_s(&pRecvLogFile, "C:\\RecvLog.txt""a+");  
    fprintf(pRecvLogFile, "%s\n", buf);  
    fclose(pRecvLogFile);  
    return pRecv(s, buf, len, flags);  }

2.hook自定义c 函数 
 举例来说明,假如有一个函数,其原型为

int RunCmd(const char* cmd);

如果要hook这个函数,可以按照以下几步来做:

  • include 声明这个函数的头文件 
  • 定义指向这个函数的函数指针,int (* RealRunCmd)(const char*) = RunCmd; 
  • 定义detour函数,例如: int DetourRunCmd(const char*); 
  • 实现detour函数,如: 
Int DetourRunCmd(const char* cmd) 
{ 
   //extend the function,add what you want :)    
    Return RealRunCme(cmd); 
}

这样就完成了hook RunCmd函数的定义,所需要的就是调用DetourAttack

    DetourTransactionBegin(); 
    DetourUpdateThread(GetCurrentThread()); 
    DetourAttach(&(PVOID&)RealRunCmd, DetourRunCmd);
     if(DetourTransactionCommit() == NO_ERROR)      { 
         //error      }

3. hook类成员函数

Hook类成员函数通过在static函数指针来实现    还是举例说明,假如有个类定义如下:

class CData { public: 
    CData(void); 
    virtual ~CData(void);
    int Run(const char* cmd); };

现在需要hook int CData::Run(const char*) 这个函数,可以按照以下几步: 

a) 声明用于hook的类

class CDataHook { public: 
    int DetourRun(const char* cmd); 
    static int (CDataHook::* RealRun)(const char* cmd); };

b) 初始化类中的static函数指针

int (CDataHook::* CDataHook::RealRun)(const char* cmd) = (int (CDataHook::*)(const char*))&CData::Run;

c) 定义detour函数

  int CDataHook::DetourRun(const char* cmd) { 
    //添加任意你想添加的代码 
    int iRet = (this->*RealRun)(cmd);
    return iRet; } 

e)     调用detourAttach函数

    DetourTransactionBegin(); 
    DetourUpdateThread(GetCurrentThread()); 
    DetourAttach(&(PVOID&)CDataHook::RealRun, (PVOID)(&(PVOID&)CDataHook::DetourRun));
     if(DetourTransactionCommit() == NO_ERROR)     {
        //error     }

4. DetourCreateProcessWithDll

CREATE_SUSPENDED 标志调用函数CreateProcess. 新进程的主线程处于
暂停状态,因此DLL能在函数被运行钱被注入。注意:被注入的DLL最少要有一个导出函数. 如用testdll.dll注入到notepad.exe中: 

#undef UNICODE
#include <cstdio> 
#include <windows.h> 
#include <detours\detours.h>
int main()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi; 
    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    si.cb sizeof(STARTUPINFO);
    char* DirPath = new char[MAX_PATH];     
    char* DLLPath = new char[MAX_PATH]; //testdll.dll
    char* DetourPath = new char[MAX_PATH]; //detoured.dll
    GetCurrentDirectory(MAX_PATH, DirPath);     
    sprintf_s(DLLPath, MAX_PATH, "%s\\testdll.dll", DirPath);
    sprintf_s(DetourPath, MAX_PATH, "%s\\detoured.dll", DirPath);
    DetourCreateProcessWithDll(NULL, "C:\\windows\\notepad.exe",NULL,NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NU
LL,&si, &pi, DetourPath, DLLPath, NULL);
    delete [] DirPath;
    delete [] DLLPath;
    delete [] DetourPath;
    return 0; 
}

5. Detouring by Address 
假如出现这种情况怎么办?我们需要hook的函数既不是一个标准的WIN32 API,也不是导出函数。这时我们需要吧我们的程序和被所要注入的程序同事编译,或者,把函数的地址硬编码:

#undef UNICODE
#include <cstdio> 
#include <windows.h> 
#include <detours\detours.h>  
typedef void (WINAPI *pFunc)(DWORD); 
void WINAPI MyFunc(DWORD);  
pFunc FuncToDetour = (pFunc)(0x0100347C); //Set it at address to detour in the process 
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) {      
    switch(Reason)      
    {
           case DLL_PROCESS_ATTACH:
           {
                DisableThreadLibraryCalls(hDLL);
                DetourTransactionBegin();               
                DetourUpdateThread(GetCurrentThread());      
                DetourAttach(&(PVOID&)FuncToDetour, MyFunc); 
                DetourTransactionCommit();         
             }           
            break;     
            case DLL_PROCESS_DETACH:
               DetourTransactionBegin();         
               DetourUpdateThread(GetCurrentThread());
               DetourDetach(&(PVOID&)FuncToDetour, MyFunc);
               DetourTransactionCommit();           
            break;     
            case DLL_THREAD_ATTACH:
            case DLL_THREAD_DETACH:
            break;     
    }          
return TRUE; 
}  
void WINAPI MyFunc(DWORD someParameter) {      //Some magic can happen here            }            

使用案例:

// HOOK.CPP 
#include "Detours.h" 
#pragma comment(lib,"detours.lib")
#pragma comment(lib,"detoured.lib") // 共享数据段 
#pragma data_seg("MyData") 
DWORD nPid 0// 受保护的进程PID 
#pragma data_seg() 
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call,LPVOID lpReserved )
{
  switch (ul_reason_for_call) { 
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH: 
    { 
    // 安装 HOOK
      SetHook(TRUE); break; 
    } 
    case DLL_PROCESS_DETACH: 
    { 
    // 卸载 HOOK 
      SetHook(FALSE);
    } 
    case DLL_THREAD_DETACH:

    defaultbreak
  } 
return TRUE; 


/*   * 函数:SetHook  *   * 作用:安装/卸载 API HOOK  *   * 参数:TRUE - 安装,FALSE - 卸载  *   * 返回:无  */  void SetHook(BOOL flag) 
  if (flag) 
  {      DetourRestoreAfterWith();
    DetourTransactionBegin();      DetourUpdateThread(GetCurrentThread());
    // HOOK 函数列表      DetourAttach(&(PVOID&)Old_OpenProcess, New_OpenProcess);

    DetourTransactionCommit(); 
  } 
  else 
  {      DetourTransactionBegin();      DetourUpdateThread(GetCurrentThread()); 
// 取消 HOOK 函数列表      DetourDetach(&(PVOID&)Old_OpenProcess, New_OpenProcess);

    DetourTransactionCommit(); }  } 
/*   * 函数:New_OpenProcess  *   * 作用:拦截系统 OpenProcess 函数调用  
*/  HANDLE WINAPI New_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) 
// 是否为可信任程序  if (strcmp(szCurrentApp, szTrustFile) != 0
// 是否为受保护进程 
  if (dwProcessId == nPid) 
  { 
    return NULL; 
  } 
return Old_OpenProcess( dwDesiredAccess, bInheritHandle, dwProcessId); 
} }
//Hook.h
static HANDLE (WINAPI* Old_OpenProcess)(DWORD dwDesiredAccess, BOOL bInheritHandle, 
DWORD dwProcessId) = OpenProcess; 
HANDLE WINAPI New_OpenProcess(DWORD dwDesiredAccess,   BOOL bInheritHandle,   DWORD dwProcessId);

然后要在def文件中将New_OpenProcess函数导出,最后将生成的HOOK.DLL注入到其他进程空间就可以了。 
不过需要说明的是,上述程序远远不够完善,我删掉了很多代码,而那些代码也是很重要的,比如设置受保护进程的PID、设置可信程序路径等函数我都删掉了,你必须发挥自己的聪明才智来解决这些问题。 
提示一下,我采用的方法是专门导出一个函数SetOption,它得到设置选项后便更新共享驱动里面的变量,这样便可以简单达到目的了

posted @ 2013-08-29 16:36  Dayw  阅读(4905)  评论(0编辑  收藏  举报