zoukankan      html  css  js  c++  java
  • D3D游戏降帧的动态创建D3D设备以及ShellCode HOOK玩法

    欢迎转载,转载请注明出处:http://blog.csdn.net/gnorth/article/details/9327971

    说白了,也就是HOOK掉Present,这种代码,其实百度上某些地方有,但是很多人估计不知道怎样得到Present的地址。

    所以就有些奇葩的例子:

    先到游戏的登录器内把CreateProcess之类的HOOK掉,让游戏进程暂停启动,然后注入游戏 HOOK Direct3DCreate9 得到 IDirect3D9 对象之后,又得到 IDirect3DDevice9 对象,最终得到Present,反正挺蛋疼的,而且就是在游戏创建对象前要HOOK掉,用户体验非常的差。

    其实自己创建个对象,从虚函数表就拿到真正的函数地址了。

    本文的一大堆废话只是为了说明实现流程,由于写得比较仓促,所以嘛,估计要有一定基础的人才看得懂,最后面有完整的代码,你不想理解流程的话直接复制去用吧。


    创建对象的代码如下:   

    BOOL InitializeD3D9(HWND hWnd, IDirect3D9 **ppD3D9, IDirect3DDevice9 **ppDev)
    {
    *ppD3D9 = Direct3DCreate9(D3D_SDK_VERSION);

    if(!(*ppD3D9))
    return FALSE;

    D3DDISPLAYMODE d3ddm;
    HRESULT hr = (*ppD3D9)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

    if(FAILED(hr))
    {
    (*ppD3D9)->Release();
    return FALSE;
    }

    D3DPRESENT_PARAMETERS d3dpp = {0};
    d3dpp.Windowed= TRUE;
    d3dpp.SwapEffect= D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat= d3ddm.Format;

    hr = (*ppD3D9)->CreateDevice(
    D3DADAPTER_DEFAULT, 
    D3DDEVTYPE_HAL, 
    hWnd, 
    D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
    &d3dpp, 
    ppDev);

    if(FAILED(hr))
    {
    (*ppD3D9)->Release();
    return FALSE;
    }

    return TRUE;
    }

    void ReleaseD3D9(IDirect3D9 *pD3D9, IDirect3DDevice9 *pDev)
    {
    if(pDev)
    pDev->Release();
    if(pD3D9)
    pD3D9->Release();
    }

    LPVOID GetDirectDeviceMemberProc(LPVOID pDev, DWORD dwOffset) //根据偏移从虚函数表获取函数地址
    {
    LPVOID pRet = NULL;
    __asm{
    mov eax, dword ptr [pDev] 
    mov ecx, dword ptr [eax] 
    mov ebx, dwOffset
    mov eax, dword ptr [ecx + ebx]
    mov pRet, eax
    }
    return pRet;
    }


    这个代码要有DXSDK才行,需要依赖这两个,d3d9.h d3d9.lib,由于这种比较恶心的代码,一般就是静态库里一丢就永远不管了,但是呢,丢静态库里,假如不用这种功能的代码调用静态库时,就要出警告提示你,你木有用到d3d9.dll中的代码云云,非常不爽。


    所以针对这个问题,我就自己调试了下,写了一个动态调用创建D3D设备对象的代码出来:最后面有完整的代码,你不想理解流程的话直接复制去用吧。


    大概流程如下:

    首先 Direct3DCreate9 是个标准API导出函数,所以,直接加载d3d9.dll,获取它即可

    HMODULE hD3d9 = LoadLibraryA("d3d9.dll");

    LPVOID _Direct3DCreate9 = (LPVOID)GetProcAddress(hD3d9, "Direct3DCreate9");
    __asm{
    push 32 //D3D_SDK_VERSION 32
    call _Direct3DCreate9
    mov pD3D9, eax
    }

    if(!pD3D9)

    return false;


    这样就得到了接口对象,接下来是Release 以及 GetAdapterDisplayMode

    77:           (*ppD3D9)->Release();
    00401243 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
    00401246 8B 11                mov         edx,dword ptr [ecx]
    00401248 8B 45 0C             mov         eax,dword ptr [ebp+0Ch]
    0040124B 8B 08                mov         ecx,dword ptr [eax]
    0040124D 8B 01                mov         eax,dword ptr [ecx]
    0040124F 8B F4                mov         esi,esp
    00401251 52                   push        edx
    00401252 FF 50 08             call        dword ptr [eax+8]

    它的偏移是8


    GetAdapterDisplayMode

    72:       HRESULT hr = (*ppD3D9)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
    00401209 8B F4                mov         esi,esp
    0040120B 8D 45 EC             lea         eax,[ebp-14h]
    0040120E 50                   push        eax
    0040120F 6A 00                push        0
    00401211 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
    00401214 8B 11                mov         edx,dword ptr [ecx]
    00401216 8B 45 0C             mov         eax,dword ptr [ebp+0Ch]
    00401219 8B 08                mov         ecx,dword ptr [eax]
    0040121B 8B 01                mov         eax,dword ptr [ecx]
    0040121D 52                   push        edx
    0040121E FF 50 20             call        dword ptr [eax+20h]

    偏移为0x20


    LPVOID d3d9_Release = GetDirectDeviceMemberProc(pD3D9, 0x08);

    LPVOID _GetAdapterDisplayMode = GetDirectDeviceMemberProc(pD3D9, 0x20);


    //先调用_GetAdapterDisplayMode

    //这里要传入一个 D3DDISPLAYMODE结构指针,但是外面并不需要对这个结构进行操作,所以这里只需要知道结构大小,然后分配一个一样大的内存给它就好。

    //sizeof(D3DDISPLAYMODE)   大小为 0x10


    BYTE d3ddm[0x10]

    HRESULT hr = NULL;
    __asm{
    lea eax, d3ddm
    push eax
    push 0
    push pD3D9
    call _GetAdapterDisplayMode
    mov hr, eax
    }


    if(FAILED(hr))  //  hr != S_OK;就释放对象,说明到这里就创建失败了,具体为啥失败我不懂
    {
    __asm{
    push pD3D9
    call d3d9_Release
    }
    return false;
    }


    接下来要用到 D3DPRESENT_PARAMETERS 结构,这个结构要在外面填参数了,所以,就要去看他的原型,看哪些是我们需要填的偏移。

    它的大小是0x38, 注意我后面注释的值是偏移。

    typedef struct _D3DPRESENT_PARAMETERS_
    {
        UINT                BackBufferWidth;       //0
        UINT                BackBufferHeight;      //4
        D3DFORMAT           BackBufferFormat;      //8
        UINT                BackBufferCount;       //c


        D3DMULTISAMPLE_TYPE MultiSampleType;       //10  enum
        DWORD               MultiSampleQuality;    //14


        D3DSWAPEFFECT       SwapEffect;            //18  enum
        HWND                hDeviceWindow;         //1C
        BOOL                Windowed;              //20
        BOOL                EnableAutoDepthStencil;//24
        D3DFORMAT           AutoDepthStencilFormat;//28  enum
        DWORD               Flags;                 //2c


        /* FullScreen_RefreshRateInHz must be zero for Windowed mode */
        UINT                FullScreen_RefreshRateInHz;//30
        UINT                PresentationInterval; //34
    } D3DPRESENT_PARAMETERS;


    我们可以从结构上看到,需要操作的偏移如下
    Windowed;              //20
    SwapEffect;            //18
    BackBufferFormat;      //8


    然后代码中可以看到,BackBufferFormat是从d3ddm里过来的
    结构是这样, Format的偏移为0x0c
    typedef struct _D3DDISPLAYMODE
    {
        UINT            Width;
        UINT            Height;
        UINT            RefreshRate;
        D3DFORMAT       Format;       //0x0c
    } D3DDISPLAYMODE;


    所以代码如下:

    BYTE d3dpp[0x38];


    memset(d3dpp, 0, 0x38);//如果不把结构填充为0,创建设备就会失败。


    __asm{
    lea eax, d3dpp
    mov dword ptr [eax + 0x20], 1
    mov dword ptr [eax + 0x18], 1
    lea ebx, d3ddm
    mov ecx, dword ptr [ebx + 0x0C]
    mov dword ptr [eax + 8], ecx
    }


    最后就是创建设备的代码


    86:       hr = (*ppD3D9)->CreateDevice(
    87:           D3DADAPTER_DEFAULT,
    88:           D3DDEVTYPE_HAL,
    89:           hWnd,
    90:           D3DCREATE_SOFTWARE_VERTEXPROCESSING,
    91:           &d3dpp,
    92:           ppDev);
    00401287 8B F4                mov         esi,esp
    00401289 8B 55 10             mov         edx,dword ptr [ebp+10h]
    0040128C 52                   push        edx
    0040128D 8D 45 B4             lea         eax,[ebp-4Ch]
    00401290 50                   push        eax
    00401291 6A 20                push        20h
    00401293 8B 4D 08             mov         ecx,dword ptr [ebp+8]
    00401296 51                   push        ecx
    00401297 6A 01                push        1
    00401299 6A 00                push        0
    0040129B 8B 55 0C             mov         edx,dword ptr [ebp+0Ch]
    0040129E 8B 02                mov         eax,dword ptr [edx]
    004012A0 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
    004012A3 8B 11                mov         edx,dword ptr [ecx]
    004012A5 8B 0A                mov         ecx,dword ptr [edx]
    004012A7 50                   push        eax
    004012A8 FF 51 40             call        dword ptr [ecx+40h]                   <-----------偏移0x40


    LPVOID _CreateDevice = GetDirectDeviceMemberProc(pD3D9, 0x40);
    //0x40


    __asm{
    lea eax, pD3D9Dev
    push eax
    lea eax, d3dpp
    push eax
    push 0x20
    push hWnd
    push 1
    push 0
    push pD3D9
    call _CreateDevice
    mov hr, eax
    }


    if(FAILED(hr))
    {
    __asm{
    push pD3D9
    call d3d9_Release
    }


    return false;


    }


    最终得到设备对象后,就用同样的方法,从虚函数表获取真正的函数地址, Present的偏移为0x44


    inline钩子函数,大概如下:

    typedef HRESULT (WINAPI *DEFPRESENT)(LPDIRECT3DDEVICE9,CONST RECT *,CONST RECT *,HWND,CONST RGNDATA *);
    DEFPRESENT PresentData = NULL;
    HRESULT WINAPI _Present(LPDIRECT3DDEVICE9 p1, CONST RECT *p2, CONST RECT *p3, HWND p4, CONST RGNDATA *p5)
    {
    Sleep(64);//这里也就是降帧了
    return PresentData(p1, p2, p3, p4, p5);
    }


    但是,这种代码,一般得写在DLL里才行,要是DLL卸载出来了,代码自然就没了,你不卸载HOOK就是一个字,“崩”!

    而有些非常时期,却又不得不卸载DLL。所以嘛,就自然有了ShellCode的代码。

    ShellCode不必完全模仿完善的inline hook 钩子函数,因为要模仿起来太累了, 因为存在PresentData中间代码实际上也是ShellCode。


    所以大概原型如下:

    __asm{

       pushad

       pushfd

          //这里call Sleep

        以及执行它的前5个字节

        mov edi, edi

        push ebp

        mov ebp, esp     好像是这个样子

       popfd

       popad

       jmp 原始Present + 5

    }

    这个代码实现起来不难,要注意的是,Sleep是不能直接调用的,因为Sleep实际上会被编译器进行包装,分配一个指针来包装它,然后call dword ptr [包装的指针]

    而这个指针,在你的dll卸载时,也会无效掉。所以我们应该自己包装Sleep

    PBYTE psc = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 4, MEM_COMMIT | MEM_RESERVE, 0x40);

    *(PDWORD)psc = (DWORD)Sleep;

    //使用VirtualAllocEx进行分配内存,使用MEM_COMMIT | MEM_RESERVE模式,这样分配出来的内存不会跟随你的DLL一起被释放掉。

    //其他的几种方式  new  会跟随dll释放

    //C函数库的 malloc也会释放

    //GlobalAlloc  不能改变为可执行可读写保护模式

    //还好最后剩VirtualAllocEx能用

    //其实我早就感觉,dll卸载时会把dll new  出来的各种内存都释放掉了,也就是通过这个例子,我终于证实了这个结论。



    ShellCode 如下

    BYTE pCode[22] = {
    0x60,     //pushad
    0x9C,    //pushfd
    0x6A, bTime,     //Sleep参数
    0xFF, 0x15, 0x00, 0x00, 0x00, 0x00,    //call dword ptr [psc]   暂时留空,在下面会对这里写入
    0x9D, //popfd
    0x61,  //popad            我断了下Sleep看过,其实被改变的通用寄存器好像只有eax ecx edx这几个,但为了代码更简单点,所以直接pushad pushfd popad popfd
    0x8B, 0xFF,   //mov edi, edi   实际上这个代码是可以不写的
    0x55,         // push ebp
    0x8B, 0xEC,   //mov ebp, esp
    0xE9, 0x00, 0x00, 0x00, 0x00,    //jmp 回 Present + 5   留到下面去写入跳转相对地址
    };


    *((PDWORD)(pCode + 6)) = (DWORD)psc; //把Sleep的包装地址写入进去,由于call dword ptr 不是用的相对地址,所以直接写入地址进去即可

    pCode是在局部分配来写ShellCode的,

    所以需要分配一个能够保留的地址,拷贝一份代码过去。

    PBYTE pShellCode = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 22, MEM_COMMIT | MEM_RESERVE, 0x40);
    memcpy(pShellCode, pCode, 22);

    PDWORD pAddr = (PDWORD)(pShellCode + 18);//得到最后4字节的指针

    *pAddr = (DWORD)pPresent - ((DWORD)pAddr - 1);//计算相对地址并写入

    //这里普及下常识,    0xE8     call             0xE9   jmp       这种远跳  调用的代码 

    //它的值为相对地址,这个地址的计算公式为:      目标地址 - (指令地址 + 指令长度)

    // (DWORD)pPresent - ((DWORD)pAddr - 1);   根据公式的话,这里看起来很诡异吧

    //首先,我们要在这里跳回Present + 5     那么目标就是 (Present + 5 )    然后,我们跳转的指令地址是在这里   0xE9, 0x00, 0x00, 0x00, 0x00,    从0xE9开始

    //但是(PDWORD)(pShellCode + 18);//这里取得最后的4字节的地址时,已经到0xE9后面的一个字节去了, 所以:跳转地址实际上为(pShellCode - 1 + 5)

    根据公式:    就应该是这样  (Present + 5 )  - (pShellCode - 1 + 5)      即: (DWORD)pPresent - ((DWORD)pAddr - 1);


    一切准备完毕之后,用同样的方法,让原始的Present头部跳到pShellCode头部,这里需要VirtualProtect  改变一下要操作的那几个字节为可读写可执行,操作完后改回去

    DWORD dwProtect = 0;
    VirtualProtect(pPresent, 5, 0x40, &dwProtect);
    *(PBYTE)pPresent = 0xE9;
    *((PDWORD)((PBYTE)pPresent + 1)) = (DWORD)pShellCode - (DWORD)pPresent - 5;
    VirtualProtect(pPresent, 5, dwProtect, NULL);




    完整的代码如下:

    LPVOID GetDirectDeviceMemberProc(LPVOID pDev, DWORD dwOffset)
    	{
    		LPVOID pRet = NULL;
    		__asm{
    			mov eax, dword ptr [pDev] 
    			mov ecx, dword ptr [eax] 
    			mov ebx, dwOffset
    			mov eax, dword ptr [ecx + ebx]
    			mov pRet, eax
    		}
    		return pRet;
    	}
    	
    
    	struct _d3dhookdev_type{
    		LPVOID pD3D9;
    		LPVOID pD3DDev;
    	};
    
    	bool InitD3D9(HWND hWnd, _d3dhookdev_type &dev)
    	{
    		HMODULE hD3d9 = LoadLibraryA("d3d9.dll");
    		LPVOID pD3D9 = NULL, pD3D9Dev = NULL;
    
    		BYTE d3ddm[0x10];
    		if(hD3d9)
    		{
    			LPVOID _Direct3DCreate9 = (LPVOID)GetProcAddress(hD3d9, "Direct3DCreate9");
    			__asm{
    				push 32
    				call _Direct3DCreate9
    				mov pD3D9, eax
    			}
    		
    			if(!pD3D9)
    				return false;
    
    			LPVOID d3d9_Release = GetDirectDeviceMemberProc(pD3D9, 0x08);
    		
    			LPVOID _GetAdapterDisplayMode = GetDirectDeviceMemberProc(pD3D9, 0x20);
    		
    			HRESULT hr = NULL;
    			__asm{
    				lea eax, d3ddm
    				push eax
    				push 0
    				push pD3D9
    				call _GetAdapterDisplayMode
    				mov hr, eax
    			}
    		
    			if(FAILED(hr))
    			{
    				__asm{
    					push pD3D9
    					call d3d9_Release
    				}
    				return false;
    			}
    		
    			BYTE d3dpp[0x38];
    			memset(d3dpp, 0, 0x38);
    
    			__asm{
    				lea eax, d3dpp
    				mov dword ptr [eax + 0x20], 1
    				mov dword ptr [eax + 0x18], 1
    				lea ebx, d3ddm
    				mov ecx, dword ptr [ebx + 0x0C]
    				mov dword ptr [eax + 8], ecx
    			}
    		
    		
    			LPVOID _CreateDevice = GetDirectDeviceMemberProc(pD3D9, 0x40);
    			//0x40
    		
    			__asm{
    				lea eax, pD3D9Dev
    				push eax
    				lea eax, d3dpp
    				push eax
    				push 0x20
    				push hWnd
    				push 1
    				push 0
    				push pD3D9
    				call _CreateDevice
    				mov hr, eax
    			}
    		
    			printf("%X
    ", hr);
    		
    			if(FAILED(hr))
    			{
    				__asm{
    					push pD3D9
    					call d3d9_Release
    				}
    			
    				return false;
    			
    			}
    
    			dev.pD3D9 = pD3D9;
    			dev.pD3DDev = pD3D9Dev;
    			return true;
    		}
    
    		return false;
    	}
    
    	void ReleaseD3D9(_d3dhookdev_type &dev)
    	{
    		if(dev.pD3D9)
    		{
    			LPVOID d3d_Release = GetDirectDeviceMemberProc(dev.pD3D9, 0x08);
    			__asm{
    				push dev.pD3D9
    					call d3d_Release
    			}
    		}
    		
    		if(dev.pD3DDev)
    		{
    			LPVOID d3ddev_Release = GetDirectDeviceMemberProc(dev.pD3DDev, 0x08);
    			__asm{
    				push dev.pD3DDev
    					call d3ddev_Release
    			}
    		}
    	}
    
    	void D3DPresentShellCodeHook(HWND hWnd, BYTE bTime)
    	{
    		_d3dhookdev_type dev = {NULL, NULL};
    		LPVOID pPresent = NULL;
    		if(InitD3D9(hWnd, dev))
    		{
    			pPresent = GetDirectDeviceMemberProc(dev.pD3DDev, 0x44);
    			ReleaseD3D9(dev);
    			if(!pPresent)
    				return;
    
    			if(0xE9 == *(PBYTE)pPresent)
    				return;
    		}
    		
    		PBYTE psc = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 4, MEM_COMMIT | MEM_RESERVE, 0x40);
    		*(PDWORD)psc = (DWORD)Sleep;
    		BYTE pCode[22] = {
    			0x60,
    			0x9C,
    			0x6A, bTime,
    			0xFF, 0x15, 0x00, 0x00, 0x00, 0x00,
    			0x9D,
    			0x61,
    			0x8B, 0xFF,
    			0x55,
    			0x8B, 0xEC,
    			0xE9, 0x00, 0x00, 0x00, 0x00, 
    		};
    		*((PDWORD)(pCode + 6)) = (DWORD)psc;
    		PBYTE pShellCode = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 22, MEM_COMMIT | MEM_RESERVE, 0x40);
    		memcpy(pShellCode, pCode, 22);
    		PDWORD pAddr = (PDWORD)(pShellCode + 18);
    		*pAddr = (DWORD)pPresent - ((DWORD)pAddr - 1);
    		DWORD dwProtect = 0;
    		VirtualProtect(pPresent, 5, 0x40, &dwProtect);
    		*(PBYTE)pPresent = 0xE9;
    		*((PDWORD)((PBYTE)pPresent + 1)) = (DWORD)pShellCode - (DWORD)pPresent - 5;
    		VirtualProtect(pPresent, 5, dwProtect, NULL);
    	}





  • 相关阅读:
    17-Flutter移动电商实战-首页_楼层区域的编写
    16-Flutter移动电商实战-切换后页面状态的保持AutomaticKeepAliveClientMixin
    15-Flutter移动电商实战-商品推荐区域制作
    AndroidStudio中Flutter打包APK
    windows下dos窗口实现持续ping显示时间保存至日志
    14-Flutter移动电商实战-ADBanner组件的编写
    13-Flutter移动电商实战-ADBanner组件的编写
    12-Flutter移动电商实战-首页导航区域编写
    11-Flutter移动电商实战-首页_屏幕适配方案和制作
    10-Flutter移动电商实战-使用FlutterSwiper制作轮播效果
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3192022.html
Copyright © 2011-2022 走看看