手游的注入与修改

2018-09-02 10,253 ℃

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

使用Riru修改手游

前篇 手游的注入与修改 使用VirtualXposed修改手游 前言 时隔半年终于把这坑填上了,一开始只是打算随便写写,不过现在看来刚好可以凑出一个系列,之前的文章...

阅读全文

使用VirtualXposed修改手游

前篇 手游的注入与修改 前言 这篇后续文章原本是打算很快就写完的,但是不知怎么一转眼就已经12月了,眼看今年都要过了,还是赶紧把这篇文章水出来吧。 在上...

阅读全文

浅谈某NetHTProtect

声明,本文只稍微提一下思路,不提供任何代码~ 这次是第二次写它了,上篇文章讲的也是这玩意,不过这次它加了个新东西,直接暴力dump dll的话会发现method co...

阅读全文

22 条评论

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

  2. 查了很多资料,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上也有很多了

  3. 用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)

    还请大佬指教一下

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

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

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

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

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

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

欢迎留言

0 + 5 =