说来话长,一直喜欢玩csgo游戏的我,前几天在同学的带领下,开始接触了外挂,csgo的wg封为两类,一波是Dll类型的,比如
OneTap.su.dll、Osiris-1.dll、Kiddos-1.dl等等、另一波是Exe类型的,比如大地球演员版、V3 了 等等系列,后来,尝试过了注入器将Dll注入到csgo程序中,开始了"wg"游戏(纯粹是为了学习,不要模仿,不提供任何DLLwg版本文件,wg文件全部来自于外网),劝大家不要开g,在我尝试了解wg的过程中,csgo账号被封停了。
最近V社,要对CSGO的引擎进行全面升级了,之前一直用的是起源1 ,据说要升级成起源2,但不知道什么时候才能升级完成,还是据说起源2会有新的VAC机制,也导致全球的wg价格直接跌了很多,之前wg邀请码2000$的价格,现在也就几十了,所以趁着现在还能用的wg,就分析了一下。
但是不过V社这种,把院子大门给你打开,但在院子里装门禁的老态不知道能不能改变。(其实我是在说,不做游戏保护,但是封号的行为)
刚开始的时候,用的是外网下载的注入器,原本以为csgo会有防止注入的保护,就反汇编IDA了一下ProjectInfinityInjector-1.exe(一个注入器),然而并没有发现他有什么牛X之处。
这是外网下载的DLL注入器,用PEID查看一下信息,发现是用C#写的。
后来我用到了IDA反汇编工具,查看了一下,虽然对C#支持不好,但是还是可以简单的看出来是通过什么来方法来注入的。
原本以为这个注入器可能会有过Csgo游戏保护的方法,后来发现我错了,原来Valve(V社)并没有做过防注入保护,直接就可以对原游戏程序进程注入,说到这,就不得不提我国鹅厂了,还是我国注重游戏的外挂保护啊,我觉得完全可以把游戏保护交给鹅肠来做(狗头保命,纯属玩笑)。
开上图的注入方法,其实就是CreateRemoteThread的方法注入的DLL,也很简单。但是完全没有想过自己开发一个注入器,因为不难,所以没做,但是这个注入器当你注入的时候,会给你谈广告,这个我是真的忍不了
所以我就自己写了一个注入器,还进行升级了一下,隐藏掉dll,这样可以躲过5e(国内最大的游戏匹配社区)外挂机制的检测了,用MFC写的,很简单,流程大概就是这样
(一)注入外挂DLL到csgo游戏
(二)注入HideModule.DLL到csgo游戏,通过断开PEB的三根链表,实现外挂DLL的隐藏
(三)远程卸载HideModule.DLL,实现无痕迹。
这里我是用ToolHelp32的方法来进行查找的,方法有很多,不过这种比较简单。
BOOL CInjectToCsgo::GetProcessIdByProcessImageName(HANDLE* ProcessID, const TCHAR* ProcessImageName ) { BOOL IsOk = FALSE; HANDLE SnapshotHandle = INVALID_HANDLE_VALUE; PROCESSENTRY32 ProcessEntry32; ProcessEntry32.dwSize = sizeof(PROCESSENTRY32); int LastError = 0; SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //TH32CS_SNAPPROCESS:Includes all processes in the system in the snapshot.To enumerate the processes, if (SnapshotHandle == INVALID_HANDLE_VALUE) { LastError = GetLastError(); return FALSE; } if (!Process32First(SnapshotHandle, &ProcessEntry32)) { LastError = GetLastError(); goto Exit; } do { if (_tcsicmp(ProcessEntry32.szExeFile, ProcessImageName) == 0) { *ProcessID = (HANDLE)ProcessEntry32.th32ProcessID; IsOk = TRUE; goto Exit; } } while (Process32NextW(SnapshotHandle, &ProcessEntry32)); Exit: if (SnapshotHandle != INVALID_HANDLE_VALUE) { CloseHandle(SnapshotHandle); } SnapshotHandle = INVALID_HANDLE_VALUE; SetLastError(LastError); return IsOk; }
通过包装OpenProcess函数,提权然后打开进程,返回句柄
HANDLE CInjectToCsgo::OpenProcess(DWORD DesiredAccess, BOOL IsInheritHandle, HANDLE ProcessID) { if (m_EnableDebugPrivilege) { EnableSeDebugPrivilege(_T("SeDebugPrivilege"), TRUE); } HANDLE ProcessHandle = ::OpenProcess(DesiredAccess, IsInheritHandle, (DWORD)ProcessID); DWORD LastError = GetLastError(); if (m_EnableDebugPrivilege) { EnableSeDebugPrivilege(_T("SeDebugPrivilege"), FALSE); } SetLastError(LastError); return ProcessHandle; } BOOL CInjectToCsgo::EnableSeDebugPrivilege(const TCHAR * PriviledgeName, BOOL IsEnable) { BOOL IsOk = FALSE; int LastError = 0; //获取当前进程句柄(伪句柄) HANDLE ProcessHandle = GetCurrentProcess(); HANDLE TokenHandle = INVALID_HANDLE_VALUE; TOKEN_PRIVILEGES TokenPrivileges = { 0 }; //通过当前进程句柄获得当前进程中令牌句柄 if (!OpenProcessToken(ProcessHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) /*BOOL OpenProcessToken( ** 得到进程的令牌句柄 __in HANDLE ProcessHandle, //要修改访问权限的进程句柄 __in DWORD DesiredAccess, //指定你要进行的操作类型 __out PHANDLE TokenHandle //返回的访问令牌指针 )*/ { LastError = GetLastError(); goto Exit; } LUID Luid; //Locally Unique Identifier if (!LookupPrivilegeValue(NULL, PriviledgeName, &Luid)) // 通过权限名称查找uID //函数查看系统权限的特权值,返回信息到一个LUID结构体里 /*BOOL LookupPrivilegeValue( LPCTSTR lpSystemName, 表示所要查看的系统,本地系统直接用NULL LPCTSTR lpName, 指向一个以零结尾的字符串,指定特权的名称 PLUID lpLuid); 接收所返回的制定特权名称的信息*/ { LastError = GetLastError(); goto Exit; } TokenPrivileges.PrivilegeCount = 1; // 要提升的权限个数 TokenPrivileges.Privileges[0].Attributes = IsEnable == TRUE ? SE_PRIVILEGE_ENABLED : 0; TokenPrivileges.Privileges[0].Luid = Luid; if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) // 启用或禁用特权一个有TOKEN_ADJUST_PRIVILEGES访问的访问令牌. /*BOOL AdjustTokenPrivileges( HANDLE TokenHandle, //包含特权的句柄 必须有 TOKEN_ADJUST_PRIVILEGES访问令牌 BOOL DisableAllPrivileges,//禁用所有权限标志 PTOKEN_PRIVILEGES NewState,//新特权信息的指针(结构体) DWORD BufferLength, //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof) PTOKEN_PRIVILEGES PreviousState,//接收被改变特权当前状态的Buffer PDWORD ReturnLength //接收PreviousState缓存区要求的大小 );*/ { LastError = GetLastError(); goto Exit; } IsOk = TRUE; Exit: if (TokenHandle != INVALID_HANDLE_VALUE) { CloseHandle(TokenHandle); TokenHandle = INVALID_HANDLE_VALUE; } SetLastError(LastError); return IsOk; }
这里写入DL路径到目标进程是为了,CreateRemoteThread的时候,LoadLibrary的参数问题。
VirtualAddress = VirtualAllocEx(csgoHandle, NULL, BufferLength, MEM_COMMIT, PAGE_READWRITE); if (VirtualAddress == NULL) { goto Exit; } if (ProcessMemoryWriteSafe(csgoHandle, VirtualAddress, FilePath.GetString(), BufferLength, &ReturnLength) == FALSE) { goto Exit; } BOOL CInjectToCsgo::ProcessMemoryWriteSafe(HANDLE ProcessHandle, LPVOID VirtualAddress, LPCVOID BufferData, SIZE_T BufferLength, SIZE_T * ReturnLength) { SIZE_T v1 = 0; SIZE_T* v2 = 0; int LastError = 0; DWORD OldProtect = 0; BOOL IsOk = FALSE; if ((ProcessHandle == 0) || (VirtualAddress == 0) || (BufferData == 0) || (BufferLength == 0)) { LastError = ERROR_INVALID_PARAMETER; goto Exit; } if (!ReturnLength) { v2 = &v1; } else { v2 = ReturnLength; } if (!WriteProcessMemory(ProcessHandle, VirtualAddress, BufferData, BufferLength, v2)) /*BOOL WriteProcessMemory( ****函数能写入某一进程的内存区域 HANDLE hProcess, 由OpenProcess返回的进程句柄。如参数传数据为 INVALID_HANDLE_VALUE 【即-1】目标进程为自身进程 LPVOID lpBaseAddress, 要写的内存首地址 再写入之前,此函数将先检查目标地址是否可用,并能容纳待写入的数据 LPVOID lpBuffer, 指向要写的数据的指针。 DWORD nSize, 要写入的字节数。 LPDWORD lpNumberOfBytesWritten 实际数据的长度 );*/ { if (VirtualProtectEx(ProcessHandle, VirtualAddress, BufferLength, PAGE_EXECUTE_READWRITE, &OldProtect)) { if (WriteProcessMemory(ProcessHandle, VirtualAddress, BufferData, BufferLength, v2)) { IsOk = TRUE; } else { LastError = GetLastError(); } VirtualProtectEx(ProcessHandle, VirtualAddress, BufferLength, OldProtect, &OldProtect); } else { LastError = GetLastError(); } } else { IsOk = TRUE; } Exit: SetLastError(LastError); return IsOk; }
用CreateRemoteThread,将线程起始地址设为LoadLibrary的地址,至于为什么加载本地的LoadLibrary地址,在所有进程中(包括在不同电脑上)都能使用,我以后可能写一篇文章(如果记得起来!)
HANDLE ThreadHandle = CreateRemoteThread ( csgoHandle, //线程所属进程的进程句柄 NULL, // 结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄 NULL,则线程获取默认安全描述符,并且不能继承句柄 0, //线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小. (LPTHREAD_START_ROUTINE)FunctionAddress, //在远程进程的地址空间中, 该线程的线程函数的起始地址. VirtualAddress, // 0, //控制线程创建的标志 NULL); //线程的创建标志 NULL则不返回线程标识符. if (ThreadHandle == NULL) { VirtualFreeEx(csgoHandle, VirtualAddress, BufferLength, MEM_RELEASE); goto Exit; } //等待远程线程结束 WaitForSingleObject(ThreadHandle, INFINITE);
我们注入OneTap.su.dll,来进行测试。
可以看到csgo.exe程序中的未知文件中并没有游戏wgDLL。
可以看到,游戏中已经加载了wgDLL,而且通过火绒
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 2020-5-12 19:35 被Bw编辑 ,原因: