利用内核函数调用耗时,感知底层篡改|来源于定制系统付费群
公开一个针对 APatch 的侧信道攻击方法,可用于检测是否存在 APatch。
作为一款 Root 解决方案,它可以通过修改 creds 的 gid 和 uid 到 0 来对指定进程授予 Root 权限。但在用户态指定允许使用 Root 权限的 uid 并通知到内核的过程中,我们需要对这个过程进行一些验证来避免任何应用都可以发送这个信息。与 KernelSU 验证管理器签名的方案 [1]不同的是,APatch 使用的是超级密钥进行鉴权;KernelSU 通过 prctl 0xdeadbeef 通讯 [2],APatch使用 truncate 的 syscall 通信 [3],作者把它叫做 supercall [4]。在 KP 0.8.5 后由于一些人提出的 issues 中表明超级密钥明文储存存在一定的风险,在后续的版本中作者采用 SHA256 哈希超级密钥,并在过程中比对。
查看源码可以知道 [5],cmd 是由当前 KP 版本 & 0xFFFF 计算得出,如果 cmd 不在 SUPERCALL_HELLO 到 SUPERCALL_MAX 的范围之内,则就直接跳过超级密钥的哈希比对,直接走原先的 truncate 系统调用;否则就对超级密钥进行 SHA256 哈希计算结果并验证是否正确,正确后进入正常的 supercall 处理过程代码并跳过调用原系统调用,修改返回码为 supercall 的结果。
有聪明的读者可能会发现,if (cmd < SUPERCALL_HELLO || cmd > SUPERCALL_MAX) return; 这个逻辑的存在反而会出现时间差攻击:
我在发现了可能存在的攻击点后,对安装了 APatch 的设备进行了不带任何参数的 truncate syscall 系统调用,得出来时间 A;然后使用了随意输入的超级密钥并且带上在范围内的 cmd 进行 truncate syscall 系统调用得出时间B,经过观察可知时间B远远大于时间A,因此这个攻击点位确实存在。
但在我的测试中,单纯的使用时间差攻击存在假阳性,尤其是在设备运行应用较多或刚开机任务较多设备运行缓慢的时候。因此我采用的办法是常用的多次实验取平均值,并不断调整到一个合适的阈值,最终得出的数据是在重复 30万次的调用下,如果时间差小于 300 毫秒,重复进行 10 轮中有 5 次即为真阳性,存在异常环境。
后来在对 Native Test 进行攻击的时候发现,北大佬也增加了这个检测,但他们的用时很短,而我的需要用时 8 秒钟。经过 stackplz 等 trace 不断地分析和交流中发现他们的方案是采用时间比,时间比方案相较于时间差方案不需要重复 30 万次之多,只需要 10 次左右即可,并且在获取每一步用时的时候直接读取寄存器中的时间来尽可能地降低无关过程的代码量来提高精度,而我则是使用的 C++ 标准库中的 std::chrono::high_resolution_clock::now(),相比之下时间比方案确实有效且降低了假阳性的概率,有兴趣的读者可以尝试这个实现。
在内部的讨论中,作者并不是很愿意为了解决这个攻击点位来污染代码,他给出的解决方案为会在管理器中提供关闭哈希验证过程的选项。因此短期内官方不会修复这个点位,我在 Peekaboo 1.3 中对其进行了修复。
[1] https://github.com/tiann/KernelSU/blob/main/kernel/manager.h#L16
[2] https://github.com/tiann/KernelSU/blob/main/kernel/core_hook.c#L207
[3] https://github.com/bmax121/KernelPatch/blob/main/kernel/patch/common/supercall.c#L269
[4] https://github.com/bmax121/KernelPatch/blob/main/kernel/patch/common/supercall.c#L181
[5] https://github.com/bmax121/KernelPatch/blob/main/kernel/patch/common/supercall.c#L254