首页
社区
课程
招聘
[原创]Xposed注入实现分析及免重启定制
发表于: 2018-1-3 11:10 31646

[原创]Xposed注入实现分析及免重启定制

2018-1-3 11:10
31646

Xposed已经作为Android的Java层Hook大哥好多年了,不仅仅是安全研究者和逆向工程师手中的神器,还拥有着很多的插件开发者,在官方仓库收录的插件也已经超过了1000+个,而其服务的用户以各搞机论坛用户为主,也是不尽其数。
Xposed的代码是完全开源的,并且代码量也不算很大,对于安全研究者来说呢,我觉得研究一下Xposed的原理还是很有必要的。
Xposed的github地址:
https://github.com/rovo89/Xposed
https://github.com/rovo89/XposedBridge

本文使用Android4.4的源码进行分析

简单的讲,Xposed是通过替换修改过的app_process来注入Zygote以实现的全局注入。

想要知道Xposed是如何实现的全局注入,首先要分析一下Android系统的启动过程,就可以很方便的理解Xposed选择的注入点。
我画了张图,Android系统的启动大致如下:
Android系统启动流程
从图中可以很方便的理解从BootLoader到显示系统界面的过程。

Zygote,中文是"受精卵"...实际上他就是一个孵化器,所有应用程序的进程都是由它fork出来的。

可以看到,这些包名格式的进程的PPID(父进程PID)都是155,即zygote进程。
Zygote启动之后会建立一个Socket Server,然后fork自身成为一个新的进程——system_server,并让它成为"前端代言人",当system_server接收到打开进程的请求时,它就会通过Socket跟Zygote通讯,Zygote就再fork自身称为新的进程。

在init.rc中可以看到,Zygote是通过app_process启动的,

这时app_process的任务也很简单,就是把自己的进程名变成Zygote,然后反射调用Zygote的主方法,即com.android.internal.os.ZygoteInit的main()。之后Zygote做的事情就是在介绍中说过的了。

那么Xposed就是通过修改app_process的方式来实现注入Zygote的。
项目地址:https://github.com/rovo89/Xposed 即是修改app_process的源码,在app_main.cpp的main函数中可以看到

可以看到,Xposed首先尝试加载自己的库,如果成功的话,就将runtime.start的类名替换成自己的类:de.robv.android.xposed.XposedBridge,因为app_processs是采用反射main()的方法来调用加载方法,所以只要在这个类中定义一个main()方法,就可以为所欲为了,最后只需在末尾调用一下com.android.internal.os.ZygoteInit的main()让系统继续跑下去就可以实现对Zygote的Hook。

既然已经拿到了Zygote的所有权了,但是现在Application还没跑起来呢,如果这个时候就执行Hook模块,肯定是无法Hook到相关代码的。那么这时候就有必要提一下从Zygote接受到请求之后做了什么,我又简单的画了张图:

值得注意的是,因为fork之后的子进程是会继承父进程的状态继续往下跑,所以到handleChildProc的时候,此时已经是子进程在执行程序了,而ActivityThread也是在新进程中loop。
在Activity的loop中,可以接收的消息也包括了Appcalition的生命周期BIND_APPLICATIONEXIT_APPLICATION。处理BIND_APPLICATION的时候是直接调用了handleBindApplication方法,Xposed便是在Zygote中Hook了这个方法,在这里面执行用户的Hook模块,因为在这个时候,已经可以拿到应用的classLoader了,然后将此classLoader封装成一个LoadPackageParam再传给各个模块的handleLoadPackage即可。
在XposedBridge的main中可看到其调用了initForZygote()

initForZygote中又Hook了handleBindApplication

Xposed在Zygote进程中执行完这些之后,每次fork出来的都是handleBindApplication已经被Hook过的进程,所以当每个应用进程被打开的时候,都会最开始就先执行Xposed的模块插件,然后才开始执行本身的代码。这样就实现了注入到每个应用程序来进行Hook。

