Skip to main content

Patching Dynamic Partitions in Android Super Image

·8 mins

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

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 to Artifacts and download cvd-host_package.tar.gz
    • Untar the package and locate the binaries in /bin

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 as imjtool’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