作者峰会帐号:windy_ll
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结构体,可以看一下该结构体的定义,如下所示:
<em style="cursor: pointer;font-size: 12px"> 复制代码</em><em style="cursor: pointer;font-size: 12px"> 隐藏代码<br></em><code><span>struct</span> <span>binder_write_read</span> {<br> <span>binder_size_t</span> write_size; <span>/* 写内容的数据总大小 */</span><br> <span>binder_size_t</span> write_consumed; <span>/* 记录了从缓冲区读取写内容的大小 */</span><br> <span>binder_uintptr_t</span> write_buffer; <span>/* 写内容的数据的虚拟地址 */</span><br> <span>binder_size_t</span> read_size; <span>/* 读内容的数据总大小 */</span><br> <span>binder_size_t</span> read_consumed; <span>/* 记录了从缓冲区读取读内容的大小 */</span><br> <span>binder_uintptr_t</span> read_buffer; <span>/* 读内容的数据的虚拟地址 */</span><br>};</code>
这个结构体是拿来描述进程间通讯过程中所传输的数据,我们读取从顾客端发送到服务端的通讯包只须要关注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结构体,现今来看一下该结构体的定义,定义如下:
<em style="cursor: pointer;font-size: 12px"> 复制代码</em><em style="cursor: pointer;font-size: 12px"> 隐藏代码<br></em><code><span>struct</span> <span>binder_transaction_data</span> {<br> <span>union</span> {<br> __u32 handle;<br> <span>binder_uintptr_t</span> ptr;<br> } target; <span>/* 该事务的目标对象 */</span><br> <span>binder_uintptr_t</span> cookie; <span>/* 只有当事务是由Binder驱动传递给用户空间时,cookie才有意思,它的值是处理该事务的Server位于C++层的本地Binder对象 */</span><br> __u32 code; <span>/* 方法编号 */</span><br> __u32 flags;<br> <span>pid_t</span> sender_pid;<br> <span>uid_t</span> sender_euid;<br> <span>binder_size_t</span> data_size; <span>/* 数据长度 */</span><br> <span>binder_size_t</span> offsets_size; <span>/* 若包含对象,对象的数据大小 */</span><br> <span>union</span> {<br> <span>struct</span> {<br> <span>binder_uintptr_t</span> buffer; <span>/* 参数地址 */</span><br> <span>binder_uintptr_t</span> offsets; <span>/* 参数对象地址 */</span><br> } ptr;<br> __u8 buf[<span>8</span>];<br> } data; <span>/* 数据 */</span><br>};</code>
有了该数据结构linux安装,我们就可以晓得顾客端调用服务端的函数的函数、参数等数据了
五、实现疗效
PS:整套系统用于商业,就不做开源处理了,这儿只给出核心结构体复印的截图,就不再发后端的截图了
读取手机通信录(ps:这儿读取下来的数据采用了Toast复印的,所以将捕捉到的Toast相应的通讯包也一起复印了下来,下同):
获取地理位置:
获取wifi信息:
六、其他
里面当然我们只剖析到了发送部份,反过来,虽然我们也可以读取返回包甚至于更改返回包,就可用于对风控的对抗,比如在内核态中更改APP恳求的设备标示信息(其实前提是app走系统提供的驱动通讯),亦或则用于逆向的工作,比如过root检查等等。
这部份验证代码就暂不放下来了,感兴趣的可以自己实现一下
-官方峰会
公众号设置“星标”,您不会错过新的消息通知
如开放注册、精华文章和周边活动等公告
以上是android linux内核层 Android 跨进程通信:IPC、Binder 与 ServiceManager 介绍的详细内容。更多信息请关注PHP中文网其他相关文章!