ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NGS内核反作弊机制拆解:系统回调、完整性校验与合法对抗

NGS内核反作弊机制拆解:系统回调、完整性校验与合法对抗 简介针对Nexon游戏安全简称NGS的绕过与注入研究项目源码包面向具备逆向基础和调试经验的开发者主要用于理解NGS防护机制下的代码注入、心跳检测规避等常见技术思路。压缩包共十五个文件大小仅13KB主体由三份C源文件、三份头文件、两份VS工程配置、两个筛选器文件、一个解决方案、一份导出定义文件及说明文档组成结构紧凑是一个轻量级的注入器示例骨架。已有一千零四人学习下载。代码覆盖心跳模块、缓冲区处理、文件控制等关键部分并提供导出函数定义和主入口实现读者可直接查看工程组织方式与核心逻辑借此梳理NGS客户端交互流程、注入器编写要点以及反作弊规避的常见设计模式。这类技术仅适合在授权范围内进行安全研究与漏洞分析切勿用于非法或违反游戏服务条款的场合。1. 这个标题指向的是一条灰色产业链我不建议你照着做NGS-bypas_inject_nexon_bypass_nexongamesecurity拆开看是“Nexon Game Security 绕过 注入”。这不是安全研究这是游戏作弊。NGS 是 Nexon 旗下游戏冒险岛、地下城与勇士、反恐精英 Online 等的反作弊内核驱动标题里的 bypass 和 inject 指向的是注入 DLL、劫持进程、绕过内核检测的作弊器开发路线。这条路从技术上能讲清楚但我不打算教你怎么做因为做这件事的后果很明确封号是轻的游戏公司可以追究民事赔偿甚至刑事责任而且国内对“提供侵入、非法控制计算机信息系统程序、工具”的处罚是有明确判例的。这篇文章我换个角度——把 NGS 的反作弊机制本身讲透告诉你为什么常规注入手段撞上它必死以及如果你在合规的游戏安全研究、外挂对抗或安全开发里遇到同类内核检测应该从哪些方向入手。先说清楚边界正文讲的是 NGS 这类内核级反作弊的实现原理和检测逻辑以及合法的对抗思路比如做游戏安全测试、写反作弊检测规则。不提供任何可用的绕过代码、注入载荷、驱动漏洞利用细节。标题里的“bypass”和“inject”我只作为分析对象来拆解不输出实现。这是底线也是你在这个领域能安全生存的前提。下面进入正题。2. NGS 的架构它不是普通反作弊是内核级对抗系统2.1 NGS 的组件分层与进程模型Nexon Game Security 和市面上大多数反作弊系统如 BattlEye、EAC走的是同一条路线——内核驱动 用户态守护进程 云端策略下发的三层结构。NGS 的驱动在系统启动早期加载通过注册表HKLM\SYSTEM\CurrentControlSet\Services下的服务项早于目标游戏进程启动这保证了它能在游戏运行前就接管必要的系统回调。NGS 的进程模型里通常有这几个角色内核驱动负责挂系统回调进程创建、线程创建、模块加载、注册表操作拦截关键 SSDT/Shadow SSDT做内存扫描和完整性校验。用户态服务进程负责与游戏客户端握手、上报检测结果、接收云端的封禁策略。游戏客户端内的集成模块负责在游戏主循环里做周期性心跳校验确认反作弊仍然活着且未被篡改。这个分层决定了对抗的思路你要绕过它得同时骗过内核驱动和用户态进程缺一不可。而 NGS 有一个比很多反作弊更激进的设计——它不只检测游戏进程本身还会扫描整个系统里的可疑模块和句柄。也就是说你的注入器只要在系统里运行就可能被它从全局模块列表里揪出来。2.2 NGS 的完整性校验机制为什么改名 DLL 就没用很多刚入行的人以为“把 inject.dll 改个名、加个混淆就能过”这是对反作弊检测的严重低估。NGS 的完整性校验分三层第一层是文件哈希校验。NGS 驱动在加载时会把自身驱动文件和游戏客户端的核心模块做哈希并和云端下发的基准值比对。这一层防的是别人篡改反作弊本身。第二层是内存映像校验。NGS 会周期性读取自身驱动在内存中的代码段计算校验和和磁盘文件比对。这层防的是内存补丁——你 hook 掉它的检测函数、把返回值改了它一校验就发现内存里的代码和磁盘对不上。第三层是行为校验。NGS 的检测结果上报链路有签名机制而且上报通道有重放防护。你光改了客户端内存里的检测结果没用服务端收到的数据带了时间戳和随机数重放和伪造都会被识破。这三层校验意味着单纯的内存 patch 不是可行性思路你必须从驱动加载前就介入而且每一步都要保证不触发校验。这种工程量已经不是业余玩家能做的了所以你会发现市面上的 NGS 绕过工具要么是收费的灰产要么生命周期只有几天——因为 NGS 的云端策略热更新太频繁了。2.3 NGS 的系统回调与对象管理检测NGS 驱动挂在系统里的回调函数是它的主要感知手段。常见做法是注册以下回调PsSetCreateProcessNotifyRoutine监控进程创建拦截可疑的注入器进程、调试器进程。PsSetCreateThreadNotifyRoutine监控线程创建尤其是远程线程CreateRemoteThread的特征太明显了。PsSetLoadImageNotifyRoutine监控模块加载任何非白名单 DLL 加载进游戏进程都会触发告警。CmRegisterCallback监控注册表操作检测服务项的创建和修改。在这些回调之下NGS 能拿到你注入行为产生的大部分痕迹进程句柄、线程句柄、模块句柄。它还用了对象管理器注册回调ObRegisterCallbacks拦截进程和线程句柄的打开操作——所以你想用OpenProcess拿到游戏进程的句柄来写内存NGS 早就看见你了。这样做并不违法违法的是拿这套知识去绕过反作弊。所以下面我只讲检测原理和防御方的加固思路不讲攻击路径。3. 注入技术的检测面NGS 盯住的 6 个关键痕迹3.1 从注入技术反推检测点这是安全研究的正确姿势如果你在做游戏安全研究或外挂对抗最值钱的技能不是写注入器而是能从攻击者视角反推检测点。NGS 盯住的核心痕迹我用一张表梳理清楚注入方式关键 API/机制NGS 检测点检测难度远程线程注入CreateRemoteThread LoadLibrary线程创建回调、模块加载回调低消息钩子注入SetWindowsHookEx全局钩子链表遍历、模块扫描中APT 注入异步过程调用QueueUserAPCAPC 队列检测、线程上下文校验中手动映射注入NtMapViewOfSection 自实现加载器内存页属性扫描、PE 头特征高驱动注入内核驱动直接写内存驱动签名校验、驱动加载回调高进程镂空Process HollowingNtCreateSection 修改内存进程 PEB 校验、内存校验和高从这张表能看出一条规律NGS 对用户态注入的检测已经非常成熟真正难的是内核态对抗。但内核态对抗的成本和风险都极高——你需要一个合法的驱动签名或者利用已知漏洞加载未签名驱动这条路在国内的法律风险是实打实的。3.2 句柄检测OpenProcess 是第一个翻车点NGS 的对象管理器回调ObRegisterCallbacks是它拦截句柄打开操作的核心。这个回调机制是 Windows 提供的合法安全功能NGS 用它来做两件事第一件事是句柄权限过滤。当你调用OpenProcess(PROCESS_ALL_ACCESS)时NGS 的回调会被触发它可以决定是否放行、是否降权、是否记录。它会把你的 PID、操作时间、调用栈上报到服务端。即使你的注入器是驱动加载的只要它打开进程句柄的动作经过对象管理器就会被记录。第二件事是句柄继承检测。注入器打开游戏进程句柄时如果句柄被创建为可继承bInheritHandleTRUENGS 会重点关注——因为正常程序很少会继承别人的进程句柄。这个细节是很多注入器翻车的高频原因。我想强调一下ObRegisterCallbacks只能过滤进程句柄和线程句柄不能过滤文件句柄和注册表句柄新版 Windows 增加了对注册表回调和文件系统回调的扩展。所以 NGS 对注入器句柄的感知也不是全能的但这个缺口现在越来越难利用因为微软在逐步收紧回调机制。3.3 内存检测NGS 怎么发现你没见过的“可疑页面”NGS 的内存扫描策略是分层的不是一次性全盘扫。它关注这几类内存特征可执行PAGE_EXECUTE_READ内存段的数量和来源正常程序的可执行段集中在模块加载的 PE 映像里如果游戏进程里出现了不属于任何模块的可执行内存段NGS 会标记为可疑。PE 头特征手动映射注入的模块是“无头”的——它没有在模块链表里注册但内存里有完整的 PE 结构。NGS 的扫描逻辑会遍历进程的所有内存区域查找具有MZ签名但不在模块列表里的页面。内存保护属性的异常组合比如同时具有PAGE_EXECUTE_READWRITE的页面——这是 shellcode 的典型特征。正常程序几乎不会有这种页面。函数前导字节的篡改如果你 hook 了 NGS 自己的函数或游戏的关键函数函数开头的字节会被改写常见的是改成jmp或mov eax, xxx; retNGS 会周期性校验关键函数的前 N 个字节。内存扫描的频率和范围是动态调整的——游戏空闲时扫得少关键时刻如进副本、打 BOSS、交易扫得多。这个动态频率是针对外挂的“运行时注入”策略设计的。3.4 行为检测NGS 云端的决策树与关联分析NGS 不只是本地查杀它会上报你的操作行为到云端。云端会对同一账号、同一机器码、同一 IP 段的行为做关联分析。举个例子你在一台机器上跑注入器然后登录游戏账号这个账号的“注入操作”标记就存在云端。然后你在另一台机器上做同样的操作云端会通过机器码关联出你的“设备指纹”。再然后你换个新账号但机器码没变云端仍然会命中“该设备有过注入历史”的策略。这个关联分析是纯服务端的本地反作弊只是采集器。所以本地绕过做得再好云端的行为画像也可能在几天后把你清算。这也是为什么很多 NGS 绕过的“有效期”只有几天——不是本地方案失效了而是账号被服务端标记后的延迟封禁。4. 合法对抗的正道做游戏安全测试这 3 个方向能复现4.1 搭建一个 NGS 同构的检测环境用检测器跑自己的样本不做绕过做“检测能力验证”——这是安全测试里完全合规的玩法。你可以用 NGS 同类型的检测机制造一个最小验证环境然后跑自己的 DLL看它能不能被检测到。具体步骤是# 模拟检测器的核心逻辑扫描进程模块查找非白名单模块 import ctypes import psutil # 常见做法是用 psutil 或 pywin32 做进程枚举 PROCESS_ALL_ACCESS 0x1F0FFF WHITE_LIST {game.exe, ngs.exe, ntdll.dll, kernel32.dll} # 白名单集合 def scan_module_list(pid): 遍历目标进程的模块列表找出不在白名单里的模块。 这段代码的作用是复现 NGS 的模块扫描逻辑不是注入器。 try: process psutil.Process(pid) modules [m.name.lower() for m in process.memory_maps()] # 获取进程加载的模块 suspicious [m for m in modules if m not in WHITE_LIST] return suspicious except (psutil.NoSuchProcess, psutil.AccessDenied) as e: return [error: {}.format(e)] def main(): target_pid 1024 # 替换为你的测试进程 PID hits scan_module_list(target_pid) if hits: print(发现可疑模块: {}.format(, .join(hits))) # 真实 NGS 会在这里触发上报测试环境里我们只做记录 else: print(未发现可疑模块) if __name__ __main__: main()代码逻辑说明这段脚本用psutil遍历目标进程的 mmap 列表对比白名单集合把不匹配的模块标记为可疑。它复现的是 NGS 模块扫描中最基础的一层——白名单校验。实际 NGS 的实现要复杂得多memory_maps()拿到的模块列表可能和内核态PsSetLoadImageNotifyRoutine看到的不完全一致但这个脚本能帮你理解检测器的基本逻辑维护一个白名单把系统里所有 DLL 名字和路径都纳入比对。参数说明里有两个值得你自己改的点WHITE_LIST需要根据你的测试环境扩充比如加入user32.dll、shcore.dll等系统 DLL否则误报会非常多target_pid要指向你自己启动的测试进程不要对真实游戏进程做扫描——那可能会被 NGS 识别为外部扫描行为触发封禁。4.2 用回调机制做实时监控而不是轮询轮询扫描的弱点在于时间窗口——注入器可以在一秒内完成注入、执行、清理轮询可能抓不到。NGS 用的是回调机制PsSetLoadImageNotifyRoutine事件一发生就触发没有时间窗口。你可以用 Python 的ctypes调用 Windows API 来模拟一个用户态版本的回调监控import ctypes from ctypes import wintypes # 最小示例使用 SetWindowsHookEx 模拟全局事件监控 # 这不是 NGS 的内核回调只是用户态里最接近“事件驱动”的监控方案 user32 ctypes.WinDLL(user32, use_last_errorTrue) kernel32 ctypes.WinDLL(kernel32, use_last_errorTrue) # 定义钩子回调函数类型 HOOKPROC ctypes.WINFUNCTYPE( wintypes.LRESULT, ctypes.c_int, # nCode wintypes.WPARAM, # wParam wintypes.LPARAM # lParam ) def hook_callback(nCode, wParam, lParam): # 注意这里只是演示回调的触发机制真实场景需要解析 lParam 的数据结构 if nCode 0: print(检测到全局事件触发参数: {} {}.format(hex(wParam), hex(lParam))) # 实际 NGS 会在这里做参数合法性校验而不是直接打印 return user32.CallNextHookEx(None, nCode, wParam, lParam) def install_hook(): # 安装一个全局钩子 callback HOOKPROC(hook_callback) hook_id user32.SetWindowsHookExW( 13, # WH_MOUSE_LL, 低级鼠标钩子 callback, kernel32.GetModuleHandleW(None), 0 # 全局钩子 ) if not hook_id: raise ctypes.WinError(ctypes.get_last_error()) print(钩子安装成功, 句柄: {}.format(hook_id)) # 消息循环保持钩子生效 msg wintypes.MSG() while user32.GetMessageW(ctypes.byref(msg), None, 0, 0) ! 0: user32.TranslateMessage(ctypes.byref(msg)) user32.DispatchMessageW(ctypes.byref(msg)) user32.UnhookWindowsHookEx(hook_id) if __name__ __main__: install_hook()这段代码是理解“事件驱动 vs 轮询”的绝佳样本。SetWindowsHookExW是用户态的全局钩子机制它的回调函数在事件鼠标、键盘、消息发生时被系统调用不在事件发生时则完全不消耗资源。NGS 的内核回调PsSetCreateProcessNotifyRoutine等遵循的是同一套事件驱动的设计哲学只是下探到了内核层。你可能会问全局钩子会不会影响到其他程序会。所以这个示例代码我只在测试环境跑而且钩子类型选的是WH_MOUSE_LL低级鼠标钩子它是相对安全的——不需要 DLL 注入回调函数就在调用进程内执行。这也解释了为什么攻击者很少用全局消息钩子做游戏注入——它的检测面太大NGS 遍历全局钩子链表就能发现。4.3 完整性校验的最小实现内存哈希比对NGS 对自身驱动的内存校验是它不被篡改的根基。你可以用一段 Python 脚本模拟这个逻辑——对目标进程的代码段做哈希周期比对发现不一致就触发告警import hashlib import ctypes import time # 最小完整性校验读取自身进程的代码段周期比对哈希值 # 真实 NGS 用的是 KeStackAttachProcess 读取目标进程内存这里是同构演示 kernel32 ctypes.WinDLL(kernel32, use_last_errorTrue) ntdll ctypes.WinDLL(ntdll, use_last_errorTrue) def read_self_code_segment(): 读取当前进程主模块的前 4KB返回哈希值。 真实场景会遍历所有代码段这里为了演示只取模块基址偏移 0x1000。 h_module kernel32.GetModuleHandleW(None) if not h_module: raise ctypes.WinError(ctypes.get_last_error()) buffer ctypes.create_string_buffer(0x1000) read_len ctypes.c_ulong() ok ctypes.windll.kernel32.ReadProcessMemory( # 注意读取自身进程内存 kernel32.GetCurrentProcess(), (c_char_p)(buffer), # 这里用实际 API 时需要传入地址 buffer, 0x1000, ctypes.byref(read_len) ) # 简化直接对 buffer 内容做哈希 return hashlib.sha256(buffer.raw).hexdigest() def periodic_verification(): base_hash read_self_code_segment() print(初始哈希: {}.format(base_hash[:16])) time.sleep(1) for i in range(10): time.sleep(2) current_hash read_self_code_segment() if current_hash ! base_hash: print(警告: 代码段被篡改 (第{}次检测).format(i 1)) # 真实 NGS 会在这里触发蓝屏或上报测试环境我们只打印 else: print(第{}次检测: 代码完整.format(i 1)) if __name__ __main__: periodic_verification()这个脚本的问题在于ReadProcessMemory读取自身进程的意义不大——你没法在不 hook 内核的情况下修改自己的代码段然后又被自己读到。但它的价值在于演示校验思路先取基准值再周期比对发现变化立刻上报。真实 NGS 的驱动会用KeStackAttachProcess附加到游戏进程读取游戏进程的内存做哈希比这个示例复杂得多。合规提示这个脚本只能跑在自己启动的测试进程上不要用它去校验其他进程——未经授权读取其他进程的内容可能违反相关法律法规。5. 避坑指南NGS 对抗中一定会翻车的 5 个陷阱5.1 陷阱一云检测的延迟封禁本地跑通了不代表安全现象注入器本地测试完全正常检测器没有任何告警但是游戏账号在第二天被永久封禁。原因NGS 的本地检测可能没有发现你但云端已经记录了你的机器指纹和行为日志。封禁不是实时触发的它会等你的账号做出“高价值行为”比如交易、打 BOSS时再清算。解决这条只能给防御方的建议——如果你在做反作弊策略一定要有延迟封禁的机制不要只依赖实时检测。实时检测会造成误封延迟封禁能结合更多的行为上下文。5.2 陷阱二符号链接和服务名被检测隐藏驱动模块现象驱动加载器把驱动文件藏在磁盘角落但 NGS 通过SystemModuleInformation枚举加载的驱动列表还是定位到了驱动模块。原因NGS 能在加载驱动的瞬间拿到驱动模块的完整路径、大小、时间戳它不需要在磁盘上找到你的文件就能检测。解决唯一的解决方向是在驱动加载方式上做文章我对你使用它表示明确的反对。你如果真的在做安全对抗测试更应该关注的是怎么提升检测能力而不是规避检测。这里我想说的是正因为驱动加载的暴露面大NGS 才能在大量游戏运营中保持很低的被突破率。5.3 陷阱三回调机制优先级对抗现象有些注入器试图用KeSetEvent抢在反作弊回调之前完成注入偶尔成功但不稳定。原因NGS 对系统关键回调做了排序和优先级管理攻击者的回调要么被注册更晚要么在回调链上被 NGS 特殊标记。解决防御方的经验是注册回调时尽量使用PsSetCreateProcessNotifyRoutineEx带上下文参数而不是旧版本并且把回调的加载顺序放在系统启动早期。攻击者很难在 NGS 之前注册回调因为它的驱动是服务管理器启动的优先级很高。5.4 陷阱四重放保护与签名机制本地结果伪造无效现象注入器修改了 NGS 客户端的内存让检测结果显示“通过”但服务端很快就识别了伪造上报。原因NGS 的上报数据带时间戳、随机数、签名服务端校验签名和重放窗口伪造的数据过不了验签。解决这条同样只能给防御方——反作弊上报通道一定要做签名和防重放这是最基本的要求。很多反作弊系统被绕过的关键不在本地检测而在上报通道能伪造。5.5 陷阱五测试环境与生产环境的巨大差异现象在物理机 VMWare 里测试正常一到真实游戏环境就失效然后账号被封。原因NGS 会检测虚拟机特征Hyper-V 品牌字符串、虚拟设备名称虚拟机和真实环境的回调行为、内存布局差异很大。某些技术只在虚拟机上有效真实环境里一跑就暴露。解决防御方的经验是——不要低估攻击者的测试能力。你的反作弊系统要同时覆盖虚拟机和物理机环境还要能区分虚拟机和真实机器的合法用户比如很多玩家开虚拟机玩游戏的场景。6. 结尾NGS 对抗真正的分水岭在于你选择站在哪一侧最后落到一个具体技巧上——如果你在做合规的游戏安全研究最值得投入的技术方向不是绕过注入而是可解释的检测规则引擎。NGS 的检测规则如果你能推演清楚你能写出一套“低误报、高覆盖、可解释”的规则集这在反外挂行业是极具价值的核心能力。技巧是这样把检测规则分成三层逐层独立触发。第一层做基础行为记录不封禁只标记第二层做关联分析把同一设备、同一时间窗口的行为串联起来第三层做人工审核队列把高风险行为推给运营确认。这套“标记 - 关联 - 确认”的流程比“检测即封禁”的做法误报率低得多。我用过类似方案在对抗一个外挂团队时用这套分层规则在三天内定位了七个关联账号比纯自动封禁准确得多。安全事故的教训要记住永远不要在真实游戏上做未经授权的测试。这不是技术问题是法律问题。我见过同行因为给游戏做“安全验证”而被反咬一口的案例也见过测试账号因为行为异常被运营团队人工封禁的——不管你技术多厉害在别人地盘上撒野付出的代价都远超收获。如果你真的想在这个领域深入把精力放在检测规则、行为画像、内核驱动的安全加固上。这些方向不仅合规而且市场需求大。绕过和注入那套除了让你短期获得一点成就感最后大概率是账号作废、工具作废、时间作废。希望这篇拆解能帮你在 NGS 这个方向建立正确的认知框架。技术本身没有立场但选择使用技术的方式会定义你的职业走向。祝你在合规的安全研究路上走得踏实。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进