其实对熟悉反汇编的人来说并不难,本来用hook也可以,不过crysis是通过CryRenderD3D9.dll显式加载d3d9.dll,IAT表中没有相关地址,hook比较麻烦。所以我还是修改反汇编来做。

    首先我们需要确定d3d9.dll中Direct3DCreate9的地址,d3d9是系统模块,地址固定,很多工具可以查,没有工具也可以,LoadLibrary即可得到d3d9.dll的地址,然后GetProcAddress即可得到Direct3DCreate9的地址。可知地址是:0x4B66AED0。

    然后找一个反汇编调试工具,我用ollyice,是ollydbg的修改版,用oll启动crysis之后,先直接运行,直到d3d9.dll出现在memory map中:

然后我们转到d3d9.dll模块中,定位到4B66AED0这个地址,设置一个断点:

然后重新运行调试Crysis,一直运行到刚才设置的这个断点,返回堆栈,我们发现Direct3DCreate9这个函数是在CrySystem.dll中调用的,而不是CryRenderD3D9.dll中(实际上后面CryRenderD3D9.dll中还会再调一次,不过我们已经不需要了):

执行到函数返回,IDirect3D9指针就存在eax内存中,从eax内存可以看到IDirect3D9指针指针地址是0x4B641A98:

实际上这也是一个固定地址。转到d3d9.dll模块的这个地址我们看到以下这个表:

这就是IDirect3D9类的函数表,而CreateDevice的偏移是0x40,也就是最后一个函数,可以看到地址是0x4B6C1660,转到这个地址,也就是CreateDevice函数的起始位置,设置一个断点:

然后继续运行,直到断点断下来,可以返回堆栈看看:

其实这里的CreateDevice通过CryRenderD3D9.dll调入,并不是真的创建设备,可能只是做一下检测之类,从返回的代码上看,它创建之后马上就删掉了,我们继续运行,CreateDevice函数又断了下来,这次是真正的创建Device操作,返回调用的函数:

可以看到,其中call ecx就是调用CreateDevice函数,上面7个push是传入参数(最后一个是direct3D指针本身),倒数第二个和第三个就是adapter和devicetype,也就是我们要修改的参数,nvperfhud的要求是这两个参数一个用nv的adapter,通常是1,跟显卡和显示器数量有关,另一个用ref(数值2),如果不修改,调试到这里会发现adapter是0,devicetype是1,如何修改呢,直接改为push 2和push 1是不行的,因为我们看到这两个数来自,dword ptr [esi+8]和dword ptr [esi+4],不管这是哪里的内存,直接push的话会跟它不对应,导致在后面游戏直接死掉,可能后面的代码有其它的匹配判断,所以我们需要连同这两个内存一个修改,容易想到的是在
3816A5FA    8B46 08         mov     eax, dword ptr [esi+8]

3816A5FE    8B46 04         mov     eax, dword ptr [esi+4]
之前分别增加两条指令
mov     dword ptr [esi+8], 2
mov     dword ptr [esi+4], 1
但这样一来
指令长度就超过地址范围了,导致后面的指令被覆盖。不过还好我们看到call后面的两条指令
cmp eax, 88760868
jnz short 3816A658
这两条指令的意思是判断createdevice的返回值,如果为0(创建成功),就跳转到下面的地址,既然到了这一步,我们就假设createdevice永远为成功,不要检测了,所以我们直接覆盖掉这些指令,但要在call之后,加一条jmp跳到那个成功的地址,否则将不正确,如下图:

大功告成,先把原来的CryRenderD3D9.dll备份一下,把修改后的模块存为CryRenderD3D9.dll。将crysis拖入nvperfhud,就能看到有分析了。