本帖最后由 ColdThunder11 于 2020-10-1 08:28 编辑
!!!本教程仅供技术交流之用,任何对软件的修改都有可能造成封号后果,请须知!!!
技术交流贴不要引战!
本人菜鸡一枚,如有错误望指正。
Tips:本文章里的所有注入操作均基于Magisk和Riru,因为使用这种方式进行注入不易被反作弊检测,基本无需特别对抗反作弊。
最近某基于Unity的手游上线,在IOS上是支持60帧的,但是在安卓上或许是出于稳定性考虑,并没有开放60帧。既然是Unity制作的,安卓大概率也有设置帧率的地方(事实上就是,通过手动修改配置文件也可以解锁),只是UI给隐藏了,那么干就完事了。
首先解包观察lib,确定是使用il2cpp模式编译的,且使用的某大厂的加固方案。但是目前为止几乎所有的il2cpp加固方案都防止不了内存dump,二话不说先搞下来一份libil2cpp.so。
接下来要知道Unity中如何设置目标帧率,Unity中通过修改Application.targetFrameRate来修改帧率上限。使用与该游戏相同的Unity版本写一个test.cs脚本,在Start中写入Application.targetFrameRate = 60;,挂到默认相机上。使用il2cpp模式编译安卓的安装包,编译完成后使用il2cppdumper获取有关信息,使用ghidra(当然喜欢IDA也可以)打开libil2cpp.so文件,使用脚本完成命名后定位到Start函数,发现其进行了一个如下的调用:
跟踪进入函数内,发现里面有一个非常明显的字符串。
在从游戏中dump出来的libil2cpp.so中搜索该字符串,很容易从引用中定位到该函数(这里的函数已经经过重命名)。
这里的函数体与我们自己编译的有一定差距,估计是热更新插件或者反作弊对其进行了修改,不过问题不大,只要知道它是实现这个功能的就行了。找到函数位置后应该可以直接尝试注入程序调用该函数来修改目标帧率,不过我们并不知道合适的调用时机,因此在这里我不使用这种方案。我们查看该函数的全部引用,还好个数不多。逐个尝试进行注入修改来找到可能是加载配置文件的函数(我似乎没有找到AArch64下好用的基于地址进行inline hook的框架,如果有老哥知道欢迎推荐我一个),最终确认为这里。
为了省事,我直接修改字节码使其始终设置目标帧率为60帧,主要代码如下。
[C] 纯文本查看 复制代码 unsigned long hack_addr = base_addr + 0x03c771a4;
unsigned char* tmp = (unsigned char*)(void*)hack_addr;
if(tmp[1]==0x03){
void* page_start = (void*)(hack_addr - hack_addr % PAGE_SIZE);
if (-1 == mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC)) {
LOGW("mprotect failed(%d)", errno);
return NULL;
}
tmp[0]=0xe1;
tmp[1]=0x0f;
tmp[2]=0x1e;
tmp[3]=0x32;
if (-1 == mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_EXEC)) {
LOGW("mprotect failed(%d)", errno);
return NULL;
}
}
根据Riru-ModuleTemplate编写Riru模块对游戏进行注入修改,注意修改完成后要还原内存为rx,否则可能被检测到游戏异常。
上机测试,成功解锁60帧。
----------
更新:游戏现已开放60帧 |