有过脱机外挂编写经历的童鞋们大概都会遇到一个很纠结的问题,现在大多数游戏都要求客户端每几秒给服务器发送一个数据包以确定是否掉线,这样一来在调试程序的时候,无论是对客户端的逆向调试,还是对自己外挂的调试,一旦下了断点程序暂停后,如果不能在短短的几秒中恢复程序运行,那么游戏就会掉线。
在彩虹岛中,心跳包的发送间隔是10秒,我发誓这个问题困扰了我两年。。
比较容易想到的是,看有没有办法能直接截取数据链路层的raw packets,再看看有没有API,或者直接操作网卡驱动来模拟封包。然而这对计算机网络知识要求很高,并且貌似Windows下raw packets的操作很少很少,并且有各种各样的安全限制(防止有人乱发包)。这个设想至今没有能够实现。
今天上课时突然想到一个点子。socket在Windows中是个文件句柄(不严格的),那么是否子进程能够继承这个句柄而做到由子进程来按时发送心跳包。这样就不会造成调试时“心跳暂停”了。
Google以后发现,socket是可以通过DuplicateHandle复制后继承的。但是MSDN给出了一种更好的多进程共享socket的方法,那就是使用WSADuplicateSocket。
查阅MSDN:http://msdn.microsoft.com/en-us/library/ms741565(VS.85).aspx
利用WSADuplicateSocket,socket更是一种共享,而不简单是继承了,亦即任何的进程之间均可共享socket。使用的方法也很简单,源进程对要共享的socket调用WSADuplicateSocket,将返回的WSAPROTOCOL_INFO结构体传递给目标进程,然后目标进程用这个结构体调用WSASocket创建一个新的socket描述符,这个socket即指向原来的socket。
需要注意的是,每次生成的WSAPROTOCOL_INFO结构只能用于创建一次共享socket。另外就是不要Windows并没有对共享socket有IO访问控制的机制,这意味这如果在新的socket上调用recv,那么原程序就没法再recv了;如果两个程序同时调用了send而没有执行同步机制,那么send的数据也将会是乱掉的。事实上,无论在linux还是Windows上,socket的共享目的主要在于父进程accept连接,然后子进程负责通讯。
我在LibCHD中写了个测试,在Socket类的connect和disconnect中分别加入以下代码:
//Connect #ifdef USE_HELPER_PING PROCESS_INFORMATION ProcessInformation; char cmdLine[128]; sprintf(cmdLine, "pinghelper.exe %d %d", GetCurrentProcessId(), (int)&ProtocolInfo); STARTUPINFO si = {sizeof(STARTUPINFO)}; CreateProcess(NULL, cmdLine, NULL, NULL, NULL, CREATE_SUSPENDED, NULL, NULL, &si, &ProcessInformation); hPingProcess = ProcessInformation.hProcess; WSADuplicateSocket(m_fd, ProcessInformation.dwProcessId, &ProtocolInfo); ResumeThread(ProcessInformation.hThread); CloseHandle(ProcessInformation.hThread); #endif //Disconnect #ifdef USE_HELPER_PING TerminateProcess(hPingProcess, -1); CloseHandle(hPingProcess); #endif
然后新建一个工程,命名为pinghelper,代码如下:
#include <WinSock2.h> #include <Windows.h> #include <ShellAPI.h> INT WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd ) { DWORD PID; WSAPROTOCOL_INFO *lpRemoteProtocolInfo; WSAPROTOCOL_INFO LocalProtocolInfo; WSADATA wsaData; int argc; WCHAR **argv = CommandLineToArgvW(GetCommandLineW(), &argc); PID = _wtoi(argv[1]); lpRemoteProtocolInfo = (WSAPROTOCOL_INFO *)_wtoi(argv[2]); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); ReadProcessMemory(hProcess, lpRemoteProtocolInfo, &LocalProtocolInfo, sizeof(WSAPROTOCOL_INFO), NULL); WSAStartup(0x202, &wsaData); SOCKET fd = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &LocalProtocolInfo, 0, 0); while (WaitForSingleObject(hProcess, 10000) == WAIT_TIMEOUT) { char Data[12] = {12, 0, 3 ,0, 0, 0, 0, 0, 0, 0, 0, 0}; send(fd, Data, 12, 0); } closesocket(fd); WSACleanup(); CloseHandle(hProcess); return 0; }
这样就实现了即便暂停程序的运行,依然能够保持与服务器的连接。
在客户端程序里也可以通过Dll注入来做到逆向时不掉线,甚至能做到将正在运行的游戏“替换”成脱机外挂,然后将游戏关掉而不掉线。
从Google的结果来看,目前外挂开发中鲜有共享socket的技术文档,仅仅在某几个Pascal写的脱机外挂的源代码中出现过。希望这篇文章能给大家一些帮助。gmsj0001首发,转载请注明出处~
以前研究过彩虹岛,做过内挂,看到你这个,才知道自己水平差很多。
这代码没问题?程序中还没有复制socket就作参数传给子进程了:
sprintf(cmdLine, “pinghelper.exe %d %d”, GetCurrentProcessId(), (int)&ProtocolInfo);
…
WSADuplicateSocket(m_fd, ProcessInformation.dwProcessId, &ProtocolInfo);
应该是复制完socket再将其作为参数传给子进程吧。
哦,抱歉,我弄错了,我自己是用IPC进行ProtocolInfo传值,你用的是传址读取父进程内存的方法,我的是在调用WSPDuplicateSocket()之后再调用Pipe进行传递。