Advanced OS Customizations & Bootloaders

Reverse Engineering Android UKIs: Extracting Kernels and Ramdisks from Unified Images

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: The Rise of Unified Images in Android

The concept of a Unified Kernel Image (UKI), popularized by projects like systemd-boot, consolidates the Linux kernel, initial ramdisk (initramfs), kernel command line, and sometimes the Device Tree Blob (DTB) into a single executable. While Android doesn’t strictly adhere to the systemd-boot UKI specification, its boot image formats have consistently aimed at unifying kernel and ramdisk components. Modern Android devices, especially those leveraging Generic Kernel Images (GKI) and A/B updates, further abstract the boot process, making component extraction a crucial skill for advanced customization, debugging, and security research.

This guide delves into the methodologies for reverse engineering these ‘unified’ Android boot images, focusing on extracting the core kernel and ramdisk components. Understanding this process is fundamental for anyone looking to build custom recoveries, flash custom kernels, or analyze low-level system behavior.

Understanding Android Boot Image Evolution

Historically, Android’s boot.img served as the primary unified image, containing the kernel, ramdisk, and an optional second stage bootloader. With Android 9 (Project Treble) and especially Android 11+ (GKI), the landscape shifted:

  • Separate Vendor Boot Image: The vendor_boot.img now encapsulates vendor-specific ramdisk components and the DTB.
  • Generic Kernel Image (GKI): The kernel itself often comes as a pre-built, standardized image, potentially bundled with its own DTB.
  • Firmware Payloads: For A/B update devices, entire firmware packages are often distributed as a payload.bin, containing numerous partitions, including the various boot-related images.

Our objective is to extract the kernel (often Image.gz-dtb or zImage) and the root filesystem (ramdisk.cpio.gz) from these consolidated or contained images, regardless of their specific Android boot image format.

Tools of the Trade

To successfully reverse engineer these images, you’ll need a Linux environment and the following tools:

  • binwalk: A fast, easy-to-use tool for analyzing, reverse engineering, and extracting firmware images.
  • dd: A command-line utility for converting and copying files, invaluable for precise extraction by offset.
  • hexdump or xxd: For viewing file contents in hexadecimal and ASCII, crucial for identifying magic numbers.
  • magiskboot: A powerful utility from the Magisk project, capable of unpacking various Android boot image formats.
  • cpio and gzip: For extracting and compressing ramdisks.
  • payload-dumper-go (or similar): For extracting partitions from A/B payload.bin archives.

Step 1: Acquiring the Target Image

The first step is to obtain the relevant boot image. This could be:

  • A standalone boot.img or vendor_boot.img from a factory image.
  • An image extracted from a firmware archive (e.g., a payload.bin from an OTA update).

Extracting from payload.bin

If your firmware comes as a payload.bin, you’ll need to extract the individual partition images first. payload-dumper-go is an excellent tool for this:

./payload-dumper-go -p boot payload.bin

This command would extract the boot.img (and other specified partitions) into a subdirectory. If you need vendor_boot.img, replace boot with vendor_boot.

Step 2: Initial Analysis with binwalk

Once you have your target image (e.g., boot.img or vendor_boot.img), binwalk is your best friend for a quick overview. It can identify embedded filesystems, compressed data (like the kernel), and CPIO archives (the ramdisk).

binwalk -e boot.img

The -e flag attempts to extract known file types. A typical output for an Android boot image might look like this:

DECIMAL       HEXADECIMAL     DESCRIPTION--------------------------------------------------------------------------------0             0x0             Android boot image header0x400000      0x400000        Linux kernel ARM64 EFI executable (little-endian)0x400000      0x400000        gzip compressed data, maximum compression, original size unknown0x6000000     0x6000000       CPIO archive (SVR4 with no CRC)

From this output, we can deduce:

  • The boot image header is at offset 0x0.
  • The compressed kernel (often Image.gz or Image.gz-dtb) starts around 0x400000.
  • The CPIO ramdisk starts around 0x6000000.

Note these offsets; they will be crucial for manual extraction.

Step 3: Dissecting the Image Manually (Offset-based Extraction)

Even if binwalk -e doesn’t fully extract everything, or if you prefer a more hands-on approach, manual extraction using dd is robust.

Identifying Kernel and Ramdisk Magic Numbers

Before using dd, it’s helpful to confirm the presence of kernel and ramdisk by their magic numbers (signatures) at their suspected offsets. For ARM64 kernels, the `zImage` or `Image.gz-dtb` often starts with `0x01646401` or `0x01666401` (little-endian, so `01 64 64 01` or `01 66 64 01` in hex dump). A CPIO archive typically begins with `0x070701` (`07 07 01 00` for ASCII newc or `0x71C7` for bin`).

Use hexdump to peek at the bytes:

hexdump -C boot.img | less

Scroll to the binwalk-identified offsets to verify.

Extracting the Kernel

Assuming the kernel starts at 0x400000 and its size is known (or estimated), we can use dd. You might need to experiment with count (block size * number of blocks) to get the full kernel without extra data. A safe starting point is to extract generously and then trim. For example, if the kernel is roughly 16-30MB:

dd if=boot.img of=kernel.gz bs=1 skip=$((0x400000)) count=$((20*1024*1024)) # 20MB extraction

If the kernel is compressed, you’ll then decompress it:

mv kernel.gz kernel.img.gzgunzip kernel.img.gz

The result, kernel.img, will be your uncompressed kernel image, potentially containing an embedded DTB.

Extracting the Ramdisk

The ramdisk is typically a gzipped CPIO archive. Using the offset from binwalk (e.g., 0x6000000) and an estimated size (typically 4-64MB for a gzipped CPIO):

dd if=boot.img of=ramdisk.cpio.gz bs=1 skip=$((0x6000000)) count=$((30*1024*1024)) # 30MB extraction

Now, decompress and extract the CPIO archive:

gunzip ramdisk.cpio.gzmkdir ramdisk_extractedcd ramdisk_extractedcpio -idm < ../ramdisk.cpio

You will now have the full root filesystem of the ramdisk in the ramdisk_extracted directory.

Step 4: Advanced Extraction with magiskboot

For Android boot images following standard formats (like boot.img and vendor_boot.img), magiskboot offers a robust and often simpler automated extraction method. It intelligently parses the boot image header and extracts components without requiring manual offset calculations.

magiskboot unpack boot.img

This command will typically create files such as:

  • kernel: The raw kernel image.
  • ramdisk.cpio: The raw CPIO ramdisk archive.
  • dtb: The Device Tree Blob (if separate).
  • header: The boot image header information.
  • recovery_dtbo: If a separate recovery DTBO partition exists.

If you’re dealing with a vendor_boot.img:

magiskboot unpack vendor_boot.img

This will yield vendor_ramdisk.cpio and dtb (if embedded).

Step 5: Analyzing Extracted Components

Analyzing the Kernel

Once you have the uncompressed kernel image (e.g., kernel.img), you can gather more information:

file kernel.imgstrings kernel.img | grep

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner