freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

小白的 Android Service 安全测试指南
2023-03-09 16:40:39
所属地 广东省

“都 2202 年了,你还在关注 Android Service 安全?” 是的,没错。但是,我们这里所说的 Service 不仅仅是 Android 四大组件之一的 Service,还包含 Android 系统 Service。Android 系统火热之初,研究应用层 Service 安全的人络绎不绝,但时至今日,Android 系统 Service 的安全性仍然很少有成体系的研究。

我们会全盘分析 Android Service 的类型及使用方式,包括应用层 Service、框架层 Service 以及 Native 层 Service,从源码的角度审视 Android Service 的架构。当然,也会穿插讲解 Android 核心组件之一的 Binder。由于不是 Android 的应用开发者和系统开发者,Binder 底层原理不过多阐述( 在 Android系 统中,IPC 机制被称为Binder。Android Binder 服务的安全性一直是许多漏洞研究者的一个研究方向),我们重点关注 Service 原理、框架以及存在的安全问题。

0x00 小白的基础知识

0x01 Binder

Binder 是 Android 开发的基于 OpenBinder 的一种新的进程间通信机制,提供远程过程调用(RPC)功能。

  • 直观上来说,Binder 是 Android SDK 中的一个类,继承 IBinder 接口;

  • 从 IPC 角度来说,Binder 是 Android 中核心的进程间通信方式,此 IPC 在 Linux 中是没有的,弥补了传统 Linux IPC 一些不足之处;

  • 从设备上来说,Binder 可以理解为一种虚拟的物理设备,设备驱动是 /dev/binder;

  • 从 Android 应用层来说,Binder 是客户端和服务端通信媒介,比如,客户端绑定 Service 的时候,会返回一个包含服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或数据(Android Service 就是这么一种典型架构)。

Binder 架构图如下所示,如果你不理解,没有关系,这张图只是给大家一个感性的认识,说明 Binder 其实自顶向下贯穿 AOSP 整个架构。你会在后文中慢慢理解这张架构图的含义。

image

0x02 Service

Android Service 既可以指 Android 开发者常说的 Android 四大组件之一的服务,也可以指 Android 框架本身提供的诸多服务。Android Service 类型如下所示

  • 应用层 Service

  • 系统 Service

    • Android Service(通常指 Java 编写的 Service)

    • Nativce Service(C++ 编写的 Service)

Android Service 是 Binder 一种典型的应用,其实 Android 绝大部分通信都离不开 Binder。宏观上,关系图如下

image

Service 通过 Binder 通道提供的 intent,实现不同组件之间的通信,通过 intent,我们可以向目标 Service 传递参数,进而实现某种功能。当然,Service 也可以提供一些接口方法,这些方法可以应用本身调用,也可以提供给其他应用调用。

0x10 应用层 Service

这里普通 Service 是指应用层 Service,应用开发者自己定义的 Service。应用层服务作为 Android 四大组件之一,主要是 Android 实现程序后台的解决方案。Activity 提供了用户界面,而 Service 刚好适合一些不需要与用户交互的操作。

这里只是简单讲一下应用层 Service 的用法。服务端定义如下

public class MyService extends Service {
    public MyService() {
    }

    @Override
    /* 绑定服务,是一个抽象方法,必须重写 */
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /* 创建service,如果service已运行,则不会执行 */
    public void onCreate() {
        super.onCreate();
    }

    /* 处理业务,接收intent */
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Log.i(TAG, intent.getStringExtra("MainActivity"));
        return super.onStartCommand(intent, flags, startId);
    }

    /* 销毁 */
    public void onDestroy() {
        super.onDestroy();
    }
}

Android Studio 会自动在 Manifest 文件注册该服务。Activity 通过 start/stopService启动或者关闭服务。也可以通过 bindService()启动服务。

final Intent intent = new Intent(this, MyService.class);
Button button = findViewById(R.id.fisrt_button);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        intent.putExtra(TAG, "This is MainActivity");
        startService(intent);	// 启动service
        stopService(intent);	// 停止service
    }
});

下图是 Android Developer官网用来说明 Service 生命周期的示例图。左图显示了创建服务时的startService()生命周期,右图显示了创建服务时的生命周期bindService()

image

两种方式的 Service 生命周期

  • startService: onCreate -> onStartCommand -> onDestory

  • bindService: onCreate -> onBind -> onDestory

0x11 组件与应用层 Service 通信

通过将组件与 Service 进行绑定,可以很方便的实现进程内或进程间通信。使用 Service 完成进程通信的方式有多种,每种方式有其特定的适用范围。

1 扩展 Binder 类

