add dtbo.img parser

also refactor some code structure
pull/31/head
cfig 6 years ago
parent 3c6ad3de94
commit f03315b08a

@ -1,8 +1,8 @@
dist: trusty
language: java
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libblkid-dev
- sudo apt -qq update
- sudo apt install -y libblkid-dev liblz4-tool
script:
- ./gradlew check

@ -0,0 +1,13 @@
t:
external/extract_kernel.py \
--input build/unzip_boot/kernel \
--output-configs kernel_configs.txt \
--output-version kernel_version.txt
t2:
rm -fr dtbo
mkdir dtbo
external/mkdtboimg.py \
dump dtbo.img \
--dtb dtbo/dtb.dump \
--output dtbo/header.dump

@ -26,6 +26,10 @@ Supported images:
- Lollipop (5.0) - Q preview
## 2. Usage
Clone this repo with minimal depth:
git clone https://github.com/cfig/Android_boot_image_editor.git --depth=1
Put your boot.img to current directory, then start gradle 'unpack' task:
cp <original_boot_image> boot.img
@ -104,3 +108,6 @@ https://source.android.com/source/build-numbers.html
kernel info extractor
https://android.googlesource.com/platform/build/+/refs/heads/master/tools/extract_kernel.py
mkdtboimg
https://android.googlesource.com/platform/system/libufdt/

@ -0,0 +1,77 @@
package cfig
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
class EnvironmentVerifier {
val hasXz: Boolean
get() : Boolean {
try {
val outputStream = ByteArrayOutputStream()
val exec = DefaultExecutor()
exec.streamHandler = PumpStreamHandler(outputStream)
exec.execute(CommandLine.parse("xz --version"))
val os = outputStream.toString().trim()
log.debug(os)
log.debug("xz available")
} catch (e: Exception) {
log.warn("'xz' not installed. Please install it manually to analyze DTB files")
if (isMacOS) {
log.warn("For Mac OS: \n\n\tbrew install xz\n")
}
return false
}
return true
}
val hasGzip: Boolean
get(): Boolean {
try {
Runtime.getRuntime().exec("gzip -V")
log.debug("gzip available")
} catch (e: Exception) {
log.warn("gzip unavailable")
return false
}
return true
}
val hasLz4: Boolean
get() : Boolean {
try {
Runtime.getRuntime().exec("lz4 --version")
log.debug("lz4 available")
} catch (e: Exception) {
log.warn("lz4 not installed")
return false
}
return true
}
val hasDtc: Boolean
get(): Boolean {
try {
Runtime.getRuntime().exec("dtc --version")
log.debug("dtc available")
} catch (e: Exception) {
log.warn("'dtc' not installed. Please install it manually to analyze DTB files")
if (isMacOS) {
log.warn("For Mac OS: \n\n\tbrew install dtc\n")
} else {
log.warn("Like this: \n\n\t$ sudo apt install device-tree-compiler")
}
return false
}
return true
}
val isMacOS: Boolean
get() = System.getProperty("os.name").contains("Mac")
companion object {
private val log = LoggerFactory.getLogger("EnvironmentVerifier")
}
}

