-
-
[原创]zygisk改机模块demo
-
发表于: 2024-11-5 18:17 2100
-
前置知识
做这个的话推荐使用Andoridstudio。
Zygote 进程
init 进程在解析 init.rc 脚本时,会创建 Zygote 进程,只有该进程才会建立起真正的 Android 运行空间,它是 Android 系统最重要的进程之一。后续 Android 中的应用进程都是由 Zygote 进程 Fork 出来的,因此,Zygote 是 Android 系统所有应用的父进程(即 Java 进程的鼻祖)。
注:Zygote 进程的出现是为了能更快的启动应用。因为在 Android 中,每个应用都有对应一个虚拟机实例(VM)为应用分配不同的内存地址。如果 Android 系统为每一个应用启动不同的 VM 实例,就会消耗大量的内存以及时间。因此,更好的办法应当是通过创建一个虚拟机进程,由该 VM 进程预加载以及初始化核心库类,然后,由该 VM 进程 Fork 出其他虚拟机进程,这样就能达到代码共享、低内存占用以及最小的启动时间,而这个 VM 进程就是 Zygote。
这里因为其他进程都是由zygote fork出去的,所以如果实现了注入zygote,那么就对几乎所有进程实现HOOK。
LD_PRELOAD
设置该属性可以强制给指定进程优先加载指定库,但是这里的优先感觉有点强制的感觉吧,我试过给ls指定一个自定义的库,然后观察日志发现ls时该库里的init函数立刻执行了。LD_PRELOAD的具体讲解也有文章不再详说。
Zygisk
magisk实现zygote注入的方式,使用zygisk可以编写zygisk格式的模块来便宜进行HOOK。具体实现有文章可以看看。
zygisk模块编写
示例模块:https://github.com/topjohnwu/zygisk-module-sample
这个github项目中module->jni->libcxx在另一个项目地址,需要单独下载的样子,直接在项目中点击libcxx那个跳转前往下载,可能git拉取下来的话不需要这样?
jni下的example.cpp存储着主要的HOOK逻辑。
Android.mk存储着编译逻辑,当然,这是使用ndk-build来编译的话,使用Cmake的话想必也是可以的,但是我想的是实现最基础的使用所以先按照项目示例中所说使用的ndk-build,具体编译就是确认mk文件无差后在module目录下使用ndk-build即可。
生成文件会在对应的这里。
然后按照zygisk模块的格式打包即可。
如图:
应该是只要有zygisk这个文件夹就可以识别出,因为我为了方便直接放在了之前做实验的magisk模块中也成功识别。这里要注意的是把相应架构的so文件放到zygisk文件夹下并重命名为相应架构。
然后就是正常安装模块。
Dobby
一个HOOK用的轻量化的框架。
在github上有项目,但是,至少我查看的时候release里,应该,是没有android的相应文件叭,我的环境似乎也无法正常编译,于是找到了一个旧的22年的:https://github.com/LSPosed/Dobby/tree/latest
该项目release有着android的相应文件,但是使用时还是出了些问题,解决方法我没记错的话是在cmake文件中加上一句set(CMAKE_POSITION_INDEPENDENT_CODE ON)后重新编译dobby.a解决问题。这个的编译我是正常通过的。
模块编写
首先肯定是要找一些改机模块进行学习,但是大都是通过magisk的resetprop这种来设置属性,这样的话对我的学习没有任何作用,还好还是找到了一篇通过riru实现对system_property_get进行HOOK改机的文章https://www.cnblogs.com/luoyesiqiu/p/magisk_riru.html
这篇文章真的是帮大忙了,学习了它的编写逻辑,大致思路就是通过riru的zygote注入,再结合dobby编写了riru模块对system_property_get进行了HOOK,修改返回系统属性实现。
但是riru已经停更好久了,而且在实验中我也编译了riru的模块来使用,但是对于安卓十三来说应该是不适用了,然后对于该原因的分析也没有找到文章,遂自己去小看了下源码,不过也没有找到具体原因。虽然有看到源码的部分变化处,比如
但是感觉我看到的变化似乎影响不是很大,在查看日志的时候我也故意使用不规范的命名,但是没有看到报错日志,所以我怀疑可能我的手机可能根本没有启用nativebridge?唉,主要还是关于riru停更后关于这种注入方式的文章我几乎找不到,也没有太多时间花在这上面,遂使用的zygisk。
riru扯得有些多了,还是把话题说回到zygisk模块编写上吧。既然有了riru的改机模块示例,那么要做的就是学习他的方法,并移植到zygisk上,然后我查看了riru和zygisk示例模块中的函数,发现相似度还是很高的。
比如riru改机模块中是将HOOK的实现放在forkAndSpecializePost函数下
forkAndSpecializePost 钩子允许开发者在新进程创建之后立即对其进行调整,而相应的我只要把HOOK的实现放在zygisk的preAppSpecialize函数中即可。
模块代码的具体实现
init函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void initDobby(){ if (hook_initialized) { LOGD( "HOOK already initialized, skipping..." ); return ; } hook_initialized = true; LOGD( "HOOK_init" ); void * sym = DobbySymbolResolver(NULL, "__system_property_get" ); / / 获取函数指针? if (NULL! = sym){ DobbyHook((void * )sym,(dobby_dummy_func_t) my_system_property_get,(dobby_dummy_func_t * ) &origin__system_property_get); } else { LOGD( "can not found the sym of __system_property_get" ); } } |
获取__system_property_get的函数指针,使用DobbyHOOK把原始函数替换为我们的自定义函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | static int my_system_property_get(const char * _Nonnull name, char * _Nonnull value){ readPropFile(PROP_CONF_PATH,propMap); if (NULL = = name || NULL = = value){ LOGD( "内容为空" ); return origin__system_property_get(name,value); } auto ret = propMap.find(name); if (ret! = propMap.end()){ LOGD( "find key '%s'" ,ret - >first.c_str()); const char * valueChs = ret - >second.c_str(); / / value的值 strcpy(value,valueChs); return strlen(valueChs); } return origin__system_property_get(name,value); } |
如果我们设置的值在被查询,则替换并返回,如果不是则调用原始函数。
除了native层还要修改java层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | static void setBuild(JNIEnv * env) { / / 查找 Build 类 jclass BuildClass = env - >FindClass( "android/os/Build" ); if (BuildClass = = NULL) { LOGD( "Failed to find class android/os/Build" ); return ; } / / 遍历 buildMap 设置属性 for (auto it = buildMap.begin(); it ! = buildMap.end(); + + it) { LOGD( "Setting build prop -> key = %s, value = %s" , it - >first.c_str(), it - >second.c_str()); / / 创建 key 和 value 的 JNI 字符串 jstring key = env - >NewStringUTF(it - >first.c_str()); jstring value = env - >NewStringUTF(it - >second.c_str()); / / 检查创建结果 if (key = = NULL || value = = NULL) { LOGD( "Failed to create JNI string for key or value." ); if (key ! = NULL) env - >DeleteLocalRef(key); if (value ! = NULL) env - >DeleteLocalRef(value); continue ; } / / 获取属性字段 ID jfieldID buildField = env - >GetStaticFieldID(BuildClass, it - >first.c_str(), "Ljava/lang/String;" ); if (env - >ExceptionCheck() || buildField = = NULL) { env - >ExceptionClear(); LOGD( "Failed to get field ID for key = %s" , it - >first.c_str()); env - >DeleteLocalRef(key); env - >DeleteLocalRef(value); continue ; } / / 设置字段值 env - >SetStaticObjectField(BuildClass, buildField, value); if (env - >ExceptionCheck()) { env - >ExceptionClear(); LOGD( "Failed to set field for key = %s" , it - >first.c_str()); } / / 释放局部引用 env - >DeleteLocalRef(key); env - >DeleteLocalRef(value); } / / 释放 BuildClass 的引用 env - >DeleteLocalRef(BuildClass); } |
这就是主要的代码,全部的代码我会传附件,但是是学习的rirugaiji,所以代码的实现和其大差不差,这里还是很感谢那位师傅能够将知识分享出来。
编译后在pixel6手机上安装模块效果图:
这个也算先告一段落了,因为这个月有很多考试,所以恐怕没有时间来完善这个改机。打算的是这个月学些零散的东西,下个月有大块时间的话想要再改进完善这个模块或者学习其他吧。这毕竟只是个小demo,实在过于简陋,下一步设想的是仿照riru添加守护进程及交互等,并尝试整合进Apatch的kp模块。
代码附件有点大,传不了,放云盘了。
通过网盘分享的文件:zygisk-module-sample-master.zip
链接: https://pan.baidu.com/s/1J6dmiAXhrv9V1_cLiBY3tw?pwd=rea1 提取码: rea1
--来自百度网盘超级会员v4的分享
使用需要在data/local/tmp下写入文件,然后会读取。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]zygisk改机模块demo 2101
- [原创]ByteCTF逆向WP 4780
- [原创]春秋杯Re2024WP 6748
- 腾讯游戏安全2024安卓初赛复现 26638