直接在继承的 Service 类中添加 binder 内部类,并定义一些公共方法。前台组件绑定该 Service,就可以调用 binder 子类的公共方法。如果服务只是进程内调用,优先使用该方式。

Service 类中,创建 binder 子类,并使用 onbinder 方法返回 binder 实例

private static final String TAG = "MyService";
private LocalBinder mBinder = new LocalBinder();

public class LocalBinder extends Binder {
    public void start() {
        Log.d(TAG, "start:");
    }
    public void end() {
        Log.d(TAG, "end:");
    }
}

@Override
/* 绑定服务,是一个抽象方法,必须重写 */
public IBinder onBind(Intent intent) {
    return mBinder;
}

需要关联的组件,创建 ServiceConnection匿名内部类

private MyService.LocalBinder loadBinder;   // 获取服务中定义的 LocalBinder

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 绑定成功,可以调用任何public方法
        loadBinder = (MyService.LocalBinder) service;
        loadBinder.start();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
};

绑定或解绑Service

bindService(intent, connection, BIND_AUTO_CREATE);
unbindService(connection);

2 使用 Messenger

Messenger 是实现 IPC(进程间通信)最简单的方法,Messenger 通过单一线程创建包含所有请求的消息队列,开发者不必考虑线程安全。

image

服务端创建 Messenger匿名内部类,并使用 onbinder 返回实例

public class MyService extends Service {
    private static final String TAG = "MyService";
    Messenger serverMessenger = new Messenger(new Handler() {
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            /* 接收消息 */
            Bundle bundle = msg.getData();
            String recv = bundle.getString("message_from_client");
            Log.d(TAG, recv);

            /* 发送消息 */
            Messenger clientMessenger = msg.replyTo;   // 客户端信使
            Message message = new Message();
            Bundle bundleSend = new Bundle();
            bundleSend.putString("message_from_server", "Hi, this is MyService");
            message.setData(bundleSend);
            try {
                clientMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    });

    @Override
    public IBinder onBind(Intent intent) {
        return serverMessenger.getBinder();
    }
}

需要关联的组件(即客户端),创建 ServiceConnection匿名内部类

private Messenger serverMessenger;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            serverMessenger = new Messenger(service); // 通过服务获取服务端信使
            Message message = new Message();
            message.replyTo = serverMessenger;
            Bundle bundle = new Bundle();
            bundle.putString("message_from_client", "Hello, this is client_MainActivity");
            message.setData(bundle);
            try {
                serverMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

绑定或解绑 Service 与之前提到的扩展 Binder 类如出一辙。

3 使用 AIDL

AIDL(Android Interface Definition Language,Android 接口定义语言),可以通过 AIDL 绑定另一个进程的服务,实现跨进程通信。 AIDL 进程间通信相比 Messenger 而言,能够让一个 Service 同时处理多个请求

Android 接口定义语言 (AIDL) 是一款可供用户用来抽象化 IPC 的工具。以在 .aidl文件中指定的接口为例,各种构建系统都会使用 aidl二进制文件构造 C++ 或 Java 绑定,以便跨进程使用该接口(无论其运行时环境或位数如何)。

Service 应用中,新建 AIDL 文件。AIDL 文件其实是一个接口文件,类似于 C 语言的头文件,其他应用可以调用 AIDL 声明的方法。

interface IMyAidlInterface {
    /* 自动生成的方法,说明当前AIDL支持的数据类型,可以不用关注 */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
	
    /* 想提供给其他应用跨进程调用的方法 */
    String getTime();
}

同步一下 AIDL信息(sync project-> rebuild),在 Service 类中创建一个内部类,继承接口的 Stub 类,并实现 AIDL 接口声明的相关方法。

public IBinder onBind(Intent intent) {
    return myBinder;
}

MyBinder myBinder = new MyBinder();

class MyBinder extends IMyAidlInterface.Stub {
    public String getTime() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return df.format(new Date());
    }

    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                    double aDouble, String aString){};
}

关于 Android Stub 类

Stub,即存根类,它实现了一个接口,但是实现后的每个方法都是空的。如果一个接口有很多方法,如果要实现这个接口,就要实现所有的方法。但是一个类从业务来说,可能只需要其中一两个方法。如果直接去实现这个接口,除了实现所需的方法,还要实现其他所有的无关方法。而如果通过继承存根类就实现接口,就免去了这种麻烦。

客户端应用复制 AIDL 文件,是需要将服务端整个 AIDL 文件都复制下来,包括包名。同样的,复制完 AIDL 文件后需要同步,否则其他类没有办法引用 AIDL 文件中声明的方法。

image

