注册

android linux内核层 Android 跨进程通信:IPC、Binder 与 ServiceManager 介绍

ebpf在Android安全上的应用:结合binder完成一个行为检查沙箱(上篇)一、IPC简单介绍

IPC是Inter-ProcessCommunication的简写,含意为进程间通讯或则跨进程通讯,是指两个进程之间进行数据交换的过程。

Android在哪些时侯会有跨进程通讯的须要?Android在恳求系统服务的时侯会有跨进程通讯的需求,比如访问手机通信录、获取定位等等行为,本文的目标即是实现一个简易的捕捉这种行为的沙箱

二、binder简单介绍

Binder是Android中的一种跨进程通讯形式,可以理解为是IPC的一种具体实现方法

三、ServiceManager简单介绍

ServiceManager是Android中一个及其重要的系统服务,从它的名称上就可以晓得,它是用于管理系统服务的

ServiceManager由init进程启动

ServiceManager负责了以下的一些功能:服务的注册与查找、进程间通讯、系统服务的启动与唤起、提供系统服务的清单实例

binder驱动决定了底层的通讯详情,这么ServiceManager则相当于导航,告诉具体的通讯该如何走,达到那儿等

四、通信剖析4.1顾客端调用JAVA层剖析

以WifiManager类的getConnectInfo函数(该函数获取wifi信息)为例进行剖析

ctrl+左键查看引用,可以发觉该函数定义在.wifi.WifiManager类中,如右图所示:

从上图可以看见,getConnectInfo函数具体代码只有一句thrownewRuntimeException("Stub!");,这告诉我们这个函数是由rom中相同的类去取代执行,该函数在这被定义是编译所须要(PS:可以参考),在android源码中的目录frameworks/base/wifi/java/amdroid/net/wifi下我们可以找到该类,之后找到该函数的具体实现,如右图所示:

可以发觉该函数调用了IWifiManager的getConnectionInfo函数,在frameworks/base/wifi/java/amdroid/net/wifi目录下可以找到IWifiManager.aidl文件,该aidl中定义了getConnectionInfo函数,如右图所示:

这儿须要引入一个概念---AIDL,AIDL是android的一种插口语言,用于公开android服务的插口,借此来实现跨进程的函数调用。AIDL在编译时会生成两个类,即Stub和Proxy两个类,Stub类是服务端具象层的彰显,Proxy是顾客端获取的实例,android通过proxy-stub这些设计模式实现了IPC

下边写一个aidl文件之后生成相应的java代码来瞧瞧是如何实现调用的,首先,我们在androidstudio中随意找一个项目,之后新建一个aidl文件,如右图所示:


之后Build->MakeProbject即可生成,生成的路径坐落build/generated/aidl_source_output_dir/debug/out/包名,如右图所示:

观察生成后的java文件可发觉,Proxy类早已生成,在Proxy类中我们可以找到我们定义的函数,如右图所示:

具体剖析一下该函数,首先通过obtain函数生成了一个Parcel实例,之后调用Parcel的write系列函数进行写入,虽然就是一个序列化的过程,之后调用了IBinder的transact函数,跟踪剖析一下该函数,在目录frameworks/base/core/java/android/os下可以找到该java文件,如右图所示:

可以发觉,IBinder仅仅是一个插口linux删除命令,其中定义了transact方式,该方式有4个参数,第一个参数code在我们的远程调用中为函数编号,服务端接受到这个编号后,会去找寻Stub类中的静态变量,因而解析出是调用哪个函数,第二个和第三个参数_data、_reply为传入的参数和返回的值,都是经过序列化后的数据android linux内核层,最后一个参数flags为指示是否须要阻塞等待结果,0为阻塞等待,1为立刻返回。

全局搜索一下,可以发觉同目录下的BinderProxy类实现了该插口(PS:值得注意的是,同目录下边还存在一个Binder类,也实现了该插口,但Binder类是服务端的实现,而不是顾客端的实现),如右图所示:


剖析该函数,可以发觉最后迈向了transactNative函数,到此为止,进行IPC通讯顾客端java层早已剖析完毕

4.2顾客端调用Native层剖析

全局搜索一下transactNative函数,可以发觉该函数在native层中注册信息,如右图所示:


跟踪一下android_os_BinderProxy_transact函数,可以发觉该函数首先通过getBPNativeData(env,obj)->mObject.get()获取到了一个BpBinder对象,之后调用了BpBinder的transact函数,如右图所示:


继续跟进下去,可以发觉步入了IPCThreadState的transact函数,如右图所示:

接着跟进,可以发觉首先调用writeTransactionData函数,该函数作用为填充binder_transaction_data结构体,为发送到binder驱动做打算,之后调用waitForResponse等待返回,如右图所示:

