已经有很长一段时间没写过游戏修改的文章了,一个原因是现在越来越多的手游厂商都开始给游戏上各种各样的保护,以前直接修改dll或者直接修改so再打包回去的方案早已经不适合这个版本了,所以打算介绍一下现在主流,或者说是我现在常用的一些修改方案。
0x1 注入
注入修改的话通常是注入一个so进行codepatch,Unity的mono环境下还可以通过注入dll来进行修改,不过注入dll一般也要先注入一个so作为loader,所以大前提还是怎么注入一个so,这里就先介绍一些常用的注入方法
1.ptrace游戏进程
作为最古老的注入方式,已经过时了,现在是个保护都有ptrace反调试,与其考虑如何过掉反调试不如选择换个方式
2.修改classes.dex
修改classes.dex在入口点的onCreate里添加代码载入自己的so,但是大多数保护都有自校验
3.Xposed
在handleLoadPackage里过滤包名后载入自己的so即可,但是Xposed作为一款出了名的框架太容易被检测了,常见的利用java反射Xposed的相关类和函数或者读取进程maps检测
4.多开类程序
VirtualApp,VirtualXposed或者平行空间,多开分身之类的软件,在程序的入口处或者其他地方load自己的so从而注入到多开的进程,优点是无需root
5.Zygote注入
安卓下进程都是从Zygote fork的,当注入so到Zygote后,之后启动的进程就都会带有这个so。可以自己注入Zygote进程或者使用Riru这个模块
0x2 修改
当我们注入一个so进游戏后,接下来就要考虑如何修改了
1.修改so
如果只是单纯的修改so,没有太多的花样,只需要操作指针修改相应的代码就行,跟直接用编辑器修改so是很像的,但是能绕过壳对so的校验
2.hook
hook能干的事就非常多了,比如Unity的mono环境下,可以hook mono_image_open_from_data_with_name在它载入dll时替换成自己修改好的,有些时候为了绕过保护可以hook更底层的函数或者其它未导出函数,比如mono_metadata_parse_mh_full,在method执行的时候将method body替换成修改好的。依托于mono这方面复杂的处理机制,可以在很多函数上做手脚,保护也很难面面俱到。
目前大部分游戏的人物怪物等基础数据都是存放在本地的,可以通过hook资源读取的相关函数实现数据修改。
还有如果游戏逻辑是用lua运行的,也可以hook lua的相关函数实现lua的替换或者lua注入。
3.dll注入
针对Unity的mono环境,这个修改方式的优点就是可以在游戏运行中的时候进行修改,缺点就是只能对付还没使用il2cpp的游戏,手头刚好有现成的代码就顺带贴一下
typedef enum {
MONO_IMAGE_OK,
MONO_IMAGE_ERROR_ERRNO,
MONO_IMAGE_MISSING_ASSEMBLYREF,
MONO_IMAGE_IMAGE_INVALID
} MonoImageOpenStatus;
long module_start_addr = get_module_base("libmono.so");
typedef void* (*mono_get_root_domain_t)();
mono_get_root_domain_t mono_get_root_domain = (mono_get_root_domain_t)(module_start_addr + 0x0);
typedef void* (*mono_thread_attach_t)(void* mDomain);
mono_thread_attach_t mono_thread_attach = (mono_thread_attach_t)(module_start_addr + 0x0);
typedef void* (*mono_image_open_from_data_t) (char *data, uint32_t data_len, int32_t need_copy, MonoImageOpenStatus *status);
mono_image_open_from_data_t mono_image_open_from_data = (mono_image_open_from_data_t)(module_start_addr + 0x0);
typedef void* (*mono_assembly_load_from_full_t)(void *image, const char *fname, MonoImageOpenStatus *status, int32_t refonly);
mono_assembly_load_from_full_t mono_assembly_load_from_full = (mono_assembly_load_from_full_t)(module_start_addr + 0x0);
typedef void* (*mono_assembly_get_image_t)(void *assembly);
mono_assembly_get_image_t mono_assembly_get_image = (mono_assembly_get_image_t)(module_start_addr + 0x0);
typedef void* (*mono_class_from_name_t)(void* image, const char* name_space, const char* name);
mono_class_from_name_t mono_class_from_name = (mono_class_from_name_t)(module_start_addr + 0x0);
typedef void* (*mono_class_get_method_from_name_t)(void* mclass, const char* name, int param_count);
mono_class_get_method_from_name_t mono_class_get_method_from_name = (mono_class_get_method_from_name_t)(module_start_addr + 0x0);
typedef void* (*mono_runtime_invoke_t)(void* method, void* obj, void** params, void** exc);
mono_runtime_invoke_t mono_runtime_invoke = (mono_runtime_invoke_t)(module_start_addr + 0x0);
mono_thread_attach(mono_get_root_domain());
FILE * pFile = fopen("UnityHack.dll", "rb");
fseek(pFile, 0, SEEK_END);
long lSize = ftell(pFile);
rewind(pFile);
char * buffer = (char *)malloc(sizeof(char)*lSize);
fread(buffer, 1, lSize, pFile);
fclose(pFile);
MonoImageOpenStatus status;
void* image = mono_image_open_from_data(buffer, lSize, 1, &status);
void* assembly = mono_assembly_load_from_full(image, "UNUSED", &status, 0);
image = mono_assembly_get_image(assembly);
void* pClass = mono_class_from_name(image, "UnityHack", "HackLoad");
void* method = mono_class_get_method_from_name(pClass, "Load", 0);
mono_runtime_invoke(method, NULL, NULL, NULL);
这里就实现了注入一个自己写的UnityHack.dll,并调用其中UnityHack namespace下HackLoad类的Load函数
在Load函数中就可以进行需要的修改了,比如游戏在运行时都会用变量来存储人物或者怪物的数据,如果是公开的就可以直接修改它们,如果是非公开的也可以使用C#万能的反射来修改,或者直接修改函数IL代码或者修改指针把关键函数替换成自己的,这方面具体的修改可以参考Harmony这个框架或者在Github上搜索下相关代码
0x3 实践篇
无root,多开程序,hook mono函数
使用VirtualXposed修改手游
root,Zygote注入,简单codepatch
使用Riru修改手游
上一篇
下一篇
版权属于:
Perfare's Blog
原文地址:
https://www.perfare.net/archives/1204
转载时必须以链接形式注明原始出处及本声明。
手游