Android插件化——高手必备的Hook技术

本文介绍了如何使用代理模式和动态代理技术,尤其是针对系统或框架源码进行修改,如HookAndroid的Instrumentation和Service。作者详细展示了如何通过HookInstrumentation来修改Activity启动流程,并利用Hook技术实现服务功能的拦截和自定义。
摘要由CSDN通过智能技术生成
  1. 代理模式的使用

val realFunction = RealFunction()

val proxyFunction = ProxyFunction(realFunction)

proxyFunction.realFunction // 执行代理方法

  • 动态代理,关于动态代理的使用和原理见另一篇文章Java动态代理

从上面的代理模式和动态代理可以看出,我们完全可以通过代理的代码去拦截和修改原来程序的逻辑,这里针对的是自己创建的类,那针对系统或框架的源码呢?是不是也可以实现呢?答案是肯定的,但从上面对Hook的定以中可以看出,我们入过要想稳定的Hook程序,那必须找到最合适且一定会执行的地方,也就是寻找Hook点,这也是重点和难点的地方,一般选择不太变换的对象最作为Hook点,如:单例、静态对象等;

2、Hook系统中的Instrumentation

通过上面的学习大概知道Hook的原理和使用,剩下部分已安卓中常见的Hook使用为例深入理解和使用Hook技术,我们现在利用Hook技术修改掉Activity的启动过程,由Android进阶知识树——Android四大组件启动过程知道,Activity在启动的开始和最后创建对象时都是通过Instrumentation类,而且Instrumentation在ActivityThread中的对象即进程中只有一个实例,正好符合Hook点,下面就一起实现Hook Instrumentation,具体步骤如下:

  • 创建Instrumentation的代理类,内部保存系统原来的Instrumentation

public class FixInstrumentation extends Instrumentation {

private Instrumentation instrumentation;

private static final String ACTIVITY_RAW = “raw_activity”;

public FixInstrumentation(Instrumentation instrumentation) {

this.instrumentation = instrumentation;

}

}

  • Hook系统的Instrumentation

Class<?> classes = Class.forName(“android.app.ActivityThread”);

Method activityThread = classes.getDeclaredMethod(“currentActivityThread”);

activityThread.setAccessible(true);

Object currentThread = activityThread.invoke(null);

Field instrumentationField = classes.getDeclaredField(“mInstrumentation”);

instrumentationField.setAccessible(true);

Instrumentation instrumentationInfo = (Instrumentation) instrumentationField.get(currentThread);

FixInstrumentation fixInstrumentation = new FixInstrumentation(instrumentationInfo);

instrumentationField.set(currentThread, fixInstrumentation);

  1. 首先反射获取进程中ActivityThread的对象,然后已此对象反射获取系统中的mInstrumentation对象

  2. 创建FixInstrumentation对象,内部保存上一步获取的mInstrumentation的实例

  3. 反射将FixInstrumentation对象设置到ActivityThread中,此时进程中的mInstrumentation就是FixInstrumentation

  • 重写Instrumentation方法,用于替换代理Activity

现在要在FixInstrumentation中实现启动Main3Activity的功能,但是Main3Activity为加载进来的插件活动,未在程序的注册清单中注册,正常启动的话一定会抛出异常,这里想利用Hook的Instrumentation对象将启动对象替换车成已注册的活动,从而逃避系统的检查,这也是Android插件化技术的方案,下面我们就在execStartActivity()方法中替换