客户端应用绑定 Service,并且在 onServiceConnected方法中调用 AIDL 的接口方法,这与之前所说的扩展 Binder 类仍然是一样的。

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        try {
            String result = iMyAidlInterface.getTime();
            Log.d(TAG, result);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

/* 绑定service */
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.example.service", "com.example.service.MyService");
intent.setComponent(componentName);
bindService(intent, connection, BIND_AUTO_CREATE);

如果不使用 setComponent 显式绑定 Service 组件,也可以使用 setAction 绑定 Service,只是这时,需要在服务端 Android Manifest.xml 文件,将 Service 组件声明为远程,并且添加 action,action 必须命名为 服务端包名.aidl

<service
    android:name=".MyService"
    android:process=":remote"
    android:exported="true">
    <intent-filter>
    	<action android:name="com.example.service.IMyAidlInterface" />
    </intent-filter>
</service>

简单总结一下,无论 Service 是扩展了 Binder 类,使用 Messenger 还是 AIDL,客户端都是用相同的方法绑定 Service,并在 ServiceConnection 实例中,重写 onServiceConnected 方法,完成相应的 IPC。

0x20 系统 Service

System Service,即 Android 系统服务,不同于应用层的 Service(Android 四大组件之一,应用开发者自己实现),系统 Service 处于 Android 系统的 Framework 层,**我们通常所说的 Android Service 是指用 Java 编写的 Service。而 Nativce Serice 则是指用 C++ 编写的 Service。**系统 Service 由 AOSP 预先定义好,厂商也可以实现自己的系统 Service,应用开发者只需要直接使用 Service。提起系统 Service,就不得不说 Service Manager。

image

所有系统 Service 都是由 Service Manager 管理的,并会在 Service Manager 注册。当我们自己的 APP 想使用系统 Service,可以通过 Service Manger 获取服务并使用。 系统 Service 与 Service Manager 处于 Framework 层。

可以使用 service list列出系统所有 Service(格式:服务名[接口名称/描述]),一加 3T 手机自带的系统服务如下

image

0x21 Android Service

服务名由 AOSP 源码的 ServiceManager 的 addService 第一个参数决定,例如 iphonesubinfo服务,注册路径:/frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneSubInfoController.java,通过 addService 注册服务,通过 getService 获取服务。

image

接口描述,即接口名,多数对应的是 AIDL 文件名

image

这么样找到这些方法的定义

按照应用层实现 AIDL 的套路,AOSP 源码搜索 IPhoneSubInfo.Stub,得到 Service 实现的代码路径 /frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneSubInfoController.java

image

客户端调用 iphonesubinfo 提供的方法,以 AOSP 提供的单元测试 Robolectric 为例(android-all-7.1.0_r7-robolectric-0.jar),客户端调用 iphonesubinfo 服务的 getDeviceId 方法,实际上就是实现了 AIDL。robolectric的类 com.android.internal.telephony.IPhoneSubInfo实现。

  • 客户端创建 Proxy 类,统一实现服务端类(com.android.internal.telephony.IPhoneSubInfo)提供的方法,但是真正的方法还是在服务端定义。当然,客户端的 IPhoneSubInfo 也需要复制服务端的 asInterface

image

  • asInterface 方法将 IBinder 对象转化为 IInterface 接口

image

  • 转化的接口对象直接调用相关方法

image

原理图如下,结合前后文的分析,可以有更加深入的理解。

image

0x22 Native Service

对于 Native Service,描述是由 IMPLEMENT_META_INTERFACE定义,例如 media.drm服务(数字版权管理)

image

该服务是一个典型 Native Service(Server) 代码如下 /frameworks/av/drm/libmediadrm/IMediaDrmService.cppCHECK_INTERFACE 用于判断客户端传入的接口是否和服务端相同。 如果不匹配会抛出错误 "Binder invocation to an incorrect interface"

image

客户端调用media.drm 服务,有多处调用,以 nuplayer 为例 /frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDrm.cpp

image

重点方法

  • Parcel.readStrongBinder():从 parcel 对象读取 IBinder 类型

  • Parcel.writeStrongBinder():Parcel 写入 IBinder

  • interface_cast:将 IBinder 对象转化为强引用类型 IInterface

结合代码再来看原理图就比较简单了。

  • Client:BpBinder.transact() 发送数据

  • Server:BBinder.onTransact() 接收数据

image

BpBinder 和 BBinder 都是 Android 通信与 Binder 相关的代表,是 IBinder 的派生类。

  • BpBinder,客户端用来与 Server 交互的代理类

  • BBinder,BpBinder 对应的服务端 IBinder。

这里需要注意的一点是:onTransact() 方法作为真正业务逻辑处理的函数,其实现并非在 BBinder 中,而是位于继承自 BBinder 的 BnBinder。相信细心的同学已经从上文案例 BnMediaDrmService 的实现看到了。

0x23 ADB 与系统 Service 通信

我们在上文中已经讲解框架和原生层的 Service,相应的客户端调用方法也大致梳理了一下,但是讲述的都是框架层的客户端调用框架层的服务,原生的客户端调用原生的服务。那么对于普通开发者,或者安全测试人员,如何直接调用系统提供的 Service 呢?

adb 提供了 service call,可以直接调用系统 Service 提供的方法,我们仍然以 iphonesubinfo 服务为例。1 代表 AIDL 文件的第一个方法,即是我们之前提过的 getDeviceId

image

当然也可以给相应的函数传参

image

可以通过正则匹配,筛选出结果

# IMEI
adb shell "service call iphonesubinfo 1 | grep -o '[0-9a-f]\{8\} ' | tail -n+3 | while read a; do echo -n \\u${a:4:4}\\u${a:0:4}; done"
# IMSI
adb shell "service call iphonesubinfo 7 | grep -o '[0-9a-f]\{8\} ' | tail -n+3 | while read a; do echo -n \\u${a:4:4}\\u${a:0:4}; done"
# ICCID
adb shell "service call iphonesubinfo 11 | grep -o '[0-9a-f]\{8\} ' | tail -n+3 | while read a; do echo -n \\u${a:4:4}\\u${a:0:4}; done"

0x24 APP 与系统 Service 通信

对于 Framework Service,提供了 AIDL 文件,这类服务可以直接和应用层 Service 一样,客户端直接使用 AIDL 就可以与其通信;Native Service,只有部分提供了 AIDL(如 HAL 层的 HIDL),需要直接获取 Binder 对象后,调用 Binder 对象的 transact 函数。

  • 使用 AIDL,不够稳定,且需要导入 AIDL 文件,对于黑盒测试不友好。用法与之前类似。

  • 反射获取 ServiceManger,稳定,但是有可能失败。

如果你有 Android 的编译环境,可以通过 android.os.ServiceManager轻松使用 ServiceManager 找到对应的 Service,并调用相关方法。

// NativeService.java
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

public class NativeService {
    private static final String SERVICE_NAME = "nativeservice";

    public static boolean invoke(int code, Parcel data, Parcel reply) throws RemoteException {
        IBinder service = ServiceManager.getService(SERVICE_NAME);
        if(service != null){
            Parcel data = Parcel.obtain();
            data.writeInterfaceToken(TOKEN);
            data.appendFrom(data,0,data.dataSize());
            boolean result = service.transact(code, data, reply, 0);
            data.recycle();
            return true;
        }
        Log.e("shell", "service not running");
        return false;
    };
}

但是很遗憾,更多时候我们只是一个普通的应用开发者, android.os.ServiceManager是一个隐藏的类,不在 android.jar 中,因此无法直接调用该方法。不过幸运的是,**任何隐藏的类都可以使用反射 API 调用。**如下所示

// ((TelephonyManager) getSystemService(TELEPHONY_SERVICE)).getDeviceId();
try {
    Parcel data = Parcel.obtain();
    Parcel replay = Parcel.obtain();
    /* 通过反射获取Service的ibinder对象*/
    IBinder iBinder = (IBinder) Class.forName("android.os.ServiceManager").getMethod("getService", String.class).invoke(null, "iphonesubinfo");
    /* 通过ibinder对象获取接口描述 */  /* 如果没有接口,即 Nativce Serivce,是不需要写入的 */
    iBinder.transact(IBinder.INTERFACE_TRANSACTION, data, replay, 0);
    /* 写入接口描述 */
    data.writeInterfaceToken(replay.toString());
    /* 传参(如果有的话)*/
    data.writeInt(0);
    Log.d(TAG, replay.readString());
    /* 发送数据 */
    iBinder.transact(1, data, replay, 0);
    /* 接收数据 */ 
    Log.d(TAG, replay.readString()); // 实际为空,原因未知
    /* 回收parcel对象 */
    data.recycle();
    replay.recycle();
} catch (Exception e) {
    e.printStackTrace();
}

注意,系统 Service 可能需要权限,那么也需要在 AndroidManifest 文件中声明该权限。Android Marshmallow(6.0) 之后,危险权限需要动态申请

image

transact方法参数说明

  • code,也叫 TransactionID,标定服务端方法号。

  • data,客户端需要传递的参数。

  • replay,服务端返回的参数。

  • flags,0 代表有返回值,1,代表无返回值。

0x25 APP 与其他应用 Service 通信

应用与其他应用 Service 通信除了我们在文章最开始部分提到的几种常规方式之外,还可以直接通过 binderService 获取 iBinder 对象,然后通过 AIDL 的方式调用。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.my_button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setComponent("com.xxx.text", "com.xxx.test.service");
                bindService(intent, connection, BIND_AUTO_CREATE);
            }
        });
    }

    IBinder binder;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            binder = service;
            // 通过bindService获取iBinder对象
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            _data.writeInterfaceToken("com.xxx.test.service.aidl.xxxExtendService");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            binder = null;
        }
    };

