From 1f925cd7424b3f4e797eb82f29353dc2b3cd3956 Mon Sep 17 00:00:00 2001 From: cfig Date: Thu, 11 Aug 2022 18:21:20 +0800 Subject: [PATCH] Issue #92, Issue #95: support ext4/erofs image unpack --- .github/workflows/main.yml | 2 +- README.md | 2 +- .../main/kotlin/utils/EnvironmentVerifier.kt | 12 ++ .../src/main/kotlin/utils/SparseImgParser.kt | 137 ++++++++++++++++-- helper/src/main/kotlin/cfig/helper/Helper.kt | 2 +- 5 files changed, 142 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a47f53..6fe314c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: java-version: 11 - name: apt - run: sudo apt install device-tree-compiler + run: sudo apt install device-tree-compiler p7zip-full # Runs a single command using the runners shell - name: Unit Test diff --git a/README.md b/README.md index ffe7edb..5383607 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A tool for reverse engineering Android ROM images. #### install required packages -Linux: `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python3 python-is-python3` +Linux: `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python3 python-is-python3 p7zip-full` Mac: `brew install lz4 xz dtc` diff --git a/bbootimg/src/main/kotlin/utils/EnvironmentVerifier.kt b/bbootimg/src/main/kotlin/utils/EnvironmentVerifier.kt index 62c5994..5e8fa80 100644 --- a/bbootimg/src/main/kotlin/utils/EnvironmentVerifier.kt +++ b/bbootimg/src/main/kotlin/utils/EnvironmentVerifier.kt @@ -69,6 +69,18 @@ class EnvironmentVerifier { return true } + val has7z: Boolean + get(): Boolean { + try { + Runtime.getRuntime().exec("7z i") + log.debug("7z available") + } catch (e: Exception) { + log.warn("7z not installed") + return false + } + return true + } + val hasDtc: Boolean get(): Boolean { try { diff --git a/bbootimg/src/main/kotlin/utils/SparseImgParser.kt b/bbootimg/src/main/kotlin/utils/SparseImgParser.kt index 8acd6fb..c7e4d1d 100644 --- a/bbootimg/src/main/kotlin/utils/SparseImgParser.kt +++ b/bbootimg/src/main/kotlin/utils/SparseImgParser.kt @@ -16,13 +16,17 @@ package cfig.utils import avb.blob.Footer import cc.cfig.io.Struct +import cfig.bootimg.Common.Companion.deleleIfExists import cfig.helper.Helper import cfig.helper.Helper.Companion.check_call +import cfig.helper.Helper.Companion.check_output import cfig.packable.IPackable import com.fasterxml.jackson.databind.ObjectMapper +import de.vandermeer.asciitable.AsciiTable import org.slf4j.LoggerFactory import java.io.File import java.io.FileInputStream +import java.util.* class SparseImgParser : IPackable { override val loopNo: Int @@ -38,22 +42,56 @@ class SparseImgParser : IPackable { } override fun capabilities(): List { - return listOf("^(system|system_ext|system_other|vendor|product|cache|userdata|super|oem|vendor_dlkm|odm_dlkm)\\.img$") + return listOf( + "^(system|system_ext|system_other|system_dlkm)\\.img$", + "^(vendor|vendor_dlkm|product|cache|userdata|super|oem|odm|odm_dlkm)\\.img$" + ) } override fun unpack(fileName: String) { clear() + var target = fileName if (isSparse(fileName)) { - simg2img(fileName, "$fileName.unsparse") + val tempFile = UUID.randomUUID().toString() + outerFsType = "sparse" + val rawFile = "$workDir${File(fileName).nameWithoutExtension}" + simg2img(fileName, tempFile) + target = if (isExt4(tempFile)) { + innerFsType = "ext4" + "$rawFile.ext4" + } else if (isErofs(tempFile)) { + innerFsType = "erofs" + "$rawFile.erofs" + } else { + "$rawFile.raw" + } + File(tempFile).renameTo(File(target)) } else if (isExt4(fileName)) { - log.info("$fileName is raw ext4 image") - } else { - log.info("$fileName is raw image") + outerFsType = "ext4" + innerFsType = "ext4" + } else if (isErofs(fileName)) { + outerFsType = "erofs" + innerFsType = "erofs" + } + when (innerFsType) { + "ext4" -> { + extractExt4(target) + } + + "erofs" -> { + extraceErofs(target) + } + + else -> { + log.warn("unsuported image type: $innerFsType") + } } + File("${workDir}mount").mkdir() + printSummary(fileName) } override fun pack(fileName: String) { - img2simg("$fileName.unsparse", "$fileName.new") + TODO("not implemented") } // invoked solely by reflection @@ -76,16 +114,12 @@ class SparseImgParser : IPackable { private fun simg2img(sparseIn: String, flatOut: String) { log.info("parsing Android sparse image $sparseIn ...") "$simg2imgBin $sparseIn $flatOut".check_call() - "file $sparseIn".check_call() - "file $flatOut".check_call() log.info("parsed Android sparse image $sparseIn -> $flatOut") } private fun img2simg(flatIn: String, sparseOut: String) { log.info("transforming image to Android sparse format: $flatIn ...") "$img2simgBin $flatIn $sparseOut".check_call() - "file $flatIn".check_call() - "file $sparseOut".check_call() log.info("transformed Android sparse image: $flatIn -> $sparseOut") } @@ -93,6 +127,11 @@ class SparseImgParser : IPackable { TODO("not implemented") } + fun clear(fileName: String) { + super.clear() + File(fileName).deleleIfExists() + } + private fun isSparse(fileName: String): Boolean { val magic = Helper.Companion.readFully(fileName, 0, 4) return Struct(">I").pack(SPARSE_MAGIC).contentEquals(magic) @@ -104,7 +143,85 @@ class SparseImgParser : IPackable { return Struct(">h").pack(0x53ef).contentEquals(magic) } + // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/magic.h#L23 + private fun isErofs(fileName: String): Boolean { + val magic = Helper.readFully(fileName, 1024, 4) + return Struct(">I").pack(0xe2e1f5e0).contentEquals(magic) + } + + private fun extractExt4(fileName: String) { + if (EnvironmentVerifier().has7z) { + val stem = File(fileName).nameWithoutExtension + val outStr = "7z x $fileName -y -o$workDir$stem".check_output() + File("$workDir/$stem.log").writeText(outStr) + } else { + log.warn("Please install 7z for ext4 extraction") + } + } + + private fun extraceErofs(fileName: String) { + log.info("sudo mount $fileName -o loop -t erofs ${workDir}mount") + } + + private fun printSummary(fileName: String) { + val stem = File(fileName).nameWithoutExtension + val tail = AsciiTable().apply { + addRule() + addRow("To view erofs contents:") + } + val tab = AsciiTable().apply { + addRule() + addRow("What", "Where") + addRule() + addRow("image ($outerFsType)", fileName) + ("$workDir$stem.ext4").let { ext4 -> + if (File(ext4).exists()) { + addRule() + addRow("converted image (ext4)", ext4) + } + } + ("$workDir$stem.erofs").let { + if (File(it).exists()) { + addRule() + addRow("converted image (erofs)", it) + tail.addRule() + tail.addRow("sudo mount $it -o loop -t erofs ${workDir}mount") + tail.addRule() + } else if (innerFsType == "erofs") { + tail.addRule() + tail.addRow("sudo mount $fileName -o loop -t erofs ${workDir}mount") + tail.addRule() + } + } + ("$workDir$stem").let { + if (File(it).exists()) { + addRule() + if (File(it).isFile) { + addRow("converted image (raw)", it) + } else { + addRow("extracted content", it) + } + } + } + ("$workDir$stem.log").let { + if (File(it).exists()) { + addRule() + addRow("log", it) + } + } + if (innerFsType == "erofs") { + addRule() + addRow("mount point", "${workDir}mount") + } + addRule() + } + log.info("\n" + tab.render() + "\n" + if (innerFsType == "erofs") tail.render() else "") + } + companion object { private val SPARSE_MAGIC: UInt = 0x3aff26edu + private val workDir = Helper.prop("workDir") + private var outerFsType = "raw" + private var innerFsType = "raw" } } diff --git a/helper/src/main/kotlin/cfig/helper/Helper.kt b/helper/src/main/kotlin/cfig/helper/Helper.kt index 272fac7..e29946b 100644 --- a/helper/src/main/kotlin/cfig/helper/Helper.kt +++ b/helper/src/main/kotlin/cfig/helper/Helper.kt @@ -200,7 +200,7 @@ class Helper { it.streamHandler = PumpStreamHandler(outputStream) it.execute(CommandLine.parse(this)) } - log.info(outputStream.toString().trim()) + log.debug(outputStream.toString().trim()) return outputStream.toString().trim() }