fps游戏一直是外挂泛滥的重灾区,iOS端的游戏外挂由于系统的限制,相对于安卓端来说,虽然略显萧条,但是也有值得探究的地方。早期iOS fps游戏外挂多是使用封包过滤类技术,由于游戏厂商在这方面的防守比较薄弱,给了这些黑灰产机会,于是乎出现了吉利服等国产免越狱注入类外挂。随着防守方的技术成熟,免越狱注入类继而逐渐退出历史舞台,接下来是越狱注入类,代表作有“鸡腿挂”等,虽然此类外挂常见于安卓端,但是也有相应制作iOS端对应的。一则名为《和平精英“鸡腿挂”被警方捣毁,背后黑产流水高达数亿元》的[新闻]彻底将此类外挂的犯罪细节进行批漏,于是鸡腿挂也就自然退出了历史,只要有利益的事情犯罪份子不会在乎风险,于是“cheto” 利用ue4 引擎类函数hook的外挂诞生,随着游戏厂商的高压打击态势,这类外挂,在几个月后也消失了。
这些已经是过往云烟,接下来,要讨论的便是近年来在iOS平台上的fps游戏外挂技术,和游戏厂商如何防守。由于对封包过滤类,注入类外挂的检测技术加强,衍生了跨进程读写绘制类外挂,代表作有“h5gg”等。游戏厂商针对此类外挂的检测主要有两点,一是检测task info里面的task_for_pid_count thread_creation_count thread_set_state_count等,只要有第三方进程附加读写,则会影响到这几个字段,绕过的方法后面会进行讨论。
bool
has_modifications(struct task_extmod_info *info)
{
if ((info->extmod_statistics.thread_creation_count > 0) ||
(info->extmod_statistics.thread_set_state_count > 0)) {
return true;
}
return false;
}
void
print_process_info(pid_t pid, struct proc_taskallinfo *pidinfo, struct task_extmod_info *info)
{
printf("PID: %d\n", pid);
printf("Name: %s\n", pidinfo->pbsd.pbi_name);
printf("External Modification Summary:\n");
printf(" Calls made by other processes targeting this process:\n");
printf(" task_for_pid: %lld\n", info->extmod_statistics.task_for_pid_count);
printf(" thread_create: %lld\n", info->extmod_statistics.thread_creation_count);
printf(" thread_set_state: %lld\n\n", info->extmod_statistics.thread_set_state_count);
}
int
task_extmod_info_for_pid(pid_t pid, struct task_extmod_info *info)
{
task_name_t task;
mach_msg_type_number_t count = TASK_EXTMOD_INFO_COUNT;
kern_return_t kr;
kr = task_name_for_pid(mach_task_self(), pid, &task);
if (kr != KERN_SUCCESS) {
return kr;
}
kr = task_info(task, TASK_EXTMOD_INFO, (task_info_t)info, &count);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Error getting info from task 0x%x: %s\n", task, mach_error_string(kr));
return kr;
}
kr = mach_port_deallocate(mach_task_self(), task);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Error deallocating task: %s\n", mach_error_string(kr));
return kr;
}
return 0;
}
第二种检测方法便是,利用缺页中断进行检测,此类检测在安卓端也比较常见,下面是检测代码
- (void)check_page{
size_t size = 1024*1024; // 1MB
void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
void* addr2 = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// 在内存块的最后地址处设置值为 888888
int* lastAddressValue = (int*)((char*)addr2);
*lastAddressValue = 888888;
while (true) {
printf("Memory read addr1 %p addr2 %p\n", addr,addr2);
printf("Content at addr: %d\n", *(int*)addr2);
[self check_vm_page_query:addr];
for (int i = 0; i < size; i++) {
*((char*)addr + i) = i % 256;
}
//
// 解除内存映射
// 创建 mincore 函数调用所需的状态数组
unsigned char vec[size / getpagesize()];
// 调用 mincore 函数
int ret = mincore(addr, size, vec);
if (ret != 0) {
perror("mincore failed");
return;
}
// 检查内存是否被访问过
int accessed = 0;
for (int i = 0; i < size / getpagesize(); i++) {
if (vec[i] & 1) {
accessed = 1;
break;
}
}
if (accessed) {
printf("内存 被访问了 accessed\n");
} else {
printf("Memory has not been accessed\n");
}
// 释放内存
sleep(2);
}
}
针对第一类检测在有ktrr绕过的越狱环境,只要patch task_info函数即可,15-16等需要多巴胺越狱的系统,只能找到task的地址,然后将task_for_pid_count thread_creation_count thread_set_state_count 清除。
而针对缺页检测,mach微内核提供了一个方法
kern_return_t mach_vm_page_query
(
vm_map_read_t target_map,
mach_vm_offset_t offset,
integer_t *disposition,
integer_t *ref_count
);
经本人调研,此类绕过读取内存检测并在第三方进程绘制的漏洞游戏厂商 暂时未进行相应的修复和防守,所以导致近期市面上出现了针对 各类fps游戏的iOS外挂泛滥,希望游戏厂商能对此类外挂进行防范。