0x23-0x25 所讲的几种方式都是安全测试人员经常用的 Service 通信方法,0x11 节更多的是从开发人员角度讲解如何与 Service 通信,一旦掌握基本的通信方式之后,后面的测试自然水到渠成。

0x26 Service 安全

对于 Service,这些组件间的接口都是我们关注的重点。对于安全测试来说,了解上文的知识对于 Android Service 的漏洞挖掘入门已经足够了,可以直接阅读章节 “Service 攻击面与安全问题”。但是如果你想从源码角度继续深究,那么后续章节 “DRM 源码案例分析” 则是一个不错的阅读选择。

0x30 硬件抽象层 HAL

Android HAL(Hardware Abstraction Layer,硬件抽象层)位于 Framework 和内核之间。Treble 项目是 Android 操作系统框架在架构方面的一项重大改变,于 Android 8.0 正式引入。旨在让制造商以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。

通过引入 HIDL(HAL 描述语言),将 Framework 和 HAL 分开,设备供应商只需要构建一次 HAL,放置 vendor 分区,即可实现系统与 HAL 的分离。AOSP 的升级不会影响厂商自定义的 HAL。设备制造商或者芯片供应商不再需要适配新版本的 Android。

image

由于历史原因,很多厂商不愿意一次性迁移到 Treble 架构,因此在 Android 8.0 以后才有了多种 HAL 并存的局面。

