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