public ActivityResult execStartActivity(

Context who, IBinder contextThread, IBinder token, Activity target,

Intent intent, int requestCode, Bundle options) {

ComponentName componentName = intent.getComponent();

String packageName = componentName.getPackageName();

String classname = componentName.getClassName();

if (classname.equals(“com.alex.kotlin.plugin.Main3Activity”)) { //判断是否为Main3Activity

intent.setClassName(who, ProxyActivity.class.getCanonicalName()); // 替换为注册的ProxyActivity启动

}

intent.putExtra(ACTIVITY_RAW, classname); // 同时保存原来的Main3Activity

try {

@SuppressLint(“PrivateApi”)

Method method = instrumentation.getClass().getDeclaredMethod(“execStartActivity”,

Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);

if (!Modifier.isPublic(method.getModifiers())) {

method.setAccessible(true);

}

return (ActivityResult) method.invoke(instrumentation, who, contextThread, token, target, intent, requestCode, options);

}

  • 创建真正启动的Activity

上面的替换操作躲过了系统的注册检查,程序执行到newActivity()创建活动时,我们要该回真正启动的目标活动,否则一切都白忙活了,下面在newActivity()完成替换,在替换完成后执行原来的逻辑;

public Activity newActivity(ClassLoader cl, String className,

Intent intent) throws InstantiationException,

IllegalAccessException {

String classnameIntent = intent.getStringExtra(ACTIVITY_RAW);

String packageName = intent.getComponent().getPackageName(); // 获取Intent中保存的真正Activity包名、类名

if (className.equals(ProxyActivity.class.getCanonicalName())) {

ComponentName componentName = new ComponentName(packageName, classnameIntent); // 替换真实Activity的包名和类名

intent.setComponent(componentName);

className = classnameIntent;

}

Log.d("FixInstrumentation == ", “set activity is original” + className);

try {

@SuppressLint(“PrivateApi”)

Method method = instrumentation.getClass().getDeclaredMethod(“newActivity”,

ClassLoader.class, String.class, Intent.class);

if (!Modifier.isPublic(method.getModifiers())) {

method.setAccessible(true);

}

return (Activity) method.invoke(instrumentation, cl, className, intent); // 执行原来的创建方法

}

}

Hook Instrumentation实现Activity插件启动总结:

  1. Hook系统的Instrumentation对象,设置创建的代理类

  2. 在代理类中修改启动Activity的Intent,将启动的目标Activity替换为占位Activity,从而避免注册清单的检查

  3. 在代理类中重写newActivity()将启动的活动换回真实目标,然后继续执行原有逻辑

3、Binder Hook(Hook 系统服务)

上面通过Hook技术修改了活动的启动过程,属于应用程序的Hook,下面尝试Hook Android的系统服务,修改系统的功能,在Hook之前还是先了解一下系统服务的获取过程,并尝试寻找Hook点;

3.1、系统获取服务的原理
  • ContextImpl.getSystemService(String name)

@Override

public Object getSystemService(String name) {

return SystemServiceRegistry.getSystemService(this, name);

}

public static Object getSystemService(ContextImpl ctx, String name) {

//1、从注册的SYSTEM_SERVICE_FETCHERS中根据名称获取ServiceFetcher

ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);

return fetcher != null ? fetcher.getService(ctx) : null; //2、ServiceFetcher中创建服务

}

在使用系统服务时会直接调用Context的getSystemService(),最终调用ContextImpl中的方法,在ContextImpl中调用SystemServiceRegistry.getSystemService(),关于SystemServiceRegistry简单介绍一下,系统在启动时会向SystemServiceRegistry中注册一系列服务,在使用过程中直接根据服务名换获取服务;

  • SYSTEM_SERVICE_FETCHERS中注册服务(以JOB_SCHEDULER_SERVICE为例)

//注册服务

registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class, new StaticServiceFetcher() {

@Override

public JobScheduler createService() {

IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE); //从ServiceManager中获取Binder

return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)); //获取Binder的代理对象

}});

private static void registerService(String serviceName, Class serviceClass,ServiceFetcher serviceFetcher) {

SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);

SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); //以键值对的形式保存服务名称、StaticServiceFetcher实例

}

从上面的注册过程知道,系统首先将每个服务的创建过程封装在对应的ServiceFetcher对象中,然后将ServiceFetcher对象以服务名称注册在SYSTEM_SERVICE_FETCHERS中,这也就是为什么获取服务时传入服务名称;

  • ServiceManager.getService():获取系统中相应服务对应的Binder对象

public JobScheduler createService() throws ServiceNotFoundException {

IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);

return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));

}

在服务获取的过程中会调用ServiceFetcher的createService()方法,在create()中首先获取系统中保存的Binder对象,然后根据Binder对象调用asInterface()查找代理类,asInterface()会先检查本进程是否存在Binder对象,如果不存在则创建一个代理对象;

  • 总结一下服务的获取过程:
  1. 在系统开始时,系统会像SYSTEM_SERVICE_FETCHERS注册封装服务的ServiceFetcher实例

  2. 在程序调用获取服务时,根据服务名称从SYSTEM_SERVICE_FETCHERS查找并返回对应的ServiceFetcher实例

  3. 调用实例的get()获取服务时,首先从ServerManager中获取系统中保存服务的Binder

  4. 调用IxxInterface的asInterface()方法查找并返回Binder的代理类

3.2、寻找Hook点
  1. 通过上面的分析知道,可以操作的地方就是obj.queryLocalInterface(),如果我们Hook了传入的Binder对象,修改他的queryLocalInterface就可以返回替代的对象的代理对象,就可实现代理;

  2. 要想实现目标1就必须确保ServerManager的查找中能返回我们指定的Binder,好在ServerManager中从系统Map缓存中获取,我们只要将代理的Binder放在缓存的Map,然后在查找时即可返回指定的Binder;

3.3、实战——以剪切版服务为例
  • 创建服务的动态代理类

