依星源码资源网,依星资源网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

【好消息,好消息,好消息】VIP会员可以发表文章赚积分啦 !
查看: 27|回复: 0

一个编写传奇封外挂(反外挂)系统的完成过程 - 线程监测篇

[复制链接] 主动推送

2636

主题

2645

帖子

3377

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3377
发表于 昨天 19:47 | 显示全部楼层 |阅读模式
一个编写传奇封外挂(反外挂)系统的完成过程 - 线程监测篇
编写游戏外挂时、免不了需要用到线程知识。一般会使用到远程线程注入(远程CALL)和代码注入并启动线程来运行被注入的代码(或DLL)。通过对线程的监控可检测到非法外挂的使用情况。

一、远程线程注入
   远程线程注入可注入DLL或一段独立的shellcode代码、注入DLL和注入shellcode的过程大同小异。一般过程如下:

1、先使用OpenProcess函数打开进程

2、 使用VirtualAllocEx函数开辟新的空间用于存储shellcode代码或要注入的dll文件路径

3、使用 WriteProcessMemory函数将数据写入第2步开辟的空间

4、使用CreateRemoteThread函数启动新的线程执行注入代码。

注入DLL演示代码:

  1. BOOL remoteCall_dll(DWORD dwPid,char* szDllPath)
  2. {
  3.         //打开进程
  4.         HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPid);
  5.         if (!hProcess)
  6.         {
  7.                 return FALSE;
  8.         }

  9.         //开辟空间
  10.         LPVOID lpDllPathAddress = VirtualAllocEx(hProcess, NULL, strlen(szDllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
  11.         if (!lpDllPathAddress)
  12.         {
  13.                 CloseHandle(hProcess);
  14.                 return FALSE;
  15.         }

  16.         //写入要注入的DLL地址
  17.         if (!WriteProcessMemory(hProcess, lpDllPathAddress, szDllPath, strlen(szDllPath) + 1, NULL))
  18.         {
  19.                 VirtualFreeEx(hProcess, lpDllPathAddress, 0, MEM_RELEASE);
  20.                 CloseHandle(hProcess);
  21.                 return FALSE;
  22.         }

  23.         //获取LoadLibraryA函数地址,一般来说不同进程的函数地址是不同的
  24.         //但是同一个操作系统下系统DLL一般加载地址相同,所以可简单地认为本进程的 LoadLibraryA 与目标进程的 LoadLibraryA 地址是相同的
  25.         HMODULE hModlKernel32 = GetModuleHandleA("kernel32.dll");
  26.         if (!hModlKernel32)
  27.         {
  28.                 VirtualFreeEx(hProcess, lpDllPathAddress, 0, MEM_RELEASE);
  29.                 CloseHandle(hProcess);
  30.                 return FALSE;
  31.         }
  32.         LPTHREAD_START_ROUTINE fnLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hModlKernel32, "LoadLibraryA");

  33.         //启动远程线程运行 LoadLibraryA
  34.         HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, fnLoadLibrary, lpDllPathAddress, 0, NULL);
  35.         if (!hRemoteThread)
  36.         {
  37.                 VirtualFreeEx(hProcess, lpDllPathAddress, 0, MEM_RELEASE);
  38.                 CloseHandle(hProcess);
  39.                 return FALSE;
  40.         }

  41.         WaitForSingleObject(hRemoteThread, INFINITE);
  42.         VirtualFreeEx(hProcess, lpDllPathAddress, 0, MEM_RELEASE);
  43.         CloseHandle(hRemoteThread);
  44.         CloseHandle(hProcess);
  45.         return TRUE;
  46. }