0x31 HAL

Android 内核基于Linux,对于传统的类 Unix 操作系统,对硬件的操作都是由内核来完成的。Linux 内核使用 GPL 协议,内核代码是需要公开的。而 Android 把对硬件的操作分为 HAL 和内核驱动,硬件厂商可以把自己的核心算法或者逻辑放在 HAL 层,这样就可以保护自己的利益。

HAL 模块代码位于 /hardware/libhardware/modules/,编译后位于 Android 文件系统 /vendor/lib/hw/。例如

# ls /vendor/lib/hw/ | grep drm
android.hardware.drm@1.0-impl.so

0x32 HAL 类型

AOSP 提供了工具 lshal,可以直接查看系统 HAL。HAL 服务既可以使用 Java 来实现,也可以使用 C++ 来实现。

Binder 化 HAL(HIDL),即 Treble 架构引入,Android 8.0 以后的版本必须使用此方式。 HAL 与 用户调用在不同的进程中,HAL 被写成 binder service,而用户接口如 frameworks 作为 binder client 通过 IPC 机制实现跨进程接口调用。这个是 Google 的最终设计目标。

image

兼容模式,该模式是为了兼容旧版的 HAL,旧版 HAL 实现仍以动态库的方式提供,只是 binder service 链接了动态库 HAL 实现,即 binder service 通过 hw_get_module 链接了旧版的 hal 实现,而用户端通过与 binder service IPC 通信,间接实现了与旧版 HAL 的交互。

  • binder 化模式

image

  • 未 binder 化

image

传统模式,Android 8.0 之前版本的 HAL 都是编译成 so,然后动态链接到各个 framework service 中去。

0x33 HIDL 与 Service

以前 Framework 与 HAL 是一起被编译进 system.img,HIDL 的出现,让 Framework 不再直接调用 HAL,而是通过 HIDL 间接调用 HAL 模块。HAL 被编译成一个单独的分区 vendor.img。每个 HAL 模块可以对应一个 HIDL 服务,Android 框架层通过 HwBinder 创建 HIDL 服务,通过 HIDL 服务获取 HAL 模块。也就是说,Service 是实现 HAL 的一种手段,可以将 HAL 模块暴露给应用层调用。当然也可以不通过 Serivce,而是直接使用 C 客户端调用 HAL

0x40 DRM 源码案例分析

Android 数字版权管理框架(DRM,Digital Rights Management)是一个典型的 Service 与 HAL 实现方案。DRM 框架可扩展,设备制造商可以根据具体的设备实现自己的许可限制管理。

image

上图是 Android 11 之前版本的 DRM 框架,对于新手来说,不结合源码还是很难理解的。DRM SERVER 与 MEDIA DRM SERVER 其实就是 Android 8.0 之前与之后的实现。在 Android 文件系统层面,表现为两个二进制程序,系统启动时,会加载这两个二进制程序。

image

0x41 传统实现

