[轉] Windows下Hook DirectX


首先說,這篇文章是很久以前為了玩成某游戲的HOOK找到的資料,雖然一直沒用上,但是還是讓我保留下來了。直接貼上了。。看不懂也不要問我,我都沒看。

也許看得懂的人對他們來說這是一個思路,不懂的就當垃圾文不看就好。。

 

我們要想擁有自己的窗口,那么就必須在諸仙的進程啟動之前得到Direct3DCreate8接口(諸仙用Direct3D8)。所以啟動過程如下:

//啟動諸仙並獲取諸仙進程句柄 
ZhuXianProc.OpenExe("C:\\游戲目錄\\誅仙\\element\\elementclient.exe"); 
if(!ZhuXianProc.GetProcess()) 
{ 
MessageBox(NULL, 
" 無法正常啟動《諸仙》主程序\n\n獲取幫助請與本工作室技術人員聯系", 
"天涯工作室程序運行錯誤提示!",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()里去。
到這里我們從諸仙得到的東西已經能滿足我們的需求了,那我們就專心的干我們的事情吧,做外掛界面吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM