本来想写xxx游戏多开补丁的,后来想想算了,这个游戏有违道德,so 选择一个微软自己的东西搞搞理论就好了。
经常用任务管理器的人都知道taskmgr默认是只能开一个的,除非电脑非常卡的时候可以开很多个,就像xxx游戏在运行一个客户端以后,迅速的再双击,又可以再运行一个差不多原理(这个跟多线程安全差不多)。
程序只能运行一个实例的方法有很多,理论就不讲了,直接开场taskmgr的方法。
首先,这个taskmgr是XP SP3系统上面的。其他自己研究。
用OD加载直接代码就是Call xxxxx 然后 jmp xxxx,如果逆向多的话,这个就很清楚了,至少是VS05以上的C/C++编译器(好像这个多开跟编译器没多大关系吧...)
继续跟下去,大家都很清楚:对于VC编译器入口还是不管是_tmain还是_tWinMain都是在调用了GetCommandLine以后有很多个push push xxx之后的;exit、ExitProcess之前的那个函数就是了。所以我假设你已经找到WinMain函数地址 0x0100538E。
WinMain代码及简陋过程分析如下:
1. 0100538E /$ 8BFF MOV EDI,EDI
2. 01005390 |. 55 PUSH EBP
3. 01005391 |. 8BEC MOV EBP,ESP
4. 01005393 |. 81EC 10040000 SUB ESP,410
5. 01005399 |. A1 94540101 MOV EAX,DWORD PTR DS:[1015494]
6. 0100539E |. 53 PUSH EBX
7. 0100539F |. 56 PUSH ESI
8. 010053A0 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
9. 010053A3 |. 57 PUSH EDI
10. 010053A4 |. 33FF XOR EDI,EDI
11. 010053A6 |. 47 INC EDI
12. 010053A7 |. 33DB XOR EBX,EBX
13. 010053A9 |. 68 4C1B0001 PUSH taskmgr.01001B4C ; /MsgName = "TaskbarCreated"
14. 010053AE |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ; |
15. 010053B1 |. 89B5 28FCFFFF MOV DWORD PTR SS:[EBP-3D8],ESI ; |
16. 010053B7 |. 8935 285E0101 MOV DWORD PTR DS:[1015E28],ESI ; |
17. 010053BD |. 89BD 2CFCFFFF MOV DWORD PTR SS:[EBP-3D4],EDI ; |
18. 010053C3 |. 899D 20FCFFFF MOV DWORD PTR SS:[EBP-3E0],EBX ; |定义一个新的窗口消息,保证整个系统中是唯一的
19. 010053C9 |. FF15 A0120001 CALL DWORD PTR DS:[<&USER32.RegisterWind>; \RegisterWindowMessageW
20. 010053CF |. 68 C0140001 PUSH taskmgr.010014C0 ; /MutexName = "NTShell Taskman Startup Mutex"
21. 010053D4 |. 57 PUSH EDI ; |InitialOwner => TRUE
22. 010053D5 |. 53 PUSH EBX ; |pSecurity => NULL
23. 010053D6 |. A3 105E0101 MOV DWORD PTR DS:[1015E10],EAX ; |创建一个互斥体,如果存在则GetLastError为 ERROR_ALREADY_EXISTS (0x0B7)
24. 010053DB |. FF15 2C110001 CALL DWORD PTR DS:[<&KERNEL32.CreateMute>; \CreateMutexW
25. 010053E1 |. 3BC3 CMP EAX,EBX
26. 010053E3 |. A3 145E0101 MOV DWORD PTR DS:[1015E14],EAX
27. 010053E8 |. BF 10270000 MOV EDI,2710
28. 010053ED |. 74 1A JE SHORT taskmgr.01005409
29. 010053EF |. FF15 84110001 CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>; [GetLastError
30. 010053F5 |. 3D B7000000 CMP EAX,0B7
31. 010053FA |. 75 0D JNZ SHORT taskmgr.01005409 ; 如果不存在互斥体,跳转
32. 010053FC |. 57 PUSH EDI ; /Timeout => 10000. ms
33. 010053FD |. FF35 145E0101 PUSH DWORD PTR DS:[1015E14] ; |有互斥体存在,等待10000ms,使其他taskmgr可以显示窗口,事实证明这个10000ms还是太短了
34. 01005403 |. FF15 40110001 CALL DWORD PTR DS:[<&KERNEL32.WaitForSin>; \WaitForSingleObject
35. 01005409 |> 68 985E0101 PUSH taskmgr.01015E98 ; /Arg3 = 01015E98
36. 0100540E |. 68 945E0101 PUSH taskmgr.01015E94 ; |Arg2 = 01015E94
37. 01005413 |. 68 905E0101 PUSH taskmgr.01015E90 ; |Arg1 = 01015E90
38. 01005418 |. E8 ACE9FFFF CALL taskmgr.01003DC9 ; \taskmgr.01003DC9
39. 0100541D |. 391D 905E0101 CMP DWORD PTR DS:[1015E90],EBX
40. 01005423 |. 74 12 JE SHORT taskmgr.01005437
41. 01005425 |. 68 9C5E0101 PUSH taskmgr.01015E9C
42. 0100542A |. FF15 30110001 CALL DWORD PTR DS:[<&KERNEL32.GetCurrent>; [GetCurrentProcessId
43. 01005430 |. 50 PUSH EAX
44. 01005431 |. FF15 34110001 CALL DWORD PTR DS:[<&KERNEL32.ProcessIdT>; kernel32.ProcessIdToSessionId
45. 01005437 |> 68 04010000 PUSH 104 ; /Count = 104 (260.)
46. 0100543C |. 8D85 30FCFFFF LEA EAX,DWORD PTR SS:[EBP-3D0] ; |
47. 01005442 |. 50 PUSH EAX ; |Buffer
48. 01005443 |. 68 13270000 PUSH 2713 ; |RsrcID = STRING "Windows 任务管理器"
49. 01005448 |. 56 PUSH ESI ; |hInst
50. 01005449 |. 8B35 BC130001 MOV ESI,DWORD PTR DS:[<&USER32.LoadStrin>; |USER32.LoadStringW
51. 0100544F |. FFD6 CALL ESI ; \LoadStringW
52. 01005451 |. 85C0 TEST EAX,EAX
53. 01005453 |. 74 6A JE SHORT taskmgr.010054BF
54. 01005455 |. 8D85 30FCFFFF LEA EAX,DWORD PTR SS:[EBP-3D0]
55. 0100545B |. 50 PUSH EAX ; /Title
56. 0100545C |. 68 02800000 PUSH 8002 ; |Class = 8002
57. 01005461 |. FF15 9C120001 CALL DWORD PTR DS:[<&USER32.FindWindowW>>; \FindWindowW
58. 01005467 |. 3BC3 CMP EAX,EBX ; 查找窗口类为0x8002的窗口,用spy++可以知道#32770 (Dialog)正是taskmgr的窗口类
59. 01005469 |. 8985 14FCFFFF MOV DWORD PTR SS:[EBP-3EC],EAX ; 所以这里是检测获取另一个窗口的窗口HWND
60. 0100546F |. 74 4E JE SHORT taskmgr.010054BF ; 关键点:如果找到窗体则激活那个窗体,将退出本进程(只要这里一直jmp就可以实现多开了)
61. 01005471 |. 8D8D 1CFCFFFF LEA ECX,DWORD PTR SS:[EBP-3E4]
62. 01005477 |. 51 PUSH ECX ; /pProcessID
63. 01005478 |. 50 PUSH EAX ; |hWnd
64. 01005479 |. 899D 1CFCFFFF MOV DWORD PTR SS:[EBP-3E4],EBX ; |
65. 0100547F |. FF15 98120001 CALL DWORD PTR DS:[<&USER32.GetWindowThr>; \GetWindowThreadProcessId
66. 01005485 |. FFB5 1CFCFFFF PUSH DWORD PTR SS:[EBP-3E4]
67. 0100548B |. FF15 94120001 CALL DWORD PTR DS:[<&USER32.AllowSetFore>; USER32.AllowSetForegroundWindow
68. 01005491 |. 8D85 18FCFFFF LEA EAX,DWORD PTR SS:[EBP-3E8]
69. 01005497 |. 50 PUSH EAX ; /pResult
70. 01005498 |. 57 PUSH EDI ; |Timeout
71. 01005499 |. 6A 02 PUSH 2 ; |Flags = SMTO_NORMAL|SMTO_ABORTIFHUNG
72. 0100549B |. 53 PUSH EBX ; |lParam
73. 0100549C |. 53 PUSH EBX ; |wParam
74. 0100549D |. BF 0B040000 MOV EDI,40B ; |
75. 010054A2 |. 57 PUSH EDI ; |Message => WM_USER+11.
76. 010054A3 |. FFB5 14FCFFFF PUSH DWORD PTR SS:[EBP-3EC] ; |hWnd
77. 010054A9 |. FF15 90120001 CALL DWORD PTR DS:[<&USER32.SendMessageT>; \SendMessageTimeoutW
78. 010054AF |. 85C0 TEST EAX,EAX
79. 010054B1 |. 74 0C JE SHORT taskmgr.010054BF
80. 010054B3 |. 39BD 18FCFFFF CMP DWORD PTR SS:[EBP-3E8],EDI
81. 010054B9 |. 0F84 FB020000 JE taskmgr.010057BA ; 已存在taskmgr则跳去释放互斥体,然后退出
82. 010054BF |> 8D85 24FCFFFF LEA EAX,DWORD PTR SS:[EBP-3DC] ; 跳到这里是说明可以生成一个taskmgr界面了
在OD里面将
JE SHORT 010054BF
改成
JMP SHORT 010054BF
会发现只是 地址0x0100546F的0x74变成了0xEB,所以多开补丁如下:
1. #include <windows.h>
2.
3. BYTE buf[] = "\x89\x85\x14\xFC\xFF\xFF\x74\x4E";
4.
5. void MyFunc()
6. {
7. STARTUPINFO sInfo;
8. PROCESS_INFORMATION pInfo;
9. BYTE RemoteMemory[8];
10. DWORD wBytes;
11.
12. sizeof(sInfo) );
13. sizeof(sInfo);
14. sInfo.dwFlags = STARTF_USESHOWWINDOW;
15. sInfo.wShowWindow = SW_SHOWNORMAL;
16. sizeof(pInfo) );
17.
18. if( CreateProcess( NULL,
19. "taskmgr.exe"),
20. NULL,
21. NULL,
22. FALSE,
23. // 线程启动后在入口暂停
24. NULL,
25. NULL,
26. &sInfo,
27. &pInfo )
28. )
29. {
30. // 读取 0x01005469开始的8个数据,包括要修改的0x0100546F处1Byte
31. if (ReadProcessMemory(pInfo.hProcess, (LPVOID)0x01005469, RemoteMemory, 8, &wBytes))
32. {
33. // 多读取的数据为了判断是不是我们要修改的数据,如果不是则不修改,
34. // 这样就不需要判断是什么系统了
35. if (memcmp(RemoteMemory, buf, 8) == 0)
36. {
37. '\xEB'; // RemoteMemory[6] = '\x74'; je 修改成 0xEB jmp
38. LPVOID)0x01005469, RemoteMemory, 8, &wBytes); //写入修改后的数据
39. // 程序继续运行
40. }
41. else
42. {
43. // 留着这个进程没作用,直接关了吧
44. TerminateProcess(pInfo.hProcess, 0);
45. }
46. }
47. CloseHandle( pInfo.hProcess );
48. CloseHandle( pInfo.hThread );
49. }
50. }
可是像上面的代码可能在某些程序里面有问题,原因是程序这样的修改是永久性的,不管程序运行多久,地址0x0100546F的数据永远都变成0xEB,如果程序自校验的就会被发现的,那么再跳转过后,就需要把这个代码改回去的,不过修改回去的方法可没有先前改的简单了,需要用到调试运行设置断点来修改,不细说了,代码如下
1. #include <windows.h>
2.
3. BYTE buf[] = "\x89\x85\x14\xFC\xFF\xFF\x74\x4E";
4. BYTE BreakPoint[] = "\x8D\x85\x24\xFC\xFF\xFF\x50\x68";
5.
6. typedef void (WINAPI * PDebugSetProcessKillOnExit)(BOOL);
7.
8. void MyFunc()
9. {
10. STARTUPINFO sInfo;
11. PROCESS_INFORMATION pInfo;
12. DEBUG_EVENT debug;
13. CONTEXT context;
14.
15. BYTE RemoteMemory[16];
16. DWORD wBytes;
17. BOOL flags = TRUE;
18.
19. sizeof(sInfo) );
20. sizeof(sInfo);
21. sInfo.dwFlags = STARTF_USESHOWWINDOW;
22. sInfo.wShowWindow = SW_SHOWNORMAL;
23. sizeof(pInfo) );
24.
25. if( CreateProcess( NULL,
26. "taskmgr.exe"),
27. NULL,
28. NULL,
29. FALSE,
30. DEBUG_ONLY_THIS_PROCESS,
31. NULL,
32. NULL,
33. &sInfo,
34. &pInfo )
35. )
36. {
37. // 在调试器退出的时候,被调试程序不会退出
38. HMODULE hmodule = LoadLibrary(TEXT("kernel32.dll"));
39. PDebugSetProcessKillOnExit DebugSetProcessKillOnExit =
40. "DebugSetProcessKillOnExit");
41. if ( DebugSetProcessKillOnExit != NULL )
42. {
43. DebugSetProcessKillOnExit(FALSE);
44. }
45.
46. sizeof(context));
47. context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
48. // 等待运行到断点处
49. while(flags)
50. {
51. WaitForDebugEvent( &debug , INFINITE );
52. switch(debug.dwDebugEventCode)
53. {
54. case CREATE_PROCESS_DEBUG_EVENT: // 启动调试事件
55. if (ReadProcessMemory(pInfo.hProcess, (LPVOID)0x01005469, RemoteMemory, 8, &wBytes))
56. {
57. if (memcmp(RemoteMemory, buf, 8) == 0)
58. {
59. // 跳过检测窗口,下断点
60. '\xEB';
61. LPVOID)0x01005469, RemoteMemory, 8, &wBytes);
62. LPVOID)0x010054BF, "\xCC", 1, &wBytes);
63. }
64. }
65. else
66. {
67. flags = FALSE;
68. TerminateProcess(pInfo.hProcess, 0);
69. }
70. break;
71. case EXCEPTION_DEBUG_EVENT: // 异常发生,捕获int 3断点就可以了
72. if ( debug.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT )
73. {
74. if ((DWORD)debug.u.Exception.ExceptionRecord.ExceptionAddress == 0x010054BF )
75. {
76. // 将数据改回去
77. LPVOID)0x01005469, buf, 8, &wBytes);
78. LPVOID)0x010054BF, BreakPoint, 4, &wBytes);
79.
80. // 将EIP往回退1byte
81. if (GetThreadContext(pInfo.hThread, &context))
82. {
83. context.Eip -= 1;
84. SetThreadContext(pInfo.hThread, &context);
85. }
86. // 退出循环
87. }
88. }
89. break;
90. }
91.
92. if (!ContinueDebugEvent(debug.dwProcessId, debug.dwThreadId, DBG_CONTINUE ))
93. {
94. // continue error
95. TerminateProcess(pInfo.hProcess, 0);
96. ExitProcess(0);
97. }
98. }
99.
100. FreeLibrary(hmodule);
101. CloseHandle( pInfo.hProcess );
102. CloseHandle( pInfo.hThread );
103. }
104. }
程序还有可能是用调试的方法加载的自身,还有可能HOOK掉了几个关键的API,还有的可能是驱动级的保护……这个技术实在太多了,想修改那些代码实现多开也是可以的,有矛必有盾嘛。
太累了,还是搞个无图无真相吧。