I've been meaning to learn some android exploitation for quite sometime now. In this post , I propose to document the method of setting up a proper debugging environment (for self reference too) to debug android applications with the good old GDB.
A short insight into the Android system
One might consider android to be an open source linux based system , although it's not completely true , android does have the linux kernel layer in it. The only similarity between android and linux is that they share a similar kernel but apart from that , everything else is mostly different. In linux on one hand , you can directly get root access with the sudo
command , but that isn't the case (unless you've rooted your phone) on android. With root access to an android phone , you can basically access it's internal linux kernel layer and make modifications at the very heart of android , thus making it even more exciting to get your phone rooted. I have mentioned that android and linux share all their similarities in their kernel , which means they also have their differences.
Linux Kernel vs Android Kernel
There are quite a few functionalities which have been added to linux kernel to make it adept to be used as the android kernel.
IPC Binder
Some apps attempt to implement IPC using traditional Linux techniques such as network sockets and shared files. However, now most apps use Android system functionality for IPC such as Intent, Binder or Messenger with a Service, and BroadcastReceiver. The Android IPC mechanisms allow you to verify the identity of the application connecting to your IPC and set security policy for each IPC mechanism.
There are 3 basic methods used for IPC on Android:
- AIDL
- Messenger
- Broadcast
When do you know what to use?
ASHMEM (Android Shared Memory)
Ashmem is an Anonymous Shared Memory system that adds interfaces so processes can share named blocks of memory. As an example, the system could use Ashmem to store icons, which multiple processes could then access when drawing their UI.
The advantage of Ashmem over traditional Linux shared memory is that it provides a means for the kernel to reclaim these shared memory blocks if they are not currently in use.
If a process then tries to access a shared memory block the kernel has freed, it will receive an error, and will then need to reallocate the block and reload the data.
Process Memory Allocator (PMEM)
This is used to manage large physically contiguous regions of memory shared between userspace and kernel drivers.
Ashmem vs Pmem
- Ashmem uses virtual memory whereas Pmem uses physically contiguous memory.
- With Ashmem, we have
reference counted
objects that can be shared between multiple processes. - Pmem doesnt work that way because it needs to maintain physical to virtual mapping. This requires the process which allocates a pmem heap to hold the FD until all other references are dropped.
Android Boot Process
These are the steps in which Android Booting happens:
Bootrom
, the write-protected flash rom which is embedded inside the processor's chip runs the very first code when an android phone is powered up.Bootloader
is started byBootrom
which executes all boot specific setup ie, copies OS related programs into the main memory before firing up the kernel.Kernel
starts up scheduling and loading drivers, setup cache , mounts all file systems, and finally executes something calledinit
which is essentially the first process.init
is the initial process which does things like mounting important directories like/sys
,/dev
and/proc
and finally runs theinit.rc
script.- The
init.rc
is responsible for starting up important native daemons like theZygote process
,Service Manager
,Media Server
etc. Android Runtime
is started by theinit
process withapp_process
command which tells it to start the process virtual machine -Art
orDalvik
and finally call thezygote
processes' main function.
A little more about Zygote Process
Zygote
is a special android process that enables shared code across process VM in contrast to Java VM where each instance has it's own copy of all shared library class files and heap objects.
Zygote preloads all classes and resources which an app may potentially need at runtime into system's memory when it first starts. This technique speeds up loading of apps. After loading all app related stuff, zygote listens for connections on it's socket for requests of any new app starts. When a new app requests to start , zygote forks itself and launches the app. The zygote
process serves as the parent
to all android apps.
Zygote gets its forking feature from the Linux kernel implementation of copy-on-write
technique. All processes starting from zygote use it's own copy. It doesnt actually copy anything , instead it maps pages of new process over those of the parent process and makes copies only when a new process writes on that page.
System Server
is the first process started by Zygote
. After starting , it lives on as a separate process from it's parent. It starts initializing all system services.
Pre-requisites
Download and install latest version of Android Studio by following this wonderful blog.
For basic creating android virtual emulator , follow this blog.
Now that everything is ready and set , let's get to some business.
Making system writeable
To run gdbserver
on our device , we need to first make the /system
folder writeable, by default , it is read-only.
By default , even if we run adb
as root , a couple of times , while trying to mount /system
as writeable we're hit with the /system not in /proc/mounts
as the /system
partition is made read only
in the very booting up of the emulator itself.
To fix that ,we fire up the emulator with -writable system
flag , and then we can remount system as writeable with adb root.
To view list of active devices , run
adb-devices
The output should look something like this since we have just one device connected.
List of devices attached
emulator-5554 device
You can simply get into the device with
abd -s emulator-5554 shell
Note that emulator-5554
is to be replaced with the name of the device we wish to connect to.
To get a list of all active emulators , navigate to the /path/to/Android/Sdk/
and run -
~/Android/Sdk/tools ❯ ./emulator -list-avds
Cyb0rG
Pixel_3a_API_30_x86
The output should be something similar.
Now , to fire up our emulator with writeable system , run
./emulator -avd Cyb0rG -writable-system
Again , Cyb0rG
should be replaced with the name of the desired avd.
After that, we run adb as root and remount it because it is by default mounted as read-only
even if -writable-system
is used.
adb root
adb remount
adb -s emulator-5554 shell
Finally, inside the device , mount the /system
as read-write.
mount -o rw,remount /system
mount -o rw,remount /
Setting up the gdbserver
Verify the architecture of your connected device with uname -m
inside the adb shell.
Navigate to the ndk
folder which should ideally be located in the Sdk
folder itself. Once there , you will find a prebuilt
folder in which we find gdbserver
for various architectures.
Since I'm running the emulator on x86 , this is the path to gdbserver in my machine.
~/Android/Sdk/ndk/22.1.7171670/prebuilt/android-x86_64/gdbserver
Once here, we push gdbserver
into our device with adb push gdbserver /system/bin
.
Now that we have pushed gdbserver
, we can easily attach to any process by it's process id. We get the pid
of a process with ps aux | grep <process_name>
.
gdbserver :8888 --attach 2741
Replace 2741
with the pid
of the process you wish to debug.
After that , we port forward from adb shell to our host machine so that we can debug from the comforts of our local machine.
adb forward tcp:8888 tcp:8888
Now, all that is left is to fire up gdb , and run target remote :8888
.
Conclusion
That's all there is to setup gdb to debug native android applications.