Research on Android system call hook (I)
When talking about Xposed, everyone should be very familiar with it. It mainly hooks the target function at the Java layer, and is powerless to call the underlying system. Since the hook system calls need to be completed in the kernel space, most of them are implemented by loading the kernel module. Here, we will start with the basics and step by step introduce how to hook system calls through the LKM (loadable kernel module.
The specific content is divided into two parts. This article mainly introduces the concepts of the kernel module and writes and runs a simple Hello world module for Android. We tested the mobile phone version of Xiaomi 4, the system is MIUI 5.7.16 development version (with root), and the kernel version is 3.4.0.
1. kernel module
If you need to add features to the kernel, you can modify the kernel source code and re-compile it. However, This method requires re-compiling the kernel upon each modification, so it is not flexible enough. In addition, many features such as hardware drivers, if all are included in the kernel, will undoubtedly increase the kernel size. Therefore, the kernel module came into being: We can dynamically load the required functions when the kernel is running, and the modular design also reduces the time required for development and debugging. In addition, as part of the kernel, the module has high permissions. In view of this, many rootkits appear in the form of INCORE modules.
Next, let's look at a simple kernel module:
# Include/module. h>
# Include/kernel. h>
MODULE_LICENSE ("GPL ");
Inttest_init (void );
Voidtest_exit (void );
Module_init (test_init );
Module_exit (test_exit );
Int test_init (void ){
Printk (KERN_ALERT "Helloworld \ n ");
Return 0;
}
Voidtest_exit (void ){
Printk (KERN_ALERT "Goodbye world \ n ");
}
Module_init and module_exit are used to register the functions to be executed when the module is loaded and detached. We output "Hello World" during loading and "Goodbyeworld" during unloading ". Note that we use printk. This is because the standard library in the user space cannot be used in the kernel module, so there are no stdio. h, stdlib. h, and so on. Printk is provided by the kernel, similar to the printf output function, and the result is output to dmesg.
Save the above. c file as testModule. c and create Makefile in the same folder:
Obj-m + = testModule. o
CROSS_COMPILE = arm-none-eabi-
KERNEL_DIR =/mnt/hgfs/liuruikai756/src/Xiaomi_Kernel_OpenSource
All:
Make-C $ (KERNEL_DIR) M = $ (PWD) ARCH = arm CROSS_COMPILE = $ (CROSS_COMPILE) modules
Clean:
Make-C $ (KERNEL_DIR) M = $ (PWD) clean
Variables in Makefile have clear meanings. The following is a brief introduction:
Obj-m: object files required for building the module
CROSS_COMPILE: the prefix of the cross compiler. You can use apt-getinstall gcc-arm-none-eabi to install the required cross-compilation tool chain.
KERNEL_DIR: Path of the kernel source code, which will be explained in detail below
By now, the code and Makefile of the module are ready. Before we compile the module, we need to prepare the kernel.
2. Prepare the kernel
The kernel module is closely related to the kernel. Because the module is in the kernel space, a serious system crash occurs when a problem occurs. Therefore, the kernel information is required when compiling the module. The general process is: first compile the kernel, and then compile the module based on the Kernel configuration and symbol information. This also means that after the system kernel is updated, external modules often need to be re-compiled to be compatible with the new kernel. In Linux, you can use the Dynamic Kernel Module Support (DKMS) to automatically recompile the Kernel Module.
This is also true for our Android phones. We need to compile the source code used by the kernel, or the source code that is as close as possible to its version. Xiaomi provides its MIUI kernel source code: https://github.com/MiCode/Xiaomi_Kernel_OpenSource
According to the instructions on the page, Xiaomi 4 uses the cancro-kk-oss branch. We first download the code for this branch:
Git clone https://github.com/MiCode/Xiaomi_Kernel_OpenSource.git-B cancro-kk-oss-single-branch-depth 1
Because Linux supports multiple platforms, We need to configure them based on the target platform. The chip used by Xiaomi 4 is Qualcomm MSM8974. In the kernel source code top-level directory, run the following command to complete the Configuration:
Export ARCH = arm
Export CROSS_COMPILE = arm-none-eabi-
Make msm8974_defconfig
Then, run
Make prepare
Make scripts
Complete relevant preparations
As mentioned earlier, before compiling a module, you must first compile the kernel. Why? So far, we have not started to compile the kernel, so we can try to compile the module directly. Run make in the directory where the Hello world module code is located. The output is as follows:
Although the Warning message "Module. symvers does not exist" is reported during compilation, the Module testModule. ko is also generated. We try to ignore the warning and load the module directly. Push the file testModule. ko to the mobile phone and load it using insmod:
As you can see, insmod fails to load the kernel, and the dmesg output indicates that the le_layout symbolic version is not found.
So what is the symbolic version? In fact, it is related to the lack of Module. symvers when compiling the kernel Module.
Module. symvers contains the version information of the kernel symbol. For the specific format, see the existing file in the system. In my Debian, the content is as follows:
This file is divided into four columns:
(1) version of the symbol (CRC)
(2) symbol name
(3) symbol, kernel (vmlinux) or module
(4) symbol type
When CONFIG_MODVERSIONS = y is set in kernel. config, the Module. symvers file will be generated in the top-level directory after kernel compilation is complete, containing the version information of the symbol. Then, the required symbolic version will be included in the Module according to the Module. symvers file during Module compilation. Then, when loading a module, the system first compares the symbolic version required by the module with the actual symbolic version of the kernel. If they are different, the module is not loaded. In this way, the compatibility between the module and the kernel is ensured, so as to avoid system crashes caused by loading incompatible modules. It is precisely because of the lack of Module. symvers that the previously compiled modules cannot be loaded.
By the way, most of the current Linux distributions contain linux-headers or similar software packages, including the configuration files, header files, and Module. symvers of the corresponding version kernel.
Back to our issue. Since Module. symvers is generated after the kernel is fully compiled, you only need to compile the kernel once to continue? Theoretically it should be correct, but in fact, I did not compile the kernel. Many problems are found during kernel compilation, and I am still not sure that the compiled kernel symbolic version is exactly the same as the actual symbolic version On Xiaomi mobile phone. Therefore, I decided to bypass the kernel compilation step. After all, we only need the Module. symvers file instead of the kernel itself.
3. Extract Module. symvers
Since it is decided not to compile the kernel, where does the symbolic version information come from? You cannot write one file in the same format.
Recall that we compared and checked the symbolic version when loading the module. We thought that the information must be stored in the actual running kernel for inspection. So, you only need to get the kernel in the mobile phone and export the symbolic version it contains. Isn't that enough? Fortunately, some predecessors have come up with this and provided the tool: https://github.com/glandium/extract-symvers.
This script extracts the symbolic version from the kernel image. Next we need to get the kernel in the mobile phone. Generally, the Linux kernel is stored in the/boot partition. On the Android phone, we first find the partition:
As you can see, the/boot partition corresponds to/dev/block/mmcblk0p19, And the content is dumped out:
Then, run kernel timg on the PC (you can install kernel timg through apt-get install kernel timg) to obtain the kernel image zImage in boot. img:
The last step is to use the extract-symvers script to extract Module. symvers. The script requires the kernel base address as the parameter. You can find the. text Segment information in dmesg to obtain the base address:
The base address 0xc0008000 is obtained.
Extract-symvers.py-B c0008000 vmlinux> Module. symvers
Then we get the long-awaited Module. symvers. We can check the Module. symvers file, which indeed contains the missing module_layout Symbol Information:
4. Compile the kernel module
Copy the Module. symvers file in the previous step to the kernel source code top-level directory, and run make again in the Module code directory. This time there is no warning that Module. symvers is missing. In fact, if we look at the directory where the module source code is located, we will find that a new file testModule. mod. c is generated. This file is used to add some ELF File segment information to the final generated module, including the symbolic version:
Push the compiled kernel module to the mobile phone and load it using insmod. This time, no error is prompted, and you can see through lsmod that the module has been loaded successfully. The familiar Hello world:
So far, we have successfully completed a simple kernel module, and readers should have a preliminary understanding of the module. So how can we use the kernel module to hook the system call? In the next article, we will discuss this part in detail.