现代iOS fps游戏攻防

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外挂泛滥,希望游戏厂商能对此类外挂进行防范。

如果有想要讨论iOS内核和逆向技术的可以加群:582047418

5 个赞

谢谢分享

大佬牛蛙