手游的注入与修改

2018-09-02 24,701 ℃

已经有很长一段时间没写过游戏修改的文章了,一个原因是现在越来越多的手游厂商都开始给游戏上各种各样的保护,以前直接修改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.多开类程序

VirtualAppVirtualXposed或者平行空间,多开分身之类的软件,在程序的入口处或者其他地方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修改手游

一个过手游反作弊中.text段校验的方法

原本打算下个月再水的,不过转念一想,反正也不可能做到每个月都水一篇,而且现在放开以后准备到处去玩了,所以还是赶紧写完了发出来。 偷梁换柱 思路...

阅读全文

在模拟器上使用Zygisk修改游戏

前言 没想到距离上次发一篇教程性质的文章已经过去整整一年了,刚好最近搞了标题里提到的东西觉得还是可以写一下的,于是就来水了这一篇。 之前我一直...

阅读全文

使用Zygisk和Il2Cpp Api来修改游戏吧

在两年前发布Riru版Il2CppDumper的时候,就打算发布这篇配套的文章,结果码了一半就被我丢到了一旁,后来每次想起来的时候总是以”既然代码都发布了大概也不...

阅读全文

32 条评论

  1. 感谢你的分享。 。我真的很喜欢VirtualXposed。它允许我在没有root用户访问权限的情况下使用Youtube Adaway Xposed模块

  2. 最近接手了个项目,需要大量用到hook,虚拟机内运行。楼主可否留个联系方式。项目有趣程度绝对非同一般。

  3. 请问学习这些有什么前置条件呀,比如这些代码,我该去学什么才能看懂?

  4. 现在有很多手游都没法用模拟器愉快的玩耍。。比如魔法少女小圆,用模拟器会被检测,神奇的是网上流传的魔改版apk居然可以过检测,不知道这个是什么原理,大佬可否略点一二?

  5. 查了很多资料,android unity3d 通过注入dll对 Assembly-CSharp.dll 里的函数进行HOOK这个是没办法实现的吧,Harmony这个框架不支持android平台的,不知道大佬怎么实现hook的?期待您的来信!

    1. 当然可以实现,我写Harmony是因为运行时修改的核心思路是一样的,只是在不同平台需要分别处理。
      其实理解了思路就可以很容易自己写代码了,基本原理就是
      1.通过反射获取MethodInfo
      2.调用RuntimeHelpers.PrepareMethod使函数jited
      3.通过MethodInfo.MethodHandle.GetFunctionPointer()获取函数指针,这里不同类型的方法比如DynamicMethod需要不同的方式处理,可以去翻下Harmony代码
      4.有了函数指针那么就可以对其进行hook了
      https://github.com/easy66/MonoHooker 这里还有一个项目你可以参考下,其实类似的项目git上也有很多了

  6. 用VirtualXposed把包含这段代码的so按照自己的libmono地址偏移注进去了,但是在void* image = mono_image_open_from_data(buffer, lSize, 1, status);这句话发生了错误

    pid: 22419, tid: 22438, name: queued-work-loo >>> app.game <<<
    r0 00000000 r1 00004000 r2 00000003 r3 cdb7ee44
    r4 c3ad2000 r5 f1984108 r6 00001000 r7 cf37f934
    r8 cd9e8000 r9 000057a3 sl cf5165b1 fp cf37f8bc
    ip cdd95cd0 sp cf37f8a0 lr cdc55118 pc cdb7e1d0 cpsr 312f3030
    backtrace:
    #00 pc 001961d0 /data/data/io.virtualapp.ex/virtual/data/app/app.game/lib/libmono.so
    #01 pc 00196dbc /data/data/io.virtualapp.ex/virtual/data/app/app.game/lib/libmono.so (mono_image_open_from_data_with_name+388)
    #02 pc 00196e30 /data/data/io.virtualapp.ex/virtual/data/app/app.game/lib/libmono.so (mono_image_open_from_data_full+60)
    #03 pc 00196e78 /data/data/io.virtualapp.ex/virtual/data/app/app.game/lib/libmono.so (mono_image_open_from_data+52)
    #04 pc 0000188f /data/app/io.virtualapp.ex-1/lib/arm/libhack.so (dll_inject(long)+126)

    还请大佬指教一下

  7. 最近在看一个android改端游的游戏,安卓上是il2cpp,看不到逻辑,pc上加密了,感觉是直接xor的,就想看看主程序有没有解密。主程序用了mono_image_open_from_data_with_name,但是只有一个xref,而且data也没看到加密。这样还有什么思路吗…

    1. 开始的时候加载了个库hook_mono的mono_image_open_from_data_with_name。库加了壳。这样的话还有什么思路…

    2. 风之大陆
      Assembly-CSharp能dump出来但是0x408开始是错的,应该是mono.dll或者游戏主进程还有另一个加密
      最近在尝试直接从内存里扣

  8. 我用这种方式把dll注入进去了,但是却不能用反射,好像是缺少dll,但是我用同样的方式把mscrolib.dll也注入进去了,还是不能引用反射的函数,是为什么呢?android平台

  9. 求問大大 日版 chain chronicle 3.8.5 的Loader 是哪個方面的保護…
    希望指引一點點方向該如何研究

  10. 会想用Unity Studio其实就是因为自己不会这么进阶的东西,所以还真是一点都看不懂。偏偏自己常玩的手游最近又开始有一些图档保护措施

  11. 感谢大佬提供思路和方法,我一直想这么修改,就是没有人带我飞。。

欢迎留言

3 + 4 =