Patching Dynamic Partitions in Android Super Image
Table of Contents
Modifying the factory image is one of the oldest tricks to customize the Android. However, recent devices (launched with Android 10+) default to dynamic partitions which merged some common partitions (e.g. system
, product
, vendor
etc.) into a single super.img
. There were scattered discussions online on how to patch a super.img
but many lack essential details to make it work for every device.
This writeup aims to fill the gap and provide a generic method of patching any Android super images.
Required Tools #
imgtool
- Unpack a super image and display its partition schema
- Download: imjtool - Android Internals Book
lpunpack
, lpmake
- Unpack and repack super images
- Build from source: Dynamic Partition Tools - Google Git
- OR download from Android CI
- Pick any
userdebug
commit, go toArtifacts
and downloadcvd-host_package.tar.gz
- Untar the package and locate the binaries in
/bin
- Pick any
fallocate
, resize2fs
, dumpe2fs
, e2fsck
- Interact with the internal partition’s file systems
- Available in common Linux distributions
Steps #
Use imjtool
to extract the partitions of super.img
. Notice that partitions in a super image are split into several groups. The exact grouping varies across devices/OS versions, so one should always use imjtool
to check first.
Take note of the output as it’s required for repacking later.
*You may also use the official lpunpack
for extraction but it can’t display the group schema.
> wget http://newandroidbook.com/tools/imjtool.tgz && tar xzvf imjtool.tgz
> ./imjtool.ELF64 super.img extract
liblp dynamic partition (super.img) - Blocksize 0x1000, 3 slots
LP MD Header @0x3000, version 10.2, with 14 logical partitions on block device of 3584 GB, at partition super, first sector: 0x800
Partitions @0x3100 in 5 groups:
Group 0: default
Group 1: google_system_dynamic_partitions_a
Name: product_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x100000 spanning 1 extents and 290 MB) - extracted
Name: system_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x12400000 spanning 1 extents and 943 MB) - extracted
Name: system_ext_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x4ec00000 spanning 1 extents and 513 MB) - extracted
Group 2: google_system_dynamic_partitions_b
Name: product_b (read-only, empty) - extracted
Name: system_b (read-only, Linux Ext2/3/4/? Filesystem Image, @0x4d400000 spanning 1 extents and 23 MB) - extracted
Name: system_ext_b (read-only, empty) - extracted
Group 3: google_vendor_dynamic_partitions_a
Name: odm_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x6ee00000 spanning 1 extents and 772 KB) - extracted
Name: vendor_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x6ef00000 spanning 1 extents and 310 MB) - extracted
Name: vendor_dlkm_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x82600000 spanning 1 extents and 340 KB) - extracted
Name: odm_dlkm_a (read-only, Linux Ext2/3/4/? Filesystem Image, @0x82700000 spanning 1 extents and 340 KB) - extracted
Group 4: google_vendor_dynamic_partitions_b
Name: odm_b (read-only, empty) - extracted
Name: vendor_b (read-only, empty) - extracted
Name: vendor_dlkm_b (read-only, empty) - extracted
Name: odm_dlkm_b (read-only, empty) - extracted
# Or use lpunpack if you just want the content
> ./bin/lpunpack super.img
> ls -lah
total 9.1G
drwxr-xr-x 2 root root 4.0K Mar 2 06:45 .
drwxr-xr-x 16 vsoc-01 vsoc-01 4.0K Mar 2 06:45 ..
-rw-r--r-- 1 root root 772K Mar 2 06:45 odm_a.img
-rw-r--r-- 1 root root 0 Mar 2 06:45 odm_b.img
-rw-r--r-- 1 root root 340K Mar 2 06:45 odm_dlkm_a.img
-rw-r--r-- 1 root root 0 Mar 2 06:45 odm_dlkm_b.img
-rw-r--r-- 1 root root 291M Mar 2 06:45 product_a.img
-rw-r--r-- 1 root root 0 Mar 2 06:45 product_b.img
-rw-r--r-- 1 vsoc-01 vsoc-01 7.0G Jan 1 2009 super.img
-rw-r--r-- 1 root root 944M Mar 2 06:45 system_a.img
-rw-r--r-- 1 root root 24M Mar 2 06:45 system_b.img
-rw-r--r-- 1 root root 514M Mar 2 06:45 system_ext_a.img
-rw-r--r-- 1 root root 0 Mar 2 06:45 system_ext_b.img
-rw-r--r-- 1 root root 311M Mar 2 06:45 vendor_a.img
-rw-r--r-- 1 root root 0 Mar 2 06:45 vendor_b.img
-rw-r--r-- 1 root root 340K Mar 2 06:45 vendor_dlkm_a.img
-rw-r--r-- 1 root root 0 Mar 2 06:45 vendor_dlkm_b.img
Let’s say we what to modify system_a.img
. Firstly, check the file format. Although it shows ext2
, but it’s in fact an ext4
image.
> file system_a.img
system_a.img: Linux rev 1.0 ext2 filesystem data, UUID=1700acd3-e598-5c89-9df1-b39764b67b9a (extents) (large files) (huge files)
All imges have been shrunk to their minimum sizes. In other words, there’s zero space left in the partition of which the image file holds. As we would like to add more files to it, we need to firstly enlarge the file to create some space, then expand the file system accordingly.
# Enlarge the file to 2G. It can be any size
> fallocate -l 2G system_a.img
> ls -lah system_a.img
-rw-r--r-- 1 root root 2.0G Mar 2 06:49 system_a.img
# Resize the filesystem
> resize2fs system_a.img 2G
resize2fs 1.44.5 (15-Dec-2018)
Resizing the filesystem on system_a.img to 524288 (4k) blocks.
The filesystem on system_a.img is now 524288 (4k) blocks long.
Before we proceed, let’s see if we can mount the image. Surprisingly, it seems we could neither mount it as ext2
nor ext4
. What’s going on here?
> sudo mount -t ext4 -o loop system_a.img system
mount: /home/vsoc-01/test/system: wrong fs type, bad option, bad superblock on /dev/loop5, missing codepage or helper program, or other error.
> sudo mount -t ext2 -o loop system_a.img system
mount: /home/vsoc-01/test/system: wrong fs type, bad option, bad superblock on /dev/loop5, missing codepage or helper program, or other error.
It turned out that system imgage in Android 10+ is formated with EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS, found by @topjohnwu. In other words, it’s read-only. We can run dumpe2fs
and check “Filesystem features” to confirm that the image has shared_blocks
turned on.
> dumpe2fs system_a.img
dumpe2fs 1.44.5 (15-Dec-2018)
Filesystem volume name: /
Last mounted on: /
Filesystem UUID: 1700acd3-e598-5c89-9df1-b39764b67b9a
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr dir_index filetype extent sparse_super large_file huge_file uninit_bg dir_nlink extra_isize shared_blocks
Luckily shared_blocks
is merely a restriction at the file level. We can simply run e2fsck
to remove it.
> e2fsck -E unshare_blocks system_a.img
e2fsck 1.44.5 (15-Dec-2018)
Pass 1: Checking inodes, blocks, and sizes
...
File /system/apex/com.android.apex.cts.shim.apex (inode #53, mod time Thu Jan 1 00:00:00 2009)
has 5 multiply-claimed block(s), shared with 10 file(s):
/system/apex/com.android.vndk.current.apex (inode #74, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.virt.apex (inode #73, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.tzdata.apex (inode #72, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.sdkext.apex (inode #70, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.scheduling.apex (inode #69, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.runtime.apex (inode #68, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.os.statsd.apex (inode #65, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.i18n.apex (inode #59, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.conscrypt.apex (inode #57, mod time Thu Jan 1 00:00:00 2009)
/system/apex/com.android.appsearch.apex (inode #54, mod time Thu Jan 1 00:00:00 2009)
...
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 3A: Optimizing directories
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/: ***** FILE SYSTEM WAS MODIFIED *****
/: 2863/5888 files (9.8% non-contiguous), 248367/524288 blocks
# Check that the shared_block feature is gone
> dumpe2fs system_a.img
dumpe2fs 1.44.5 (15-Dec-2018)
Filesystem volume name: /
Last mounted on: /
Filesystem UUID: 1700acd3-e598-5c89-9df1-b39764b67b9a
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr dir_index filetype extent sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags: signed_directory_hash
Now we can mount the partition and do whatever modifications we like.
> sudo mount -t ext4 -o loop system_a.img system
> ls system
acct bugreports d debug_ramdisk init lost+found odm postinstall sdcard sys vendor
apex cache data dev init.environ.rc metadata odm_dlkm proc second_stage_resources system vendor_dlkm
bin config data_mirror etc linkerconfig mnt oem product storage system_ext
> sudo umount system
Once finished, we can start to prepare the image for repacking. The idea is simple: we firstly check and fix the errors in the file system (there’ll always be some, probably due to bugs in the previous tools), then shrink the image file back to its minimum size.
> e2fsck -yf system_a.img
e2fsck 1.44.5 (15-Dec-2018)
Pass 1: Checking inodes, blocks, and sizes
Inode 1227 extent tree (at level 2) could be narrower. Optimize? yes
Pass 1E: Optimizing extent trees
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/: ***** FILE SYSTEM WAS MODIFIED *****
/: 2864/5888 files (11.0% non-contiguous), 248364/524288 blocks
# Shrink the file system to minimum
> ls -lah system_a.img
-rw-r--r-- 1 root root 2.0G Mar 2 08:08 system_a.img
> resize2fs -M system_a.img
resize2fs 1.44.5 (15-Dec-2018)
Resizing the filesystem on system_a.img to 248714 (4k) blocks.
The filesystem on system_a.img is now 248714 (4k) blocks long.
> ls -lah system_a.img
-rw-r--r-- 1 root root 972M Mar 2 08:09 system_a.img
Before we repack, we need to know the sizes of all images in bytes.
# Get the size of all images
root@matrisea-cvd-jucd5n:~/test# stat -c '%n %s' *.img
odm_a.img 790528
odm_b.img 0
odm_dlkm_a.img 348160
odm_dlkm_b.img 0
product_a.img 304930816
product_b.img 0
super.img 7516192768
system_a.img 1018732544
system_b.img 24203264
system_ext_a.img 537972736
system_ext_b.img 0
vendor_a.img 325787648
vendor_b.img 0
vendor_dlkm_a.img 348160
vendor_dlkm_b.img 0
Our last quest is to craft a lengthy lpmake
command to build the new super.img
. Essentially that translates to three things:
- Define the groups
- Define which partition goes into which group
- Assign image files that correspond to each partition
Most information can be obtained from the previous imjtool
. Some of the flags to take note of are:
--metadata-slots
: same asimjtool
’s output--device-size
: for most phones, 4GB should be enough (4*1024^3 = 4294967296 bytes). It’s okay to leave some extras--group
: format of<name>:<size>
. The size should be the sum of all sub-partitions under the group--partition
: format of<name>:<attributes>:<size>[:group]
, attrs must be ’none’ or ‘readonly’.--image
: for each partition, specify a corresponding image file
Once done, you should now have the new super image.
> ./bin/lpmake --metadata-size 65536\
--device-size=4294967296\
--metadata-slots=3\
--group=google_system_dynamic_partitions_a:2222931968\
--partition=odm_a:none:700416:google_system_dynamic_partitions_a\
--partition=product_a:none:266579968:google_system_dynamic_partitions_a\
--partition=system_a:none:1363767296:google_system_dynamic_partitions_a\
--partition=system_ext_a:none:359391232:google_system_dynamic_partitions_a\
--partition=vendor_a:none:232493056:google_system_dynamic_partitions_a\
--image=odm_a=./odm_a.img\
--image=product_a=./product_a.img\
--image=system_a=./system_a.img\
--image=system_ext_a=./system_ext_a.img\
--image=vendor_a=./vendor_a.img\
--group=google_system_dynamic_partitions_b:24563712\
--partition=odm_b:none:0:google_system_dynamic_partitions_b\
--partition=product_b:none:0:google_system_dynamic_partitions_b\
--partition=system_b:none:24563712:google_system_dynamic_partitions_b\
--partition=system_ext_b:none:0:google_system_dynamic_partitions_b\
--partition=vendor_b:none:0:google_system_dynamic_partitions_b\
--image=odm_b=./odm_b.img\
--image=product_b=./product_b.img\
--image=system_b=./system_b.img\
--image=system_ext_b=./system_ext_b.img\
--image=vendor_b=./vendor_b.img\
--sparse \
--output ./super.new.img
References #
Implementing Dynamic Partitions - Android Source
Editing system.img inside super.img and flashing our modifications - XDA Forum