findAndHookMethod想必是大家写Xposed插件最常用及最熟悉的一个方法,我们就通过这个方法来分析Xposed是如何实现的Java Hook。

大家知道这个方法的参数是个可变长参数列表,然后它会取最后一个参数来作为callback,即hook时的注入的代码。然后通过反射的方法来得到原本的Method。最后将这两个传给hookMethod。

可以看到hookMethod实际上时调用了一个native函数hookMethodNative,其他参数都很好理解,但是取的这个slot是个什么东西?翻了一下百度,在Dalvik的源码中dalvik/vm/reflect.c:

原来这个slot就是此method在ClassObject的methods中的偏移,directMethods为负,virtualMethods为正。

hookMethodNative对应C函数XposedBridge_hookMethodNative

首先是通过slot获取到Method对象在内存中的Method结构体指针,然后将它设置为一个native方法,再将他的nativeFunc即执行时对应的函数地址设置为hookedMethodCallback的指针,将保存着原method信息的hookInfo暂时存放在本是dalvik字节码的insnsreflectedMethod是原method的java对象,additionalInfo是AdditionalHookInfo对象。
然后当调用到这个方法的时候,执行的就变成了hookedMethodCallback函数了。

主要就是又调了一个java方法methodXposedBridgeHandleHookedMethod,解释一下各个参数,originalReflected就是上面hookInfo的reflectedMethodoriginal是原method结构体指针,后面的不必解释了吧。

那么methodXposedBridgeHandleHookedMethod做的事情也很好理解,就是先把所有的beforeHookedMethod给调用一遍,然后再还原原来的method调用一遍,最后再把所有的afterHookMethod的给调用一遍,就完成了。

执行原来的方法调用的是native方法invokeOriginalMethodNative。

emm..就是通过拿到备份的method结构体,然后直接调用dvmInvokeMethod即可。

大家在写Xposed模块的时候是不是有一个很不爽的地方,就是每次改两行代码之后又得重启,非常的烦,那么清楚了注入的原理之后,发现这点好像不是必须的!只是rovo89可能是考虑到性能的问题,所以才采用这种方案。实际上,只要轻轻的改动两行代码,就可以实现免重启更新!

Xposed原本是在app_process的时候一次性将模块列表读取,Load之后将其hook类放到一个Set里面,在de.robv.android.xposed.XposedBridgemain()中可以看到

待到应用启动的时候再分别调用每个handleLoadPackage,之后就不再读取插件。因为这样,所以每次fork之后,插件已经被加载在内存中了,就算更新插件也不会被读取加载,所以必须得重启才能使得插件生效。

之前说到,Xposed通过HookhandleBindApplication来进入每个应用进程,那只要在这个时候再loadModules,那不就ojbk了吗?
loadModule中,进行加载不仅有handleLoadPackage,还有hook资源及hook命令行程序的包,

但是我的需求就是只需要处理handleLoadPackage就可以了,所以给这个参数加个开关isLoadHookLoadPackage,我的代码如下


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2018-2-24 01:40 被葫芦娃编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (44)
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
,我也一直在用xposed,虽然不清楚具体原理
2018-1-3 13:34
0
雪    币: 387
活跃值: (897)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
唯一的缺点就是initZygote重复调用
2018-1-3 19:19
0
雪    币: 387
活跃值: (897)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
铭天星 唯一的缺点就是initZygote重复调用
也非常感谢楼主的思路
2018-1-3 19:58
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
2018-1-3 20:35
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
6
铭天星 唯一的缺点就是initZygote重复调用
不需要的哦,仔细看文
2018-1-3 21:14
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
7
牛鼻学习了!
2018-1-3 21:35
0
雪    币: 387
活跃值: (897)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8



Hoimk

