[Android] Binder 的 Oneway 拦截 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iofomo
V2EX    Android

[Android] Binder 的 Oneway 拦截

  •  
  •   iofomo 2024-04-30 15:14:14 +08:00 8278 次点击
    这是一个创建于 598 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在某些虚拟化,免安装,打点,环境检测,拦截器等场景,针对Android系统服务接口的拦截是常用的技术方案。通常只是针对正向的接口调用,如果涉及被动的服务回调拦截,则实现起来就有些许麻烦。

    说明

    由于我们容器产品的特性,需要将应用完整的运行起来,所以必须要对各系统服务(超过100+系统服务)通信进行拦截过滤,修正和还原接口通信的数据。让系统和应用可以无感知运行,实现免安装运行的效果。

    整个方案基本上都聚焦在服务模块主动调用的拦截上,系统回调的拦截涉及较少,但随着功能的深入,越来越需要对服务回调的接口(Binder for Oneway)进行拦截。在这里将整个通用的拦截方案和实现过程分享出来,希望对大家有益。

    原理

    BinderOneway回调机制,即应用进程向系统服务注册回调(通常注册和取消注册成对出现),当服务端有相应时间时,可以直接回调给改Binder对象实例。

    如常见的AMS服务接口:

    // source code: /frameworks/base/core/java/android/app/IActivityManager.aidl interface IActivityManager { // ... Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String requiredPermission, int userId, int flags ); void unregisterReceiver(in IIntentReceiver receiver); // ... } 

    我们的目标:

    1. 拦截AMSregisterReceiver方法,将参数receiver通过Proxy创建一个新的扩展类对象传递出去。
    2. 为了参数校验通过,所以对象的类名是合法的(如:android.content.IIntentReceiver
    3. 服务端实际拿到的是我们扩展的接口对象,因此当服务端,通过 Binder 数据还原成服务端的同名对象。
    4. 当服务端有事件回调时,则我们扩展的接口对象优先处理,然后再像原对象调用传递。
    5. 当应用注销回调时,同样需要将我们扩展的对象通知服务端解除。

    1.0 方案:源码导入

    由于通常系统接口类(如:IActivityManager.aidlIPackageManager.aidl等)均为隐藏类,因此很自然的想法是将系统的aidl源文件导入到工程中。

    配置好目录:

     sourceSets { main { aidl.srcDirs = ['src/main/aidl'] } } 

    编译后我们就可以连接该类,并进行继承扩展了,如:

    public class StubIntentReceiver extends IIntentReceiver.Stub { Object mOrigin; protected StubIntentReceiver(Object org) { this.mOrigin = org; } private static Method sMethod_performReceive; public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { // TODO something here ... if (null == sMethod_performReceive) { sMethod_performReceive = ReflectUtils.getDeclaredMethod( mOrigin, "performReceive", Intent.class, int.class, String.class, Bundle.class, boolean.class, boolean.class, int.class ); } sMethod_performReceive.invoke(mOrigin, intent, resultCode, data, extras, ordered, sticky, sendingUser); } } 

    对于IIntentReceiver.aidl的回调接口来说,这样就可以解决了,因为他满足了几个特性:

    1. 足够简单,就只有一个函数。
    2. 足够稳定,从9.0 ~ 14.0接口名和参数都一致。

    然而更多的接口并非如此,接口类函数不仅仅是多个,而且不同版本类方法各异,同函数参数也都不相同,这才是常态,所以我们自然的解决方案就是:flavor

    2.0 方案:Flavor

    既然每个版本可能不一致,那就编译多版本就可以解决了,如:

    这样确实能解决多版本系统接口变化的问题,但同时带来了新的问题:

    1. 多版本的编译,维护,加载运行导致工作量成倍增加,是个灾难。
    2. 通常接口中我们感兴趣的只是其中一部分,其他的接口则是直接放过。
    3. 很多系统接口参数又是继承于Parcelable的对象,而该对象又为隐藏类,因此又需要继续导入关联的类确保编译运行正常,导致越来越臃肿。
    4. 某些接口厂商还会在该类定制新的接口,无法做到默认兼容。

    3.0 方案:接口模板

    我们对于复杂的方案生来恐惧,越复杂越做不稳定,所以我们的目标:

    1. 无需多版本编译,一套代码适配所有版本。
    2. 仅需处理我们关心的接口,对于其他接口默认可放过。

    于是我们通过编译后的源码我们目标锁定在BinderonTransact函数,如:

    public interface IIntentReceiver extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements android.content.IIntentReceiver { private static final java.lang.String DESCRIPTOR = "android.content.IIntentReceiver"; /** Construct the stub at attach it to the interface. */ @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags ) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { case TRANSACTION_performReceive: { data.enforceInterface(DESCRIPTOR); Intent _arg0; if (0 != data.readInt()) { _arg0 = Intent.CREATOR.createFromParcel(data); } else { _arg0 = null; } int _arg1 = data.readInt(); String _arg2 = data.readString(); Bundle _arg3; if (0 != data.readInt()) { _arg3 = (Bundle)Bundle.CREATOR.createFromParcel(data); } else { _arg3 = null; } boolean _arg4 = 0 != data.readInt(); boolean _arg5 = 0 != data.readInt(); int _arg6 = data.readInt(); // call function here !!! this.performReceive(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } } 

    于是我们的方案:

    1. 定义目标接口类(如:IIntentReceiver.aidl),该接口无方法,仅保持名字一致,目的只是为了编译出IIntentReceiver.class类。
    2. 定义扩展类继承于接口代理类。
    3. 重载实现onTransact方法,仅处理感兴趣的codeaidl文件编译后函数对应的编号),其他的默认调用原对象方法。

    于是我们扩展实现类为:

    import android.content.IIntentReceiver; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.text.TextUtils; public class OnewayIIntentReceiver extends IIntentReceiver.Stub { private final Object mArgument; private static int TRANSACTION_performReceive = -1; public OnewayIIntentReceiver(Object org) { mArgument = org; if (TRANSACTION_performReceive < 0) { TRANSACTION_performReceive = ReflectUtils.getMethodCode(org, "TRANSACTION_performReceive"); } } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException { if (TRANSACTION_performReceive == code) { data.enforceInterface(getInterfaceDescriptor()); Intent _arg0; if (0 != data.readInt()) { _arg0 = Intent.CREATOR.createFromParcel(data); } else { _arg0 = null; } int _arg1 = data.readInt(); String _arg2 = data.readString(); Bundle _arg3; if (0 != data.readInt()) { _arg3 = (Bundle)Bundle.CREATOR.createFromParcel(data); } else { _arg3 = null; } boolean _arg4 = 0 != data.readInt(); boolean _arg5 = 0 != data.readInt(); int _arg6 = data.readInt(); // do call origin Method method = ReflectUtils.getDeclaredMethod( mArgument.mOrigin, "performReceive", Intent.class, int.class, String.class, Bundle.class, boolean.class, boolean.class, int.class ); method.invoke(mOrigin, _arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6); reply.writeNoException(); return true; } return doTransact(code, data, reply, flags); } public boolean doTransact(int code, Parcel data, Parcel reply, int flags) { Method method = ReflectUtils.getDeclaredMethod( mOrigin, "onTransact", int.class, Parcel.class, Parcel.class, int.class ); } try { return (Boolean) method.invoke(mOrigin, code, data, reply, flags); } catch (Throwable e) { Logger.e(e); } return false; } } 

    至此,我们找到了相对简单,兼容性好的系统接口回调的拦截方案。

    如果该服务为Native实现,则需要参考我们的另一篇文章 深入 Binder 拦截 来解决。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1301 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 17:08 PVG 01:08 LAX 09:08 JFK 12:08
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86