最近看了《加密与解密》,想起来自己一直玩儿的一个游戏有个BUG,于是想自己DIY游戏程序将它修正。本文内容很简单,欢迎指点、批评。
顺便申请邀请码,想转正啊~~如果可以的话,EMAIL:naylon@126.com~~是与否都先感谢
这款游戏叫GTA:ViceCity(侠盗:罪恶都市),各位可以去网上搜一搜
VC这款游戏有个严重BUG,在多核心处理器的计算机上很容易死机(全屏的话基本上就得重启),而且有时候游戏里鼠标不能动,必须来回按ESC/Resume才能解决(有时候RP不好就直接黑屏了)。解决方法倒是也容易,打开任务管理器,找到gta-vc.exe进程,右键“关系设置”,然后只勾选一个CPU(通常是CPU0),点确定即可。
可是每回进游戏都得再切出来一次调试,实在是很麻烦,于是我就想修改gta-vc.exe,让它启动时自动设置为在CPU0上运行。
1.分析任务管理器的内部实现
首先要分析taskmgr.exe,也就是系统的任务管理器,看看他是怎么实现为进程指定CPU运行的,既然要操作进程,那么它内部肯定需要用OpenProcess函数打开进程,然后用返回的句柄操作,那么我们就用OD调试之,给OpenProcess下断(注意要暂停刷新,否则都会一直断在OpenProcess里面)下好断之后,选中进程点关系设置,这时就断在了OpenProcess里,返回程序领空,来看看关键代码。
代码:
0100BA26 /$ 55 PUSH EBP 0100BA27 |. 8BEC MOV EBP,ESP 0100BA29 |. 51 PUSH ECX 0100BA2A |. 53 PUSH EBX 0100BA2B |. 56 PUSH ESI 0100BA2C |. 57 PUSH EDI 0100BA2D |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; /ProcessId 0100BA30 |. 33DB XOR EBX,EBX ; | 0100BA32 |. 53 PUSH EBX ; |Inheritable => FALSE 0100BA33 |. 68 00060000 PUSH 600 ; |Access = SET_INFORMATION|QUERY_INFORMATION 0100BA38 |. 8BF1 MOV ESI,ECX ; | 0100BA3A |. FF15 14110001 CALL DWORD PTR DS:[<&kernel32.OpenProces>; \OpenProcess 0100BA40 |. 8BF8 MOV EDI,EAX 0100BA42 |. 85FF TEST EDI,EDI 0100BA44 |. 74 4E JE SHORT taskmgr.0100BA94 0100BA46 |. 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4] 0100BA49 |. 50 PUSH EAX ; /pSystemAffinityMask 0100BA4A |. 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8] ; | 0100BA4D |. 50 PUSH EAX ; |pProcessAffinityMask 0100BA4E |. 57 PUSH EDI ; |hProcess 0100BA4F |. FF15 08110001 CALL DWORD PTR DS:[<&kernel32.GetProcess>; \GetProcessAffinityMask 0100BA55 |. 85C0 TEST EAX,EAX 0100BA57 |. 74 30 JE SHORT taskmgr.0100BA89 0100BA59 |. 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8] 0100BA5C |. 50 PUSH EAX ; /lParam 0100BA5D |. 68 5BB60001 PUSH taskmgr.0100B65B ; |DlgProc = taskmgr.0100B65B 0100BA62 |. FF76 04 PUSH DWORD PTR DS:[ESI+4] ; |hOwner 0100BA65 |. 6A 7C PUSH 7C ; |pTemplate = 7C 0100BA67 |. FF35 705D0101 PUSH DWORD PTR DS:[1015D70] ; |hInst = 01000000 0100BA6D |. FF15 FC130001 CALL DWORD PTR DS:[<&user32.DialogBoxPar>; \DialogBoxParamW 0100BA73 |. 83F8 01 CMP EAX,1 0100BA76 |. 75 0E JNZ SHORT taskmgr.0100BA86 0100BA78 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] 0100BA7B |. 57 PUSH EDI 0100BA7C |. FF15 0C110001 CALL DWORD PTR DS:[<&kernel32.SetProcess>; kernel32.SetProcessAffinityMask 0100BA82 |. 85C0 TEST EAX,EAX 0100BA84 |. 74 03 JE SHORT taskmgr.0100BA89 0100BA86 |> 33DB XOR EBX,EBX 0100BA88 |. 43 INC EBX 0100BA89 |> 57 PUSH EDI ; /hObject 0100BA8A |. FF15 5C110001 CALL DWORD PTR DS:[<&kernel32.CloseHandl>; \CloseHandle 0100BA90 |. 85DB TEST EBX,EBX 0100BA92 |. 75 14 JNZ SHORT taskmgr.0100BAA8 0100BA94 |> FF15 68110001 CALL DWORD PTR DS:[<&kernel32.GetLastErr>; [GetLastError 0100BA9A |. 50 PUSH EAX ; /Arg3 0100BA9B |. 68 68750000 PUSH 7568 ; |Arg2 = 00007568 0100BAA0 |. FF76 04 PUSH DWORD PTR DS:[ESI+4] ; |Arg1 0100BAA3 |. E8 789AFFFF CALL taskmgr.01005520 ; \taskmgr.01005520 0100BAA8 |> 5F POP EDI 0100BAA9 |. 5E POP ESI 0100BAAA |. 8BC3 MOV EAX,EBX 0100BAAC |. 5B POP EBX 0100BAAD |. C9 LEAVE 0100BAAE \. C2 0400 RETN 4
由此我们可以知道,用GetProcessAffinityMask可以取得一个进程的CPU设置,用SetProcessAffinityMask则可以对其进行设置,我们要是自动设置gta-vc.exe在CPU0上运行的话,显然是要用到后者,上MSDN查查这个函数,上面把用法什么的写的很详细,详见:http://msdn.microsoft.com/en-us/libr...8VS.85%29.aspx
2.修改gta-vc.exe
经过一系列搜索,我得知只要如此调用:SetProcessAffinityMask(ProcessHandle, 1)即可把对应的进程设置为在CPU0上运行,下面就是要自己修改gta-vc.exe,让它自己执行这个函数。
先用eXeScope看一下gta-vc.exe的导入表,我找到了Direct3DCreate函数,由d3d8.dll导出,显然这个函数是跟DirectX相关的,应该是在初始化阶段被调用。下面用OD断一下,看看能不能利用这个函数。
下断,然后运行,马上就断了下来,然后回到gta-vc.exe领空,可见调用代码如下:
代码:
0065B845 > \8B8424 440400>MOV EAX,DWORD PTR SS:[ESP+444] ; 案例 0 --> 分支 0065B5DA 0065B84C . 55 PUSH EBP 0065B84D . 6A 78 PUSH 78 0065B84F . 8B08 MOV ECX,DWORD PTR DS:[EAX] 0065B851 . 890D A4977800 MOV DWORD PTR DS:[7897A4],ECX 0065B857 . E8 007D0000 CALL <JMP.&d3d8.Direct3DCreate8> 0065B85C . 33FF XOR EDI,EDI 0065B85E . A3 B4977800 MOV DWORD PTR DS:[7897B4],EAX 0065B863 . 3BC7 CMP EAX,EDI 0065B865 . 75 0C JNZ SHORT gta-vc-b.0065B873 0065B867 . 5D POP EBP 0065B868 . 5F POP EDI 0065B869 . 33C0 XOR EAX,EAX 0065B86B . 5E POP ESI 0065B86C . 81C4 30040000 ADD ESP,430 0065B872 . C3 RETN ……
代码:
0065B857 . E8 007D0000 CALL <JMP.&d3d8.Direct3DCreate8>
但是在此之前还有个问题需要处理,那就是得处理SetProcessAffinityMask的地址,gta-vc.exe原版并没有导入SetProcessAffinityMask函数,虽然我们也可以用OD直接改成“call SetProcessAffinityMask”,不过这样的话是用的是硬编码,不支持VISTA/7及以上系统,所以我们还得自己给gta-vc.exe导入这个函数。
用LordPE载入之,找到导入表,然后右键“添加导入表”,DLL是kernel32.dll,函数名即SetProcessAffinityMask,然后点“+”,记下ThunkRVA(我们后面调用要用到),确定即可。
我添加的ThunkRVA值为00614026,关掉LordPE,重新用OD载入,现在可以开始修改了。
按照上面的步骤再操作一遍,找到我们之前找好的那一段NOP(0065C853),在这里加入如下代码:
代码:
0065C853 push 1 ;参数2:1代表CPU0 0065C855 push -1 ;参数1:-1代表当前进程(GetCurrentProcess() == -1) 0065C857 call dword ptr [00A14026] ;调用SetProcessAffinityMask
调用完之后我们还得正常调用Direct3DCreate8,但是0065C853这段NOP不够用了,得先short jmp到后面的另一小段NOP:
代码:
0065C85D JMP SHORT 0065C863 ;jmp到后面 …… 0065C863 call 0066355C ;Direct3DCreate8
代码:
0065C868 jmp 0065B85C ;跳回原始CALL Direct3DCreate8的下一个指令位置
都改好了,OD主界面右键“复制到可执行文件-所有修改-全部复制”,然后再右键“保存文件”,保存成gta-vc.exe(最好提前备份原版文件)。
3.终
至此便大功告成,运行游戏,再使用任务管理器看一看“关系设置”,会发现已经自动设置为在CPU0上运行,不用每回再手动改了,我的目的也就达到了。游戏功能没有任何影响。
4.另一种方法
其实还有一种更简单的方法,不过技术含量不高,还要附带一个DLL文件。
我们知道PE加载器在加载一个PE文件时,会处理导入表,找到相关的导入模块,然后加载之,再修正IAT。那么我们可以自己写一个DLL,导出一个函数,然后在目标文件的导入表里导入这个函数,同时把该DLL放到与可执行文件相同的目录里,那么该程序在加载的时候就会载入我们的hijack.dll,相当于注入,之后就都好办了,DllMain里CreateThread执行我们的工作,完毕后线程返回即可。
实现代码如下:
代码:
#include "misc.h" // 需要一个导出函数,以便加入gta-vc.exe导入表 __declspec (dllexport) VOID ExportFunction(void) { //MessageBox(0, "Function export succeed!", "Goodboy", 0); return; } BOOLEAN ThreadProc(LPVOID lpdwThreadParam) { Sleep(2000); SetProcessAffinityMask(GetCurrentProcess(), 1); return TRUE; } VOID SetWorkingThread() { SECURITY_ATTRIBUTES sa; HANDLE tid; HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, 0, 0, (LPDWORD)&tid); if (hThread) { CloseHandle(hThread); } } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: SetWorkingThread(); break; case DLL_PROCESS_DETACH: break; } return TRUE; }