复制代码
注入shellcode演示代码:
  1. BOOL remoteCall_shellcode(DWORD dwPid, char* szShellcode, DWORD dwCodeLength, LPVOID lpParameter)
  2. {
  3.         //打开进程
  4.         HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPid);
  5.         if (!hProcess)
  6.         {
  7.                 return FALSE;
  8.         }

  9.         //开辟空间
  10.         LPVOID lpCodeAddress = VirtualAllocEx(hProcess, NULL, dwCodeLength, MEM_COMMIT, PAGE_READWRITE);
  11.         if (!lpCodeAddress)
  12.         {
  13.                 CloseHandle(hProcess);
  14.                 return FALSE;
  15.         }

  16.         //写入要注入的 shellcode 代码
  17.         if (!WriteProcessMemory(hProcess, lpCodeAddress, szShellcode, dwCodeLength, NULL))
  18.         {
  19.                 VirtualFreeEx(hProcess, lpCodeAddress, 0, MEM_RELEASE);
  20.                 CloseHandle(hProcess);
  21.                 return FALSE;
  22.         }

  23.         //启动远程线程运行 shellcode
  24.         HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpCodeAddress, lpParameter, 0, NULL);
  25.         if (!hRemoteThread)
  26.         {
  27.                 VirtualFreeEx(hProcess, lpCodeAddress, 0, MEM_RELEASE);
  28.                 CloseHandle(hProcess);
  29.                 return FALSE;
  30.         }

  31.         WaitForSingleObject(hRemoteThread, INFINITE);
  32.         VirtualFreeEx(hProcess, lpCodeAddress, 0, MEM_RELEASE);
  33.         CloseHandle(hRemoteThread);
  34.         CloseHandle(hProcess);
  35.         return TRUE;
  36. }
复制代码
二、如何通过线程检测外挂
    我们已经知道外挂会向我们的游戏进程注入代码启动线程,那么我们就可以通过对线程的检测来判断玩家是否使用外挂。比如知名的简X挂,就是注入一段代码在user32.dll的节空闲空间,然后启动线程来执行的。

      需要注意:通过线程检测游戏外挂有一些特殊情况需要处理,比如说输入法有时候也会启动线程,安全软件也可能会启动新线程,win10,win11的内存压缩功能也会启动新线程。会给我们通过线程来判断是否外挂的方案造成干扰。

    应对办法: 一) 尽量采集这些合法的线程特征,加以排除。二) 对确定是外挂的线程特征加入黑名单。具体实施方案请大家自行思考处理。做成服务器端收集这些线程数据,再人为判断加入特诊库的方式,类似杀毒软件的特征库。

   但是本文介绍另一种简单的懒得维护特征库的方法,配合内存模块监测法一起使用。我们不求一种方案能完全监测出所有的外挂,只要能监测出一类特征就行。将我们整个系列介绍的所有方法汇集在一起合理地使用、基本就可以杜绝所有的外挂形式了。这里介绍的方法我把它叫做‘’野线程监测法”,所谓野线程,就是我们可以预料的所有合法空间(野空间)以外的线程。其中合法空间包括PE文件正常代码节空间,和自己所申请的空间。而第三方外挂进程通过 VirtualAllocEx 函数申请的空间自然就在合法空间之外。我们可以简单地判断:只要是线程的启动地址(甚至定时监测线程的eip地址),只要在野空间中,就判定为是非法外挂。

   这里判断线程可以使用线程枚举或通过peb获取线程列表、还有一种是被加载进内的DllMain函数也会接收到线程的启动和结束事件 DLL_THREAD_ATTACH/DLL_THREAD_DETACH。枚举线程会用到这些函数 CreateToolhelp32Snapshot、Thread32First、Thread32Next 以及使用微软未公开函数 ZWQueryInformationThread来获取线程的入口地址。

  1. typedef enum _THREADINFOCLASS {
  2.              ThreadBasicInformation = 0,
  3.              ThreadTimes = 1,
  4.              ThreadPriority = 2,
  5.              ThreadBasePriority = 3,
  6.              ThreadAffinityMask = 4,
  7.              ThreadImpersonationToken = 5,
  8.              ThreadDescriptorTableEntry = 6,
  9.              ThreadEnableAlignmentFaultFixup = 7,
  10.              ThreadEventPair_Reusable = 8,
  11.              ThreadQuerySetWin32StartAddress = 9,
  12.              ThreadZeroTlsCell = 10,
  13.              ThreadPerformanceCount = 11,
  14.              ThreadAmILastThread = 12,
  15.              ThreadIdealProcessor = 13,
  16.              ThreadPriorityBoost = 14,
  17.              ThreadSetTlsArrayAddress = 15,   // Obsolete
  18.              ThreadIsIoPending = 16,
  19.              ThreadHideFromDebugger = 17,
  20.              ThreadBreakOnTermination = 18,
  21.              ThreadSwitchLegacyState = 19,
  22.              ThreadIsTerminated = 20,
  23.              ThreadLastSystemCall = 21,
  24.              ThreadIoPriority = 22,
  25.              ThreadCycleTime = 23,
  26.              ThreadPagePriority = 24,
  27.              ThreadActualBasePriority = 25,
  28.              ThreadTebInformation = 26,
  29.              ThreadCSwitchMon = 27,   // Obsolete
  30.              ThreadCSwitchPmu = 28,
  31.              ThreadWow64Context = 29,
  32.              ThreadGroupInformation = 30,
  33.              ThreadUmsInformation = 31,   // UMS
  34.              ThreadCounterProfiling = 32,
  35.              ThreadIdealProcessorEx = 33,
  36.              ThreadCpuAccountingInformation = 34,
  37.              ThreadSuspendCount = 35,
  38.              ThreadActualGroupAffinity = 41,
  39.              ThreadDynamicCodePolicyInfo = 42,
  40.              MaxThreadInfoClass = 45,
  41. } THREADINFOCLASS;

  42. typedef DWORD(WINAPI* type_ZWQueryInformationThread)(
  43.             _In_      HANDLE          ThreadHandle,
  44.             _In_      THREADINFOCLASS ThreadInformationClass,
  45.             _In_      PVOID           ThreadInformation,
  46.             _In_      ULONG           ThreadInformationLength,
  47.             _Out_opt_ PULONG          ReturnLength
  48.         );

  49. HMODULE hMod_Ntdll = LoadLibraryA("ntdll.dll");

  50. type_ZWQueryInformationThread ZWQueryInformationThread =  (type_ZWQueryInformationThread )GetProcAddress(hMod_Ntdll ,"ZWQueryInformationThread");
