找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1972

积分

0

好友

282

主题
发表于 6 天前 | 查看: 9| 回复: 0

一、追踪目标代码

进入游戏对局后,发现全局World地址无法读取到正确的数据。查看进程的内存映射(maps)发现,该地址对应的虚拟内存区域(VMA)属性已经被设置为---p(即PROT_NONE,不可访问)。

GWorld内存区域的反汇编与maps信息

既然外部无法访问,那么进程自身是如何正常访问这些数据的呢?通过在内核层挂钩(hook)mprotectsigaction系统调用,发现游戏为SIGSEGV(信号11)注册了自定义的信号处理器(signal_handler),专门用于处理因访问被保护内存而触发的异常。

内核挂钩捕获的mprotect与sigaction调用日志

二、SIGSEGV处理概览

游戏注册的sigsegv_handler是整套保护机制的核心。其处理逻辑可以概括为:捕获异常、解析指令、模拟执行、修复上下文、恢复流程。ARM指令的编码规则是理解后续分析的基础,可参考官方文档:ARM Architecture Reference Manual

下面是sigsegv_handler函数的主要逻辑框架代码:

SIGSEGV信号处理函数 sigsegv_handler 主要逻辑

处理函数首先会判断异常地址(si_addr)是否落在预先设定的受保护内存范围内。如果命中,则根据引发异常的指令地址(pc)读取指令,并按照ARM指令集规范对指令进行分类处理,主要是 加载/存储指令 (Loads & Stores)系统指令 (SYS)

三、异常指令分类处理

1、处理 Loads & Stores Instructions

这类指令直接读写内存。处理器的第一步是解析指令,提取指令中使用的寄存器编号、立即数等操作数信息,为后续的模拟执行准备上下文(context)数据。

解析Loads/Stores指令并获取寄存器信息

接着,从异常上下文(ucontext_t)中读取目标寄存器的“加密值”。这个值实际上是原始数据与保护密钥进行异或等运算后的结果。处理函数会使用对应的密钥(xor_key1, xor_key2)和内存块信息(size)进行解密,计算出原始数据。

随后,代码会调用sub_75A299FDA0函数。该函数负责为当前线程加载并准备好用于模拟执行单条指令shellcode代码段,并将需要单步执行的指令(可能是修改后的形式)填充到shellcode的特定位置。

sub_75A299FDA0函数内部会通过mmap申请一块具有读、写、执行权限(RWX)的内存,并将预编译好的shellcode模板写入其中。每个线程独立一份,下文会详述。

选择对应shellcode并填充待执行指令

在调用shellcode之前,处理器会刷新其所在内存区域的指令缓存和数据缓存(通过DC CVAUIC IVAU指令),确保CPU能取到最新的指令。然后,使用已经修正了寄存器值(即写入了解密后数据)的异常上下文来执行这段shellcode

shellcode的本质是将传入的context结构体加载到真实的物理寄存器中,然后执行之前被填充进去的那一条指令,最后将执行结果(寄存器状态)再保存回context结构体。处理器从执行后的context中提取出指令的执行结果。

刷新缓存、模拟执行并回写结果

最终,将shellcode执行得到的结果写回SIGSEGV的上下文寄存器中,并修改程序计数器(PC)使其跳过引发异常的指令,然后恢复进程的正常执行。

2、处理 SYS Instructions

系统指令(如MSR, MRS)的处理逻辑与加载/存储指令类似。处理器同样会解析指令,获取指令隐式使用的寄存器值,进行解密操作后写回上下文。

随后,直接利用准备好的shellcode模拟执行这条系统指令。通过模拟执行来修正指令执行后的上下文状态,最后同样通过修改PC来跳过该异常指令。

处理SYS指令的代码逻辑

SYS指令处理中的shellcode执行与上下文恢复

3、模拟执行shellcode构造

sub_75A299FDA0函数是构造模拟执行环境的关键。它通过mmap分配RWX内存,并写入用于模拟执行的shellcode。这段shellcode实现了标准流程:从指定地址加载context到所有寄存器 -> 执行一条指令 -> 将所有寄存器状态保存回context

有趣的是,代码预置了四个功能逻辑几乎完全一致的shellcode副本。它们唯一的区别在于:context结构体中加载数据时,用于存储context基地址的寄存器不同,分别是X0X1X2X3

这样设计的目的是为了防止“解析context参数的寄存器”与“异常指令本身要使用的寄存器”发生冲突,确保模拟执行的准确性。

为线程构造模拟执行shellcode环境

构造完成后,shellcode的地址会被存储在一个与线程相关的自定义结构体中(类似于Windows的TEB,但这里是游戏保护方自己实现的)。同时,函数会刷新这片新shellcode区域的缓存。

缓存刷新与线程本地存储设置

以下是以X0寄存器传递context地址的shellcode实例,其他版本的shellcode逻辑与此一致,仅基址寄存器不同。

使用X0寄存器传递context的shellcode前半部分

shellcode后半部分:恢复寄存器并返回

以上技术分析内容仅供游戏安全领域学习交流,切不可用于非法目的!

总结

这套保护机制的核心思路是:通过mprotect将关键内存区域设置为PROT_NONE(不可访问),同时利用sigaction注册自定义信号处理器来接管访问这些内存时触发的SIGSEGV异常。

sigsegv_handler中,保护系统完成了一系列精细操作:

  1. 解析与判断:解析异常地址,确认其属于受保护范围;解析引发异常的ARM指令,获取其操作数、寄存器等详细信息。
  2. 解密数据:从异常上下文(context)中读取“加密”的寄存器值,使用预存的密钥进行解密,并将解密后的原始数据写回context
  3. 模拟执行:动态构造或复用线程专用的shellcode,将修正后的context传入,在隔离环境中模拟单步执行引发异常的指令。
  4. 恢复执行shellcode将指令执行结果回写到context中,处理器只需修改PC跳过原指令,即可恢复进程执行,此时上下文中的数据已是解密状态。

至此,内存访问和数据解密在异常处理流程中透明完成,对游戏正常逻辑无感。

引用


本文由看雪论坛优秀文章改编,更多深度技术分析,欢迎访问云栈社区交流讨论。




上一篇:如何理解2亿像素传感器的等效像素计算,及其与小米Ultra主摄的对比
下一篇:AI掉队、Fleet难产,JetBrains在开发者工具新时代会重蹈Borland覆辙吗?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 11:55 , Processed in 0.266044 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

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