IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
Android在什么时候会有跨进程通信的需要?Android在请求系统服务的时候会有跨进程通信的需求,例如访问手机通讯录、获取定位等等行为,本文的目标即是实现一个简易的捕捉这些行为的沙箱
Binder是Android中的一种跨进程通信方式,可以理解为是IPC的一种具体实现方式
ServiceManager是Android中一个及其重要的系统服务,从它的名称上就可以知道,它是用于管理系统服务的
ServiceManager由init进程启动
ServiceManager负责了以下的一些功能:服务的注册与查找、进程间通信、系统服务的启动与唤醒、提供系统服务的清单实例
binder驱动决定了底层的通信详情,那么ServiceManager则相当于导航,告诉具体的通信该怎么走,达到那里等
以WifiManager类的getConnectInfo函数(该函数获取wifi信息)为例进行分析
ctrl+左键查看引用,可以发现该函数定义在android.net.wifi.WifiManager类中,如下图所示:


从上图可以看到,getConnectInfo函数具体代码只有一句throw new RuntimeException("Stub!");,这告诉我们这个函数是由rom中相同的类去替代执行,该函数在这被定义是编译所需要(PS:可以参考8e7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4c8@1K9$3q4@1M7X3W2F1j5g2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1k6V1k6i4c8S2K9h3I4K6i4K6u0r3y4K6j5I4z5o6l9$3y4o6p5`.),在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代码来看看是怎么实现调用的,首先,我们在android studio中随便找一个项目,然后新建一个aidl文件,如下图所示:


然后Build->Make Probject即可生成,生成的路径位于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仅仅是一个接口,其中定义了transact方法,该方法有4个参数,第一个参数code在我们的远程调用中为函数编号,服务端接受到这个编号后,会去寻找Stub类中的静态变量,从而解析出是调用那个函数,第二个和第三个参数_data、_reply为传入的参数和返回的值,都是经过序列化后的数据,最后一个参数flags为指示是否需要阻塞等待结果,0为阻塞等待,1为立即返回。
全局搜索一下,可以发现同目录下的BinderProxy类实现了该接口(PS:值得注意的是,同目录下面还存在一个Binder类,也实现了该接口,但Binder类是服务端的实现,而不是客户端的实现),如下图所示:


分析该函数,可以发现最后走向了transactNative函数,到此为止,进行IPC通信客户端java层已经分析完毕

全局搜索一下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层分析完毕
到此处,我们的ebpf程序就可以开始捕捉然后解析数据格式了
当用户层调用ioctl时,会进入内核态,进入binder_ioctl内核函数(ps:可在内核设备源码中的binder.c找到相应的描述符),分析一下binder_ioctl函数,可发现该函数主要作用为在两个进程之间首发数据,我们的通信数据ioctl命令是BINDER_WRITE_READ,当遇到该命令的时候,会调用binder_ioctl_write_read函数,如下图所示:


跟进binder_ioctl_write_read函数,可以发现,该函数首先将unsigned long类型的arg参数指向的地址的值读取到结构体binder_write_read中,说明当ioctl命令为BINDER_WRITE_READ时,传递进来的参数为指向结构的binder_write_read的指针,如下图所示:
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!