不需要的哦,仔细看文
我分析的结果:第二次调用loadModules(true)的时候也会再调用一次initZygote。
如果改成initZygote加上条件!isLoadHookLoadPackage的话,就会导致调用initZygote,handleLoadPackage在不同的实例。
如有分析不正确的地方,望楼主一同交流核实
2018-1-3 21:53
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
9
铭天星 Hoimk 不需要的哦,仔细看文 我分析的结果:第二次调用loadModules(true)的时候也会再调用一次initZygote。如果改成initZ ...
loadModules里不会调用initZygot的。他只是把xposed模块一个个加载进当前进程而已。initZygote从开机到启动进程只会执行一次,即开机通过app_process启动的那一次。
2018-1-4 01:51
0
雪    币: 387
活跃值: (897)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
考查以下模块:

public class TestModule implements IXposedHookZygoteInit, IXposedHookLoadPackage {

private String modulePath;

@Override
public void initZygote(StartupParam startupParam) throws Throwable {
this.modulePath = startupParam.modulePath;
XposedBridge.log("initZygote modulePath=" + this.modulePath);
}

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.isFirstApplication && lpparam.appInfo != null &&
(lpparam.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) {
XposedBridge.log("handleLoadPackage modulePath=" + this.modulePath + ", processName=" + lpparam.processName + ", dataDir=" + lpparam.appInfo.dataDir);
}
}
}

官方的xposed开机调用一次initZygote,每次启动App调用handleLoadPackage时modulePath不为null

测试模块见附件
上传的附件:
2018-1-4 08:26
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
11
铭天星 考查以下模块: public class TestModule implements IXposedHookZygoteInit, IXposedHookLoadPackage { priva ...
噢好吧,你说的是这个,这个没做处理,我一直以为你说的是XposedInit.initForZygote();  ....
2018-1-4 08:57
0
雪    币: 416
活跃值: (509)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
12
先去赞一个
2018-1-4 10:16
0
雪    币: 0
活跃值: (878)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
13
赞,葫芦娃  文章读着很爽 
2018-1-4 16:10
0
雪    币: 371
活跃值: (94)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
14
支持楼主,顶顶顶
2018-1-4 16:44
0
雪    币: 2
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
支持楼主,有更快的方法,这是另一种思路
http://blog.csdn.net/u011956004/article/details/78612502
2018-1-4 20:51
0
雪    币: 324
活跃值: (384)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
16
膜  葫芦娃
2018-1-6 22:43
0
雪    币: 42
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
很棒,能否把编译好的发一份,自己编译太麻烦了
2018-1-11 01:59
0
雪    币: 1759
活跃值: (2334)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
18
如何hook的进程是系统服务中的代码,不知可否免重启呢?
2018-1-18 11:50
0
雪    币: 1759
活跃值: (2334)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
19
钱八斤 支持楼主,有更快的方法,这是另一种思路http://blog.csdn.net/u011956004/article/details/78612502
你这个应该不支持hook系统服务的代码
2018-1-18 11:50
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
20
又见飞刀z 如何hook的进程是系统服务中的代码,不知可否免重启呢?
需要重启服务
2018-1-18 15:12
0
雪    币: 37
活跃值: (12)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
21
帖子写的非常详细,感谢.
2018-1-27 15:57
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
你好,我们公司在做防作弊相关的这方面东西,可以交流下吗?QQ:646241923
2018-1-30 18:14
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
23
WXAdamancy 你好,我们公司在做防作弊相关的这方面东西,可以交流下吗?QQ:646241923
有什么问题可站内信私我
2018-1-31 23:36
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
谢谢,通过反作弊手段识别设备,比如通过设备id,imsi,imei,root, hook,模拟器检测能识别真机的百分比高吗?反作弊一期应该采集哪些设备信息(主要针对设备标别)
2018-2-1 11:14
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
25
WXAdamancy 谢谢,通过反作弊手段识别设备,比如通过设备id,imsi,imei,root, hook,模拟器检测能识别真机的百分比高吗?反作弊一期应该采集哪些设备信息(主要针对设备标别)
不高,很多模拟器都对大部分API、文件、系统函数进行了处理,通常情况下获取到的很多信息都是模拟器伪造的。没什么特别精准的公开方案
2018-2-1 17:27
0
游客
登录 | 注册 方可回帖
返回
//