首页
社区
课程
招聘
[求助]Hook DirectX API 怎么Hook
发表于: 2009-2-13 11:48 20809

[求助]Hook DirectX API 怎么Hook

2009-2-13 11:48
20809
Hook DirectX API 怎么Hook?不是Hook Direct3DCreate8这个函数,而是Hook CreateDevice这样的函数,这是COM组件的函数,用什么方法Hook?有没有相关的介绍资料,或者是示范源程序?

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (6)
雪    币: 200
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
找到了一篇文章《对DirectX/COM接口的挂钩》
原文出处:http://blog.csdn.net/xieqidong/archive/2008/05/05/2391111.aspx

对DirectX/COM接口的挂钩

 一般的挂钩(Hook)都是针对Windows API或消息的,而本文要讲的是如何挂钩一个DirectX/COM接口,有意思吧,请往下看,文中以DirectInput作为范例进行讲解。
 
 
         目标
         相比挂钩一个API调用,拦截一个COM接口的方法需要做更多的工作,如果我们要拦截的DLL已经被作者仔细检查过,只导出了类似create这样的接口函数,那么怎样才能达到我们挂钩的目的呢?
 
 
 
 
         从本质上来说,一个COM接口就是一张与其链接在一起的虚函数指针列表,所以,我们只需跟踪链接,并查看每一个节点,直到找到想要替换的函数指针即可。
 
 
         第一步
         从上图也可以看到,只有类似create的接口COM函数是可见的,由于DirectInputCreate函数会返回一个COM接口,所以就从它开始跟踪,在此,可以把DLL注入到目标程序的输入地址表(IAT)中。
 
 
         第二步
         如果目标程序调用了DirectInputCreate,我们的函数也会被调用,而且,会得到一个指针,其指向了虚函数表的指针,而这个虚函数表就是DirectInput的接口。
 
 
DECLARE_INTERFACE_(IDirectInputW, IUnknown)
{
   /*** IUnknown methods ***/
   STDMETHOD(QueryInterface)(THIS_ REFIID riid,
                             LPVOID * ppvObj) PURE;
   STDMETHOD_(ULONG,AddRef)(THIS) PURE;
   STDMETHOD_(ULONG,Release)(THIS) PURE;
 
   /*** IDirectInputW methods ***/
   STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEW *,
                           LPUNKNOWN) PURE;
   STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKW,
                          LPVOID,DWORD) PURE;
   STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE;
   STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE;
   STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE;
};
 
 
         第三步
         现在,就可用CreateDevice创建自己的设备了,在此会再次得到一个不同的虚函数指针表地址,它代表了设备。
 
 
 
 
         选择需要替换的方法,并在适当位置修改虚函数指针表以注入我们自己的函数。
 
 
         实现
         下面是实现步骤
 
 
         第一步
         要对一个API函数进行挂钩,可以使用SetWindowsHookEx这个Windows API,在此,我们创建了一个系统钩子,以监视启动的进程,检查其中是否有我们的目标程序。在确定之后,必须把它的输入模块名与想要进行替换的DLL进行比较,因为我们是对DirectInput进行挂钩,所以此项为DINPUT8.DLL。要找到此DLL,需遍历描述符。
 
 
//遍历每个输入描述符,在必要时重定向
   while ( pImportDesc->FirstThunk )
   {
      PSTR pszImportModuleName = MakePtr( PSTR, hModEXE,
                                          pImportDesc->Name);
 
      if ( lstrcmpi( pszImportModuleName, Hook->Name ) == 0 )
      {
         sprintf(dbBuffer,"Dll Found in module %s replace it\n",
                 Hook->Name );
         WriteToLog(dbBuffer);
         RedirectIAT( Hook, pImportDesc, (PVOID)hModEXE );
      }
 
      pImportDesc++;    //继续下一个输入描述符
   }
 
 
         找到之后,应使用VirtualQuery( pIAT, &mbi, sizeof(mbi) )从IAT中移除写保护,这样就可以写入到内存中了。在内存打开之后,还需遍历IAT查找入口项。
 
 