public class FixBinder implements InvocationHandler {

private static final String TAG = “BinderHookHandler”;

// 原来的Service对象 (IInterface)

Object base;

public FixBinder(IBinder base, Class<?> stubClass) {

try {

Method asInterfaceMethod = stubClass.getDeclaredMethod(“asInterface”, IBinder.class);//获取原接口的asInterface

this.base = asInterfaceMethod.invoke(null, base); //使用原来的Binder反射执行获取本来服务的代理类

} catch (Exception e) {

throw new RuntimeException(“hooked failed!”);

}

}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 欺骗系统,使之认为剪切版上一直有内容

if (“hasPrimaryClip”.equals(method.getName())) {

return true;

}

return method.invoke(base, args); //其余方法使用原Binder代理反射执行

}

}

  1. 这里和前面直接保存系统对象不同,因为在查找服务时首先获得的是系统的Binder,只有自己利用Binder其查找才会返回代理类

  2. 在构造函数中传入系统中查找的Binder对象,然后反射调用asasInterface()获取并保存系统服务本身的服务的代理类

  3. 拦截剪切方法,拦截hasPrimaryClip()方法返回true,使系统一直认为剪切板上有内容

  • 创建Binder对象

public class ProxyBinder implements InvocationHandler {

IBinder base;

Class<?> stub;

Class<?> iinterface;

public ProxyBinder(IBinder base) {

this.base = base; //(1)

try {

this.stub = Class.forName("android.content.IClipboard$Stub”); //(2)

this.iinterface = Class.forName(“android.content.IClipboard”);

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (“queryLocalInterface”.equals(method.getName())) { //(3)

return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),//(4)

// asInterface 的时候会检测是否是特定类型的接口然后进行强制转换

// 因此这里的动态代理生成的类型信息的类型必须是正确的,即必须是以下3个接口实例

new Class[] { IBinder.class, IInterface.class, this.iinterface },

new FixBinder(base, stub));

}

return method.invoke(base, args);

}

}

第一创建了系统服务的代理类,由前面分析知道了,代理类的使用是由Binder查询出来的,所以下一步要创建一个Binder类,并且内部拦截查询的queryLocalInterface()方法,让此方法返回第一步的代理类,创建步骤:

  1. 和普通代理一样,在代理内部保存ServerManager中原来真正的Binder

  2. 利用反射获取IClipboard$Stub类,用于查找代理类

  3. Hook了queryLocalInterface方法

  4. 在invoke()拦截到方法后,使用动态代理创建并返回IClipboard的代理Binder

  • Hook 替换ServerManager中的Binder

final String CLIPBOARD_SERVICE = “clipboard”;

// 下面这一段的意思实际就是: ServiceManager.getService(“clipboard”);

Class<?> serviceManager = Class.forName(“android.os.ServiceManager”);

Method getService = serviceManager.getDeclaredMethod(“getService”, String.class);

// (1)ServiceManager里面管理的原始的Clipboard Binder对象

IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);

//(2) Hook 掉这个Binder代理对象的 queryLocalInterface 方法

IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),

new Class<?>[] { IBinder.class },

new BinderProxyHookHandler(rawBinder));

// (3)把这个hook过的Binder代理对象放进ServiceManager的cache里面

Field cacheField = serviceManager.getDeclaredField(“sCache”);

cacheField.setAccessible(true);

Map<String, IBinder> cache = (Map) cacheField.get(null);

cache.put(CLIPBOARD_SERVICE, hookedBinder);

通过前面两部已经将所有要创建的代理Binder实现了,剩下的就是要将ProxyBinder放入系统ServiceManager的缓存中,这样在查询时才会按我们的要求返回Binder,后面的套路才能执行下去,具体的Hook过程见代码注释;

4、Hook 系统服务AMS(Android 9.0)

上面两个例子已经将Hook的使用介绍清楚了,接下来再利用Hook技术拦截系统的AMS,改变系统的服务启动,也是插件化启动服务的原理,这里实现启动未注册的MyService,关于Service的启动过程点击上面的四大组件的链接查看,因为AMS也是通过Binder通信的所以Hook的第一步要实现Binder的动态代理

  • 创建AMS的代理,实现功能拦截服务的启动过程

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我的面试经验分享可能不会去罗列太多的具体题目,因为我依然认为面试经验中最宝贵的不是那一个个具体的题目或者具体的答案,而是结束面试时,那一刻你的感受以及多天之后你的回味~

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

的形式给大家展示一部分。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-7n08bsOW-1713264791138)]

【算法合集】

[外链图片转存中…(img-jQKzT2ma-1713264791139)]

【延伸Android必备知识点】

[外链图片转存中…(img-wEzuMqyO-1713264791140)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值