| 关键词: nbsp DWORD 100118 push eax ptr 00 100119 QQGame 运行 |
本系列仅以学习交流使用,非法使用者,后果自负。 最近闲来无聊就赔朋友一起打打牌,是QQ游戏斗地主游戏,我们通常在一个桌(Table)打牌,这样配合胜率会高一点.有一次他正在和别人打牌,四人已满,我就不能和他一起打了,就随便点了一下发现能进得此桌,原来游戏提供了"旁观"的功能,不过是当时才知道的.于是我就一个个地"旁观"别人的牌,将牌符告诉我的朋友,他果然"运筹帷幄,决胜千里",呵呵.... 于是想到了一个"坏点子":自己开两个号打牌,一个用来旁观,另一个用来打牌,岂不是很容易就取胜?当我兴冲冲地准备"作祟"时,发现QQGame并不能同时运行两个实例,所以我上述的想法就暂时不能实现了.要想实现,就要突破这一关,于是抱着试试看的心理分析下"QQGame不能同时运行两个实例"的原理. 程序实现只能运行一个实例的原理通常是使用互斥元,临界区,信号量等机制. 于是就OD载入,下断: bp CreateMutexA F9运行,中断,再次F9......若干次后会看到堆栈提示: 0013FE90 100118DF /CALL 到 CreateMutexA 来自 Utility.100118D9 0013FE94 00000000 |pSecurity = NULL 0013FE98 00000001 |InitialOwner = TRUE 0013FE9C 00F88700 \MutexName = "QQGame_Mutex03/01/2003" MutexName为"QQGame_Mutex03/01/2003"的就是我们所关心的. 如果知道了这一点,那么要想绕过"QQGame不能同时运行两个实例"就很简单了,我这里采用的是在不破坏原程序完整性 的方法,现介绍如下: MutexName为"QQGame_Mutex03/01/2003"的串存放在一个叫做MainLogi.dll文件中,在游戏安装目录下的Logic文件夹里.如果每次运行游戏程序MutexName与上次的都不相同,不就可以绕过了吗?也就是每次运行程序前偷偷地改变MainLogi.dll文件中的MutexName.具体实现见代码及分析: program QQgameSpy; uses windows,dialogs,sysutils,classes,graphics,shellapi ,registry; var reg:TRegistry; strPath:string; strFileName:string; strNewFile:string; strGameFile:string; strMutex:string; i:integer; hMutex:DWORD; hFile:DWORD; dwBytesOfRW:Cardinal; buffer:BYTE; DstFile:TFileStream; hwnd:DWORD; {$r QQgameSpy.RES} function RenameFile(hdl:THandle;lpOrgName,lpNewName:PAnsichar):boolean; var shf:TSHFILEOPSTRUCT; begin try FillChar(shf,SizeOf(shf),0); shf.Wnd :=hdl; shf.wFunc :=FO_RENAME; shf.pFrom :=lpOrgName; shf.pTo :=lpNewName; shf.fFlags :=FOF_NOCONFIRMATION+FOF_SILENT; Result:=(0=SHFileOperation(shf)); except Result:=false; end; end; { 主程序开始 } begin reg:= TRegistry.Create; reg.RootKey:=HKEY_CURRENT_USER; if reg.OpenKeyReadOnly('\Software\Tencent\QQGame\SYS')= false then exit; strPath:=reg.ReadString('GameDirectory'); //showmessage(strPath); reg.CloseKey; reg.Free; strFileName:=strPath+'Logic\MainLogi.dll'; strMutex:='QQGame_Mutex03/01/2000'; for i:=0 to 3 do begin hMutex:=CreateMutex(nil,false,pchar(strMutex)); if hMutex=0 then continue; if GetLastError<>ERROR_ALREADY_EXISTS then break; Inc( strMutex[Length(strMutex)] ); end; //showmessage(strMutex); buffer:=BYTE(strMutex[Length(strMutex)]); if hMutex=0 then exit; CloseHandle(hMutex); //DstFile:=TFileStream.Create(strFileName,fmOpenReadWrite); //DstFile.Seek($581C1,FILE_BEGIN); //DstFile.WriteBuffer(buffer,sizeof(buffer)); for i:=0 to 2 do begin strNewFile:=strFileName+inttostr(i); DeleteFile(strNewFile); end; for i:=0 to 2 do begin strNewFile:=strFileName+inttostr(i); if FileExists(strNewFile)=false then break; end; RenameFile(0,pchar(strFileName),pchar(strNewFile)); CopyFile(pchar(strNewFile),pchar(strFileName),true); hFile:=CreateFile(pchar(strFileName),GENERIC_WRITE or GENERIC_READ,FILE_SHARE_WRITE or FILE_SHARE_READ, nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); if hFile=INVALID_HANDLE_VALUE then exit; SetFilePointer(hFile,$581C1,nil,FILE_BEGIN); WriteFile(hFile,buffer,sizeof(buffer),dwBytesOfRW,nil); CloseHandle(hFile); strGameFile:=strPath+'QQGame.exe'; //showmessage(strGameFile); ShellExecute(0,'Open',pchar(strGameFile),nil,nil,SW_SHOWNORMAL); end. 解释: 当程序运行时通过注册表查询QQGame的安装目录,进而获取MainLogi.dll文件的完整文件名。由于QQ游戏一般来说没桌最多不超过四人,所以每次运行前首先查看MutexName为: 'QQGame_Mutex03/01/2000'; 'QQGame_Mutex03/01/2001'; 'QQGame_Mutex03/01/2002'; 'QQGame_Mutex03/01/2000'; 的互斥元是否存在,如果遇到有不存在的,说明可以以此互斥元名运行QQ游戏,就改写原MainLogi.dll文件名为其他,复制一份MainLogi.dll文件并更换其中的MutexName,最后ShellExecute QQ游戏,这样当QQ游戏运行检测时就会使用我们更改过的MutexName,由于我们实现已经检测过这个MutexName还没有被使用,当然可以成功运行了。 这里你可能很诧异:MainLogi.dll文件被游戏加载过一次,下次还能被改名吗?我试过了,删除此文件不可以,但是可以对其重命名。上面声明的RenameFile就是实现更改文件名功能的。 运行后,可以开N个大厅,于是乎狂玩了一下午,很快刷到了600多分,胜负率高达60%以上啊!不过也就是当时一会的劲头,再后来就没有玩过了,现在将此方法公布与大家分享一下,希望玩得愉快! 说明:在我欲发此贴时,QQGame有所更新,使用上述方法不行了,如果不给出一个新的解决方法,总感觉不甚完美,于是重新又跟了一次。思路如下: 如果已有一个登录窗体运行了,那么再运行实例会给那个已运行的实例一个焦点,让用户知道“你已经运行啦”,然后退出。 于是 Bp FindWindowA Bp FindWindowExA F9运行,中断一个,看堆栈提示: 0013FE7C 100119A3 /CALL 到 FindWindowExA 来自 Utility.1001199D 0013FE80 00000000 |hParent = NULL 0013FE84 00000000 |hAfterWnd = NULL 0013FE88 01069BD0 |Class = "QQGame_MainFrame" 0013FE8C 00000000 \Title = NULL 返回到调用的过程: 《Utility领空》 1001199D FF15 18920110 call dword ptr [<&USER32.FindWindowEx>; USER32.FindWindowExA 100119A3 8BF8 mov edi, eax 100119A5 57 push edi 100119A6 FF15 78920110 call dword ptr [<&USER32.IsWindow>] ; USER32.IsWindow 100119AC 85C0 test eax, eax 100119AE 74 0E je short 100119BE 100119B0 53 push ebx 100119B1 53 push ebx 100119B2 FF76 50 push dword ptr [esi+50] 100119B5 57 push edi 100119B6 FF15 7C920110 call dword ptr [<&USER32.PostMessageA>; USER32.PostMessageA 100119BC EB 10 jmp short 100119CE 100119BE 83C6 14 add esi, 14 100119C1 68 1CE70110 push 1001E71C 100119C6 56 push esi 100119C7 E8 4A4FFFFF call 10006916 100119CC 59 pop ecx 100119CD 59 pop ecx 100119CE 5F pop edi 100119CF 5E pop esi 100119D0 5B pop ebx 100119D1 C3 retn 执行流程是这样的,检测是否已有窗体运行了,如果有找到它,给它发个消息(这个消息估计就是让那个窗体拥有焦点的)然后退出。 我们由此过程再次返回,发现是 100118F5 E8 92000000 call 1001198C 调用的上述过程,前后浏览一下代码: 《Utility领空》 10011846 55 push ebp 10011847 8BEC mov ebp, esp 10011849 51 push ecx 1001184A 8B45 18 mov eax, dword ptr [ebp+18] 1001184D 8365 FC 00 and dword ptr [ebp-4], 0 10011851 53 push ebx 10011852 56 push esi 10011853 8BF1 mov esi, ecx 10011855 57 push edi 10011856 BB 00010000 mov ebx, 100 1001185B 8946 54 mov dword ptr [esi+54], eax 1001185E 8B45 14 mov eax, dword ptr [ebp+14] 10011861 8DBE 58010000 lea edi, dword ptr [esi+158] 10011867 8946 50 mov dword ptr [esi+50], eax 1001186A 85FF test edi, edi 1001186C 74 21 je short 1001188F 1001186E 837D 10 00 cmp dword ptr [ebp+10], 0 10011872 74 1B je short 1001188F 10011874 53 push ebx 10011875 8D4E 45 lea ecx, dword ptr [esi+45] 10011878 FF75 10 push dword ptr [ebp+10] 1001187B E8 16050000 call 10011D96 10011880 85C0 test eax, eax 10011882 74 0B je short 1001188F 10011884 FF75 10 push dword ptr [ebp+10] 10011887 57 push edi 10011888 E8 19580000 call <jmp.&MSVCRT.strcpy> 1001188D 59 pop ecx 1001188E 59 pop ecx 1001188F 8D7E 58 lea edi, dword ptr [esi+58] 10011892 85FF test edi, edi 10011894 74 21 je short 100118B7 10011896 837D 0C 00 cmp dword ptr [ebp+C], 0 1001189A 74 1B je short 100118B7 1001189C 53 push ebx 1001189D 8D4E 45 lea ecx, dword ptr [esi+45] 100118A0 FF75 0C push dword ptr [ebp+C] 100118A3 E8 EE040000 call 10011D96 100118A8 85C0 test eax, eax 100118AA 74 0B je short 100118B7 100118AC FF75 0C push dword ptr [ebp+C] 100118AF 57 push edi 100118B0 E8 F1570000 call <jmp.&MSVCRT.strcpy> 100118B5 59 pop ecx 100118B6 59 pop ecx 100118B7 837D 08 00 cmp dword ptr [ebp+8], 0 100118BB 74 5F je short 1001191C 100118BD FF75 08 push dword ptr [ebp+8] 100118C0 E8 E7570000 call <jmp.&MSVCRT.strlen> 100118C5 F7D8 neg eax 100118C7 1BC0 sbb eax, eax 100118C9 59 pop ecx 100118CA 40 inc eax 100118CB 8945 10 mov dword ptr [ebp+10], eax 100118CE 75 4C jnz short 1001191C 100118D0 FF75 08 push dword ptr [ebp+8] 100118D3 6A 01 push 1 100118D5 5F pop edi 100118D6 57 push edi 100118D7 6A 00 push 0 100118D9 FF15 E8900110 call dword ptr [<&KERNEL32.CreateMutexA>] ; kernel32.CreateMutexA 100118DF 85C0 test eax, eax 100118E1 8946 4C mov dword ptr [esi+4C], eax 100118E4 74 24 je short 1001190A 100118E6 FF15 AC900110 call dword ptr [<&KERNEL32.GetLastError>] ; ntdll.RtlGetLastWin32Error 100118EC 3D B7000000 cmp eax, 0B7 100118F1 8BCE mov ecx, esi 100118F3 75 07 jnz short 100118FC 100118F5 E8 92000000 call 1001198C 100118FA EB 2A jmp short 10011926 100118FC E8 64010000 call 10011A65 10011901 85C0 test eax, eax 10011903 74 21 je short 10011926 10011905 897D FC mov dword ptr [ebp-4], edi 10011908 EB 1C jmp short 10011926 1001190A 83C6 14 add esi, 14 1001190D 68 F8E60110 push 1001E6F8 10011912 56 push esi 10011913 E8 FE4FFFFF call 10006916 10011918 59 pop ecx 10011919 59 pop ecx 1001191A EB 0A jmp short 10011926 1001191C 8BCE mov ecx, esi 1001191E E8 42010000 call 10011A65 10011923 8945 FC mov dword ptr [ebp-4], eax 10011926 8B45 FC mov eax, dword ptr [ebp-4] 10011929 5F pop edi 1001192A 5E pop esi 1001192B 5B pop ebx 1001192C C9 leave 1001192D C2 1400 retn 14 在运行到 100118D9 FF15 E8900110 call dword ptr [<&KERNEL32.CreateMutexA>] ; kernel32.CreateMutexA 一句时,MutexName没有变,就是和我们之前的分析是一样的,只是多了一个对窗口的检测,呵呵,好对付。 再次返回: 《MainLogi领空》 00F6714E 56 push esi 00F6714F 8BF1 mov esi, ecx 00F67151 57 push edi 00F67152 83BE A8010000 0>cmp dword ptr [esi+1A8], 0 00F67159 75 25 jnz short 00F67180 00F6715B E8 BF0F0100 call 00F7811F 00F67160 85C0 test eax, eax 00F67162 74 1C je short 00F67180 00F67164 8B10 mov edx, dword ptr [eax] 00F67166 68 02900004 push 4009002 00F6716B 68 8440F800 push 00F84084 ; ASCII "Utility.dll" 00F67170 56 push esi 00F67171 8BC8 mov ecx, eax 00F67173 FF52 08 call dword ptr [edx+8] 00F67176 85C0 test eax, eax 00F67178 74 06 je short 00F67180 00F6717A 8986 A8010000 mov dword ptr [esi+1A8], eax 00F67180 8B86 A8010000 mov eax, dword ptr [esi+1A8] 00F67186 8BD6 mov edx, esi 00F67188 F7DA neg edx 00F6718A 8D48 04 lea ecx, dword ptr [eax+4] 00F6718D 8D7E 04 lea edi, dword ptr [esi+4] 00F67190 1BD2 sbb edx, edx 00F67192 6A 00 push 0 00F67194 8B01 mov eax, dword ptr [ecx] 00F67196 23D7 and edx, edi 00F67198 52 push edx 00F67199 FF10 call dword ptr [eax] 00F6719B 8B8E A8010000 mov ecx, dword ptr [esi+1A8] 00F671A1 68 5E100000 push 105E 00F671A6 68 861F0000 push 1F86 00F671AB 68 1887F800 push 00F88718 ; ASCII "QQGame_MainFrame" 00F671B0 8B01 mov eax, dword ptr [ecx] 00F671B2 68 EC80F800 push 00F880EC ; ASCII "QQGame" 00F671B7 68 0087F800 push 00F88700 ; ASCII "QQGame_Mutex03/01/2003" 00F671BC FF50 0C call dword ptr [eax+C] ; 进 00F671BF 8BF8 mov edi, eax 00F671C1 83C6 08 add esi, 8 00F671C4 57 push edi 00F671C5 68 C886F800 push 00F886C8 ; ASCII "Logout_Optimize CheckMultiInstanceRunning() return [%d]" 00F671CA 56 push esi 00F671CB E8 EEF1FEFF call 00F563BE 00F671D0 83C4 0C add esp, 0C 00F671D3 8BC7 mov eax, edi 00F671D5 5F pop edi 00F671D6 5E pop esi 00F671D7 C3 retn 再次返回 《QQGame领空》 004010CE |. FF50 38 call dword ptr [eax+38] ;进 004010D1 |. 85C0 test eax, eax 004010D3 |. 74 13 je short 004010E8 注意这个返回值就很重要了,如果暴力一点的话就就将004010D3 je short 004010E8 Nop掉就OK了。 更新的版本主要是多了一个对窗口的检测,标志值存放在eax里,如果想跟踪的话就关键看对eax的修改即可,我就不跟了。 不过这样有一个缺陷,那就是如果程序中增加了文件校验就会发现程序被修改而拒绝执行,不过“兵来将挡,水来土掩”,知道了这个机理解决办法还是不少的。 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|