Setup
Purchase
The following is written with a pixel 9 bought from the google store without financing. Do not unlock the bootloader or root your personal use cellphone. Unlocking the bootloader will erase data on the pixel 9 and if flashed incorrectly can brick the device.
- Buying from sources other than Google can hinder unlocking of the bootloader.
- It has been reported Verizon disallows unlocking the bootloader of financed pixel 9 and when paid off will require you take additional actions (calling/emailing) for unlocking the bootloader.
- Note that carrier locking is different than bootloader locking
If you don't already have a cable to connect the pixel to a laptop I would suggest the 4-1 100W cable from xcool since it can connect to all common kinds of USB ports.
Depending on your work the mission darkness faraday bag with window and usb passthrough works well. With max screen brightness you'll be able to observe the pixel 9 screen:

The pixel 9 has a variety of RF attack surface and can be interesting to reverse engineer. While there's cheaper ways to go about this one option is to use a B205 mini from Ettus Research.
- https://store.google.com/product/pixel_9
- https://mosequipment.com/products/mission-darkness-window-charge-shield-faraday-bag
- https://www.coolgadget.com/products/xcool-cable-monster-usb-c-charger-cable
- https://www.ettus.com/all-products/usrp-b205mini-i/
Unlock Bootloader
Unlocking the bootloader can provide the following:
- Obtain root with Magisk
- Downgrade to older software to develop against known vulns
- Rewrite kernel / vendor code for various purposes
- Recover from future bootloops
1. Enable developer mode:
- Settings -> About Phone
- Click on Build Number until developer mode is enabled
2. Enable OEM unlocking
- Settings -> Developer Options
- Click: OEM unlocking
- Enable: Allow OEM unlocking
3. Install fastboot on a laptop/desktop: fastboot
4. Reboot device into fast boot mode
- Restart device
- Hold volume down during restart process
- Fastboot menu should now be visible
5. Fastboot unlock
This step will erase data
- Connect device via usb to laptop
- On laptop execute: fastboot devices
- ensure serial number is in stdout
- On laptop execute: fastboot flashing unlock
- Pixel 9 screen should display a warning regarding unlocking the bootloader
- Use volume buttons to change: "Do not unlock the bootloader" to "Unlock the bootloader"
- Press power button to trigger a reboot
- Debug menu should show: Device state: unlocked
- Press power button with option: Start
Each restart will display a warning regarding the bootloader being unlocked. Fastboot can be used to lock the bootloader.
- https://android.googlesource.com/platform/system/core/+/master/fastboot
Root
Root is not strictly required but can make your life much easier when working with a pixel. You'll be able to interact with more drivers, search the filesystem (ls /dev), disable selinux and more. Without root you'll still be able to ssh with a laptop over usb and install packages in termux.
With the bootloader unlocked Magisk can be used to provide root.
1. Record the build number to download correct factory image.
- Settings -> About Phone
- Scroll to bottom
- At the time of writing my pixel 9's build number is: BP1A.250505.005. Confirmed that the rooting section works with BP2A.250605.031.A2 (june 2025).
Recording an incorrect build number can brick your device
2. Obtain a factory image from factory images for your device/build number.
Links to each factory image for each variant of the pixel devices:
- tokay aka Pixel 9
- komodo aka Pixel 9 Pro XL
- caiman aka Pixel 9 Pro
- comet aka Pixel 9 Pro Fold
- tegu for Pixel 9a
Take note of image variants for Verizon, DTAG, TW, EMEA, T-Mobile and Telestra. You don't want to mix and match variants (don't flash a Verizon variant if you didn't buy the pixel 9 from verizon). The outcome is not tested.
3. After downloading the image check the sha256 hash matches.
If the hash doesn't match you will likely brick your device
4. Extract init_boot.img from factory image on laptop
- unzip tokay-bp1a.250505.005-factory-5e869188.zip
- unzip image-tokay-bp1a.250505.005.zip
- Observe init_boot.img
5. Download Magisk
- Download the latest apk from releases
6. Connect and Enable USB File Transfer.
- Move init_boot.img to Download folder of pixel's internal storage
- Move Magisk apk to Download folder of pixel's internal storage
7. Install Magisk apk
- Enable: Install unknown apps
- Click Magisk apk in Downloads folder to install
8. Patch init_boot.img from Magisk
- Execute Magisk: Install
- Select and Patch a File
- Select: init_boot.img (if init_boot.img is incorrect device will be bricked after flashing)
- Select: LET'S GO
- A newly created magisk_patched-29000_oMslj.img should be created next to init_boot.img on the pixel
- Transfer magisk_patched-29000_oMslj.img to your laptop
9. Enable Developer Mode / USB Debugging
- Enable developer mode again since it could be wiped after unlocking the bootloader
- Enable USB Debugging for adb
- Trust laptop for usb debugging
10. Reboot into bootloader
- Execute on laptop: adb devices
- A single device (your pixel phone) should be listed from previous command
- Execute on laptop: adb reboot bootloader
- Pixel should restart into fastboot mode
11. Flash patched init_boot.img
- Execute on laptop: fastboot flash init_boot /path/to/patched_init_boot.img
leland@light:~/Downloads/$ fastboot flash init_boot ./magisk_patched-29000_oMsIj.img
Sending 'init_boot_a' (8192 KB) OKAY [ 0.364s]
Writing 'init_boot_a' OKAY [ 0.074s]
Finished. Total time: 0.442s
- Execute on laptop: fastboot reboot
Finsh Setup QoL
1. Install Termux. Download the latest non-beta release for arm64 from
2. Execute in Termux: su
3. Grant Termux Root
- Execute in termux: su
- Magisk prompt will appear: grant termux superuser priv
4. Execute in Termux: pkg install openssh clang vim
5. Execute in Termux (pick password): passwd
6. SSH over usb with adb and termux
- Execute in Termux: sshd
- Execute on laptop: adb forward tcp:8022 tcp:8022
- Execute on laptop: ssh -p 8022 localhost
Enumeration
Background: Drivers
On android and unix in general a special file called a character device driver exists to control physical hardware (gpu, camera, microphones, battery, gps, usb, etc). These files expose an interface for userland to interact with physical hardware/kernel module. This is done by the OS redirecting calls such as open, ioctl, read, write specified by the structure file_operations.
static const struct file_operations kbase_fops = {
.owner = THIS_MODULE,
.open = kbase_open,
.release = kbase_release,
.read = kbase_read,
.poll = kbase_poll,
.unlocked_ioctl = kbase_ioctl,
.compat_ioctl = kbase_ioctl,
.mmap = kbase_mmap,
.check_flags = kbase_check_flags,
.get_unmapped_area = kbase_get_unmapped_area,
};
In the above example userland calls to open of the character device is redirected to kbase_open and ioctl calls to the opened device is defined by kbase_ioctl and so on.
Direct Enumeration
To list character devices (root required):
# find / -type c 2>/dev/null | sort
SELinux Enumeration
SELinux (Security-Enhanced Linux) is a mandatory access control. SELinux specifies rules of how different context can interact. Multiple files can be in a single context. Each file contains context information in xattr.
The context of /dev/mali0 is gpu_device shown by ls -Z
:/ # ls -Z /dev/mali0
u:object_r:gpu_device:s0 /dev/mali0
Code snippet to get current context
#include "stdio.h"
#include "selinux/selinux.h"
#include "stdlib.h"
int main(void) {
//check if selinux enabled
if (is_selinux_enabled() != 1) {
printf("selinux not enabled\n");
return 0;
}
char *ctx = NULL;
if (getcon(&ctx) == 0 && ctx) {
printf("%s\n", ctx);
freecon(ctx);
}
return 0;
}
Compile and execute:
$ clang main.c -lselinux
$ ./a.out
u:r:untrusted_app_27:s0:c32,c257,c512,c768
$ su
# run-as com.untrusted /data/data/com.untrusted/a.out
u:r:runas_app:s0:c33,c257,c512,c768
These rules can be reviewed to help understand the overall system and answer important questions such as: what can I do with an untrusted context?
SELinux rules are stored in a policy file on pixel9 is located /sys/fs/selinux/policy and can be parsed with sesearch. I have dockerized sesearch here with a parser to generate d3 javascript graphs of the contexts and permissions.
- 16.0.0 (BP2A.250605.031.A2, Jun 2025)
- allow and allowxperm rules: policy
- allow and allowxperm rules (character device file): policy
- 15.0.0 (BP1A.250505.005, May 2025)
- allow and allowxperm rules: policy
- allow and allowxperm rules (character device file): policy
- allow and allowxperm rules (character device file and untrusted context): policy
Untrusted Context
Applications have untrusted_app context by default, making untrusted_app and untrusted_app_all highly valuable to review to prioritize time.
run-as in conjuction with a debuggable apk is a convenient way to iteratively execute code in an untrusted context.
- Debug enabled apk (com.untrusted): app-debug.apk
1. Install apk (laptop):
leland@rising pixel9 % adb install app-debug.apk
Performing Streamed Install
Success
2. Prep (on pixel):
# cp a.out /data/data/com.untrusted
# pkg_uid=$(stat -c %u /data/data/com.untrusted)
# chown $pkg_uid:$pkg_uid /data/data/com.untrusted/a.out
# chmod 700 /data/data/com.untrusted/a.out
3. execute from untrusted context:
# run-as com.untrusted /data/data/com.untrusted/a.out
Untrusted Allow Rules: BP2A.250605.031.A2: June 2025
All allow with untrusted context source
Open rules with untrusted context source
edgetpu_device and gxp_device: BP2A.250605.031.A2: June 2025
usb_device and usbaccessory_device allow open: BP2A.250605.031.A2: June 2025
Character Device Context: BP2A.250605.031.A2: June 2025
Graph of character device context with neighbors of distance 1
Note that magisk will be included in this graph if used to root