DRM 服务列表如下,drm.drmManager是服务名,drm.IDrmManagerService是服务的接口描述,它们是如何得到的呢?下面我们从代码层面去分析服务是如何注册的。

image

DRM SERVER 是一个二进制,对应的源码位置 /frameworks/av/drm/drmserver/,从 main 函数入口,层层向下分析,用于注册服务。

image

DrmManagerService类使用 Binder 提供的 addService 将服务注册到 Service Manager

image

DrmManagerService类继承自 BnDrmManagerService,这个类在 IDrmManagerService中实现。IDrmManagerService 实现了 Service 接口描述、可供远程调用的方法定义(实现 onTransact 方法)、客户端代理。

image

loadPlugIns 函数在 libdrmframework.so 实现,用于加载 so

image

该目录下就是厂商自己实现的 HAL 层动态库

image

0x42 Treble 实现

Treble 项目引入的 HIDL 带来了 Android HAL 架构的革新。与“传统实现” 类似, MEDIA DRM SERVER 同样用于注册服务,只不过该 Service 的二进制代码位于 /frameworks/av/services/mediadrm/ ,Binder IPC 代理的代码还是位于老位置 /frameworks/av/drm/libmediadrm/ 。

image

上文已经分析 Service 的注册,在此只做简要陈述。mediadrmserver 二进制注册的服务是 media.drmIMediaDrmService.cpp定义接口描述 android.media.IMediaDrmService,并实现了 onTransact 方法(Native Service 章节已有简单分析),不再赘述。

