ebpf在Android安全上的应用:结合binder完成一个行为检测沙箱(下篇)

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


7.png (4.63 KB, 下载次数: 0)
下载附件
2024-4-24 19:03 上传



8.png (67.62 KB, 下载次数: 0)
下载附件
2024-4-24 19:03 上传

从上图可以看到,getConnectInfo函数具体代码只有一句throw new RuntimeException("Stub!");,这告诉我们这个函数是由rom中相同的类去替代执行,该函数在这被定义是编译所需要(PS:可以参考https://blog.csdn.net/ttkatrina/article/details/76180641),在android源码中的目录frameworks/base/wifi/java/amdroid/net/wifi下我们可以找到该类,然后找到该函数的具体实现,如下图所示:


9.png (68.7 KB, 下载次数: 0)
下载附件
2024-4-24 19:04 上传

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


10.png (72.82 KB, 下载次数: 0)
下载附件
2024-4-24 19:04 上传



11.png (12.54 KB, 下载次数: 0)
下载附件
2024-4-24 19:04 上传

这里需要引入一个概念 --- AIDL,AIDL是android的一种接口语言,用于公开android服务的接口,以此来实现跨进程的函数调用。AIDL在编译时会生成两个类,即Stub和Proxy两个类,Stub类是服务端抽象层的体现,Proxy是客户端获取的实例,android通过proxy-stub这种设计模式实现了IPC
下面写一个aidl文件然后生成相应的java代码来看看是怎么实现调用的,首先,我们在android studio中随便找一个项目,然后新建一个aidl文件,如下图所示:


12.png (104.43 KB, 下载次数: 0)
下载附件
2024-4-24 19:04 上传



13.png (36.47 KB, 下载次数: 0)
下载附件
2024-4-24 19:04 上传

然后Build->Make Probject即可生成,生成的路径位于build/generated/aidl_source_output_dir/debug/out/包名,如下图所示:


14.png (20.7 KB, 下载次数: 0)
下载附件
2024-4-24 19:04 上传

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


15.png (169.35 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传

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


16.png (40.17 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传



17.png (89.78 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传

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


18.png (46.28 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传



19.png (164.76 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传

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


20.png (24.34 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传

4.2 客户端调用Native层分析
全局搜索一下transactNative函数,可以发现该函数在native层中注册信息,如下图所示:


21.png (31.28 KB, 下载次数: 0)
下载附件
2024-4-24 19:05 上传

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


22.png (95.16 KB, 下载次数: 0)
下载附件
2024-4-24 19:06 上传



23.png (106.15 KB, 下载次数: 0)
下载附件
2024-4-24 19:06 上传

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


24.png (43.31 KB, 下载次数: 0)
下载附件
2024-4-24 19:06 上传

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


25.png (85.17 KB, 下载次数: 0)
下载附件
2024-4-24 19:06 上传



26.png (107.54 KB, 下载次数: 0)
下载附件
2024-4-24 19:06 上传



27.png (111.77 KB, 下载次数: 0)
下载附件
2024-4-24 19:07 上传

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


28.png (78.88 KB, 下载次数: 0)
下载附件
2024-4-24 19:07 上传



29.png (122.52 KB, 下载次数: 0)
下载附件
2024-4-24 19:07 上传

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


30.png (36.31 KB, 下载次数: 0)
下载附件
2024-4-24 19:07 上传



31.png (139.61 KB, 下载次数: 0)
下载附件
2024-4-24 19:07 上传

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


32.png (137.64 KB, 下载次数: 0)
下载附件
2024-4-24 19:07 上传

到这里其实我们内核态的分析已经可以结束了,我们已经观察到了我们想要的数据了,即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指向的数据结构大致如下:


33.png (11.77 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传

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


34.png (137.68 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传

可以发现,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; /* 数据 */
};
有了该数据结构,我们就可以知道客户端调用服务端的函数的函数、参数等数据了
五、实现效果
PS:整套系统用于商业,就不做开源处理了,这里只给出核心结构体打印的截图,就不再发前端的截图了
读取手机通讯录(ps:这里读取出来的数据采用了Toast打印的,所以将捕捉到的Toast相应的通信包也一起打印了出来,下同):


1.png (86.34 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传



2.png (70.63 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传

获取地理位置:


3.png (92.43 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传



4.png (75.5 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传

获取wifi信息:


5.png (42.8 KB, 下载次数: 0)
下载附件
2024-4-24 19:08 上传



6.png (69.5 KB, 下载次数: 0)
下载附件
2024-4-24 19:09 上传

六、其他
上面其实我们只分析到了发送部分,反过来,其实我们也可以读取返回包甚至于修改返回包,就可用于对风控的对抗,例如在内核态中修改APP请求的设备标识信息(当然前提是app走系统提供的驱动通信),亦或者用于逆向的工作,例如过root检测等等。
这部分验证代码就暂不放出来了,感兴趣的可以自己实现一下

下载次数, 函数

xixicoco   

系列ebpf文章啊,欢迎
Abs1nThe   

围观大佬
jifL88   

谢谢分享~
deffedyy   

围观大佬
szba1120   

是要出系列教程吗?
WeiZhiDeR8   

感谢大佬分享,学习学习
n1kOvO   

牛呀!!!!
LHCAILGT   

太厉害了,很感谢您的分享
您需要登录后才可以回帖 登录 | 立即注册

返回顶部