while ( pIteratingIAT->u1.Function )
{
   void* HookFn = 0;
 
   if ( !IMAGE_SNAP_BY_ORDINAL( pINT->u1.Ordinal ) )
   {
      PIMAGE_IMPORT_BY_NAME pImportName =
         MakePtr( PIMAGE_IMPORT_BY_NAME, pBaseLoadAddr,
                  pINT->u1.AddressOfData );
 
      //遍历挂钩函数
      SFunctionHook* FHook = DLLHook->Functions;
      while ( FHook->Name )
      {
         if ( lstrcmpi( FHook->Name, (char*)pImportName->Name ) == 0 )
         {
            sprintf(dbBuffer,"Hooked function: %s\n",
                    (char*)pImportName->Name );
            WriteToLog(dbBuffer);
            //在结构SFunctionHook中保存被替换的函数
            FHook->OrigFn = (unsigned long*)pIteratingIAT->u1.Function;
            HookFn = FHook->HookFn;
            break;
         }
 
         FHook++;
      }
 
   }
}
 
 
         现在,可替换为自己的函数了。
 
 
//在挂钩之后,替换IAT函数指针
if ( HookFn )
{
   //检查是代码还是数据
   //如果是代码,不应写入。
   if ( IsBadWritePtr( (PVOID)pIteratingIAT->u1.Function, 1 ) )
   {
      pIteratingIAT->u1.Function = (DWORD)HookFn;
   }
   else if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
   {
      //检查“桩”是否在2GB内存地址空间之上
      if ( pIteratingIAT->u1.Function > (DWORD)0x80000000 )
         pIteratingIAT->u1.Function = (DWORD)HookFn;
   }
}
 
 
         最后就是还原内存属性。
 
 
VirtualProtect( pIAT, sizeof(PVOID) * cFuncs, flOldProtect, &flDontCare);
 
 
         第二步
         在CreateInterface方法内部,通过把我们的CreateDevice函数指针注入到虚函数表(Vtbl)中,就可以挂钩到COM接口内部,而虚函数表则由返回的ppvOut指针得到。
 
 
 DirectInput8Create_Type OldFn = 
      (DirectInput8Create_Type)D3DHook.Functions[D3DFN_DirectInput8Create].OrigFn;
    HRESULT hr = OldFn( hinst, dwVersion, riidltf, ppvOut, punkOuter );
 
 
         解析此指针,直到找到指向虚函数表接口的指针,而在这个地址上,需要再次移除内存保护,以便注入自己的函数到表中,并且保存原函数指针。
         接下来,把我们自己的函数指针注入到虚函数表接口内CreateDevice函数指针的偏移量上,并还原内存保护。
         可以看到,CreateDevice是DirectInput接口的第4个方法,这意味着其在虚函数表内的偏移量为0x0C(指针乘3)
 
 
typedef struct IDirectInput *LPDIRECTINPUT;
 
#if !defined(__cplusplus) || defined(CINTERFACE)
#define IDirectInput_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b)
#define IDirectInput_AddRef(p) (p)->lpVtbl->AddRef(p)
#define IDirectInput_Release(p) (p)->lpVtbl->Release(p)
#define IDirectInput_CreateDevice(p,a,b,c) (p)->lpVtbl->CreateDevice(p,a,b,c)
#define IDirectInput_EnumDevices(p,a,b,c,d) (p)->lpVtbl->EnumDevices(p,a,b,c,d)
#define IDirectInput_GetDeviceStatus(p,a) (p)->lpVtbl->GetDeviceStatus(p,a)
#define IDirectInput_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b)
#define IDirectInput_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b)
#else
#define IDirectInput_QueryInterface(p,a,b) (p)->QueryInterface(a,b)
#define IDirectInput_AddRef(p) (p)->AddRef()
#define IDirectInput_Release(p) (p)->Release()
#define IDirectInput_CreateDevice(p,a,b,c) (p)->CreateDevice(a,b,c)
#define IDirectInput_EnumDevices(p,a,b,c,d) (p)->EnumDevices(a,b,c,d)
#define IDirectInput_GetDeviceStatus(p,a) (p)->GetDeviceStatus(a)
#define IDirectInput_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b)
#define IDirectInput_Initialize(p,a,b) (p)->Initialize(a,b)
#endif
 
 
         在知道从何处注入之后,下面就要考虑如何实现了。可以查看CreateDevice在dinput.h头文件中的声明,会发现它与DirectX Help中的并不匹配。
 
 
