2019-06-25 20,758 ℃
前篇
前言
时隔半年终于把这坑填上了,一开始只是打算随便写写,不过现在看来刚好可以凑出一个系列,之前的文章也重新编辑了一下,感兴趣的人可以翻回去看看。
这次要介绍的Riru模块对于一些人应该并不陌生,Riru+EdXposed是现在的安卓9以上Xposed环境的解决方案之一。Riru的原理是通过替换会被Zygote加载的libmemtrack.so从而实现Zygote注入,而安卓应用进程都是从Zygote fork的,注入了Zygote也就等同于注入了接下来会启动的游戏,也就可以轻松实现修改了。
Riru是Magisk的模块,所以首先要安装Magisk,不过现在手机root应该都是选择Magisk了,然后去Riru的Github的Releases页面下载最新的riru-core包,在magisk里安装,安装好Riru后就可以动手写自己的修改模块了。
写自己的Riru模块
去Riru的Github上下载所有代码,根据官方README,先给自己的模块取个名字,比如perfare,然后修改riru-module-template/jni/main/Android.mk中的模块名字
LOCAL_MODULE := libriru_perfare
接着修改riru-module-template/build.gradle中的模块信息
def moduleName = "perfare"
这两个地方的模块名字一定要一样,其他模块信息自己看着修改就好,修改好后就开始写代码吧,打开riru-module-template/jni/main/main.cpp
在main.cpp中本身已经写好了一些函数,我们只需要关注两个函数
nativeForkAndSpecializePre 通过解析参数appDataDir从而得到当前启动的进程包名,判断是不是我们要修改的游戏
nativeForkAndSpecializePost 在这里创建新线程进行修改
首先写个函数判断包名
static int enable_hack; static const char* game_name = "com.aniplex.fategrandorder"; int isGame(JNIEnv *env, jstring appDataDir) { if (!appDataDir) return 0; const char *app_data_dir = env->GetStringUTFChars(appDataDir, NULL); int user = 0; static char package_name[256]; if (sscanf(app_data_dir, "/data/%*[^/]/%d/%s", &user, package_name) != 2) { if (sscanf(app_data_dir, "/data/%*[^/]/%s", package_name) != 1) { package_name[0] = '\0'; LOGW("can't parse %s", app_data_dir); return 0; } } env->ReleaseStringUTFChars(appDataDir, app_data_dir); if (strcmp(package_name, game_name) == 0) { LOGD("detect game: %s", package_name); return 1; } else { return 0; } }
然后在nativeForkAndSpecializePre里调用这个函数
void nativeForkAndSpecializePre( JNIEnv *env, jclass clazz, jint *_uid, jint *gid, jintArray *gids, jint *runtime_flags, jobjectArray *rlimits, jint *_mount_external, jstring *se_info, jstring *se_name, jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote, jstring *instructionSet, jstring *appDataDir, jstring *packageName, jobjectArray *packagesForUID, jstring *sandboxId) { // packageName, packagesForUID, sandboxId exists from Android Q enable_hack = isGame(env, *appDataDir); }
之后就可以在nativeForkAndSpecializePost里根据enable_hack来修改了,这里选择启动一个新线程来修改
int nativeForkAndSpecializePost(JNIEnv *env, jclass clazz, jint res) { if (res == 0) { // in app process if (enable_hack) { int ret; pthread_t ntid; if ((ret = pthread_create(&ntid, NULL, hack_thread, NULL))) { LOGE("can't create thread: %s\n", strerror(ret)); } } } else { // in zygote process, res is child pid // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66 } return 0; }
至于hack_thread里要怎么修改游戏就看你自己了,这里随便示范一个简单的libil2cpp.so修改,通过不断读取/proc/self/maps确定libil2cpp.so的载入和获取基址,然后直接通过指针修改text段代码
unsigned long get_module_base(const char* module_name) { FILE *fp; unsigned long addr = 0; char *pch; char filename[32]; char line[1024]; snprintf(filename, sizeof(filename), "/proc/self/maps"); fp = fopen(filename, "r"); if (fp != NULL) { while (fgets(line, sizeof(line), fp)) { if (strstr(line, module_name)) { pch = strtok(line, "-"); addr = strtoul(pch, NULL, 16); if (addr == 0x8000) addr = 0; break; } } fclose(fp); } return addr; } void *hack_thread(void *arg) { LOGD("hack thread :%d", gettid()); unsigned long base_addr; while (true) { base_addr = get_module_base("libil2cpp.so"); if (base_addr != 0) { break; } } LOGD("detect libil2cpp.so %lx", base_addr); LOGD("hack game begin"); unsigned long hack_addr = base_addr + 偏移; //设置属性可写 void* page_start = (void*)(hack_addr - hack_addr % PAGE_SIZE); if (-1 == mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC)) { LOGE("mprotect failed(%d)", errno); return NULL; } unsigned char* tmp = (unsigned char*)(void*)hack_addr; tmp[0] = 0x00; tmp[1] = 0x00; tmp[2] = 0x00; tmp[3] = 0x00; LOGD("hack game finish"); return NULL; }
到这里代码就写完了,可以使用gradlew.bat assembleMagiskRelease命令直接编译或者用Android Studio,在release文件夹下就会生成zip包,在Magisk安装即可
后记
这个系列到这里就暂时结束了,虽然只介绍了两个方案,不过这两个方案对付现在市面上99%的游戏都是毫无问题的,所以其他偏门方案也就没有介绍的必要了。接下来可能会摸些详细介绍修改的文章,比如在本博客评论区突然火起来的NetHTProtect,对付这玩意实际上就可以考虑使用dll注入反射修改的方式,这样就可以直接无视它的加密以及其他一些检测,当然文章感觉大概率还是会咕咕咕。另外最近已经有些保护厂商做了内存中text段的检验,如何找到并绕过这些检测可能会成为接下来修改游戏的重点。
大佬 riru编写的模块在安卓11不生效怎么解决
我在isgame里面家里log,发现安卓11环境下,游戏进程没有匹配到,不知道什么原因
自己代码问题,又或者riru模块没能正常加载
大佬问下,riru那个模块模板里使用try catch啊,网上普遍去android.mk里去加,但是现在的模板里没有这个配置文件夹了
我的riru没看到有try catch
大佬能加个Q嘛 1294246096
大佬写下绕过网易检测外挂的方法吧!gg一修改过一会就被服务端给封号了
大佬,Magisk模块有没有免重启调式模块的方法呀,每次调试都要重启浪费好多时间
膜拜大佬,光遇在昨日游戏更新了,官方加强so防GG,大佬还望能去看看。目前我也不知道要怎么过检
膜拜大佬
mprotect有时候会返回Permission denied错误,会不会是多线程的原因?
能否问一下写出来的模块不加载是怎么回事?
按照教程弄出来的东西编译安装都没问题
结果调试的时候log什么都不输出
adb logcat -s “xxxx”
——— beginning of system
——— beginning of main
看magisk本身自己的so又已经加载了
建议检查一下游戏内是否有自己的so,换几个地方打log,如果还有问题也只能你自己检查
想知道内存中text段的检验的遊戲是哪款
mark
个人感觉网易盾的自己写dll去反射玩法限制太低了。而且有so注入检测最新网易盾。NetHTProtect,我研究到网易盾的我直接还原方法体后修改放回去加载。干掉了替换检测。也可以写Va进行注入。xposed!都行。有兴趣可以扣扣聊
大佬qq多少?
②零五五五四三一九
大佬还加qq吗,我的qq 1924460583
2185428205我的企鹅
日常膜拜
有点好奇实时检验内存的Text段不会降低执行效能吗?
还是有什麽我不懂的黑科技
10分钟检查一次
先赞后看!
惯例
这个大佬也膜拜一下