Csgo(反恐精英:全球攻势)的游戏之旅----Dll注入和隐藏(Hacker) 上

2020-05-20 13:44:11 Author: bbs.pediy.com
觉得文章还不错?,点我收藏




   说来话长,一直喜欢玩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,实现无痕迹。

(一)注入外挂DLL到csgo游戏 

第一步:获取进程ID(通过进程名字)

这里我是用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;

}

 第二步:打开进程获取句柄(通过进程ID、提权)

 通过包装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;
}

 第三步:在csgo进程中申请内存,并将DLL路径写入目标进程地址空间

 这里写入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编辑 ,原因:



觉得文章还不错?,点我收藏



如果文章侵犯到您的版权,请联系我:buaq.net[#]pm.me