# service call media.drm 1
Result: Parcel(
  0x00000000: 73682a85 00000113 00000002 00000000 '.*hs............'
  0x00000010: 00000000 00000000                   '........
# 返回一个Crypto/CryptoHal对象

客户端最终可以通过 Binder 调用远程服务端 makeCryto/Hal,返回一个 CryptoHal 对象,此方法在 /frameworks/av/drm/libmediadrm/CryptoHal.cpp实现

image

原先:Crypto的每个方法都可以调用,通过 Binder,代理注册在 ICrypto.cpp接口。

现在:CryptoHal的每个方法都可以被调用。此接口的作用是允许非特权应用解密 DRM 数据

  • makeCryptoFactories -> ICryptoFactory -> ICryptoFactory.hal(将部分方法以 HIDL 接口的形式公开,隐硬件厂商可以自己实现 HIDL 声明的方法)

  • makeCryptoPlugin -> ICryptoPlugin -> ICryptoPlugin.hal

0x50 Service 攻击面与安全问题

Service 和其他组件一样,存在很多安全问题。

0x51 权限绕过

Service 也是有权限管控的,并不是所有应用都有权限调用任何 Service。应用层 Service 使用的就是普通 App 的权限管理,直接扫描 AndroidManifest.xml 即可。但是对于系统 Service,暂时没有看到一个通用的方法直接找到调用一个系统 Service 所需要的权限

AOSP 原生的系统 Service,使用 android::checkPermission 这样的函数判断调用者进程的权限,一般来说是没有问题的。例如,media.extractor 服务, 说明允许程序从系统服务获取系统 dump 信息 ,是需要 android.permission.DUMP 权限的。

image

但是一些手机厂商或者 Android 模拟器,往往会有自己的业务,由于系统 Service 没有统一的权限管控方案,他们在实现自己的服务时,可能没有做好权限管控,导致存在权限绕过的安全问题。例如

image

上述服务本身没有权限管控,函数 sendDataToPc 是调用者可以直接控制的。服务本身设计为只有 System 权限的应用才可以使用,但是 appUid 不是从系统中获得,而是 service 入参控制。

0x52 越界读

再来看一个实际的案例:CVE-2018-9411,一个影响多个高权限 Android 服务的漏洞。这个漏洞与 HIDL 设计有关。 在 HIDL 情况下,共享内存的一个重要对象是 hidl_memory。hidl_memory是一种可用于在进程之间传输共享内存片段的结构。

image

通过 HIDL 中的 Binder 传输结构体,复杂数据结构会有自己的定义(如 hidl_handle, hidl_string),而简单数据结构如整型(uint64_t),则原样传输。但是一些旧的动态库仍然使用 32 位数据。例如映射 hidle_memory 对象的 ashmem

image

mem.size() 是 64 bit,而 mmap 签名中长度字段的类型是 size_t,这意味着它的位数与进程的位数相匹配。在64位进程中没有问题,一切都是64位。而在32位进程中,它的大小则被截断为32位,因此仅会使用低32位。 所以,当 HAL 模块被编译为 32 位时,申请超过 UINT32_MAX(0xFFFFFFFF)的 hidl_memory,就会被截断。

细节不再过多描述,最终符合的漏洞服务是 MediaCasService,而这个普通服务却有读写 TEE 的权限,越界读导致了提权。

0x53 拒绝服务

应用层 Service 拒绝服务是一个老生常谈的话题,在移动互联网之处时代,大行其道,但是如今很少有人关注了。其实系统 Service 拒绝服务也是常见的一个问题,Service 并没有对远程调用者传入的参数进行判断,直接引用客户端对象,也没有捕获异常,导致服务崩溃,例如,以下是一个 Service 实现的案例,此方法中并没有对入参进行判断,就直接打印结果

image

系统会根据 AIDL 文件生成 onTransact 方法,这里的方法捕获了异常

image

但是实际的系统开发者,往往需要自己实现 onTransact 方法,很有可能没有捕获异常,当客户端调用 getAge,输入 ”xiaoming“,则导致空指针解引用

image

很多 Service 的漏洞,如 CVE-2015-1526,就会导致拒绝服务。

0x54 序列化

Java 提供了 Serializable,Android 提供了 Parcelable ,用于序列化对象。序列化导致的拒绝服务也是早期应用层 Service 常见的一个问题,也是像上面的问题一样,捕获异常即可。

Serializable

Intent i = getIntent();
if(i.getAction().equals("serializable_action"))
{
	i.getSerializableExtra("serializable_key");//未做异常判断
}

Parcelable

this.b =(RouterConfig);
this.getIntent().getParcelableExtra("filed_router_config"); //引发转型异常崩溃

和应用层常见的一些漏洞相似,序列化和反序列化还有其他类型的漏洞,如 CVE-2015-3525 、CVE-2014-7911,这些漏洞同样可以用于提权。由于漏洞年代比较久远,有兴趣的读者可以自行查阅。

0x55 缓冲区溢出

历史上,有关 Service 漏洞也有一些,较为典型的一个是 Android Drm service 堆溢出漏洞( CVE-2017-13253 )。这个漏洞较为复杂,需要读者深谙 Android Service 架构,如果你想深入理解 Natvie Service,这是一个很好的学习入口。由于我们在“DRM 源码案例分析” 章节已经分析过 DRM,回过头来再看这个漏洞的成因难度就没有那么大了。分析这个漏洞,最关键的是要理清楚两点

  • Service 与 HIDL 数据流

  • 复杂数据结构

漏洞点,函数 CryptoPlugin:: decrypt memcpy 存在溢出 /frameworks/av/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp,这里重点分析数据流,理清 Service 与 HAL 之间的调用关系,具体的数据结构,以及溢出原因查看参考文献即可。

image

上层调用 /hardware/interfaces/drm/1.0/default/CryptoPlugin.cpp(HAL 编译工具自动生成)

image

/hardware/interfaces/drm/1.0/ICryptoPlugin.hal由 Framework 层的 /frameworks/av/drm/libmediadrm/ICrypto.cpp调用

image

而 ICrypto 正是我们之前分析的 media.drm 服务 Binder IPC Proxy 的实现,外部可控。

1.Service 的名称和接口是不是一一对应的?

大多数情况下,一个 Service 可能没有,或者最多有一个接口,但是也有两个 Service 共享同一个接口的情况,例如 android.hardware.ICamera

image

2.每个 Service 都有接口名吗?

有些 Native 服务是没有接口描述的,例如 media.extractor

image

其实在相应的代码实现中也能看出来。

image

0x60 Fuzzing

对于 Java 实现的 Service(即我们所说的 Framework Service),我们需要关注 ADIL 或者具体实现的 xxx extends xxx.Stub 中的方法会不会存在安全问题。对于 C 实现的 Service(即我们所说的 Native Service),我们需要关注 BnBinder 实现的 onTransact 方法会不会存在安全问题。这些其实都是 Service 的入口函数。

image

其中 Fuzz 引擎的入口函数为 tansact(code, data, reply, flag)

  • code 是 int 类型,指定了服务方法号;

  • data 是 parcel 类型,是发送的数据,满足 binder 协议规则;

  • reply 也是 parcel 类型,是通信结束后返回的数据;

  • flag 是标记位,0 为普通 RPC,需要等待,调用发起后处于阻塞状态直到接收到返回,1 为 one-way RPC,表示“不需要等待回复的”事务,一般为无返回值的单向调用。

data 作为 binder 发送的参数,组成为 RPC header+参数1+参数2+….,RPC header 就是所谓的接口名称(前文提到的接口描述)。至此,我们就完成了 Fuzzing 的前置知识。

0x61 Service Call or Transact

adb 提供了可以直接调用 Service 的工具,并且支持传入各种参数。这种方式简单粗暴,但是 adb 提供的传入参数类型有限,对于稍稍复杂的类型或者一些自定义类型参数,adb 调用 service 的能力就受到限制了。所以,可以编写 APP 调用系统 Service,相关方法其实在上面的章节 "组件与系统 Service 通信" 也提到过,这里进一步方法化。

获取系统所有服务

/**
 * 获取系统中所有的服务名
 * @return 服务列表
 */
public String[] ListService() {
    String  ServiceList[]={};
    try{
        ServiceList = (String [])Class.forName("android.os.ServiceManager").getMethod("listServices").invoke(null);
        //  Log.d(TAG,"I find there are "+ServiceList.length+" System Services");
        for(int i=0;i< ServiceList.length;i++){
            //  Log.d(TAG, ServiceList[i]);
        }
    } catch(Exception e) {
        e.printStackTrace();
    }

    return ServiceList;
}

获取 IBinder 接口对象

/**
 * 使用反射机制获取服务的IBinder接口
 * @param ServiceName 服务名
 * @return 返回服务的IBinder接口
 * @throws Exception
 */
private static IBinder getIBinder(String ServiceName) throws Exception{
    return (IBinder) Class.forName("android.os.ServiceManager").getMethod("getService", String.class).invoke(null, ServiceName);
}

获取接口描述或者说是接口名称

/**
 * 获取接口名
 * @param serHandle 服务的IBinder接口
 * @return 返回接口名字符串
 * @throws RemoteException
 */
private static String getInterfaceName(IBinder IBinder) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    IBinder.transact(IBinder.INTERFACE_TRANSACTION, data, reply, 0);
    String interfacename = reply.readString();
    data.recycle();
    reply.recycle();
    return interfacename;
}