跟进waitForResponse函数,可以发觉该函数最重要的就是调用talkWithDriver函数,剖析一下talkWithDriver函数,可以发觉最终调用了ioctl,如右图所示:

四处为止,顾客端native层剖析完毕

4.3内核层剖析(binder驱动剖析)

到此处android linux内核层,我们的ebpf程序就可以开始捕捉之后解析数据格式了

当用户层调用ioctl时,会步入内核态,步入binder_ioctl内核函数(ps:可在内核设备源码中的binder.c找到相应的描述符),剖析一下binder_ioctl函数,可发觉该函数主要作用为在两个进程之间首发数据,我们的通讯数据ioctl命令是BINDER_WRITE_READ,当遇见该命令的时侯,会调用binder_ioctl_write_read函数,如右图所示:

跟进binder_ioctl_write_read函数,可以发觉,该函数首先将unsignedlong类型的arg参数指向的地址的值读取到结构体binder_write_read中,说明当ioctl命令为BINDER_WRITE_READ时,传递进来的参数为指向结构的binder_write_read的表针,如右图所示:

到这儿虽然我们内核态的剖析早已可以结束了,我们早已观察到了我们想要的数据了,即binder_write_read结构体,可以看一下该结构体的定义,如下所示:

 复制代码 隐藏代码
struct binder_write_read {
    binder_size_t write_size; /* 写内容的数据总大小 */
    binder_size_t write_consumed; /* 记录了从缓冲区读取写内容的大小 */
    binder_uintptr_t write_buffer; /* 写内容的数据的虚拟地址 */
    binder_size_t read_size; /* 读内容的数据总大小 */
    binder_size_t read_consumed; /* 记录了从缓冲区读取读内容的大小 */
    binder_uintptr_t read_buffer; /* 读内容的数据的虚拟地址 */
};

这个结构体是拿来描述进程间通讯过程中所传输的数据,我们读取从顾客端发送到服务端的通讯包只须要关注write_size、write_consumed、write_buffer,其中,write_buffer指向的是一个链表,这个字段中就包含了binder_transaction_data结构体,这个结构体在native层writeTransactionData函数填充的,我们的目标通讯包即是这个结构体,其中,write_buffer和read_buffer指向的数据结构大致如下:

通常来说,驱动会一次性传递多条命令和地址的组合,而我们要在其中找到binder_transaction_data结构体对应的指令,在binder.h头文件中我们可以找到驱动定义的所有指令(+/refs/heads/android-mainline/include/uapi/linux/android/binder.h),如右图所示:

可以发觉,BC_TRANSACTION和BC_REPLY指令都对应着binder_transaction_data结构体参数,但我们只须要顾客端发往驱动的数据包,所以我们只须要BC_TRANSACTION指令对应的参数即可

经过前面的剖析,我们找到了我们须要的核心数据---binder_transaction_data结构体,现今来看一下该结构体的定义,定义如下:

 复制代码 隐藏代码
struct binder_transaction_data {
    union {
        __u32 handle;
        binder_uintptr_t ptr;
    } target; /* 该事务的目标对象 */
    binder_uintptr_t cookie; /* 只有当事务是由Binder驱动传递给用户空间时,cookie才有意思,它的值是处理该事务的Server位于C++层的本地Binder对象 */
    __u32 code; /* 方法编号 */
    __u32 flags;
    pid_t sender_pid;
    uid_t sender_euid;
    binder_size_t data_size; /* 数据长度 */
    binder_size_t offsets_size; /* 若包含对象,对象的数据大小 */
    union {
        struct {
            binder_uintptr_t buffer; /* 参数地址 */
            binder_uintptr_t offsets; /* 参数对象地址 */
        } ptr;
        __u8 buf[8];
    } data; /* 数据 */
};

有了该数据结构linux安装,我们就可以晓得顾客端调用服务端的函数的函数、参数等数据了

五、实现疗效

PS:整套系统用于商业,就不做开源处理了,这儿只给出核心结构体复印的截图,就不再发后端的截图了

读取手机通信录(ps:这儿读取下来的数据采用了Toast复印的,所以将捕捉到的Toast相应的通讯包也一起复印了下来,下同):

获取地理位置

获取wifi信息:

六、其他

里面当然我们只剖析到了发送部份,反过来,虽然我们也可以读取返回包甚至于更改返回包,就可用于对风控的对抗,比如在内核态中更改APP恳求的设备标示信息(其实前提是app走系统提供的驱动通讯),亦或则用于逆向的工作,比如过root检查等等。

这部份验证代码就暂不放下来了,感兴趣的可以自己实现一下

以上就是android linux内核层 Android 跨进程通信:IPC、Binder 与 ServiceManager 介绍的详细内容,更多请关注CTO智库其它相关文章!