突然觉得这个分类,应该改名奇巧淫技,或许会更好听点。偷鸡摸狗的话,并不是说这个是见不得人的东西。不过以后再说吧,毕竟不是今天的重点
前言
公司要每个技术序列的人都要做技术分享,一开始的时候确实没有想好要讲什么,毕竟同行,说的浅了没技术含量,说的深了担心自己给自己挖坑(滑稽脸)。最后,还是选择了 xposed 这个主题,因为 xposed 对于安卓来说确实是一个牛逼的存在,使用起来还是挺牛逼的,但是过程不是很复杂,作为技术去分享的话,可以自己控制一下。比如,设计的 Android 系统启动,xposed 的原理分析,还有一些 xposed 项目的分析,感觉这些应该能蒙混过去的把。。。心慌的,毕竟第一次。
xposed 是什么
百度说:
Xposed 框架是一款可以在不修改 APK 的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
维基说:
Xposed框架(Xposed framework)是一套开源的、在 Android 高权限模式下运行的框架服务,可以在不修改 APK 文件的情况下修改程序的运行(修改系统),基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。这套框架需要设备打开了 Root 权限方可安装使用。
OK,其实 xposed 就是一个框架,通过这个框架你能拿到很多系统级别的东西,比如系统级别的数据返回( Mac、手机型号、IMEI 等),这样你就可以直接欺骗一些应用,去设置你想设置的手机参数;再比如可以拿到运行在系统上的各个应用的试了,这样你就可以调用这些实例的方法,把 App 的界面改成自己想要的样子,去掉界面里不喜欢的东西(广告),自动抢红包,消息防撤回,步数修改等等。
当然,高收益常常伴随的是高风险,在这里,想要使用 xposed,风险就是需要 root 你的手机,经常搞基的人是知道这个的,具体操作就不在这里细说了,我用的机子是小米的,系统刷成开发版可以直接修改 root 权限的,还是挺方便的,VirtualXposed 倒是可以不用 root,不过属于阉割版,可玩性肯定就没那么好了。
有了 xposed,我们可以加载很多 App 插件,理论上通过这些插件,我们可以 hook 到系统任意一个 Java 进程(zygote、systemserver、systemui)。
xposed原理
首先,我们来简单梳理一下 Android 系统的启动流程:
启动电源以及系统启动
当电源按下时引导芯片代码开始从预定义的地方(固化在 ROM)开始执行。加载引导程序 Bootloader 到RAM,然后执行。
引导程序 BootLoader
引导程序 BootLoader 是在 Android 操作系统开始运行前的一个小程序,它的主要作用是把系统 OS 拉起来并运行。
Linux 内核启动
内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置,它首先在系统文件中寻找 init.rc 文件,并启动 init 进程。
init 进程启动
初始化和启动属性服务,并且启动 zygote 进程。
init 进程有两个责任,一是挂载目录,比如/sys、/dev、/proc,二是运行 init.rc 脚本。
zygote 进程启动
创建 JavaVM 并为 JavaVM 注册 JNI,创建服务端 Socket,启动 SystemServer 进程。在这个阶段,你可以看到启动动画。
SystemServer 进程启动
启动 Binder 线程池和 SystemServiceManager,并且启动各种系统服务。
Launcher启动
被 SystemServer 进程启动的 ActivityManagerService 会启动 Launcher,Launcher 启动后会将已安装应用的快捷图标显示到界面上。
在 Android 系统中,应用程序进程以及系统服务进程 SystemServer 都是有 zygote 进程孵化出来的,而 zygote 进程则是由 init 进程启动的(毕竟基于 Linux 系统),zygote 进程在启动的时候会创建一个 Dalvik(5.0以前默认,之后ART,空间交换时间) 虚拟机实每当它孵化一个新的应用程序简称时,都会将这个 Dalvik/ART 虚拟机实例复制到新的应用进程里面去,从而使得每一个应用程序进程都有各一个独立的 Dalvik/ART 虚拟机实例。而 zygote 进程对应的执行文件是/system/bin/app_process,而 Xposed 所做的就是替换了 app_process。
Xposed 用自己实现的 app_process 替换了系统提供的 app_process,加载一个额外的jar包,然后入口从原来的:com.android.internal.osZygoteInit.main() 被替换成了: de.robv.android.xposed.XposedBridge.main(), 然后创建的 Zygote 进程就变成 Hook 的 Zygote 进程了,而后面 Fork 出来的进程也是被Hook过的。
相关的一些文档:
- 官网:http://repo.xposed.info/
- 作者Github仓库:https://github.com/rovo89
- 官方教程:https://github.com/rovo89/XposedBridge/wiki/Development-tutorial
- XposedBridge.jar下载:https://jcenter.bintray.com/de/robv/android/xposed/api
工程组成
XposedInstaller
这是 Xposed 的插件管理和功能控制 APP,也就是说 Xposed 整体管控功能就是由这个APP来完成的,它包括启用Xposed 插件功能,下载和启用指定插件APP,还可以禁用 Xposed 插件功能等。注意,这个app要正常无误得运行必须能拿到 root 权限。
Xposed
这个项目属于 Xposed 框架,其实它就是单独搞了一套 xposed 版的 zygote。这个 zygote 会替换系统原生的zygote。所以,它需要由 XposedInstaller 在 root 之后放到 /system/bin 下。
XposedBridge
这个项目也是 Xposed 框架,它属于 Xposed 框架的Java部分,编译出来是一个 XposedBridge.jar 包。
XposedTools
Xposed 和 XposedBridge 编译依赖于Android源码,而且还有一些定制化的东西。所以XposedTools就是用来帮助我们编译 Xposed 和 XposedBridge的。
Xposed 项目创建
要上主菜了,先给大家说几句,其实 Xposed 插件就是一个完整的 apk,跟我们平常做的 apk 并没有多大的不同,只不过在项目中加了一些标识,让 Xposed 能狗识别,给这个 apk 特殊的权限,可以去影响其他 apk 的运行。
创建 Android 项目
当然,首先应该创建一个 Android 项目。
修改 AndroidManifest.xml
在 application 节点下添加三个
1 | <meta-data |
- xposedmodule: 一般设置为true,表示这是一个xposed模块
- xposeddescription: 一句话描述该模块的用途,可以引用string.xml中的字符串
- xposedminversion:要求支持的Xposed Framework最低版本。
引入 Xposed Framework API
在 app/build.gradle
文件中声明 Xposed Framework API 的jar包依赖。
1 | compileOnly 'de.robv.android.xposed:api:82' |
- 请留意,这个82是Xposed Framework API的版本号,叫做xposedminversion。
- xposedminversion可以在这里进行查询: https://bintray.com/rovo89/de.robv.android.xposed/api
- Xposed Framework API文档请参考:http://api.xposed.info/reference/packages.html
创建 Xposed 入口文件
在 assets 文件夹下面创建一个名为 xposed_init 的文件, XposedBridge 会从 assets 目录中的 xposed_init 中获取入口点,比如:
1 | top.programan.xposeddemo.XposedUtil |
编写入口类
在此之前我们先修改下我们的MainActivity.java,修改下TextView显示的文字:
1 | class MainActivity : AppCompatActivity() { |
然后编写入口类,代码如下:
1 | class XposedInit : IXposedHookLoadPackage { |
OK,到这里一个简单的 Xposed 项目已经完成了,安装我们完成的 apk,记得要在 XposedInstaller 里面选择该插件,然后重启手机。不出意外的话,应该是可以正常运行的。
如上发现,一个普通的 Xposed 项目是不难实现,更难实现的应该是怎么样去逆向逆向 hook 的应用,并找到逆向 hook 对应的代码。
Xposed 相关 API
IXposedHookLoadPackage
1
2
3
4
5
6
7
8 > /**
> * Get notified when an app ("Android package") is loaded.
> * This is especially useful to hook some app-specific methods.
> *
> * <p>This interface should be implemented by the module's main class. Xposed will take care of
> * registering it as a callback automatically.
> */
>
翻译起来就是:
当APP加载的时候就会收到通知,对于hook一些应用的特殊方法时十分有效,插件的主方法应该实现这个接口,Xposed将会自动的为他注册回调。
所以来说这个接口的作用很明显,如果是一个应用级别的插件那么入口类就需要实现的这个接口,当插件要hook的应用启动时就会回调这个接口的方法。Xposed自动帮我们做,很贴心。猜测源码实现应该是一个观察者模式。
使用代码如下:
1 | class XposedInit : IXposedHookLoadPackage { |
IXposedHookLoadPackage接口的回调方法handleLoadPackage的参数XC_LoadPackage.LoadPackageParam的源码如下:
1 | /** |
可以看到主要是要加载的应用的一些相关信息。
IXposedHookZygoteInit
1
2
3
4
5
6
7
8
9
10
11
12 > /**
> * Hook the initialization of Zygote process(es), from which all the apps are forked.
> *
> * <p>Implement this interface in your module's main class in order to be notified when Android is
> * starting up. In {@link IXposedHookZygoteInit}, you can modify objects and place hooks that should
> * be applied for every app. Only the Android framework/system classes are available at that point
> * in time. Use {@code null} as class loader for {@link XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)}
> * and its variants.
> *
> * <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
> */
>
翻译起来的大体意思就是:
hook初始的Zygote进程,所有的应用都会适用。在你插件的主方法实现这个接口,这样当android启动的时候这个接口就会被通知,你修改和hook的地方对所有的应用都会有效,假如你只想对某个应用起作用建议你只需要实现IXposedHookLoadPackage这个接口就行了。
使用代码如下:
1 | class XposedZygoteInit : IXposedHookZygoteInit { |
IXposedHookZygoteInit接口的回调方法initZygote的参数StartupParam的源码如下:
1 | /** Data holder for {@link #initZygote}. */ |
在Zygote启动时调用,用于系统服务的Hook 回调方法initZygote()。
IXposedHookInitPackageResources
1
2
3
4
5
6
7
8 > /**
> * Get notified when the resources for an app are initialized.
> * In {@link #handleInitPackageResources}, resource replacements can be created.
> *
> * <p>This interface should be implemented by the module's main class. Xposed will take care of
> * registering it as a callback automatically.
> */
>
翻译起来的大体意思就是:
当应用资源被初始化的时候会被通知调用,在handleInitPackageResources方法里面可以替换资源。应该在主方法里面被实现,Xposed会自动注册通知调用回调方法。
1 | class XposedZygoteInit : IXposedHookInitPackageResources { |
IXposedHookInitPackageResources接口的回调方法handleInitPackageResources的参数InitPackageResourcesParam的源码如下:
1 | /** |
有了这个XResource对象,就可以拿到通过覆写hookLayout方法拿到布局资源树。
1 | class XposedZygoteInit : IXposedHookInitPackageResources { |
我们看下回调接口LayoutInflatedParam的源码:
1 | /** |
注意上面的View变量,这个就是布局资源树,可以进行遍历,拿到特定的控件,玩一些狂拽炫酷的东西。
XposedHelpers
findAndHookMethod
具体的使用和相关的参数介绍上面已经说过了,就不再重复,不过需要关注下findAndHookMethod的参数MethodHookParam,具体的源码如下:
1 | /** |
需要关注下thisObject,代表调用该方法的对象实例,如果是静态方法,返回一个Null,比如这里调用onCreate()方法的是MainActivity,那么获得的就是MainActivity实例。
其他的一些方法
- callMethod(Object obj,String methodName, Object… args):在APP中调用特定方法; 参数依次是:调用方法的所在类,调用方法名,方法参数
- findClass(String className,ClassLoader classLoader):获取class类实例 参数依次是类名,类加载器
- findMethodExact:通过反射查找类的成员方法(可setAccessible(true)设置非私有)
- findConstructorExact:通过反射查找构造函数(同样可设置可访问下性)
- findAndHookXXX:查找并Hook
- 只能Hook方法与构造方法,不能Hook接口和抽象方法
Xposed 模块开发优势和不足
优势
- 功能强大,既可以修改系统应用,也可以修改其他应用。hook android,hook everything.
- 使用灵活,既可以针对一款应用进行Hook,也可以针对所有应用进行Hook。
不足
- 无法调试。只能通过打印日志进行跟踪。(例如:XposedBridge.log)
- 无法即时生效。启用/禁用模块,你需要重启手机。
- multidex 支持不足。详见Multidex support
抖音自动翻页
如果有时间的话,跟大家一块 hook 一下抖音,实现自动翻页的功能。
小结
以上,是本次分享的全部了。温馨提示,搞基需谨慎。