Home >System Tutorial >LINUX >android linux kernel layer Android cross-process communication: introduction to IPC, Binder and ServiceManager
Author Summit Account: windy_ll
Application of ebpf in Android security: Combined with binder to complete a behavior inspection sandbox (Part 1) 1. Brief introduction to IPC
IPC is the abbreviation of Inter-Process Communication, which means inter-process communication or cross-process communication. It refers to the process of data exchange between two processes.
When does Android need cross-process communication? When Android requests system services, there will be a need for cross-process communication, such as accessing mobile phone address books, obtaining location, etc. The goal of this article is to implement a simple sandbox to capture this behavior
2. Brief introduction to binder
Binder is a form of cross-process communication in Android, which can be understood as a specific implementation method of IPC
3. Brief introduction to ServiceManager
ServiceManager is an extremely important system service in Android. As you can tell from its name, it is used to manage system services
ServiceManager is started by the init process
ServiceManager is responsible for the following functions: service registration and search, inter-process communication, startup and arousal of system services, and providing list instances of system services
The binder driver determines the underlying communication details, so the ServiceManager is equivalent to navigation, telling the specific communication how to go, where to get there and so on
4. Communication Analysis 4.1 Client Call JAVA Layer Analysis
Take the getConnectInfo function of the WifiManager class (this function obtains wifi information) as an example for analysis
Ctrl+left click to view the reference, you can find that the function is defined in the .wifi.WifiManager class, as shown on the right:
As you can see from the picture above, the specific code of the getConnectInfo function only has one sentence: thrownewRuntimeException("Stub!");, which tells us that this function is replaced and executed by the same class in rom. The function defined here is required for compilation (PS : For reference), we can find this class in the directory frameworks/base/wifi/java/amdroid/net/wifi in the android source code, and then find the specific implementation of the function, as shown on the right:
You can find that this function calls the getConnectionInfo function of IWifiManager. You can find the IWifiManager.aidl file in the frameworks/base/wifi/java/amdroid/net/wifi directory. The getConnectionInfo function is defined in the aidl, as shown on the right:
A concept needs to be introduced here---AIDL. AIDL is a socket language of android, which is used to expose the socket of android service to realize cross-process function calls. AIDL will generate two classes when compiling, namely Stub and Proxy. The Stub class is the manifestation of the server-side representation layer, and the Proxy is the instance obtained by the client. Android implements IPC through these design patterns of proxy-stub
Write an aidl file below and then generate the corresponding java code to see how the call is implemented. First, we find a random project in Android Studio, and then create a new aidl file, as shown on the right:
After that, Build->MakeProbject can be generated. The generated path is located in build/generated/aidl_source_output_dir/debug/out/package name, as shown on the right:
Observing the generated java file, we can find that the Proxy class has already been generated. In the Proxy class we can find the functions we defined, as shown in the picture on the right:
Let’s analyze this function in detail. First, a Parcel instance is generated through the obtain function, and then Parcel’s write series functions are called to write. Although it is a serialization process, IBinder’s transact function is then called, and the function is traced and analyzed. The java file can be found in the directory frameworks/base/core/java/android/os, as shown on the right:
It can be found that IBinder is just a socket linux delete command, which defines the transact method. This method has 4 parameters. The first parameter code is the function number in our remote call. After the server receives this number, it will Find the static variables in the Stub class, so you can parse out which function is called. The second and third parameters _data and _reply are the parameters passed in and the returned value, which are all serialized dataandroid linux Kernel layer , the last parameter flags indicates whether it is necessary to block and wait for the result, 0 means blocking and waiting, and 1 means returning immediately.
After a global search, you can find that the BinderProxy class in the same directory implements this socket (PS: It is worth noting that there is also a Binder class in the same directory, which also implements this socket, but the Binder class is a server-side implementation, not a Client implementation), as shown in the picture on the right:
After analyzing this function, we can find that it finally moves towards the transactNative function. At this point, the analysis of the client Java layer for IPC communication has been completed
4.2 Analysis of client calling Native layer
Search the transactNative function globally and you can find that the function registers information in the native layer, as shown on the right:
Trace the android_os_BinderProxy_transact function. You can find that the function first obtains a BpBinder object through getBPNativeData(env,obj)->mObject.get(), and then calls the transact function of BpBinder, as shown on the right:
Continue to follow up and you will find that you have entered the transact function of IPCThreadState, as shown on the right:
Following up, you can find that the writeTransactionData function is first called, which is used to fill in the binder_transaction_data structure and prepare to send it to the binder driver, and then calls waitForResponse to wait for the return, as shown on the right:
Following the waitForResponse function, we can find that the most important thing about this function is to call the talkWithDriver function. Analyzing the talkWithDriver function, we can find that the ioctl is finally called, as shown on the right:
So far, the client native layer analysis is completed
4.3 Kernel layer analysis (binder driver analysis)
At this pointandroid linux kernel layer, our ebpf program can start to capture and parse the data format
When the user layer calls ioctl, it will enter the kernel state and enter the binder_ioctl kernel function (ps: the corresponding descriptor can be found in binder.c in the kernel device source code). Analyze the binder_ioctl function and you will find the main function of this function. In order to send data between two processes, our communication data ioctl command is BINDER_WRITE_READ. When this command is encountered, the binder_ioctl_write_read function will be called, as shown on the right:
Following the binder_ioctl_write_read function, we can find that the function first reads the value of the address pointed to by the unsignedlong type arg parameter into the structure binder_write_read, indicating that when the ioctl command is BINDER_WRITE_READ, the parameter passed in is the pointer pointing to the binder_write_read structure , as shown on the right:
Although our kernel state analysis can be finished here, we have already observed the data we want, that is, the binder_write_read structure. You can take a look at the definition of this structure, as shown below:
<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>
This structure is used to describe the data transmitted during inter-process communication. When we read the communication packet sent from the client to the server, we only need to pay attention to write_size, write_consumed, and write_buffer. Among them, write_buffer points to a linked list. This field It contains the binder_transaction_data structure. This structure is filled in the native layer writeTransactionData function. Our target communication package is this structure. Among them, the data structures pointed to by write_buffer and read_buffer are roughly as follows:
Generally speaking, the driver will pass multiple command and address combinations at one time, and we need to find the instructions corresponding to the binder_transaction_data structure. In the binder.h header file, we can find all the instructions defined by the driver (+/refs /heads/android-mainline/include/uapi/linux/android/binder.h), as shown on the right:
可以发觉,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检查等等。
这部份验证代码就暂不放下来了,感兴趣的可以自己实现一下
-官方峰会
公众号设置“星标”,您不会错过新的消息通知
如开放注册、精华文章和周边活动等公告
The above is the detailed content of android linux kernel layer Android cross-process communication: introduction to IPC, Binder and ServiceManager. For more information, please follow other related articles on the PHP Chinese website!