Kernel debugging gives security researchers a tool to monitor and control a device under analysis. On desktop platforms such as Windows, macOS, and Linux, this is easy to perform. However, it is more difficult to do kernel debugging on Android devices such as the Google Nexus 6P . In this post, I describe a method to perform kernel debugging on the Nexus 6P and the Google Pixel, without the need for any specialized hardware.
Joshua J. Drake and Ryan Smith built a UART debug cable for this purpose, which works well. However, for some people not as skilled in hardware building (such as software engineers like myself), this can be difficult. Alternately, kernel debugging via the serial-over-usb channel is also possible.
The method proposed for this dates back to 2010, which means that some parts of the instructions are now outdated. The method I've found still uses this as the key point, but can be used on modern Android devices. Researchers can use debugging to determine the current state of CPU execution, making analysis go more quickly. So how does this process work?
Android is built on the Linux kernel, which includes a built-in kernel debugger, KGDB. KGDB relies on a serial port to connect the debugging device and the target device. A typical scenario is shown below:
Figure. 1 KGDB working model
The target and debugging devices are connected via a serial cable. The user on the debugging machine uses GDB to attach the serial device file (for example, /dev/ttyS1) using the command target remote /dev/ttyS1. After that, GDB can communicate with KGDB in the target device via the serial cable.
The KGDB core component handles the actual debugging tasks such as setting break points and fetching the data in memory. The KGDB I/O component is a glue that connects the KGDB core component with low-level serial drivers to take care of the transmission of debug information.
However, Android devices generally don’t have hardware serial ports. The first challenge becomes how to find a channel so that KGDB can send the debugging information to an outside device with GDB. Various channels have been used, but the most practical solution is a USB cable.
The Linux kernel's USB driver supports USB ACM class devices, which can emulate a serial device. In short, an Android device can connect to a serial device by USB cable. This is all in code that is already part of the Linux kernel, so we do not need to add any specific lines of code. Here are the steps to activate this debugging feature:
- Build a version of the AOSP (Android Open Source Project) withaosp_angler-eng and the corresponding linux kernel. Please refer here.
- Connect the target device with the debugging machine by USB cable.
- Use the flashboot command to write the image to the target device with the command fastboot flashall –w.
- Run the adb command to enable the adb network service: adb tcpip 6666
- On the debugging machine, run adb to connect the device: adb connect <device ‘s IP>:6666
- Run the adb shell: adb shell
- In the adb shell, go to the USB gadget driver control folder /sys/class/android_usb/android0/.
- In the adb shell, using following commands to enable the USB ACM function: echo 0 > enable //close USB connection echo tty > f_acm/acm_transports //specific transport type echo acm > functions //enable ACM function on USB gadget driver echo 1 > enable //enable USB connection
At this point, the USB ACM function should be enabled. Two checks should be done to verify if it is the case: first, in the adb shell, use the command ls /dev/ttyGS*. A device file should exist. Secondly, on the debugging machine, use the command ls /dev/ttyACM*. A device file should be here as well. The debugging machine can communicate with the target device using these two device files.
The second challenge is KGDB needs a lower-level communication driver (either the serial driver or USB gadget driver) to provide a polling interface to both get and write a character. Why? Because KGDB's communication channel needs to work in the KGDB's kernel exception handler. In that context, the interrupt is disabled and only one CPU works to run this code. The lower-level driver doesn't depend on interrupts and needs to actively poll for changed registers or memory I/O space. Do not use sleep or spinlock in this context.
The Nexus 6P use a DWC3 controller for its USB connection. This USB driver doesn't provide a polling function directly, so I added this feature into the DWC3 device driver. The concept behind this code is simple: I have removed the dependencies on interrupts. Instead, I use loops to query the corresponding changed in registers or memory space. To keep things simple, I did the following:
- I hardcoded the ACM device file name as /dev/ttyGS0 in the kgdboc_init_jack function in kgdboc.c.
- I changed the handle function of f_acm/acm_transports to enable KGDB. This allows KGDB to be turned on with the simple command echo kgdb > f_acm/acm_transports
This turns the KGDB working model into the following:
Figure 2. New KGDB working model
Here are the steps to merge the code above to kernel code. While you can use any kernel code, I have taken mine from Google's own Git repository, specifically the branch origin/android-msm-angler-3.10-nougat-hwbinder.
- Compile the kernel code with following configuration changing on the original configure setting:
- CONFIG_KGDB=y CONFIG_KGDB_SERIAL_CONSOLE=y
- CONFIG_MSM_WATCHDOG_V2 = n If this is enabled, the kernel will take the polling loop as a dead loop and restart the device. This must be disabled.
- CONFIG_FORCE_PAGES=y If this is not enabled, the soft breakpoint isn't set.
- Use the fastboot command to flash the kernel and Android image onto the target device.
- Start the Android system on the debugging machine. Run the following command to enable the adb network service: adb tcpip 6666
- On the debugging machine, run the following commands: adb connect <device’s ip address>:6666 adb shell
- Inside the adb shell , run the following commands: echo 0 > enable echo tty > f_acm/acm_transports echo acm > functions echo 1 > enable echo kgdb > f_acm/acm_transports
- On the debugging machine, run gdb. The Nexus 6P has an aarch64 kernel, so you need gdb for aarch64: sudo <path>/aarch64-linux-android-gdb <path>/vmlinux target remote /dev/ttyACM0
- Inside the adb shell, input following command: echo g > /proc/sysrq-trigger Note: The time interval between step 6's last command and step 7 should not be too long. The shorter, the better.
- If successful, GDB will output the following:
Figure 3. GDB output
I have used this method to perform kernel debugging on a Nexus 6P. This method should work for Google Pixel, so long as they also use the DWC3 USB controller.
We hope that sharing this technique may be useful to other Android researchers by giving the research community a new method to better understand the behavior of mobile malware. Debugging gives researchers better reverse engineering capabilities, giving them a clearer understanding of how malware behaves within an Android device.