【Android】Binder的Oneway拦截

android,binder,oneway · 浏览次数 : 7

小编点评

**Android IIntentReceiver 接口的定义** ```java public interface IIntentReceiver extends android.content.IIntentReceiver { /** * 处理接收的 Intent。 * * @param code The transaction code。 * @param data The Intent 数据。 * @param reply The 回调数据。 * @param flags The 回调标志。 * @return true 如果处理成功,false 否则。 */ boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags); } ``` **OnewayIIntentReceiver 扩展类** ```java 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 here !!! 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; } } ``` **使用方法:** 1. 创建一个 `OnewayIIntentReceiver` 对象,并将其设置到您想要拦截的 `IIntentReceiver` 接口的实现类。 2. 在 `onTransact` 方法中,根据 `code` 值处理不同的请求。 3. 在 `doTransact` 方法中,根据 `code` 值处理具体请求。

正文

在某些虚拟化,免安装,打点,环境检测,拦截器等场景,针对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拦截 ☜ 来解决。

与【Android】Binder的Oneway拦截相似的内容:

【Android】Binder的Oneway拦截

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

Android 自定义带动画的柱状图

功能分析 假设要使用柱状图展示用户一周的数据,通用的做法是对接三方图表SDK或者自己通过代码绘制。 1、三方SDK通常包体较大,且定制性差,对特定的UI需求兼容性差; 2、自己绘制,比较复杂,而且要考虑各种兼容适配; 今天,我们使用一种简单的方式,来制作柱状图,不仅代码简单,而且支持UI样式、动画自

Android 屏幕适配

目录一、目的二、环境三、相关概念3.1 屏幕尺寸(screen size)3.2 屏幕分辨率(Resolution)3.3 像素(pixel)3.4 ppi3.5 dpi3.6 dp/dip3.7 sp四、Q&A4.1 为啥dpi = 160?4.2 为啥Android要引入dp概念?五、代码仓库地

有隙可乘 - Android 序列化漏洞分析实战

本文主要描述了FileProvider,startAnyWhere实现,Parcel不对称漏洞以及这三者结合产生的漏洞利用实战,另外阐述了漏洞利用的影响和修复预防措施,这个漏洞波及了几乎所有的Android手机,希望能带给读者提供一些经验和启发。

Android无障碍自动化结合opencv实现支付宝能量自动收集

Android无障碍服务可以操作元素,手势模拟,实现基本的控制。opencv可以进行图像识别。两者结合在一起即可实现支付宝能量自动收集。opencv用于识别能量,无障碍服务用于模拟手势,即点击能量。 当然这两者结合不单单只能实现这些,还能做很多自动化的程序,如芭芭农场自动施肥、蚂蚁庄园等等的自动化,

Android Media Framework(三)OpenMAX API阅读与分析

这篇文章我们将聚焦Control API的功能与用法,为实现OMX Core、Component打下坚实的基础。 1、OMX_Core.h OMX Core在OpenMAX IL架构中的位置位于IL Client与实际的OMX组件之间,OMX Core提供了两组API给IL Client使用,一组A

Android应用程序启动源码浅析-(三万字长文慎点&Android14)

在Android桌面Launcher源码浅析中介绍了Android的桌面程序Launcher是如何响应用户点击事件并启动App的,这篇文章继续介绍App在Android系统层的启动流程。 一、启动流程 sequenceDiagram participant User participant Laun

Android桌面Launcher源码浅析

在Android启动过程-万字长文(Android14)中介绍了Android系统的启动过程,本篇文章将继续介绍桌面应用Launcher。 一、Launcher介绍 在Android启动过程-万字长文(Android14)中提到Launcher是Android系统启动后,由SystemServerA

Android启动过程-万字长文(Android14)

在计算机启动过程和Linux内核Kernel启动过程介绍了计算机启动和内核加载,本篇文章主要介绍Android系统是如何启动的。 一、Android启动流程 Android系统的启动流程与Linux接近: sequenceDiagram participant Bootloader as 引导加载程

Android Media Framework(一)OpenMAX Spec阅读与框架简介

学习开源代码最快的方式是先阅读它的文档,再查看它的头文件,最后研读代码实现并进行编译调试。Android早期引入OpenMAX IL作为使用音视频编解码器的标准接口,了解Android Media框架的底层运行原理要从OMX IL开始。在这一节,我们将阅读整理OpenMAX IL Spec中的介绍和