调用系统服务的相关方法

void test(String sername, int code) {
    for (int i = 0; i < testcaseint.length; i++)
        for (int j = 0; j < testcaseint.length; j++) {
            try {
                Log.d(tag, "-" + sername + "-" + code + "-arg1-" + testcaseint[i] + "-arg2-" + testcaseint[j]);
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                IBinder ib = getIBinder(sername);
                String in = getInterfaceName(ib);
                data.writeInterfaceToken(in);
                data.writeInt(testcaseint[i]);
                data.writeInt(testcaseint[j]);
                ib.transact(code, data, reply, 0);
                Log.d(tag, "-" + sername + "-" + code + "-" + "reply is \n" + reply.readString());
                reply.readException();
                data.recycle();
                reply.recycle();
            } catch (Exception e) {
            }
        }
}

传统 Service Fuzzing 的局限性在于,无法提前预知目标服务方法的参数类型,虽然我们可以从源码找到,但是这样必须针对单个方法,重新回填参数类型;同时,对于多级接口,也很难进行构造。

0x62 Native Service Fuzz

FANS是清华大学刘宝证发表在 USENIX Security'20 用于 Android Service Fuzzing 的开源工具。解决了传统 Fuzzing 中,如何自动构造接口模型测试用例的问题。其最突出的贡献是,自动从目标接口的抽象语法树 (AST) 中提取接口模模型,简言之,可以自动推断目标方法的入参类型。

image

接口收集器收集目标服务中的所有接口,包括顶级接口和多级接口。然后,接口模型提取器提取每个收集的接口中每个候选事务的输入和输出格式以及变量语义,即变量名称和类型。提取器还收集与变量相关的结构、枚举和类型别名的定义。接下来,依赖项推断器推断接口依赖项,以及事务内和事务间变量依赖项。最后,基于上述信息,模糊器引擎随机生成事务,并调用相应的接口来模糊本机系统服务。模糊器引擎还有一个管理器负责在主机和正在测试的手机之间同步数据。

FANS 缺点在于,必须有源码编译环境,针对不同厂商可能需要进行一定的适配。

总结

Binder 作为 Android 核心组件之一,为 Android 提供了独特的进程间通信方式。Android Service 则是 Binder 最好的体现。通过 Service,我们可以更好的理解 Binder。本文系统介绍了 Android Service 分类、原理与使用方法,然后结合实际安全漏洞剖析其常见的安全问题,最后给出了相应的 Fuzzing 模型。

参考文献

本文的完成也得益于网上各位大佬的杰作,少数文字描述和一些架构图来自于以下文章,再次表示感谢!剩余一些架构图是由笔者绘制,文中源码截图由笔者在 Android XRef8.0/9.0 完成,命令行截图是笔者在一加 3 (Android 8.0.0)手机上执行的结果。关于 Andorid 系统架构的文章或书籍卷帙浩繁,如果你想了解更多知识,细读以下博客,相信会有意想不到的收获!

# android安全 # 系统安全 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录