zoukankan      html  css  js  c++  java
  • [转] Windows下Hook DirectX

    首先说,这篇文章是很久以前为了玩成某游戏的HOOK找到的资料,虽然一直没用上,但是还是让我保留下来了。直接贴上了。。看不懂也不要问我,我都没看。

    也许看得懂的人对他们来说这是一个思路,不懂的就当垃圾文不看就好。。

    我们要想拥有自己的窗口,那么就必须在诸仙的进程启动之前得到Direct3DCreate8接口(诸仙用Direct3D8)。所以启动过程如下:

    //启动诸仙并获取诸仙进程句柄 
    ZhuXianProc.OpenExe("C:\游戏目录\诛仙\element\elementclient.exe"); 
    if(!ZhuXianProc.GetProcess()) 
    { 
    MessageBox(NULL, 
    " 无法正常启动《诸仙》主程序
    
    获取帮助请与本工作室技术人员联系", 
    "天涯工作室程序运行错误提示!",MB_OK); 
    return TRUE; 
    } 
    //在程序运行之前先HOOK住所需要HOOK的API 
    HookApi("C:\游戏目录\诛仙\element\elementclient.exe","C:\游戏目录\诛仙\element\ZxDll.dll"); 
    ZhuXianFunc(); 
    ZhuXianProc.CloseAllHandle(); 
    

     关于ZhuXianProc是一个CGetProc类型,这个类主要是打开进程和取得进程的一些信息,GetProcess()取的改进程的句柄。这个类里面要解释下的是:

    void CGetProc::OpenExe(CString str) 
    { 
    memset(&si,0,sizeof(si)); 
    si.cb=sizeof(si); 
    si.wShowWindow=SW_SHOW; 
    si.dwFlags=STARTF_USESHOWWINDOW; 
    CreateProcess(str,NULL,NULL,FALSE,NULL,Create_SUSPENDED,NULL,NULL,&si,&pi); 
    } 
    
    
     
    

    “Create_SUSPENDED”指明该进程并不是一开始就让他运行,原因是我们要想得到Direct3DCreate8接口就必须在运行进程之前注入我们的DLL,并让我们的DLL里的HOOK Direct3DCreate8接口跑到他的初始化之前。

    我们来看看HookApi()的内容:

    bool CUIThread::HookApi(char* pszFileExe,char* pszFileDll) 
    { 
        //让程序启动的时候JMP到自己的DLL中去 
    HANDLE hProcess = ZhuXianProc.GetProcess(); 
    // 在目标进程申请空间,存放字符串pszDllName,作为远程线程的参数 
    int cbSize = (strlen(pszFileDll) + 1); 
    LPVOID lpRemoteDllName = ::VirtualAllocEx(hProcess, NULL, cbSize, MEM_COMMIT, PAGE_READWRITE); 
    ::WriteProcessMemory(hProcess, lpRemoteDllName, pszFileDll, cbSize, NULL); 
    // 取得LoadLibraryA函数的地址,我们将以它作为远程线程函数启动 
    HMODULE hModule=::GetModuleHandle ("kernel32.dll"); 
    LPTHREAD_START_ROUTINE pfnStartRoutine = (LPTHREAD_START_ROUTINE)::GetProcAddress(hModule, "LoadLibraryA"); 
    // 启动远程线程 
    ::ResumeThread(ZhuXianProc.GetThread()); 
    HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL); 
    if(hRemoteThread == NULL) 
    { 
      ::CloseHandle(hProcess); 
      return FALSE; 
    } 
    ::CloseHandle(hRemoteThread); 
    return TRUE; 
    } 
    

     这段关键是在:

    // 启动远程线程 
    ::ResumeThread(ZhuXianProc.GetThread()); 
    HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL); 
    

    必须这样,ResumeThread诸仙进程之后立即启动我们的DLL。呵呵,也不能弄反,如果进程没启动,我们注入的DLL就启动了,进程可能就崩溃了。 在这里有个小技巧,诸仙进程启动不代表就立即进行Direct3DCreate8初始化,他还有些事情要做,到他初始化的时候我们的DLL早就跑了一段了:)。

    3.来看看我们的重点,我们注入的ZXDLL.DLL到底做了些什么事情。

    #pragma comment(lib, "d3d8.lib")
    // CZxDllApp
    
    BEGIN_MESSAGE_MAP(CZxDllApp, CWinApp)
    END_MESSAGE_MAP()
    // CZxDllApp 构造
    
    CZxDllApp::CZxDllApp()
    {
        // TODO: 在此处添加构造代码,
        // 将所有重要的初始化放置在 InitInstance 中
    }
    
    // 唯一的一个 CZxDllApp 对象
    CZxDllApp theApp;
    
    CAPIHook hookapi2("d3d8.dll","Direct3DCreate8",(PROC)NewDirect3DCreate8);
    // CZxDllApp 初始化
    
    BOOL CZxDllApp::InitInstance()
    {
        CWinApp::InitInstance();
        return TRUE;
    }
    

     看完DLL的这一段小程序,基本上是VC向导完成的,只有一句:
    CAPIHook hookapi2("d3d8.dll","Direct3DCreate8",(PROC)NewDirect3DCreate8);
    这句在DLL一运行的时候他就运行了,并把Direct3DCreate8给变成了新的入口地址NewDirect3DCreate8了,那么当诸仙运行的时候这个函数就跑到我们的NewDirect3DCreate8里来了,呵呵,正好,我们抓住了Direct3DCreate8接口了,来我们一起看看NewDirect3DCreate8函数里的内容。

    IDirect3D8 * WINAPI NewDirect3DCreate8(UINT SDKVersion)
    {
        static int count = 0;
        static IDirect3D8* test = NULL;
    
        hookapi2.Unhook();
        IDirect3D8 * m = Direct3DCreate8(SDKVersion);
        hookapi2.Rehook();//程序一共3个3维平面驱动
    
        count++;
        if(count==2){//1,窗口模式请用2,全屏模式请用3
            lpD3D = m;
            //替换VTable,实现对IDirect3Draw 的 COM接口的挂钩
            NewlpD3d = new MyIDirect3D8;
            m = (IDirect3D8*)NewlpD3d;
        }
    
        return m;
    }
    

     呵呵,诸仙对IDirect3D8接口其实是驱动了3次,我没查出来第一次是干什么的,但是后两次一个是在窗口模式下用的,一个是在全屏模式下用的。光得到IDirect3D8接口是没用的这里我们还要进行COM HOOK 获得Direct3DDevice8(D3D 设备) 的接口的指针从而得到我们的Render该放到什么地方。
    COM HOOK其实就是写一个同样的类用来替换COM的VTable,不做详细的解释,实在搞不懂就google(俺也是这么得来的:))。MyIDirect3D8就是一个新的IDirect3D8类,他是从IDirect3D8继承来的,定义如下:

    class MyIDirect3D8 : public IDirect3D8
    {
    public:
        HRESULT APIENTRY QueryInterface(REFIID riid, void** ppvObj);
        ULONG APIENTRY AddRef();
        ULONG APIENTRY Release();
    
        /*** IDirect3D8 methods ***/
        HRESULT APIENTRY RegisterSoftwareDevice(void* pInitializeFunction);
        UINT APIENTRY  GetAdapterCount();
        HRESULT APIENTRY GetAdapterIdentifier(UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER8* pIdentifier);
        UINT APIENTRY  GetAdapterModeCount(UINT Adapter);
        HRESULT APIENTRY EnumAdapterModes(UINT Adapter,UINT Mode,D3DDISPLAYMODE* pMode);
        HRESULT APIENTRY GetAdapterDisplayMode(UINT Adapter,D3DDISPLAYMODE* pMode);
        HRESULT APIENTRY CheckDeviceType(UINT Adapter,D3DDEVTYPE CheckType,D3DFORMAT DisplayFormat,D3DFORMAT BackBufferFormat,BOOL Windowed);
        HRESULT APIENTRY CheckDeviceFormat(UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat);
        HRESULT APIENTRY CheckDeviceMultiSampleType(UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType);
        HRESULT APIENTRY CheckDepthStencilMatch(UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat);
        HRESULT APIENTRY GetDeviceCaps(UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS8* pCaps);
        HMONITOR APIENTRY  GetAdapterMonitor(UINT Adapter);
        HRESULT APIENTRY CreateDevice(UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface);
        MyIDirect3D8(void);
    
        IDirect3D8 * lpD3D;
        IDirect3DDevice8 * lpD3DD8;
        IDirect3DDevice8 * lpD3DD8bak;
        ULONG m_count;
    };
    

    pGame就是我们的外挂的主类包括界面处理等等,在下一点讲解。 IpD3DDevice是Direct3DDevice8(D3D 设备) 的接口的指针,我们也要想办法解决,不急,等下慢慢说。

    替换VTable其实很简单,我们只需要new一个我们自己的的MyIDirect3D8把老的IDirect3D8的指针内容直接替换就行了,呵呵:

    //替换VTable,实现对IDirect3Draw 的 COM接口的挂钩
    NewlpD3d = new MyIDirect3D8;
    m = (IDirect3D8*)NewlpD3d;
    
    Direct3DDevice8(D3D 设备) 的接口的指针是在IDirect3D8里面Create的我们再看看MyIDirect3D8的CreateDevice函数如何定义:
    
    HRESULT APIENTRY MyIDirect3D8::CreateDevice(UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface)
    {
        static MyIDirect3DDevice8 * id3dd8 = NULL;
    
        HRESULT m = lpD3D->CreateDevice(Adapter,DeviceType,hFocusWindow,BehaviorFlags,pPresentationParameters,ppReturnedDeviceInterface);
        lpD3DD8 = *ppReturnedDeviceInterface;
    
        //Hook IDirect3DDevice8
        ::ShowWindow(hFocusWindow,SW_HIDE);
        lpD3D->CreateDevice(Adapter,DeviceType,hFocusWindow,BehaviorFlags,pPresentationParameters,&lpD3DD8bak); 
        ::ShowWindow(hFocusWindow,SW_SHOW);
        ::SetFocus(hFocusWindow);
    
        id3dd8 = new MyIDirect3DDevice8(lpD3DD8bak);
        *ppReturnedDeviceInterface = (IDirect3DDevice8*)id3dd8;
    
        return m;
    }
    

     至于::ShowWindow(hFocusWindow,SW_HIDE);初始化后::ShowWindow(hFocusWindow,SW_SHOW);保证进程不挂,呵呵。
    IDirect3DDevice8要想得到IDirect3DDevice8的里面的内容,我们也采用同样方法的偷粱换柱子。MyIDirect3DDevice8定义就不再贴出来了,浪费页面。要解释下的地方是

    HRESULT APIENTRY MyIDirect3DDevice8::BeginScene()
    {
        return g_pD3DDevice->BeginScene();
    }
    
    
    HRESULT APIENTRY MyIDirect3DDevice8::EndScene()
    {
        if(pGame!=NULL) pGame->Render();
        return g_pD3DDevice->EndScene();
    }
    

     我们的画图函数按道理讲要放到BeginScene()之后,但是我们不是写自己的3D游戏,而是在做外挂,
    程序是这么处理的:
    别人调用BeginScene();
    别人Render();
    别人调用EndScene();
    看看这个,我们把自己的pGame->Render();放到MyIDirect3DDevice8::BeginScene()里,结果就是自己的画图全被别人的图覆盖了,所有选择放到MyIDirect3DDevice8::EndScene()里去。
    到这里我们从诸仙得到的东西已经能满足我们的需求了,那我们就专心的干我们的事情吧,做外挂界面吧。

  • 相关阅读:
    如何定义开发完成?(Definition of Done)
    Git协同工作流介绍
    Git常用命令拾遗
    搭建基于Docker社区版的Kubernetes本地集群
    Mqtt学习指南
    JavaWeb 学习总结
    异常:org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
    MySQL 插入中文错误:java.sql.SQLException: Incorrect string value:
    Servlet 中文乱码问题解析及详细解决方法
    常用正则表达式
  • 原文地址:https://www.cnblogs.com/briny/p/3265188.html
Copyright © 2011-2022 走看看