@ -2,7 +2,10 @@ package avb
import cfig.io.Struct3
import org.junit.Assert
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
/*
https://github.com/cfig/Android_boot_image_editor/blob/master/doc/layout.md#32-avb-footer-vboot-20
@ -55,6 +58,19 @@ data class Footer constructor(
vbMetaSize = info[5] as ULong
}
@Throws(IllegalArgumentException::class)
constructor(image_file: String) : this() {
FileInputStream(image_file).use { fis ->
fis.skip(File(image_file).length() - Footer.SIZE)
val footer = Footer(fis)
this.versionMajor = footer.versionMajor
this.versionMinor = footer.versionMinor
this.originalImageSize = footer.originalImageSize
this.vbMetaOffset = footer.vbMetaOffset
this.vbMetaSize = footer.vbMetaSize
}
}
fun encode(): ByteArray {
return Struct3(FORMAT_STRING).pack(MAGIC,
this.versionMajor,

@ -1,6 +1,7 @@
package cfig
import cfig.bootimg.BootImgInfo
import cfig.dtb_util.DTC
import cfig.kernel_util.KernelExtractor
import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable
@ -11,6 +12,7 @@ import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.lang.IllegalArgumentException
import java.nio.ByteBuffer
import java.nio.ByteOrder
@ -133,6 +135,13 @@ class Parser {
log.info("dtb dumped to ${param.dtb}")
InfoTable.instance.addRule()
InfoTable.instance.addRow("dtb", param.dtb)
//extract DTB
if (EnvironmentVerifier().hasDtc) {
if (DTC().decompile(param.dtb!!, param.dtb + ".src")) {
InfoTable.instance.addRow("\\-- decompiled dts", param.dtb + ".src")
}
}
//extract DTB
} else {
InfoTable.missingParts.add("dtb")
if (info2.headerVersion > 1U) {
@ -149,7 +158,11 @@ class Parser {
fun verifyAVBIntegrity(fileName: String, avbtool: String) {
val cmdline = "$avbtool verify_image --image $fileName"
log.info(cmdline)
DefaultExecutor().execute(CommandLine.parse(cmdline))
try {
DefaultExecutor().execute(CommandLine.parse(cmdline))
} catch (e: Exception) {
throw IllegalArgumentException("$fileName failed integrity check by \"$cmdline\"")
}
}
fun readShort(iS: InputStream): Short {

@ -0,0 +1,51 @@
package cfig.dtb_util
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory
class DTC {
private val log = LoggerFactory.getLogger(DTC::class.java)
fun decompile(dtbFile: String, outFile: String): Boolean {
log.info("parsing DTB: $dtbFile")
val cmd = CommandLine.parse("dtc -I dtb -O dts").let {
it.addArguments("$dtbFile")
it.addArguments("-o $outFile")
}
CommandLine.parse("fdtdump").let {
it.addArguments("$dtbFile")
}
DefaultExecutor().let {
try {
it.execute(cmd)
log.info(cmd.toString())
} catch (e: org.apache.commons.exec.ExecuteException) {
log.error("can not parse DTB: $dtbFile")
return false
}
}
return true
}
fun compile(dtsFile: String, outFile: String): Boolean {
log.info("compiling DTS: $dtsFile")
val cmd = CommandLine.parse("dtc -I dts -O dtb").let {
it.addArguments("$dtsFile")
it.addArguments("-o $outFile")
}
DefaultExecutor().let {
try {
it.execute(cmd)
log.info(cmd.toString())
} catch (e: org.apache.commons.exec.ExecuteException) {
log.error("can not compile DTB: $dtsFile")
return false
}
}
return true
}
}

@ -1,7 +1,7 @@
package cfig.kernel_util
import cfig.EnvironmentVerifier
import cfig.InfoTable
import de.vandermeer.asciitable.AsciiTable
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.slf4j.Logger
@ -12,31 +12,8 @@ class KernelExtractor {
val log: Logger = LoggerFactory.getLogger("KernelExtractor")
fun envCheck(): Boolean {
try {
Runtime.getRuntime().exec("lz4 --version")
log.debug("lz4 available")
} catch (e: Exception) {
log.warn("lz4 unavailable")
return false
}
try {
Runtime.getRuntime().exec("xz --version")
log.debug("xz available")
} catch (e: Exception) {
log.warn("xz unavailable")
return false
}
try {
Runtime.getRuntime().exec("gzip -V")
log.debug("gzip available")
} catch (e: Exception) {
log.warn("gzip unavailable")
return false
}
return true
val envv = EnvironmentVerifier()
return envv.hasLz4 && envv.hasXz && envv.hasGzip
}
fun run(fileName: String, workDir: File? = null) {

@ -0,0 +1,50 @@
package cfig.packable
import cfig.*
import cfig.bootimg.BootImgInfo
import de.vandermeer.asciitable.AsciiTable
import org.slf4j.LoggerFactory
import java.io.File
import java.lang.IllegalArgumentException
class BootImgParser : IPackable {
private val log = LoggerFactory.getLogger(BootImgParser::class.java)
override fun capabilities(): List<String> {
return listOf("^boot\\.img$", "^recovery\\.img$")
}
override fun unpack(fileName: String) {
if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively()
File(UnifiedConfig.workDir).mkdirs()
try {
val info = Parser().parseBootImgHeader(fileName, avbtool = "avb/avbtool")
InfoTable.instance.addRule()
InfoTable.instance.addRow("image info", ParamConfig().cfg)
if (info.signatureType == BootImgInfo.VerifyType.AVB) {
log.info("continue to analyze vbmeta info in $fileName")
Avb().parseVbMeta(fileName)
InfoTable.instance.addRule()
InfoTable.instance.addRow("AVB info", Avb.getJsonFileName(fileName))
}
Parser().extractBootImg(fileName, info2 = info)
InfoTable.instance.addRule()
val tableHeader = AsciiTable().apply {
addRule()
addRow("What", "Where")
addRule()
}
log.info("\n\t\t\tUnpack Summary of $fileName\n{}\n{}", tableHeader.render(), InfoTable.instance.render())
log.info("Following components are not present: ${InfoTable.missingParts}")
} catch (e: IllegalArgumentException) {
log.error(e.message)
log.error("Parser can not continue")
}
}
override fun pack(fileName: String) {
Packer().pack(mkbootfsBin = "mkbootfs/build/exe/mkbootfs/mkbootfs")
Signer.sign(avbtool = "avb/avbtool", bootSigner = "boot_signer/build/libs/boot_signer.jar")
}
}

@ -0,0 +1,85 @@
package cfig.packable
import cfig.EnvironmentVerifier
import cfig.UnifiedConfig
import cfig.dtb_util.DTC
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileInputStream
import java.util.*
class DtboParser(val workDir: File) : IPackable {
constructor() : this(File("."))
private val log = LoggerFactory.getLogger(DtboParser::class.java)
private val envv = EnvironmentVerifier()
override fun capabilities(): List<String> {
return listOf("^dtbo\\.img$")
}
override fun unpack(fileName: String) {
val outputDir = UnifiedConfig.workDir
val dtbPath = File("$outputDir/dtb").path!!
val headerPath = File("$outputDir/dtbo.header").path!!
val cmd = CommandLine.parse("external/mkdtboimg.py dump $fileName").let {
it.addArguments("--dtb $dtbPath")
it.addArguments("--output $headerPath")
}
DefaultExecutor().let {
it.workingDirectory = this.workDir
try {
log.info(cmd.toString())
it.execute(cmd)
} catch (e: org.apache.commons.exec.ExecuteException) {
log.error("can not parse $fileName")
return
}
}
val props = Properties()
props.load(FileInputStream(File(headerPath)))
if (envv.hasDtc) {
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {
val inputDtb = "$dtbPath.$i"
val outputSrc = File(UnifiedConfig.workDir + "/" + File(inputDtb).name + ".src").path
DTC().decompile(inputDtb, outputSrc)
}
} else {
log.error("'dtc' is unavailable, task aborted")
}
}
override fun pack(fileName: String) {
if (!envv.hasDtc) {
log.error("'dtc' is unavailable, task aborted")
return
}
val headerPath = File("${UnifiedConfig.workDir}/dtbo.header").path!!
val props = Properties()
props.load(FileInputStream(File(headerPath)))
val cmd = CommandLine.parse("external/mkdtboimg.py create $fileName.clear").let {
it.addArguments("--version=1")
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {
val dtsName = File(UnifiedConfig.workDir + "/dtb.$i").path
it.addArguments("$dtsName")
}
it
}
DefaultExecutor().let {
it.workingDirectory = this.workDir
try {
log.info(cmd.toString())
it.execute(cmd)
} catch (e: org.apache.commons.exec.ExecuteException) {
log.error("can not parse $fileName")
return
}
}
}
}

@ -0,0 +1,9 @@
package cfig.packable
interface IPackable {
fun capabilities(): List<String> {
return listOf("^dtbo\\.img$")
}
fun unpack(fileName: String = "dtbo.img")
fun pack(fileName: String = "dtbo.img")
}

@ -0,0 +1,56 @@
package cfig.packable
import cfig.UnifiedConfig
import org.slf4j.LoggerFactory
import java.io.File
import java.util.regex.Pattern
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
class PackableLauncher
fun main(args: Array<String>) {
val log = LoggerFactory.getLogger(PackableLauncher::class.java)
val packablePool = mutableMapOf<List<String>, KClass<IPackable>>()
listOf(DtboParser(), VBMetaParser(), BootImgParser()).forEach {
packablePool.put(it.capabilities(), it::class as KClass<IPackable>)
}
packablePool.forEach {
log.debug("" + it.key + "/" + it.value)
}
var targetFile: String? = null
var targetHandler: KClass<IPackable>? = null
run found@{
File(".").listFiles().forEach { file ->
packablePool.forEach { p ->
for (item in p.key) {
if (Pattern.compile(item).matcher(file.name).matches()) {
log.debug("Found: " + file.name + ", " + item)
targetFile = file.name
targetHandler = p.value
return@found
}
}
}
}
}
if (targetHandler != null) {
log.warn("Active image target: $targetFile")
when (args[0]) {
"unpack" -> {
if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively()
File(UnifiedConfig.workDir).mkdirs()
targetHandler!!.createInstance().unpack(targetFile!!)
}
"pack" -> {
targetHandler!!.createInstance().pack(targetFile!!)
}
else -> {
log.error("Unknown cmd: " + args[0])
}
}
} else {
log.warn("Nothing to do")
}
}

@ -0,0 +1,17 @@
package cfig.packable
import cfig.Avb
class VBMetaParser: IPackable {
override fun capabilities(): List<String> {
return listOf("^vbmeta\\.img$")
}
override fun unpack(fileName: String) {
Avb().parseVbMeta(fileName)
}
override fun pack(fileName: String) {
Avb().packVbMetaWithPadding()
}
}

@ -0,0 +1,34 @@
import cfig.EnvironmentVerifier
import org.junit.Test
import org.junit.Assert.*
import org.junit.Before
class EnvironmentVerifierTest {
private val envv = EnvironmentVerifier()
@Test
fun getHasLz4() {
val hasLz4 = envv.hasLz4
println("hasLz4 = $hasLz4")
}
@Test
fun getHasDtc() {
val hasDtc = envv.hasDtc
println("hasDtc = $hasDtc")
}
@Test
fun getHasXz() {
val hasXz = envv.hasXz
println("hasXz = $hasXz")
}
@Test
fun getGzip() {
val h = envv.hasGzip
println("hasGzip = $h")
}
}

@ -195,6 +195,20 @@ task rr {
}
}
task u(type: JavaExec, dependsOn: ["bbootimg:jar"]) {
main = "cfig.packable.PackableLauncherKt"
classpath = files("bbootimg/build/libs/bbootimg.jar")
maxHeapSize '512m'
args "unpack"
}
task p(type: JavaExec, dependsOn: ["bbootimg:jar", "mkbootfs:mkbootfsExecutable"]) {
main = "cfig.packable.PackableLauncherKt"
classpath = files("bbootimg/build/libs/bbootimg.jar")
maxHeapSize '512m'
args "pack"
}
int parseGradleVersion(String version) {
Pattern VERSION_PATTERN = Pattern.compile("((\\d+)(\\.\\d+)+)(-(\\p{Alpha}+)-(\\w+))?(-(SNAPSHOT|\\d{14}([-+]\\d{4})?))?")
Matcher matcher = VERSION_PATTERN.matcher(version)

@ -0,0 +1,12 @@
1c1
< #!/usr/bin/env python
---
> #!/usr/bin/env python2.7
181c181
< "Cannot extract kernel configs in {}".format(args.input.name))
---
> "Cannot extract kernel configs in {}\n".format(args.input.name))
189c189
< "Cannot extract kernel versions in {}".format(args.input.name))
---
> "Cannot extract kernel versions in {}\n".format(args.input.name))

1003
external/mkdtboimg.py vendored

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit 3cddd2559cb7965abf96f2bbd6b3469a959abf63
Subproject commit b33e958f598f8cb56df60a6f50654c09e85b4996

@ -0,0 +1,5 @@
#!/bin/bash
set -x
git submodule init
git submodule update
Loading…
Cancel
Save