一、基础知识
这款游戏加速器是以动态库方式注入到APP
中加载运行的,其介绍说只能在越狱手机运行,因为动态库的注入依赖于框架:MobileSubstrate
。但是在非越狱手机也可以实现注入的,一个方便的框架可以参考 MonkeyDev。
动态库本质是一个Mach-O
格式的文件,和Windows
的DLL
文件、Linux
的so
文件作用类似。其不能直接运行,而是通过系统链接加载器dyld
完成加载和初始化。dyld
是开源的,可在 Source Browser 查看。简单介绍下动态库dylib
的加载过程:
- 1、系统内核加载
dyld
,执行其中的__dyld_start()
函数 - 2、初始化运行环境,如
setContext(mainExecutableMH, ...)
- 3、加载共享缓存,这个是为了优化程序启动,即映射在内存的动态库只会加载一次
- 4、加载
DYLD_INSERT_LIBRARIES
指定的动态库 - 5、链接主程序,如
link(sMainExecutable, ...)
- 6、链接依赖的动态库
- 7、弱符号绑定
- 8、调用所有的初始化方法,包括动态库的构造器,
load
方法等 - 9、查找主程序的入口点然后返回
二、加速器原理
2.1 构造器入口
本节主要使用 Hopper Disassembler 来进行分析。Hopper
分析完成后如下图所示:

其中加速器的构造器函数为:__GLOBAL__sub_I_MFAccelerator.mm
。反编译结果如下:
int __GLOBAL__sub_I_MFAccelerator.mm() {
sp = sp - 0x1c;
if (objc_getClass("UnityAppController") == 0x0) goto loc_510a;
loc_50d6:
*0x87a0 = 0x1;
MSHookMessageEx();
goto loc_5150;
loc_5150:
r0 = [[NSDistributedNotificationCenter defaultCenter] addObserver:[MFAcceleratorMgr sharedMgr] selector:@selector(handleDistributedNotification:) name:[NSString stringWithUTF8String:"com.cyjh.ifengwoo.port.accelerator"] object:0x0];
return r0;
.l1:
return r0;
loc_510a:
r4 = objc_getClass("EAGLView");
r0 = objc_getClass("CCEAGLView");
r0 = r0 | r4;
if (r0 == 0x0) goto .l1;
loc_512c:
MSHookFunction();
goto loc_5150;
}
其区分了游戏的类型:U3D
和cocos2d
。伪代码如下:
Class u3d = objc_getClass("UnityAppController");
if(u3d) {
MSHookMessageEx(u3d, @selector(application:didFinishLaunchingWithOptions:), (IMP)fake_unity3d_finish_launching, &ori_unity3d_finish_launching)
} else {
Class cocos2d_f1 = objc_getClass("EAGLView");
Class cocos2d_f2 = objc_getClass("CCEAGLView");
if(cocos2d_f1 || cocos2d_f2) {
MSHookFunction((void *)gettimeofday, (void *)mygettimeofday, (void **)&orig_gettimeofday);
}
}
可以看见,对于 cocos2d
,其是通过内联HOOK gettimeofday
函数完成的。而对U3D
游戏相关时间函数的内联HOOK
是在fake_unity3d_finish_launching
中完成的,其的反编译代码很长,这儿只给出了关键的部分:
int __ZL29fake_unity3d_finish_launchingP10objc_classP13objc_selectormm(void * arg0, void * arg1, long arg2, long arg3) {
...
r4 = dlsym(0x1 ^ 0xffffffff, "vm_region");
r0 = 0x3972;
asm{ vmov.i32 q4, #0x0 };
r5 = sp + 0x38;
r8 = 0x0;
*(r0 + 0x4e1e) = r4;
stack[2026] = _mach_task_self_;
goto loc_4e54;
...
loc_4f48:
MSHookFunction();
goto loc_4f62;
...
loc_4f48:
MSHookFunction();
goto loc_4f62;
}
通过动态调试发现:U3D
游戏的内联HOOK
是通过内存搜索特征串得到的。大致流程如下:
- 1、使用
vm_region
获取当前可执行文件的内存空间 - 2、在此内存空间搜索两个特征串,如下:

- 3、只有在这两个特征串都存在的情况下,才会内联
HOOK
。至于这两个特征串的含义,笔者暂时不知。 - 4、然后在距这两个特征串特定的位置进行内联
HOOK
,如下:


对于不同的U3D
程序,特征串在的位置并不固定。
2.2 cocos2d 游戏加速
cocos2d
的游戏加速实现主要在__ZL13$gettimeofdayP7timevalPv
中,主要反编译逻辑如下:
int __ZL13$gettimeofdayP7timevalPv(void * arg0, void * arg1) {
r4 = arg0;
r2 = *0x87a8;
r0 = (r2)(r4, arg1, r2); // 这儿是调用gettimeofday并填充timeval结构
if (r0 == 0x0) {
r5 = *(r4 + 0x4);
r6 = SAR(r5, 0x1f);
r8 = 0x87b8;
asm{ smlal r5, r6, r0, r1 };
r0 = *r8;
r1 = *(r8 + 0x4);
if (r2 != 0x0) { // 这儿是判断全局变量 saved_usecs 是否有值
r0 = r5 - r0;
r0 = __floatdisf();
r0 << 0x10 | r0;
s0 = *0x8700;
r1 = 0x37ba;
*(r8 + 0x4) = r6;
asm{ vmul.f32 d0, d16, d0 };
*r8 = r5;
r10 = *(r1 + 0x5006);
r11 = *(r1 + 0x500a);
r5 = __fixsfdi() + r10;
r6 = r11 + CARRY(FLAGS) + r1 + 0x5006;
*r4 = __divdi3();
*(r4 + 0x4) = __moddi3();
}
else { // 第一次调用才会到这
// 保存 timeval 到 saved_usecs
*0x87bc = r6;
*0x87b8 = r5;
}
// 保存上次获取的timeval值到saved_usecs_last
*0x87c4 = r6;
*0x87c0 = r5;
r0 = 0x0;
}
return r0;
}
体现加速的代码主要在if (r2 != 0x0) {...}
中,对应的汇编代码如下:

所以加速器伪代码大致为:
int ret = gettimeofday(&tv, NULL);
if(ret == 0) {
if(saved_usecs->tv_sec && saved_usecs->tv_usec) {
long A = tv->tv_usec/1000000000