这篇文章主要讲解了“FPS游戏反作弊系统设计方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“FPS游戏反作弊系统设计方法是什么”吧!
目前大部分游戏外挂不再是以前那种createremotethread + loadlibary注入方式了,因为大部分反作弊有自己的minifilter文件过滤驱动与imageloadcallback镜像加载回调做判断,大部分反作弊软件在这种过滤钩子中做这种操作:
if(!CheckFileCertificateByR3(FilePatch)){ //把文件路径传回r3,r3判断文件数字签名是否在白名单数字签名里面(比如微软数字签名),如果是白名单文件,就放行,如果不是白名单文件,就拦截 //不是白名单文件...拦截 block; } //放行 pass;
所以,外挂是特别难通过dll直接注入到游戏里面.因此大部分外挂通过一种 无文件落地注入方式 所谓无文件落地注入方式,就是直接在游戏进程里面开辟一个内存空间,把外挂的dll的shellcode写入,之后手动修复输入表,然后解析pe文件头拿到dllmain,再通过createremotethread,apc或者hook方式让游戏执行这块内存地址,这样子外挂就注入了
具体代码如下(抄自google):
//以下代码来自与谷歌搜索 void InjectorDLLByManualMap(const char* filepath, HANDLE hProcess) { LPVOID lpBuffer; HANDLE hFile; DWORD dwLength; DWORD dwBytesRead; DWORD dwThreadId; ULONG_PTR lpReflectiveLoader; LPVOID lpRemoteDllBuffer; //打开文件 hFile = CreateFileA(filepath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); //得到文件大小 dwLength = GetFileSize(hFile, NULL); lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength); //读入文件 ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL); //修复导入表 dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer); //给游戏进程分配一段内存空间 lpRemoteDllBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE); //写入文件shellcode到分配的内存空间 WriteProcessMemory(hProcess, lpRemoteDllBuffer, lpBuffer, dwLength, NULL) lpReflectiveLoader = (ULONG_PTR)lpRemoteDllBuffer + dwReflectiveLoaderOffset; //启动进程 CreateRemoteThread(hProcess, NULL, 1024*1024, (LPTHREAD_START_ROUTINE)lpReflectiveLoader, NULL, NULL, &dwThreadId) }
其特点是:内存标志为PAGE_EXECUTE_READWRITE,MEM_PRIVATE,无文件,无模块,不会触发minifilter和imageloadcallbacks,无法通过正常方式枚举到外挂模块,隐蔽性非常高.
之前的方法看起来非常的"无敌"实际上也是可以对抗的,因为其特征也非常明显:
内存属性为MEM_PRIVATE,内存标志为PAGE_EXECUTE_READWRITE.大小会很大.
所以检测方法也有几个:
1.暴力搜索PE头,大部分这种内存加载的dll都有pe头.一个内存属性为mem_private居然还有pe头,就说明是外挂了.目前大部分反作弊都有这个机制
外挂反制: 抹掉pe头.不止pe头,还可以抹掉一切pe特征.
2.createthreadcallbacks得到线程地址,判断线程地址是否在一个内存属性的mem_private的内存里面.如果是,说明就是外挂了.
外挂反制:不创建线程,使用hook方启动外挂.
3.api调用回溯.顾名思义,外挂总要调用一些api地址的,我们可以通过回溯是谁调用了api地址,然后判断这个调用地方内存属性是不是mem_private.有两种方法,一个是hook所有关键api,在hook部位用_returnaddres()得到调用地址(其实是读ESP/RSP寄存器)第二种通过int3断点触发异常,使用异常处理函数处理这个异常,判断调用者.
外挂反制: 第一种内联hook方式,直接写跳转跳过hook,比如你hook的时候:
jmp 你的hook地址
push ebp
push eax
call xxxx;
外挂可以直接从push ebp调用,不再调用你jmp ,就可以绕过
第二种外挂反制目前没有特别的能反制的地方.除非外挂自己构造api函数调用更底层的api.当然我们可以混淆原底层api的地址(无限套娃),具体以后在说.
为了实现调用回溯,我们需要实现如下步骤:
1. 设置异常处理程序去捕获异常,代码如下:
AddVectoredExceptionHandler
2. 拷贝原API地址到自己的内存区域,然后填充原API地址为int,代码如下:
LPVOID pHOOKAdress; pHOOKAdress = Megrez_GetProAdress(pszModuleName, pszProcName); vecInt3HookedAdress.push_back((DWORD)pHOOKAdress); //用于检测 if (pHOOKAdress == 0) { return 0; } DWORD dProSize = 0; LPBYTE pTemp = (LPBYTE)pHOOKAdress; BYTE bTemp = 0; for (dProSize = 0; ; ) { bTemp = *pTemp++; dProSize++; if (bTemp == 0xcc) { break; } } DWORD dFileSize = dProSize - 1; PVOID pNewAddr = VirtualAlloc(NULL, dFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pNewAddr == NULL) { return 0; } Megrez_SetMemoryAttr(pHOOKAdress, dProSize); memcpy(pNewAddr, pHOOKAdress, dProSize - 1); memset(pHOOKAdress, 0xcc, 1); memset((PBYTE)pHOOKAdress + 1, 0xc3, 1); memset((PBYTE)pHOOKAdress + 2, 0x90, dProSize - 1 -2); memset((PBYTE)pHOOKAdress + 2 + dProSize - 1 - 2 - 1, 0xcc, 1); //memset((PBYTE)pHOOKAdress + 2 + dProSize - 3 - 2 , 0xcc, 2); mapAdress.insert(pair<DWORD, DWORD>((DWORD)pHOOKAdress, (DWORD)pNewAddr)); Megrez_SetMemoryAttr(pHOOKAdress, dProSize); Megrez_SetMemoryAttr(pNewAddr, dFileSize);
这样子原api函数就会变成int3 当调用时候就回触发int3异常 然后被我们的异常处理捕获
3. 查询异常位置内存信息,如果是meme_private者调用的代码,则报告给服务端,代码如下(记住,x32位下保存调用者地址的是esp,x64位下保存调用者地址的是rsp,):
size_t sizeQuery = VirtualQuery((PVOID)caller_function, lpBuffer, sizeof(MEMORY_BASIC_INFORMATION)); bool non_commit = lpBuffer->State != MEM_COMMIT; bool foreign_image = lpBuffer->Type != MEM_IMAGE && lpBuffer->RegionSize > 0x2000; bool spoof = *(PWORD)caller_function == 0x23FF; // jmp qword ptr [rbx],这是为了防止被欺骗 return sizeQuery || non_commit || foreign_image || spoof; //返回
处理完异常后,我们要跳到原来的保存的api内存里面正常调用(设置eip保存的内存地址)
ExceptionInfo->ContextRecord->Eip = mapAdress[(DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress]; #ifdef DEBUG WCHAR _buf[256] = { 0 }; swprintf_s(_buf, 256, L"eIP:0x%08X\n", ExceptionInfo->ContextRecord->Eip); OutputDebugStringW(_buf); #endif //已经处理了异常要再调用下一个异常处理来处理此异常 return EXCEPTION_CONTINUE_EXECUTION; } //调用下一个处理器 return EXCEPTION_CONTINUE_SEARCH;
可以看到,这样子就得到了api调用者的信息,从而做出判断.
这样,一个能检测出绝大部分内存加载外挂的东西就做好了(谁调用谁就会被检测)
感谢各位的阅读,以上就是“FPS游戏反作弊系统设计方法是什么”的内容了,经过本文的学习后,相信大家对FPS游戏反作弊系统设计方法是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。