复制代码
下面是本项目实际开发中用到的函数部分参考

  1. void game_thread::detectthread(bool bForInit)
  2. {
  3.         int i;
  4.         //枚举线程,检查线程函数地址是否在模块空间内
  5.         THREADENTRY32 te32 = { 0 };
  6.         te32.dwSize = sizeof(THREADENTRY32);
  7.         HANDLE hThreadSnap = g_pApis->CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  8.         if (hThreadSnap == INVALID_HANDLE_VALUE)
  9.         {
  10.                 return;
  11.         }
  12.         DWORD dwCurPID = g_pApis->GetCurrentProcessId();
  13.         if (g_pApis->Thread32First(hThreadSnap, &te32))
  14.         {
  15.                 do
  16.                 {
  17.                         if (te32.th32OwnerProcessID == dwCurPID)
  18.                         {
  19.                                 cslock lock(this->m_threadlist_pcs);
  20.                                 GAME_THREAD_ITEM_INFORMATION* pinfo = this->findinfo(te32.th32ThreadID);
  21.                                 if (pinfo == NULL || pinfo->iswhite == FALSE)
  22.                                 {
  23.                                         HANDLE hThread = g_pApis->OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
  24.                                         //获取线程地址 dwStartAddress = 0 的是主线程 ,在 bForInit = true 时已加入白名单
  25.                                         DWORD dwStartAddress = 0;
  26.                                         if (g_pApis->ZWQueryInformationThread(hThread, THREADINFOCLASS::ThreadQuerySetWin32StartAddress, (PVOID)&dwStartAddress, sizeof(dwStartAddress), NULL) == 0L
  27.                                                 && dwStartAddress != 0
  28.                                                 )
  29.                                         {
  30.                                                //具体处理过程隐藏,请按照自己的项目需要自行设计
  31.                     }
  32.                                 }
  33.                                 if (pinfo != NULL)
  34.                                 {
  35.                                         pinfo->detect_count++;
  36.                                 }
  37.                         }
  38.                 } while (g_pApis->Thread32Next(hThreadSnap, &te32));
  39.         }
  40.         g_pApis->CloseHandle(hThreadSnap);

  41.         //计数累加
  42.         if (!bForInit)
  43.         {
  44.                 this->m_detect_called_count++;
  45.         }

  46.         {
  47.                 cslock lock(this->m_threadlist_pcs);

  48.                 for (i = 0; i < this->m_thread_list.Get_Count(); i++)
  49.                 {
  50.                         GAME_THREAD_ITEM_INFORMATION* pinfo = this->m_thread_list.Get_ItemAt(i);
  51.                          
  52.                         //具体处理过程隐藏,请按照自己的项目需要自行设计
  53.                 }
  54.         }

  55.        
  56. }