HRESULT CreateDevice(
 
    REFGUID rguid,
    LPDIRECTINPUTDEVICE *lplpDirectInputDevice,
    LPUNKNOWN pUnkOuter
);
 
 
         这是它在dinput.h头文件中的定义,所以我们必须添加第四个参数,其为接口指针,下面是完整的函数声明:
 
 
HRESULT __stdcall   PASCAL MyCreateDevice(LPVOID *ppvOut,REFGUID rguid,
    LPDIRECTINPUTDEVICE *lplpDirectInputDevice,
    LPUNKNOWN pUnkOuter
 
 
         另外有一点非常重要,必须在声明中使用 __stdcall调用约定。__stdcall调用约定常用于调用Win32 API函数,被调用者负责清理堆栈;而__cdecl则是C和C++程序的默认调用约定,由调用者来负责清理堆栈,这可不是我们想要的。
         当查看此调用的反汇编时,会看到堆栈指针验证函数 _RTC_CheckEsp在对接口函数调用之后被调用。
 
 
if (lpdi->CreateDevice(GUID_SysKeyboard, &lpdikey, NULL)!=DI_OK)
00401365 mov         esi,esp 
00401367 push        0   
00401369 push        offset lpdikey (4552C8h) 
0040136E push        offset _GUID_SysKeyboard (44643Ch) 
00401373 mov         eax,dword ptr [lpdi (4552C4h)] 
00401378 mov         ecx,dword ptr [eax] 
0040137A mov         edx,dword ptr [lpdi (4552C4h)] 
00401380 push        edx 
00401381 mov         eax,dword ptr [ecx+0Ch] 
00401384 call        eax 
00401386 cmp         esi,esp 
00401388 call        _RTC_CheckEsp (4026A0h) 
0040138D test        eax,eax 
0040138F je          Game_Init+78h (401398h) 
   return(0);
00401391 xor         eax,eax 
00401393 jmp         Game_Init+107h (401427h) 
 
//设置协作级
if (lpdikey->SetCooperativeLevel(main_window_handle,DDSCL_NORMAL);
 
 
         如果忘了把函数声明为 __stdcall,函数也会正常工作,但是esp指针测试将会失败,因为其会设置eax,并在函数调用之后对它进行测试。
 
 
         第三步
         现在,当创建设备时,调用会重定向到我们的CreateDevice函数中。另外,在我们调用原始函数之后,会在lplpDirectInputDevice中得到一个新的指针,它可以把我们直接带到设备的虚函数表中。
 
 
HRESULT hr = OldCreateDev(ppvOut,rguid,lplpDirectInputDevice,pUnkOuter);
 
 
         比如说,我们要替换GetDeviceState这个函数,为得到它的偏移量,必须在DInput.dll内部查找其定义,可以看到它是第10个方法,所以偏移量为0x24。知道了偏移量,按照第二步中介绍的步骤:移除内存保护、保存原始指针、注入自己的函数、还原内存保护,一气呵成。
2009-2-20 12:18
0
雪    币: 129
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
完全看不懂。。。。
2009-2-24 17:08
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
太烦了,可以直接HOOK接口,利用HookCode
2009-3-3 14:01
0
雪    币: 247
活跃值: (10)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
5
一般找到指定的函数后,采取inline hook,相对而言,比较方便一些~~~
2009-3-3 14:17
0
雪    币: 210
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
detours可以HOOK成员函数
2009-3-3 16:21
0
雪    币: 100
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
现在有一问题:
      利用修改iat的方式来挂接directinput8create,采用dll+exe的方式ok,如果在一个dll中直接挂接directinput8create就失败。代码如下:

   bool RedirectIAT( SDLLHook* DLLHook, PIMAGE_IMPORT_DESCRIPTOR pImportDesc, PVOID pBaseLoadAddr )
{
    PIMAGE_THUNK_DATA pIAT;     // Ptr to import address table
    PIMAGE_THUNK_DATA pINT;     // Ptr to import names table
    PIMAGE_THUNK_DATA pIteratingIAT;

    // Figure out which OS platform we're on
    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionEx( &osvi );
       

    // If no import names table, we can't redirect this, so bail
    if ( pImportDesc->OriginalFirstThunk == 0 )
        {
        }
       // return false;
    pIAT = MakePtr( PIMAGE_THUNK_DATA, pBaseLoadAddr, pImportDesc->FirstThunk );
    pINT = MakePtr( PIMAGE_THUNK_DATA, pBaseLoadAddr, pImportDesc->OriginalFirstThunk );

    // Count how many entries there are in this IAT.  Array is 0 terminated
    pIteratingIAT = pIAT;

        /*char sztest[10];
                                itoa(*pIteratingIAT->u1.Function,sztest,16);
                                WriteToFile("1");
                                WriteToFile("\r\n");
                                WriteToFile(sztest);
                                WriteToFile("\r\n");*/

    unsigned cFuncs = 0;
    while ( pIteratingIAT->u1.Function )
    {
        cFuncs++;
        pIteratingIAT++;
    }

/*        char sztest1[10];
                                itoa(*pIteratingIAT->u1.Function,sztest1,16);
                                WriteToFile("2");
                                WriteToFile("\r\n");
                                WriteToFile(sztest1);
                                WriteToFile("\r\n");*/

    if ( cFuncs == 0 )  // If no imported functions, we're done!
        return false;

    // These next few lines ensure that we'll be able to modify the IAT,
    // which is often in a read-only section in the EXE.
    DWORD flOldProtect, flNewProtect, flDontCare;
    MEMORY_BASIC_INFORMATION mbi;
   
    // Get the current protection attributes                           
    VirtualQuery( pIAT, &mbi, sizeof(mbi) );
   
    // remove ReadOnly and ExecuteRead attributes, add on ReadWrite flag
    flNewProtect = mbi.Protect;
    flNewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ);
    flNewProtect |= (PAGE_READWRITE);
   
    if ( !VirtualProtect(   pIAT, sizeof(PVOID) * cFuncs,
                            flNewProtect, &flOldProtect) )
    {
        return false;
    }

    // If the Default hook is enabled, build an array of redirection stubs in the processes memory.
    DLPD_IAT_STUB * pStubs = 0;
    if ( DLLHook->UseDefault )
    {
        // Allocate memory for the redirection stubs.  Make one extra stub at the
        // end to be a sentinel
        pStubs = new DLPD_IAT_STUB[ cFuncs + 1];
        if ( !pStubs )
            return false;
    }

    // Scan through the IAT, completing the stubs and redirecting the IAT
    // entries to point to the stubs
    pIteratingIAT = pIAT;
char sztest2[10];
                                itoa(*pIteratingIAT->u1.Function,sztest2,16);
                                WriteToFile("3");
                                WriteToFile("\r\n");
                                WriteToFile(sztest2);
                                WriteToFile("\r\n");
    while ( pIteratingIAT->u1.Function )
    {
                char sztest4[10];
                itoa(*pIteratingIAT->u1.Function,sztest4,16);
                WriteToFile("4");
                                WriteToFile("\r\n");
                                WriteToFile(sztest4);
                                WriteToFile("\r\n");
        void* HookFn = 0;  // Set to either the SFunctionHook or pStubs.

        if ( !IMAGE_SNAP_BY_ORDINAL( pINT->u1.Ordinal ) )  // import by name
        {
            PIMAGE_IMPORT_BY_NAME pImportName = MakePtr( PIMAGE_IMPORT_BY_NAME, pBaseLoadAddr, pINT->u1.AddressOfData );

            // Iterate through the hook functions, searching for this import.
            SFunctionHook* FHook = DLLHook->Functions;
                       
            while ( FHook->Name )
            {
                if ( lstrcmpi( FHook->Name, (char*)pImportName->Name ) == 0 )
                {
                    OutputDebugString( "Hooked function: " );
                    OutputDebugString( (char*)pImportName->Name );
                    OutputDebugString( "\n" );

                    // Save the old function in the SFunctionHook structure and get the new one.
                    FHook->OrigFn = reinterpret_cast<void*>(pIteratingIAT->u1.Function);
                    HookFn = FHook->HookFn;
                    break;
                }

                FHook++;
            }
            // If the default function is enabled, store the name for the user.
            if ( DLLHook->UseDefault )
                        {       
                pStubs->pszNameOrOrdinal = (DWORD)&pImportName->Name;
                        }
        }
        else
        {
            // If the default function is enabled, store the ordinal for the user.
            if ( DLLHook->UseDefault )
                        {
                pStubs->pszNameOrOrdinal = pINT->u1.Ordinal;
                        }
        }

        // If the default function is enabled, fill in the fields to the stub code.
        if ( DLLHook->UseDefault )
        {
            pStubs->data_call = (DWORD)(PDWORD)DLLHook->DefaultFn
                                - (DWORD)(PDWORD)&pStubs->instr_JMP;
            pStubs->data_JMP = *(PDWORD)pIteratingIAT - (DWORD)(PDWORD)&pStubs->count;

            // If it wasn't manually hooked, use the Stub function.
            if ( !HookFn )
                        {
                HookFn = (void*)pStubs;
                        }
        }

        // Replace the IAT function pointer if we have a hook.
        if ( HookFn )
        {
            // Cheez-o hack to see if what we're importing is code or data.
            // If it's code, we shouldn't be able to write to it
            if ( IsBadWritePtr( (PVOID)pIteratingIAT->u1.Function, 1 ) )
            {
                       
                                 char sztest[10];
                                itoa(*pIteratingIAT->u1.Function,sztest,16);
                                WriteToFile(sztest);
                                WriteToFile("\r\n");
                               
                               
                pIteratingIAT->u1.Function = reinterpret_cast<DWORD*>(HookFn);

                                char sztest1[10];
                                itoa(*pIteratingIAT->u1.Function,sztest1,16);
                                WriteToFile(sztest1);
                                WriteToFile("\r\n");
               
            }
            else if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
            {
                // Special hack for Win9X, which builds stubs for imported
                // functions in system DLLs (Loaded above 2GB).  These stubs are
                // writeable, so we have to explicitly check for this case
              //  if ( pIteratingIAT->u1.Function > 0x80000000 )
               //     pIteratingIAT->u1.Function = reinterpret_cast<DWORD>(HookFn);
            }
        }

        if ( DLLHook->UseDefault )
                {
            pStubs++;           // Advance to next stub
                }
      
        pIteratingIAT++;    // Advance to next IAT entry
        pINT++;             // Advance to next INT entry
               
        //        char sztest[10];
        //        itoa(*(pIteratingIAT->u1.Function),sztest,16);
        //        WriteToFile(sztest);
    }
/*        char sztest5[10];
                                itoa(*pIteratingIAT->u1.Function,sztest5,16);
                                WriteToFile(sztest5);
                                WriteToFile("\r\n");*/
        WriteToFile("****************4");

    if ( DLLHook->UseDefault )
        {
        pStubs->pszNameOrOrdinal = 0;   // Final stub is a sentinel
        }

    // Put the page attributes back the way they were.
    VirtualProtect( pIAT, sizeof(PVOID) * cFuncs, flOldProtect, &flDontCare);
   
    return true;
}

//===========================================================================
// Top level routine to find the EXE's imports, and redirect them
bool HookAPICalls( SDLLHook* Hook )
{
    if ( !Hook )
        return false;

    HMODULE hModEXE = GetModuleHandle( 0 );

    PIMAGE_NT_HEADERS pExeNTHdr = PEHeaderFromHModule( hModEXE );
   
    if ( !pExeNTHdr )
        return false;

    DWORD importRVA = pExeNTHdr->OptionalHeader.DataDirectory
                        [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    if ( !importRVA )
        return false;

    // Convert imports RVA to a usable pointer
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = MakePtr( PIMAGE_IMPORT_DESCRIPTOR,
                                                    hModEXE, importRVA );

    // Save off imports address in a global for later use
    g_pFirstImportDesc = pImportDesc;   

    // Iterate through each import descriptor, and redirect if appropriate
    while ( pImportDesc->FirstThunk )
    {
        PSTR pszImportModuleName = MakePtr( PSTR, hModEXE, pImportDesc->Name);

        if ( lstrcmpi( pszImportModuleName, Hook->Name ) == 0 )
        {

            OutputDebugString( "Found " );
            OutputDebugString( Hook->Name );
            OutputDebugString( "...\n" );
                       

            RedirectIAT( Hook, pImportDesc, (PVOID)hModEXE );
        }
        
        pImportDesc++;  // Advance to next import descriptor
    }

    return true;
}
请大家帮忙分析
2009-8-18 11:37
0
游客
登录 | 注册 方可回帖
返回
//