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

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的代理,实现功能拦截服务的启动过程

public class HookProxyBinder implements InvocationHandler {

public static final String HookProxyBinder = “HookProxyBinder”;

Object binder;

public HookProxyBinder(Object binder) {

this.binder = binder;

}

@Override

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

Log.e(“HookProxyBinder==”, method.getName());

if (“startService”.equals(method.getName())) { //拦截启动服务

int i = 0;

Intent intent = null;

for (int index = 0; index < args.length; index++) {

if (args[index] instanceof Intent) {

i = index;

intent = (Intent) args[index];

break;

}

}

String packageName = intent.getComponent().getPackageName();

String className = intent.getComponent().getClassName();

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

intent.setClassName(packageName, ProxyService.class.getCanonicalName());

intent.putExtra(HookProxyBinder, className);

}

args[i] = intent;

}

return method.invoke(binder, args);

}

}

在invoke()方法中拦截startService(),整体实现和上面Activity的启动一样,通过在拦截到方法后替换为注册的占位服务,实现服务的启动

  • Hook系统AMS(以Android 9.0 为例)

//1、反射获取ActivityManager类中的静态实例IActivityManagerSingleton

Class<?> manager = Class.forName(“android.app.ActivityManager”);

Field field = manager.getDeclaredField(“IActivityManagerSingleton”);

field.setAccessible(true);

Object object = field.get(null);

//反射获取Singleton中的mInstance实例,mInstance就是调用create之后创建的对象,此处就是IActivityManager的代理实例

Class<?> singlen = Class.forName(“android.util.Singleton”);

Field field1 = singlen.getDeclaredField(“mInstance”);

field1.setAccessible(true);

Object binder = field1.get(object); //2、获取此IActivityManagerSingleton内部的mInstance

Class<?> iActivityManagerInterface = Class.forName(“android.app.IActivityManager”);

//3、创建代理IActivityManager

Object binder1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerInterface}, new HookProxyBinder(binder));

//4、将重写的代理IActivityManager设置给mInstance

field1.set(object, binder1);

上面的Hook过程也就是利用反射获取系统原来的代理类,然后将创建的代理类设置到系统中,只是这里牵扯到了Singleton类, 系统中的AMS是使用Singleton单例提供的,所以只能反射从Singleton中获取,详情见Android进阶知识树——Android四大组件启动过程

  • 注册和创建代理服务

public class ProxyService extends Service {

public ProxyService() {

}

@Override

public IBinder onBind(Intent intent) {

throw null;

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

String service = intent.getStringExtra(HookProxyBinder.HookProxyBinder);

Log.e(“============++++”, service);

return super.onStartCommand(intent, flags, startId);

}

}

  • 启动服务

Intent intentService = new Intent(MainActivity.this, MyService.class); //此处启动服务并未注册

startService(intentService);

插件启动Service过程:

  1. 通过Hook技术将系统的AMS替换为代理类,拦截启动服务的方法

  2. 在拦截方法中将启动的目标Service替换为占位Service,同时保存目标服务

  3. 在占位服务启动后获取目标服务并调用其相应方法,实现插件服务的启动

本文从基础只是和实战的角度介绍下Hook技术,Hook技术也是插件化中必备的技术,Hook从技术角度上还是比较简单,但需要对目标原理和过程进行理解,确定合理的Hook点,才能实现真正的效果;

大家如果还想了解更多Android 相关的更多知识点可以点进我的GitHub项目中,里面记录了许多的Android 知识点。

总结

【Android 详细知识点思维脑图(技能树)】

image

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-rEuIB1my-1714430410638)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值