自己玩过这个东西的话,其实大家都能清楚VAC没有对游戏进程做防注入的保护,最简单的远程线程都可以直接把DLL注入到游戏进程当中;当然哈,CSGO这游戏市面上还是有很多在卖钱的外挂,无论是不是只能支持官匹,反正卖钱就vans了!这篇文章会分享对于CSGO的DLL注入和隐藏,可能又是一片影响大佬销量的文章了!
- 注入cheats.dll到csgo.exe
- 注入hidden.dll到csgo.exe
- 卸载hidden.dll
首先注入cheats.dll到游戏进程中,然后注入hidden.dll到游戏来断开PEB的三根链表实现隐藏cheats.dll,最后远程卸载hidden.dll来做到毫无痕迹的完成整个注入流程。
- 注入cheats.dll到csgo.exe
1.1 获取进程ID;通过ToolHelp32的方法来进行查找进程,其实方法有很多,ToolHelp32毕竟简单暴力。
BOOL 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;
}
1.2 打开进程获取句柄;通过进程ID提权,通过包装的OpenProcess函数来提权,然后打开进程返回进程句柄。
HANDLE 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;
}
1.3 在csgo.exe进程中申请内存空间,并将cheats.dll路径写入目标进程地址空间。
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;
}
1.4 远程线程注入;利用CreateRemoteThread,将线程其实地址设为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);