复制代码
总结

    本文主要介绍通过野线程判定法判断是否正在使用外挂的情况。


相关帖子

扫码关注微信公众号,及时获取最新资源信息!下载附件优惠VIP会员6折;永久VIP4折
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

免责声明:
1、本站提供的所有资源仅供参考学习使用,版权归原著所有,禁止下载本站资源参与商业和非法行为,请在24小时之内自行删除!
2、本站所有内容均由互联网收集整理、网友上传,并且以计算机技术研究交流为目的,仅供大家参考、学习,请勿任何商业目的与商业用途。
3、若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。
4、论坛的所有内容都不保证其准确性,完整性,有效性,由于源码具有复制性,一经售出,概不退换。阅读本站内容因误导等因素而造成的损失本站不承担连带责任。
5、用户使用本网站必须遵守适用的法律法规,对于用户违法使用本站非法运营而引起的一切责任,由用户自行承担
6、本站所有资源来自互联网转载,版权归原著所有,用户访问和使用本站的条件是必须接受本站“免责声明”,如果不遵守,请勿访问或使用本网站
7、本站使用者因为违反本声明的规定而触犯中华人民共和国法律的,一切后果自己负责,本站不承担任何责任。
8、凡以任何方式登陆本网站或直接、间接使用本网站资料者,视为自愿接受本网站声明的约束。
9、本站以《2013 中华人民共和国计算机软件保护条例》第二章 “软件著作权” 第十七条为原则:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。若有学员需要商用本站资源,请务必联系版权方购买正版授权!
10、本网站如无意中侵犯了某个企业或个人的知识产权,请来信【站长信箱312337667@qq.com】告之,本站将立即删除。
郑重声明:
本站所有资源仅供用户本地电脑学习源代码的内含设计思想和原理,禁止任何其他用途!
本站所有资源、教程来自互联网转载,仅供学习交流,不得商业运营资源,不确保资源完整性,图片和资源仅供参考,不提供任何技术服务。
本站资源仅供本地编辑研究学习参考,禁止未经资源商正版授权参与任何商业行为,违法行为!如需商业请购买各资源商正版授权
本站仅收集资源,提供用户自学研究使用,本站不存在私自接受协助用户架设游戏或资源,非法运营资源行为。
 
在线客服
点击这里给我发消息 点击这里给我发消息 点击这里给我发消息
售前咨询热线
312337667

微信扫一扫,私享最新原创实用干货

QQ|免责声明|小黑屋|依星资源网 ( 鲁ICP备2021043233号-3 )|网站地图

GMT+8, 2025-2-5 14:52

Powered by Net188.com X3.4

邮箱:312337667@qq.com 客服QQ:312337667(工作时间:9:00~21:00)

快速回复 返回顶部 返回列表