【Android】Binder的Oneway拦截 | xxx【Android】Binder的Oneway拦截 – xxx
菜单

【Android】Binder的Oneway拦截

五月 10, 2024 - FreeBuf

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

【Android】Binder的Oneway拦截

说明

由于我们容器产品的特性,需要将应用完整的运行起来,所以必须要对各系统服务(超过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源文件导入到工程中。

【Android】Binder的Oneway拦截

配置好目录:

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

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

【Android】Binder的Oneway拦截

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

  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);         }       }     }        } 

于是我们的方案:

【Android】Binder的Oneway拦截

  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拦截 ☜来解决。

# 黑客 # 数据泄露 # 系统安全 # 数据安全 # 企业安全
【Android】Binder的Oneway拦截

【Android】Binder的Oneway拦截 已在FreeBuf发表 0 篇文章
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022

Notice: Undefined variable: canUpdate in /var/www/html/wordpress/wp-content/plugins/wp-autopost-pro/wp-autopost-function.php on line 51