parent
8bda017e85
commit
716e8363ff
@ -1,8 +0,0 @@
|
||||
package cfig
|
||||
|
||||
import de.vandermeer.asciitable.AsciiTable
|
||||
|
||||
object InfoTable {
|
||||
val instance = AsciiTable()
|
||||
val missingParts = mutableListOf<String>()
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package cfig
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
data class ParamConfig(
|
||||
//file input
|
||||
var kernel: String = UnifiedConfig.workDir + "kernel",
|
||||
var ramdisk: String? = UnifiedConfig.workDir + "ramdisk.img.gz",
|
||||
var second: String? = UnifiedConfig.workDir + "second",
|
||||
var dtbo: String? = UnifiedConfig.workDir + "recoveryDtbo",
|
||||
var dtb: String? = UnifiedConfig.workDir + "dtb",
|
||||
var cfg: String = UnifiedConfig.workDir + "bootimg.json",
|
||||
val mkbootimg: String = "./aosp/system/tools/mkbootimg/mkbootimg.py")
|
||||
|
@ -1,86 +0,0 @@
|
||||
package cfig
|
||||
|
||||
import avb.AVBInfo
|
||||
import avb.alg.Algorithms
|
||||
import cfig.Avb.Companion.getJsonFileName
|
||||
import cfig.bootimg.BootImgInfo
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import org.apache.commons.exec.DefaultExecutor
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
class Signer {
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(Signer::class.java)
|
||||
|
||||
fun sign(avbtool: String, bootSigner: String) {
|
||||
log.info("Loading config from ${ParamConfig().cfg}")
|
||||
val info2 = UnifiedConfig.readBack2()
|
||||
val cfg = ObjectMapper().readValue(File(ParamConfig().cfg), UnifiedConfig::class.java)
|
||||
|
||||
when (info2.signatureType) {
|
||||
BootImgInfo.VerifyType.VERIFY -> {
|
||||
log.info("Signing with verified-boot 1.0 style")
|
||||
val sig = ImgInfo.VeritySignature()
|
||||
val bootSignCmd = "java -jar $bootSigner " +
|
||||
"${sig.path} ${cfg.info.output}.clear " +
|
||||
"${sig.verity_pk8} ${sig.verity_pem} " +
|
||||
"${cfg.info.output}.signed"
|
||||
log.info(bootSignCmd)
|
||||
DefaultExecutor().execute(CommandLine.parse(bootSignCmd))
|
||||
}
|
||||
BootImgInfo.VerifyType.AVB -> {
|
||||
log.info("Adding hash_footer with verified-boot 2.0 style")
|
||||
val ai = ObjectMapper().readValue(File(Avb.getJsonFileName(cfg.info.output)), AVBInfo::class.java)
|
||||
val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())
|
||||
val bootDesc = ai.auxBlob!!.hashDescriptors[0]
|
||||
val newAvbInfo = ObjectMapper().readValue(File(getJsonFileName(cfg.info.output)), AVBInfo::class.java)
|
||||
|
||||
//our signer
|
||||
File(cfg.info.output + ".clear").copyTo(File(cfg.info.output + ".signed"))
|
||||
Avb().addHashFooter(cfg.info.output + ".signed",
|
||||
info2.imageSize,
|
||||
partition_name = bootDesc.partition_name,
|
||||
newAvbInfo = newAvbInfo)
|
||||
//original signer
|
||||
CommandLine.parse("$avbtool add_hash_footer").apply {
|
||||
addArguments("--image ${cfg.info.output}.signed2")
|
||||
addArguments("--partition_size ${info2.imageSize}")
|
||||
addArguments("--salt ${Helper.toHexString(bootDesc.salt)}")
|
||||
addArguments("--partition_name ${bootDesc.partition_name}")
|
||||
addArguments("--hash_algorithm ${bootDesc.hash_algorithm}")
|
||||
addArguments("--algorithm ${alg!!.name}")
|
||||
if (alg.defaultKey.isNotBlank()) {
|
||||
addArguments("--key ${alg.defaultKey}")
|
||||
}
|
||||
newAvbInfo.auxBlob?.let { newAuxblob ->
|
||||
newAuxblob.propertyDescriptor.forEach { newProp ->
|
||||
addArguments(arrayOf("--prop", "${newProp.key}:${newProp.value}"))
|
||||
}
|
||||
}
|
||||
addArgument("--internal_release_string")
|
||||
addArgument(ai.header!!.release_string, false)
|
||||
log.info(this.toString())
|
||||
|
||||
File(cfg.info.output + ".clear").copyTo(File(cfg.info.output + ".signed2"))
|
||||
DefaultExecutor().execute(this)
|
||||
}
|
||||
//TODO: decide what to verify
|
||||
//Parser.verifyAVBIntegrity(cfg.info.output + ".signed", avbtool)
|
||||
//Parser.verifyAVBIntegrity(cfg.info.output + ".signed2", avbtool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mapToJson(m: LinkedHashMap<*, *>): String {
|
||||
val sb = StringBuilder()
|
||||
m.forEach { k, v ->
|
||||
if (sb.isNotEmpty()) sb.append(", ")
|
||||
sb.append("\"$k\": \"$v\"")
|
||||
}
|
||||
return "{ $sb }"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
package cfig
|
||||
|
||||
import cfig.bootimg.BootImgInfo
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import java.io.File
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
data class UnifiedConfig(
|
||||
var info: MiscInfo = MiscInfo(),
|
||||
var kernel: CommArgs = CommArgs(),
|
||||
var ramdisk: CommArgs? = null,
|
||||
var secondBootloader: CommArgs? = null,
|
||||
var recoveryDtbo: CommArgs? = null,
|
||||
var dtb: CommArgs? = null,
|
||||
var signature: Any? = null
|
||||
) {
|
||||
data class CommArgs(
|
||||
var file: String? = null,
|
||||
var position: String = "0",
|
||||
var size: String = "0",
|
||||
var loadOffset: String = "0")
|
||||
|
||||
data class MiscInfo(
|
||||
var output: String = "",
|
||||
var headerVersion: UInt = 0U,
|
||||
var headerSize: UInt = 0U,
|
||||
var loadBase: String = "",
|
||||
var tagsOffset: String = "0",
|
||||
var board: String? = null,
|
||||
var pageSize: UInt = 0U,
|
||||
var cmdline: String = "",
|
||||
var osVersion: String? = null,
|
||||
var osPatchLevel: String? = null,
|
||||
var hash: ByteArray = byteArrayOf(),
|
||||
var verify: BootImgInfo.VerifyType = BootImgInfo.VerifyType.VERIFY,
|
||||
var imageSize: Long = 0)
|
||||
|
||||
fun toBootImgInfo(): BootImgInfo {
|
||||
val ret = BootImgInfo()
|
||||
ret.kernelOffset = this.kernel.loadOffset.removePrefix("0x").toUInt(16)
|
||||
ret.kernelLength = Integer.decode(this.kernel.size).toUInt()
|
||||
|
||||
ret.kernelOffset = this.kernel.loadOffset.removePrefix("0x").toUInt(16)
|
||||
ret.kernelLength = Integer.decode(this.kernel.size).toUInt()
|
||||
|
||||
this.ramdisk?.let {
|
||||
ret.ramdiskOffset = it.loadOffset.removePrefix("0x").toUInt(16)
|
||||
ret.ramdiskLength = it.size.removePrefix("0x").toUInt(16)
|
||||
}
|
||||
|
||||
this.secondBootloader?.let {
|
||||
ret.secondBootloaderOffset = it.loadOffset.removePrefix("0x").toUInt(16)
|
||||
ret.secondBootloaderLength = it.size.removePrefix("0x").toUInt(16)
|
||||
}
|
||||
|
||||
this.recoveryDtbo?.let {
|
||||
ret.recoveryDtboOffset = it.loadOffset.removePrefix("0x").toULong(16)
|
||||
ret.recoveryDtboLength = it.size.removePrefix("0x").toUInt(16)
|
||||
}
|
||||
|
||||
this.dtb?.let {
|
||||
ret.dtbOffset = it.loadOffset.removePrefix("0x").toULong(16)
|
||||
ret.dtbLength = it.size.removePrefix("0x").toUInt(16)
|
||||
}
|
||||
|
||||
ret.headerSize = this.info.headerSize
|
||||
ret.headerVersion = this.info.headerVersion
|
||||
this.info.board?.let { ret.board = it }
|
||||
ret.tagsOffset = this.info.tagsOffset.removePrefix("0x").toUInt(16)
|
||||
ret.cmdline = this.info.cmdline
|
||||
ret.osVersion = this.info.osVersion
|
||||
ret.osPatchLevel = this.info.osPatchLevel
|
||||
ret.hash = this.info.hash
|
||||
ret.pageSize = this.info.pageSize
|
||||
ret.signatureType = this.info.verify
|
||||
ret.imageSize = this.info.imageSize
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val workDir = "build/unzip_boot/"
|
||||
|
||||
fun fromBootImgInfo(info: BootImgInfo): UnifiedConfig {
|
||||
val ret = UnifiedConfig()
|
||||
val param = ParamConfig()
|
||||
ret.kernel.file = param.kernel
|
||||
ret.kernel.loadOffset = "0x${Integer.toHexString(info.kernelOffset.toInt())}"
|
||||
ret.kernel.size = "0x${Integer.toHexString(info.kernelLength.toInt())}"
|
||||
ret.kernel.position = "0x${Integer.toHexString(info.kernelPosition.toInt())}"
|
||||
|
||||
ret.ramdisk = CommArgs()
|
||||
ret.ramdisk!!.loadOffset = "0x${Integer.toHexString(info.ramdiskOffset.toInt())}"
|
||||
ret.ramdisk!!.size = "0x${Integer.toHexString(info.ramdiskLength.toInt())}"
|
||||
ret.ramdisk!!.position = "0x${java.lang.Long.toHexString(info.ramdiskPosition.toInt().toLong())}"
|
||||
if (info.ramdiskLength > 0U) {
|
||||
ret.ramdisk!!.file = param.ramdisk
|
||||
}
|
||||
|
||||
ret.secondBootloader = CommArgs()
|
||||
ret.secondBootloader!!.loadOffset = "0x${Integer.toHexString(info.secondBootloaderOffset.toInt())}"
|
||||
ret.secondBootloader!!.size = "0x${Integer.toHexString(info.secondBootloaderLength.toInt())}"
|
||||
ret.secondBootloader!!.position = "0x${Integer.toHexString(info.secondBootloaderPosition.toInt())}"
|
||||
if (info.secondBootloaderLength > 0U) {
|
||||
ret.secondBootloader!!.file = param.second
|
||||
}
|
||||
|
||||
if (info.headerVersion > 0U) {
|
||||
ret.recoveryDtbo = CommArgs()
|
||||
if (info.recoveryDtboLength > 0U) {
|
||||
ret.recoveryDtbo!!.file = param.dtbo
|
||||
}
|
||||
ret.recoveryDtbo!!.loadOffset = "0x${java.lang.Long.toHexString(info.recoveryDtboOffset.toLong())}"
|
||||
ret.recoveryDtbo!!.size = "0x${Integer.toHexString(info.recoveryDtboLength.toInt())}"
|
||||
ret.recoveryDtbo!!.position = "0x${java.lang.Long.toHexString(info.recoveryDtboPosition.toLong())}"
|
||||
}
|
||||
|
||||
if (info.headerVersion > 1U) {
|
||||
ret.dtb = CommArgs()
|
||||
if (info.dtbLength > 0U) {
|
||||
ret.dtb!!.file = param.dtb
|
||||
}
|
||||
ret.dtb!!.loadOffset = "0x${Integer.toHexString(info.dtbOffset.toInt())}"
|
||||
ret.dtb!!.size = "0x${Integer.toHexString(info.dtbLength.toInt())}"
|
||||
ret.dtb!!.position = "0x${java.lang.Long.toHexString(info.dtbPosition.toLong())}"
|
||||
}
|
||||
|
||||
//ret.info.output = //unknown
|
||||
ret.info.headerSize = info.headerSize
|
||||
ret.info.headerVersion = info.headerVersion
|
||||
ret.info.loadBase = "0x${java.lang.Long.toHexString(0)}"
|
||||
ret.info.board = if (info.board.isBlank()) null else info.board
|
||||
ret.info.tagsOffset = "0x${java.lang.Long.toHexString(info.tagsOffset.toLong())}"
|
||||
ret.info.cmdline = info.cmdline
|
||||
ret.info.osVersion = info.osVersion
|
||||
ret.info.osPatchLevel = info.osPatchLevel
|
||||
ret.info.hash = info.hash!!
|
||||
ret.info.pageSize = info.pageSize
|
||||
ret.info.verify = info.signatureType!!
|
||||
ret.info.imageSize = info.imageSize
|
||||
return ret
|
||||
}
|
||||
|
||||
fun readBack2(): BootImgInfo {
|
||||
val param = ParamConfig()
|
||||
return ObjectMapper().readValue(File(param.cfg),
|
||||
UnifiedConfig::class.java).toBootImgInfo()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,309 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import cfig.Helper
|
||||
import cfig.ParamConfig
|
||||
import cfig.io.Struct3
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.MessageDigest
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.pow
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
open class BootImgHeader(
|
||||
var kernelLength: UInt = 0U,
|
||||
var kernelOffset: UInt = 0U,
|
||||
|
||||
var ramdiskLength: UInt = 0U,
|
||||
var ramdiskOffset: UInt = 0U,
|
||||
|
||||
var secondBootloaderLength: UInt = 0U,
|
||||
var secondBootloaderOffset: UInt = 0U,
|
||||
|
||||
var recoveryDtboLength: UInt = 0U,
|
||||
var recoveryDtboOffset: ULong = 0UL,//Q
|
||||
|
||||
var dtbLength: UInt = 0U,
|
||||
var dtbOffset: ULong = 0UL,//Q
|
||||
|
||||
var tagsOffset: UInt = 0U,
|
||||
|
||||
var pageSize: UInt = 0U,
|
||||
|
||||
var headerSize: UInt = 0U,
|
||||
var headerVersion: UInt = 0U,
|
||||
|
||||
var board: String = "",
|
||||
|
||||
var cmdline: String = "",
|
||||
|
||||
var hash: ByteArray? = null,
|
||||
|
||||
var osVersion: String? = null,
|
||||
var osPatchLevel: String? = null) {
|
||||
@Throws(IllegalArgumentException::class)
|
||||
constructor(iS: InputStream?) : this() {
|
||||
if (iS == null) {
|
||||
return
|
||||
}
|
||||
log.warn("BootImgHeader constructor")
|
||||
val info = Struct3(FORMAT_STRING).unpack(iS)
|
||||
assert(20 == info.size)
|
||||
if (info[0] != magic) {
|
||||
throw IllegalArgumentException("stream doesn't look like Android Boot Image Header")
|
||||
}
|
||||
this.kernelLength = info[1] as UInt
|
||||
this.kernelOffset = info[2] as UInt
|
||||
this.ramdiskLength = info[3] as UInt
|
||||
this.ramdiskOffset = info[4] as UInt
|
||||
this.secondBootloaderLength = info[5] as UInt
|
||||
this.secondBootloaderOffset = info[6] as UInt
|
||||
this.tagsOffset = info[7] as UInt
|
||||
this.pageSize = info[8] as UInt
|
||||
this.headerVersion = info[9] as UInt
|
||||
val osNPatch = info[10] as UInt
|
||||
if (0U != osNPatch) { //treated as 'reserved' in this boot image
|
||||
this.osVersion = parseOsVersion(osNPatch.toInt() shr 11)
|
||||
this.osPatchLevel = parseOsPatchLevel((osNPatch and 0x7ff.toUInt()).toInt())
|
||||
}
|
||||
this.board = info[11] as String
|
||||
this.cmdline = (info[12] as String) + (info[14] as String)
|
||||
this.hash = info[13] as ByteArray
|
||||
|
||||
if (this.headerVersion > 0U) {
|
||||
this.recoveryDtboLength = info[15] as UInt
|
||||
this.recoveryDtboOffset = info[16] as ULong
|
||||
}
|
||||
|
||||
this.headerSize = info[17] as UInt
|
||||
assert(this.headerSize.toInt() in intArrayOf(BOOT_IMAGE_HEADER_V2_SIZE, BOOT_IMAGE_HEADER_V1_SIZE))
|
||||
|
||||
if (this.headerVersion > 1U) {
|
||||
this.dtbLength = info[18] as UInt
|
||||
this.dtbOffset = info[19] as ULong
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseOsVersion(x: Int): String {
|
||||
val a = x shr 14
|
||||
val b = x - (a shl 14) shr 7
|
||||
val c = x and 0x7f
|
||||
return String.format("%d.%d.%d", a, b, c)
|
||||
}
|
||||
|
||||
private fun parseOsPatchLevel(x: Int): String {
|
||||
var y = x shr 4
|
||||
val m = x and 0xf
|
||||
y += 2000
|
||||
return String.format("%d-%02d-%02d", y, m, 0)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private fun packOsVersion(x: String?): Int {
|
||||
if (x.isNullOrBlank()) return 0
|
||||
val pattern = Pattern.compile("^(\\d{1,3})(?:\\.(\\d{1,3})(?:\\.(\\d{1,3}))?)?")
|
||||
val m = pattern.matcher(x)
|
||||
if (m.find()) {
|
||||
val a = Integer.decode(m.group(1))
|
||||
var b = 0
|
||||
var c = 0
|
||||
if (m.groupCount() >= 2) {
|
||||
b = Integer.decode(m.group(2))
|
||||
}
|
||||
if (m.groupCount() == 3) {
|
||||
c = Integer.decode(m.group(3))
|
||||
}
|
||||
assert(a < 128)
|
||||
assert(b < 128)
|
||||
assert(c < 128)
|
||||
return (a shl 14) or (b shl 7) or c
|
||||
} else {
|
||||
throw IllegalArgumentException("invalid os_version")
|
||||
}
|
||||
}
|
||||
|
||||
private fun packOsPatchLevel(x: String?): Int {
|
||||
if (x.isNullOrBlank()) return 0
|
||||
val ret: Int
|
||||
val pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})")
|
||||
val matcher = pattern.matcher(x)
|
||||
if (matcher.find()) {
|
||||
val y = Integer.parseInt(matcher.group(1), 10) - 2000
|
||||
val m = Integer.parseInt(matcher.group(2), 10)
|
||||
// 7 bits allocated for the year, 4 bits for the month
|
||||
assert(y in 0..127)
|
||||
assert(m in 1..12)
|
||||
ret = (y shl 4) or m
|
||||
} else {
|
||||
throw IllegalArgumentException("invalid os_patch_level")
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@Throws(CloneNotSupportedException::class)
|
||||
private fun hashFileAndSize(vararg inFiles: String?): ByteArray {
|
||||
val md = MessageDigest.getInstance("SHA1")
|
||||
for (item in inFiles) {
|
||||
if (null == item) {
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(0)
|
||||
.array())
|
||||
log.debug("update null $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
} else {
|
||||
val currentFile = File(item)
|
||||
FileInputStream(currentFile).use { iS ->
|
||||
var byteRead: Int
|
||||
val dataRead = ByteArray(1024)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
md.update(dataRead, 0, byteRead)
|
||||
}
|
||||
log.debug("update file $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(currentFile.length().toInt())
|
||||
.array())
|
||||
log.debug("update SIZE $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
private fun get_recovery_dtbo_offset(): UInt {
|
||||
return Helper.round_to_multiple(this.headerSize, pageSize) +
|
||||
Helper.round_to_multiple(this.kernelLength, pageSize) +
|
||||
Helper.round_to_multiple(this.ramdiskLength, pageSize) +
|
||||
Helper.round_to_multiple(this.secondBootloaderLength, pageSize)
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
val param = ParamConfig()
|
||||
//refresh kernel size
|
||||
if (0U == this.kernelLength) {
|
||||
throw java.lang.IllegalArgumentException("kernel size can not be 0")
|
||||
} else {
|
||||
this.kernelLength = File(param.kernel).length().toUInt()
|
||||
}
|
||||
//refresh ramdisk size
|
||||
if (0U == this.ramdiskLength) {
|
||||
param.ramdisk = null
|
||||
this.ramdiskOffset = 0U
|
||||
} else {
|
||||
this.ramdiskLength = File(param.ramdisk!!).length().toUInt()
|
||||
}
|
||||
//refresh second bootloader size
|
||||
if (0U == this.secondBootloaderLength) {
|
||||
param.second = null
|
||||
this.secondBootloaderOffset = 0U
|
||||
} else {
|
||||
this.secondBootloaderLength = File(param.second!!).length().toUInt()
|
||||
}
|
||||
//refresh recovery dtbo size
|
||||
if (0U == this.recoveryDtboLength) {
|
||||
param.dtbo = null
|
||||
this.recoveryDtboOffset = 0U
|
||||
} else {
|
||||
this.recoveryDtboLength = File(param.dtbo!!).length().toUInt()
|
||||
this.recoveryDtboOffset = get_recovery_dtbo_offset().toULong()
|
||||
log.warn("using fake recoveryDtboOffset $recoveryDtboOffset (as is in AOSP avbtool)")
|
||||
}
|
||||
//refresh dtb size
|
||||
if (0U == this.dtbLength) {
|
||||
param.dtb = null
|
||||
} else {
|
||||
this.dtbLength = File(param.dtb!!).length().toUInt()
|
||||
}
|
||||
//refresh image hash
|
||||
val imageId = when (this.headerVersion) {
|
||||
0U -> {
|
||||
hashFileAndSize(param.kernel, param.ramdisk, param.second)
|
||||
}
|
||||
1U -> {
|
||||
hashFileAndSize(param.kernel, param.ramdisk, param.second, param.dtbo)
|
||||
}
|
||||
2U -> {
|
||||
hashFileAndSize(param.kernel, param.ramdisk, param.second, param.dtbo, param.dtb)
|
||||
}
|
||||
else -> {
|
||||
throw java.lang.IllegalArgumentException("headerVersion ${this.headerVersion} illegal")
|
||||
}
|
||||
}
|
||||
this.hash = imageId
|
||||
}
|
||||
|
||||
fun encode(): ByteArray {
|
||||
this.refresh()
|
||||
val pageSizeChoices: MutableSet<Long> = mutableSetOf<Long>().apply {
|
||||
(11..14).forEach { add(2.0.pow(it).toLong()) }
|
||||
}
|
||||
assert(pageSizeChoices.contains(pageSize.toLong())) { "invalid parameter [pageSize=$pageSize], (choose from $pageSizeChoices)" }
|
||||
return Struct3(FORMAT_STRING).pack(
|
||||
"ANDROID!",
|
||||
//10I
|
||||
kernelLength,
|
||||
kernelOffset,
|
||||
ramdiskLength,
|
||||
ramdiskOffset,
|
||||
secondBootloaderLength,
|
||||
secondBootloaderOffset,
|
||||
tagsOffset,
|
||||
pageSize,
|
||||
headerVersion,
|
||||
(packOsVersion(osVersion) shl 11) or packOsPatchLevel(osPatchLevel),
|
||||
//16s
|
||||
board,
|
||||
//512s
|
||||
cmdline.substring(0, minOf(512, cmdline.length)),
|
||||
//32b
|
||||
hash!!,
|
||||
//1024s
|
||||
if (cmdline.length > 512) cmdline.substring(512) else "",
|
||||
//I
|
||||
recoveryDtboLength,
|
||||
//Q
|
||||
if (headerVersion > 0U) recoveryDtboOffset else 0,
|
||||
//I
|
||||
when (headerVersion) {
|
||||
0U -> 0
|
||||
1U -> BOOT_IMAGE_HEADER_V1_SIZE
|
||||
2U -> BOOT_IMAGE_HEADER_V2_SIZE
|
||||
else -> java.lang.IllegalArgumentException("headerVersion $headerVersion illegal")
|
||||
},
|
||||
//I
|
||||
dtbLength,
|
||||
//Q
|
||||
if (headerVersion > 1U) dtbOffset else 0
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val log = LoggerFactory.getLogger(BootImgInfo::class.java)
|
||||
const val magic = "ANDROID!"
|
||||
const val FORMAT_STRING = "8s" + //"ANDROID!"
|
||||
"10I" +
|
||||
"16s" + //board name
|
||||
"512s" + //cmdline part 1
|
||||
"32b" + //hash digest
|
||||
"1024s" + //cmdline part 2
|
||||
"I" + //dtbo length [v1]
|
||||
"Q" + //dtbo offset [v1]
|
||||
"I" + //header size [v1]
|
||||
"I" + //dtb length [v2]
|
||||
"Q" //dtb offset [v2]
|
||||
const val BOOT_IMAGE_HEADER_V2_SIZE = 1660
|
||||
const val BOOT_IMAGE_HEADER_V1_SIZE = 1648
|
||||
|
||||
init {
|
||||
assert(BOOT_IMAGE_HEADER_V2_SIZE == Struct3(FORMAT_STRING).calcSize())
|
||||
}
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import cfig.ParamConfig
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import java.io.InputStream
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class BootImgInfo(iS: InputStream?) : BootImgHeader(iS) {
|
||||
constructor() : this(null)
|
||||
|
||||
val kernelPosition: UInt
|
||||
get() {
|
||||
return getHeaderSize(this.pageSize)
|
||||
}
|
||||
|
||||
val ramdiskPosition: UInt
|
||||
get() {
|
||||
return (kernelPosition + this.kernelLength +
|
||||
getPaddingSize(this.kernelLength, this.pageSize))
|
||||
}
|
||||
|
||||
val secondBootloaderPosition: UInt
|
||||
get() {
|
||||
return ramdiskPosition + ramdiskLength +
|
||||
getPaddingSize(ramdiskLength, pageSize)
|
||||
}
|
||||
|
||||
val recoveryDtboPosition: ULong
|
||||
get() {
|
||||
return secondBootloaderPosition.toULong() + secondBootloaderLength +
|
||||
getPaddingSize(secondBootloaderLength, pageSize)
|
||||
}
|
||||
|
||||
val dtbPosition: ULong
|
||||
get() {
|
||||
return recoveryDtboPosition + recoveryDtboLength +
|
||||
getPaddingSize(recoveryDtboLength, pageSize)
|
||||
}
|
||||
|
||||
var signatureType: BootImgInfo.VerifyType? = null
|
||||
|
||||
var imageSize: Long = 0
|
||||
|
||||
private fun getHeaderSize(pageSize: Int): Int {
|
||||
val pad = (pageSize - (1648 and (pageSize - 1))) and (pageSize - 1)
|
||||
return pad + 1648
|
||||
}
|
||||
|
||||
private fun getHeaderSize(pageSize: UInt): UInt {
|
||||
val pad = (pageSize - (1648U and (pageSize - 1U))) and (pageSize - 1U)
|
||||
return pad + 1648U
|
||||
}
|
||||
|
||||
private fun getPaddingSize(position: UInt, pageSize: UInt): UInt {
|
||||
return (pageSize - (position and pageSize - 1U)) and (pageSize - 1U)
|
||||
}
|
||||
|
||||
private fun getPaddingSize(position: Int, pageSize: Int): Int {
|
||||
return (pageSize - (position and pageSize - 1)) and (pageSize - 1)
|
||||
}
|
||||
|
||||
fun toCommandLine(): CommandLine {
|
||||
val param = ParamConfig()
|
||||
val ret = CommandLine(param.mkbootimg)
|
||||
ret.addArgument(" --header_version ")
|
||||
ret.addArgument(headerVersion.toString())
|
||||
ret.addArgument(" --base ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(0))
|
||||
ret.addArgument(" --kernel ")
|
||||
ret.addArgument(param.kernel)
|
||||
ret.addArgument(" --kernel_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(kernelOffset.toInt()))
|
||||
if (this.ramdiskLength > 0U) {
|
||||
ret.addArgument(" --ramdisk ")
|
||||
ret.addArgument(param.ramdisk)
|
||||
}
|
||||
ret.addArgument(" --ramdisk_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(ramdiskOffset.toInt()))
|
||||
if (this.secondBootloaderLength > 0U) {
|
||||
ret.addArgument(" --second ")
|
||||
ret.addArgument(param.second)
|
||||
ret.addArgument(" --second_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(this.secondBootloaderOffset.toInt()))
|
||||
}
|
||||
if (!board.isBlank()) {
|
||||
ret.addArgument(" --board ")
|
||||
ret.addArgument(board)
|
||||
}
|
||||
if (headerVersion > 0U) {
|
||||
if (this.recoveryDtboLength > 0U) {
|
||||
ret.addArgument(" --recovery_dtbo ")
|
||||
ret.addArgument(param.dtbo)
|
||||
}
|
||||
}
|
||||
if (headerVersion > 1U) {
|
||||
if (this.dtbLength > 0U) {
|
||||
ret.addArgument("--dtb ")
|
||||
ret.addArgument(param.dtb)
|
||||
}
|
||||
ret.addArgument("--dtb_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(this.dtbOffset.toLong()))
|
||||
}
|
||||
ret.addArgument(" --pagesize ")
|
||||
ret.addArgument(Integer.toString(pageSize.toInt()))
|
||||
ret.addArgument(" --cmdline ")
|
||||
ret.addArgument(cmdline, false)
|
||||
if (!osVersion.isNullOrBlank()) {
|
||||
ret.addArgument(" --os_version ")
|
||||
ret.addArgument(osVersion)
|
||||
}
|
||||
if (!osPatchLevel.isNullOrBlank()) {
|
||||
ret.addArgument(" --os_patch_level ")
|
||||
ret.addArgument(osPatchLevel)
|
||||
}
|
||||
ret.addArgument(" --tags_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(tagsOffset.toInt()))
|
||||
ret.addArgument(" --id ")
|
||||
ret.addArgument(" --output ")
|
||||
//ret.addArgument("boot.img" + ".google")
|
||||
|
||||
log.debug("To Commandline: " + ret.toString())
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
enum class VerifyType {
|
||||
VERIFY,
|
||||
AVB
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import cfig.EnvironmentVerifier
|
||||
import cfig.Helper
|
||||
import cfig.dtb_util.DTC
|
||||
import cfig.io.Struct3.InputStreamExt.Companion.getInt
|
||||
import cfig.kernel_util.KernelExtractor
|
||||
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.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.MessageDigest
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class Common {
|
||||
data class VeritySignature(
|
||||
var type: String = "dm-verity",
|
||||
var path: String = "/boot",
|
||||
var verity_pk8: String = "",
|
||||
var verity_pem: String = "",
|
||||
var jarPath: String = "")
|
||||
|
||||
data class Slice(
|
||||
var srcFile: String,
|
||||
var offset: Int,
|
||||
var length: Int,
|
||||
var dumpFile: String
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(Common::class.java)
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun packOsVersion(x: String?): Int {
|
||||
if (x.isNullOrBlank()) return 0
|
||||
val pattern = Pattern.compile("^(\\d{1,3})(?:\\.(\\d{1,3})(?:\\.(\\d{1,3}))?)?")
|
||||
val m = pattern.matcher(x)
|
||||
if (m.find()) {
|
||||
val a = Integer.decode(m.group(1))
|
||||
var b = 0
|
||||
var c = 0
|
||||
if (m.groupCount() >= 2) {
|
||||
b = Integer.decode(m.group(2))
|
||||
}
|
||||
if (m.groupCount() == 3) {
|
||||
c = Integer.decode(m.group(3))
|
||||
}
|
||||
assert(a < 128)
|
||||
assert(b < 128)
|
||||
assert(c < 128)
|
||||
return (a shl 14) or (b shl 7) or c
|
||||
} else {
|
||||
throw IllegalArgumentException("invalid os_version")
|
||||
}
|
||||
}
|
||||
|
||||
fun parseOsVersion(x: Int): String {
|
||||
val a = x shr 14
|
||||
val b = x - (a shl 14) shr 7
|
||||
val c = x and 0x7f
|
||||
return String.format("%d.%d.%d", a, b, c)
|
||||
}
|
||||
|
||||
fun packOsPatchLevel(x: String?): Int {
|
||||
if (x.isNullOrBlank()) return 0
|
||||
val ret: Int
|
||||
val pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})")
|
||||
val matcher = pattern.matcher(x)
|
||||
if (matcher.find()) {
|
||||
val y = Integer.parseInt(matcher.group(1), 10) - 2000
|
||||
val m = Integer.parseInt(matcher.group(2), 10)
|
||||
// 7 bits allocated for the year, 4 bits for the month
|
||||
assert(y in 0..127)
|
||||
assert(m in 1..12)
|
||||
ret = (y shl 4) or m
|
||||
} else {
|
||||
throw IllegalArgumentException("invalid os_patch_level")
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
fun parseOsPatchLevel(x: Int): String {
|
||||
var y = x shr 4
|
||||
val m = x and 0xf
|
||||
y += 2000
|
||||
return String.format("%d-%02d-%02d", y, m, 0)
|
||||
}
|
||||
|
||||
fun parseKernelInfo(kernelFile: String): List<String> {
|
||||
KernelExtractor().let { ke ->
|
||||
if (ke.envCheck()) {
|
||||
return ke.run(kernelFile, File("."))
|
||||
}
|
||||
}
|
||||
return listOf()
|
||||
}
|
||||
|
||||
fun dumpKernel(s: Slice) {
|
||||
Helper.extractFile(s.srcFile, s.dumpFile, s.offset.toLong(), s.length)
|
||||
parseKernelInfo(s.dumpFile)
|
||||
}
|
||||
|
||||
fun dumpRamdisk(s: Slice, root: String) {
|
||||
Helper.extractFile(s.srcFile, s.dumpFile, s.offset.toLong(), s.length)
|
||||
Helper.unGnuzipFile(s.dumpFile, s.dumpFile.removeSuffix(".gz"))
|
||||
unpackRamdisk(s.dumpFile.removeSuffix(".gz"), root)
|
||||
}
|
||||
|
||||
fun dumpDtb(s: Slice) {
|
||||
Helper.extractFile(s.srcFile, s.dumpFile, s.offset.toLong(), s.length)
|
||||
//extract DTB
|
||||
if (EnvironmentVerifier().hasDtc) {
|
||||
DTC().decompile(s.dumpFile, s.dumpFile + ".src")
|
||||
}
|
||||
}
|
||||
|
||||
fun getPaddingSize(position: UInt, pageSize: UInt): UInt {
|
||||
return (pageSize - (position and pageSize - 1U)) and (pageSize - 1U)
|
||||
}
|
||||
|
||||
fun getPaddingSize(position: Int, pageSize: Int): Int {
|
||||
return (pageSize - (position and pageSize - 1)) and (pageSize - 1)
|
||||
}
|
||||
|
||||
@Throws(CloneNotSupportedException::class)
|
||||
fun hashFileAndSize(vararg inFiles: String?): ByteArray {
|
||||
val md = MessageDigest.getInstance("SHA1")
|
||||
for (item in inFiles) {
|
||||
if (null == item) {
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(0)
|
||||
.array())
|
||||
log.debug("update null $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
} else {
|
||||
val currentFile = File(item)
|
||||
FileInputStream(currentFile).use { iS ->
|
||||
var byteRead: Int
|
||||
val dataRead = ByteArray(1024)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
md.update(dataRead, 0, byteRead)
|
||||
}
|
||||
log.debug("update file $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(currentFile.length().toInt())
|
||||
.array())
|
||||
log.debug("update SIZE $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
fun assertFileEquals(file1: String, file2: String) {
|
||||
val hash1 = hashFileAndSize(file1)
|
||||
val hash2 = hashFileAndSize(file2)
|
||||
log.info("$file1 hash ${Helper.toHexString(hash1)}, $file2 hash ${Helper.toHexString(hash2)}")
|
||||
if (hash1.contentEquals(hash2)) {
|
||||
log.info("Hash verification passed: ${Helper.toHexString(hash1)}")
|
||||
} else {
|
||||
log.error("Hash verification failed")
|
||||
throw UnknownError("Do not know why hash verification fails, maybe a bug")
|
||||
}
|
||||
}
|
||||
|
||||
fun packRootfs(rootDir: String, ramdiskGz: String) {
|
||||
val mkbootfs = Helper.prop("mkbootfsBin")
|
||||
log.info("Packing rootfs $rootDir ...")
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
DefaultExecutor().let { exec ->
|
||||
exec.streamHandler = PumpStreamHandler(outputStream)
|
||||
val cmdline = "$mkbootfs $rootDir"
|
||||
log.info(cmdline)
|
||||
exec.execute(CommandLine.parse(cmdline))
|
||||
}
|
||||
Helper.gnuZipFile2(ramdiskGz, ByteArrayInputStream(outputStream.toByteArray()))
|
||||
log.info("$ramdiskGz is ready")
|
||||
}
|
||||
|
||||
fun padFile(inBF: ByteBuffer, padding: Int) {
|
||||
val pad = padding - (inBF.position() and padding - 1) and padding - 1
|
||||
inBF.put(ByteArray(pad))
|
||||
}
|
||||
|
||||
fun File.deleleIfExists() {
|
||||
if (this.exists()) {
|
||||
if (!this.isFile) {
|
||||
throw IllegalStateException("${this.canonicalPath} should be regular file")
|
||||
}
|
||||
log.info("Deleting ${this.path} ...")
|
||||
this.delete()
|
||||
}
|
||||
}
|
||||
|
||||
fun writePaddedFile(inBF: ByteBuffer, srcFile: String, padding: UInt) {
|
||||
log.info("adding $srcFile into buffer ...")
|
||||
assert(padding < Int.MAX_VALUE.toUInt())
|
||||
writePaddedFile(inBF, srcFile, padding.toInt())
|
||||
}
|
||||
|
||||
fun writePaddedFile(inBF: ByteBuffer, srcFile: String, padding: Int) {
|
||||
FileInputStream(srcFile).use { iS ->
|
||||
var byteRead: Int
|
||||
val dataRead = ByteArray(128)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
inBF.put(dataRead, 0, byteRead)
|
||||
}
|
||||
padFile(inBF, padding)
|
||||
}
|
||||
}
|
||||
|
||||
fun unpackRamdisk(ramdisk: String, root: String) {
|
||||
val rootFile = File(root).apply {
|
||||
if (exists()) {
|
||||
log.info("Cleaning [$root] before ramdisk unpacking")
|
||||
deleteRecursively()
|
||||
}
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
DefaultExecutor().let { exe ->
|
||||
exe.workingDirectory = rootFile
|
||||
exe.execute(CommandLine.parse("cpio -i -m -F " + File(ramdisk).canonicalPath))
|
||||
log.info(" ramdisk extracted : $ramdisk -> ${rootFile}")
|
||||
}
|
||||
}
|
||||
|
||||
fun probeHeaderVersion(fileName: String): Int {
|
||||
return FileInputStream(fileName).let { fis ->
|
||||
fis.skip(40)
|
||||
fis.getInt(ByteOrder.LITTLE_ENDIAN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package cfig
|
||||
|
||||
data class ImgInfo(
|
||||
//kernel
|
||||
var kernelPosition: Int = 0,
|
||||
var kernelLength: Int = 0,
|
||||
//ramdisk
|
||||
var ramdiskPosition: Int = 0,
|
||||
var ramdiskLength: Int = 0,
|
||||
//second bootloader
|
||||
var secondBootloaderPosition: Int = 0,
|
||||
var secondBootloaderLength: Int = 0,
|
||||
//dtbo
|
||||
var recoveryDtboPosition: Int = 0,
|
||||
var recoveryDtboLength: Int = 0,
|
||||
|
||||
var headerSize: Int = 0,
|
||||
var hash: ByteArray = ByteArray(0),
|
||||
|
||||
//signature
|
||||
var signature: Any? = null
|
||||
) {
|
||||
data class AvbSignature(
|
||||
var type: String = "avb",
|
||||
var originalImageSize: Int? = null,
|
||||
var imageSize: Int? = null,
|
||||
var partName: String? = null,
|
||||
var salt: String = "",
|
||||
var hashAlgorithm: String? = null,
|
||||
var algorithm: String? = null)
|
||||
|
||||
data class VeritySignature(
|
||||
var type: String = "dm-verity",
|
||||
var path: String = "/boot",
|
||||
var verity_pk8: String = "aosp/security/verity.pk8",
|
||||
var verity_pem: String = "aosp/security/verity.x509.pem",
|
||||
var jarPath: String = "aosp/boot_signer/build/libs/boot_signer.jar")
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
package cfig
|
||||
|
||||
import cfig.bootimg.BootImgInfo
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
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.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.MessageDigest
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class Packer {
|
||||
private val log = LoggerFactory.getLogger("Packer")
|
||||
|
||||
@Throws(CloneNotSupportedException::class)
|
||||
private fun hashFileAndSize(vararg inFiles: String?): ByteArray {
|
||||
val md = MessageDigest.getInstance("SHA1")
|
||||
for (item in inFiles) {
|
||||
if (null == item) {
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(0)
|
||||
.array())
|
||||
log.debug("update null $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
} else {
|
||||
val currentFile = File(item)
|
||||
FileInputStream(currentFile).use { iS ->
|
||||
var byteRead: Int
|
||||
val dataRead = ByteArray(1024)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
md.update(dataRead, 0, byteRead)
|
||||
}
|
||||
log.debug("update file $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(currentFile.length().toInt())
|
||||
.array())
|
||||
log.debug("update SIZE $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
private fun writePaddedFile(inBF: ByteBuffer, srcFile: String, padding: UInt) {
|
||||
assert(padding < Int.MAX_VALUE.toUInt())
|
||||
writePaddedFile(inBF, srcFile, padding.toInt())
|
||||
}
|
||||
|
||||
private fun writePaddedFile(inBF: ByteBuffer, srcFile: String, padding: Int) {
|
||||
FileInputStream(srcFile).use { iS ->
|
||||
var byteRead: Int
|
||||
val dataRead = ByteArray(128)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
inBF.put(dataRead, 0, byteRead)
|
||||
}
|
||||
padFile(inBF, padding)
|
||||
}
|
||||
}
|
||||
|
||||
private fun padFile(inBF: ByteBuffer, padding: Int) {
|
||||
val pad = padding - (inBF.position() and padding - 1) and padding - 1
|
||||
inBF.put(ByteArray(pad))
|
||||
}
|
||||
|
||||
private fun writeData(info2: BootImgInfo, outputFile: String) {
|
||||
log.info("Writing data ...")
|
||||
val param = ParamConfig()
|
||||
|
||||
val bf = ByteBuffer.allocate(1024 * 1024 * 64)//assume total SIZE small than 64MB
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
writePaddedFile(bf, param.kernel, info2.pageSize)
|
||||
if (info2.ramdiskLength > 0U) {
|
||||
writePaddedFile(bf, param.ramdisk!!, info2.pageSize)
|
||||
}
|
||||
if (info2.secondBootloaderLength > 0U) {
|
||||
writePaddedFile(bf, param.second!!, info2.pageSize)
|
||||
}
|
||||
if (info2.recoveryDtboLength > 0U) {
|
||||
writePaddedFile(bf, param.dtbo!!, info2.pageSize)
|
||||
}
|
||||
if (info2.dtbLength > 0U) {
|
||||
writePaddedFile(bf, param.dtb!!, info2.pageSize)
|
||||
}
|
||||
//write
|
||||
FileOutputStream("$outputFile.clear", true).use { fos ->
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
}
|
||||
}
|
||||
|
||||
private fun packRootfs(mkbootfs: String) {
|
||||
val param = ParamConfig()
|
||||
log.info("Packing rootfs ${UnifiedConfig.workDir}root ...")
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val exec = DefaultExecutor()
|
||||
exec.streamHandler = PumpStreamHandler(outputStream)
|
||||
val cmdline = "$mkbootfs ${UnifiedConfig.workDir}root"
|
||||
log.info(cmdline)
|
||||
exec.execute(CommandLine.parse(cmdline))
|
||||
Helper.gnuZipFile2(param.ramdisk!!, ByteArrayInputStream(outputStream.toByteArray()))
|
||||
log.info("${param.ramdisk} is ready")
|
||||
}
|
||||
|
||||
private fun File.deleleIfExists() {
|
||||
if (this.exists()) {
|
||||
if (!this.isFile) {
|
||||
throw IllegalStateException("${this.canonicalPath} should be regular file")
|
||||
}
|
||||
log.info("Deleting ${this.path} ...")
|
||||
this.delete()
|
||||
}
|
||||
}
|
||||
|
||||
fun pack(mkbootfsBin: String) {
|
||||
val param = ParamConfig()
|
||||
log.info("Loading config from ${param.cfg}")
|
||||
val cfg = ObjectMapper().readValue(File(param.cfg), UnifiedConfig::class.java)
|
||||
val info2 = cfg.toBootImgInfo()
|
||||
|
||||
//clean
|
||||
File(cfg.info.output + ".google").deleleIfExists()
|
||||
File(cfg.info.output + ".clear").deleleIfExists()
|
||||
File(cfg.info.output + ".signed").deleleIfExists()
|
||||
File(cfg.info.output + ".signed2").deleleIfExists()
|
||||
File("${UnifiedConfig.workDir}ramdisk.img").deleleIfExists()
|
||||
|
||||
if (info2.ramdiskLength > 0U) {
|
||||
if (File(param.ramdisk!!).exists() && !File(UnifiedConfig.workDir + "root").exists()) {
|
||||
//do nothing if we have ramdisk.img.gz but no /root
|
||||
log.warn("Use prebuilt ramdisk file: ${param.ramdisk}")
|
||||
} else {
|
||||
File(param.ramdisk!!).deleleIfExists()
|
||||
packRootfs(mkbootfsBin)
|
||||
}
|
||||
}
|
||||
|
||||
val encodedHeader = info2.encode()
|
||||
//write
|
||||
FileOutputStream(cfg.info.output + ".clear", false).use { fos ->
|
||||
fos.write(encodedHeader)
|
||||
fos.write(ByteArray((Helper.round_to_multiple(encodedHeader.size.toUInt(), info2.pageSize) - encodedHeader.size.toUInt()).toInt()))
|
||||
}
|
||||
writeData(info2, cfg.info.output)
|
||||
|
||||
val googleCmd = info2.toCommandLine().apply {
|
||||
addArgument(cfg.info.output + ".google")
|
||||
}
|
||||
log.info(googleCmd.toString())
|
||||
DefaultExecutor().execute(googleCmd)
|
||||
|
||||
val ourHash = hashFileAndSize(cfg.info.output + ".clear")
|
||||
val googleHash = hashFileAndSize(cfg.info.output + ".google")
|
||||
log.info("ours hash ${Helper.toHexString(ourHash)}, google's hash ${Helper.toHexString(googleHash)}")
|
||||
if (ourHash.contentEquals(googleHash)) {
|
||||
log.info("Hash verification passed: ${Helper.toHexString(ourHash)}")
|
||||
} else {
|
||||
log.error("Hash verification failed")
|
||||
throw UnknownError("Do not know why hash verification fails, maybe a bug")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package cfig
|
||||
|
||||
import cfig.bootimg.BootImgInfo
|
||||
import cfig.dtb_util.DTC
|
||||
import cfig.kernel_util.KernelExtractor
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import org.apache.commons.exec.DefaultExecutor
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class Parser {
|
||||
private fun verifiedWithAVB(fileName: String): Boolean {
|
||||
val expectedBf = "AVBf".toByteArray()
|
||||
FileInputStream(fileName).use { fis ->
|
||||
fis.skip(File(fileName).length() - 64)
|
||||
val bf = ByteArray(4)
|
||||
fis.read(bf)
|
||||
return bf.contentEquals(expectedBf)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unpackRamdisk(workDir: String, ramdiskGz: String) {
|
||||
val exe = DefaultExecutor()
|
||||
exe.workingDirectory = File(workDir + "root")
|
||||
if (exe.workingDirectory.exists()) exe.workingDirectory.deleteRecursively()
|
||||
exe.workingDirectory.mkdirs()
|
||||
val ramdiskFile = File(ramdiskGz.removeSuffix(".gz"))
|
||||
exe.execute(CommandLine.parse("cpio -i -m -F " + ramdiskFile.canonicalPath))
|
||||
log.info(" ramdisk extracted : $ramdiskFile -> ${exe.workingDirectory.path}")
|
||||
}
|
||||
|
||||
fun parseBootImgHeader(fileName: String, avbtool: String): BootImgInfo {
|
||||
val info2 = BootImgInfo(FileInputStream(fileName))
|
||||
val param = ParamConfig()
|
||||
if (verifiedWithAVB(fileName)) {
|
||||
info2.signatureType = BootImgInfo.VerifyType.AVB
|
||||
verifyAVBIntegrity(fileName, avbtool)
|
||||
} else {
|
||||
info2.signatureType = BootImgInfo.VerifyType.VERIFY
|
||||
}
|
||||
info2.imageSize = File(fileName).length()
|
||||
|
||||
val cfg = UnifiedConfig.fromBootImgInfo(info2).apply {
|
||||
info.output = File(fileName).name
|
||||
}
|
||||
|
||||
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(param.cfg), cfg)
|
||||
log.info("image info written to ${param.cfg}")
|
||||
|
||||
return info2
|
||||
}
|
||||
|
||||
private fun parseKernelInfo(kernelFile: String) {
|
||||
val ke = KernelExtractor()
|
||||
if (ke.envCheck()) {
|
||||
ke.run(kernelFile, File("."))
|
||||
}
|
||||
}
|
||||
|
||||
fun extractBootImg(fileName: String, info2: BootImgInfo) {
|
||||
val param = ParamConfig()
|
||||
|
||||
InfoTable.instance.addRule()
|
||||
if (info2.kernelLength > 0U) {
|
||||
Helper.extractFile(fileName,
|
||||
param.kernel,
|
||||
info2.kernelPosition.toLong(),
|
||||
info2.kernelLength.toInt())
|
||||
log.info(" kernel dumped to: ${param.kernel}, size=${info2.kernelLength.toInt() / 1024.0 / 1024.0}MB")
|
||||
InfoTable.instance.addRow("kernel", param.kernel)
|
||||
parseKernelInfo(param.kernel)
|
||||
} else {
|
||||
throw RuntimeException("bad boot image: no kernel found")
|
||||
}
|
||||
|
||||
if (info2.ramdiskLength > 0U) {
|
||||
Helper.extractFile(fileName,
|
||||
param.ramdisk!!,
|
||||
info2.ramdiskPosition.toLong(),
|
||||
info2.ramdiskLength.toInt())
|
||||
log.info("ramdisk dumped to: ${param.ramdisk}")
|
||||
Helper.unGnuzipFile(param.ramdisk!!, param.ramdisk!!.removeSuffix(".gz"))
|
||||
unpackRamdisk(UnifiedConfig.workDir, param.ramdisk!!.removeSuffix(".gz"))
|
||||
InfoTable.instance.addRule()
|
||||
InfoTable.instance.addRow("ramdisk", param.ramdisk!!.removeSuffix(".gz"))
|
||||
InfoTable.instance.addRow("\\-- extracted ramdisk rootfs", "${UnifiedConfig.workDir}root")
|
||||
} else {
|
||||
InfoTable.missingParts.add("ramdisk")
|
||||
log.info("no ramdisk found")
|
||||
}
|
||||
|
||||
if (info2.secondBootloaderLength > 0U) {
|
||||
Helper.extractFile(fileName,
|
||||
param.second!!,
|
||||
info2.secondBootloaderPosition.toLong(),
|
||||
info2.secondBootloaderLength.toInt())
|
||||
log.info("second bootloader dumped to ${param.second}")
|
||||
InfoTable.instance.addRule()
|
||||
InfoTable.instance.addRow("second bootloader", param.second)
|
||||
} else {
|
||||
InfoTable.missingParts.add("second bootloader")
|
||||
log.info("no second bootloader found")
|
||||
}
|
||||
|
||||
if (info2.recoveryDtboLength > 0U) {
|
||||
Helper.extractFile(fileName,
|
||||
param.dtbo!!,
|
||||
info2.recoveryDtboPosition.toLong(),
|
||||
info2.recoveryDtboLength.toInt())
|
||||
log.info("recovery dtbo dumped to ${param.dtbo}")
|
||||
InfoTable.instance.addRule()
|
||||
InfoTable.instance.addRow("recovery dtbo", param.dtbo)
|
||||
} else {
|
||||
InfoTable.missingParts.add("recovery dtbo")
|
||||
if (info2.headerVersion > 0U) {
|
||||
log.info("no recovery dtbo found")
|
||||
} else {
|
||||
log.debug("no recovery dtbo for header v0")
|
||||
}
|
||||
}
|
||||
|
||||
if (info2.dtbLength > 0U) {
|
||||
Helper.extractFile(fileName,
|
||||
param.dtb!!,
|
||||
info2.dtbPosition.toLong(),
|
||||
info2.dtbLength.toInt())
|
||||
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) {
|
||||
log.info("no dtb found")
|
||||
} else {
|
||||
log.debug("no dtb for header v0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger("Parser")!!
|
||||
|
||||
fun verifyAVBIntegrity(fileName: String, avbtool: String) {
|
||||
val cmdline = "$avbtool verify_image --image $fileName"
|
||||
log.info(cmdline)
|
||||
try {
|
||||
DefaultExecutor().execute(CommandLine.parse(cmdline))
|
||||
} catch (e: Exception) {
|
||||
throw IllegalArgumentException("$fileName failed integrity check by \"$cmdline\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import avb.AVBInfo
|
||||
import avb.alg.Algorithms
|
||||
import cfig.Avb
|
||||
import cfig.Avb.Companion.getJsonFileName
|
||||
import cfig.Helper
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import org.apache.commons.exec.DefaultExecutor
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
class Signer {
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(Signer::class.java)
|
||||
|
||||
fun signAVB(output: String, imageSize: Long) {
|
||||
val avbtool = Helper.prop("avbtool")
|
||||
log.info("Adding hash_footer with verified-boot 2.0 style")
|
||||
val ai = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java)
|
||||
val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())
|
||||
val bootDesc = ai.auxBlob!!.hashDescriptors[0]
|
||||
val newAvbInfo = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java)
|
||||
|
||||
//our signer
|
||||
File("$output.clear").copyTo(File("$output.signed"), overwrite = true)
|
||||
Avb().addHashFooter("$output.signed",
|
||||
imageSize,
|
||||
partition_name = bootDesc.partition_name,
|
||||
newAvbInfo = newAvbInfo)
|
||||
//original signer
|
||||
CommandLine.parse("$avbtool add_hash_footer").apply {
|
||||
addArguments("--image ${output}.signed2")
|
||||
addArguments("--partition_size ${imageSize}")
|
||||
addArguments("--salt ${Helper.toHexString(bootDesc.salt)}")
|
||||
addArguments("--partition_name ${bootDesc.partition_name}")
|
||||
addArguments("--hash_algorithm ${bootDesc.hash_algorithm}")
|
||||
addArguments("--algorithm ${alg!!.name}")
|
||||
if (alg.defaultKey.isNotBlank()) {
|
||||
addArguments("--key ${alg.defaultKey}")
|
||||
}
|
||||
newAvbInfo.auxBlob?.let { newAuxblob ->
|
||||
newAuxblob.propertyDescriptor.forEach { newProp ->
|
||||
addArguments(arrayOf("--prop", "${newProp.key}:${newProp.value}"))
|
||||
}
|
||||
}
|
||||
addArgument("--internal_release_string")
|
||||
addArgument(ai.header!!.release_string, false)
|
||||
log.info(this.toString())
|
||||
|
||||
File("$output.clear").copyTo(File("$output.signed2"), overwrite = true)
|
||||
DefaultExecutor().execute(this)
|
||||
}
|
||||
Common.assertFileEquals("$output.signed", "$output.signed2")
|
||||
//TODO: decide what to verify
|
||||
//Parser.verifyAVBIntegrity(cfg.info.output + ".signed", avbtool)
|
||||
//Parser.verifyAVBIntegrity(cfg.info.output + ".signed2", avbtool)
|
||||
}
|
||||
|
||||
fun signVB1(src: String, tgt: String) {
|
||||
val bootSigner = Helper.prop("bootSigner")
|
||||
log.info("Signing with verified-boot 1.0 style")
|
||||
val sig = Common.VeritySignature(
|
||||
verity_pk8 = Helper.prop("verity_pk8"),
|
||||
verity_pem = Helper.prop("verity_pem"),
|
||||
jarPath = Helper.prop("bootSigner")
|
||||
)
|
||||
val bootSignCmd = "java -jar $bootSigner " +
|
||||
"${sig.path} $src " +
|
||||
"${sig.verity_pk8} ${sig.verity_pem} " +
|
||||
"$tgt"
|
||||
log.info(bootSignCmd)
|
||||
DefaultExecutor().execute(CommandLine.parse(bootSignCmd))
|
||||
}
|
||||
|
||||
fun mapToJson(m: LinkedHashMap<*, *>): String {
|
||||
val sb = StringBuilder()
|
||||
m.forEach { k, v ->
|
||||
if (sb.isNotEmpty()) sb.append(", ")
|
||||
sb.append("\"$k\": \"$v\"")
|
||||
}
|
||||
return "{ $sb }"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package cfig.bootimg.v2
|
||||
|
||||
import cfig.Helper
|
||||
import cfig.bootimg.Common
|
||||
import cfig.io.Struct3
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.InputStream
|
||||
import kotlin.math.pow
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
open class BootHeaderV2(
|
||||
var kernelLength: UInt = 0U,
|
||||
var kernelOffset: UInt = 0U,
|
||||
|
||||
var ramdiskLength: UInt = 0U,
|
||||
var ramdiskOffset: UInt = 0U,
|
||||
|
||||
var secondBootloaderLength: UInt = 0U,
|
||||
var secondBootloaderOffset: UInt = 0U,
|
||||
|
||||
var recoveryDtboLength: UInt = 0U,
|
||||
var recoveryDtboOffset: ULong = 0UL,//Q
|
||||
|
||||
var dtbLength: UInt = 0U,
|
||||
var dtbOffset: ULong = 0UL,//Q
|
||||
|
||||
var tagsOffset: UInt = 0U,
|
||||
|
||||
var pageSize: UInt = 0U,
|
||||
|
||||
var headerSize: UInt = 0U,
|
||||
var headerVersion: UInt = 0U,
|
||||
|
||||
var board: String = "",
|
||||
|
||||
var cmdline: String = "",
|
||||
|
||||
var hash: ByteArray? = null,
|
||||
|
||||
var osVersion: String? = null,
|
||||
var osPatchLevel: String? = null) {
|
||||
@Throws(IllegalArgumentException::class)
|
||||
constructor(iS: InputStream?) : this() {
|
||||
if (iS == null) {
|
||||
return
|
||||
}
|
||||
log.warn("BootImgHeader constructor")
|
||||
val info = Struct3(FORMAT_STRING).unpack(iS)
|
||||
assert(20 == info.size)
|
||||
if (info[0] != magic) {
|
||||
throw IllegalArgumentException("stream doesn't look like Android Boot Image Header")
|
||||
}
|
||||
this.kernelLength = info[1] as UInt
|
||||
this.kernelOffset = info[2] as UInt
|
||||
this.ramdiskLength = info[3] as UInt
|
||||
this.ramdiskOffset = info[4] as UInt
|
||||
this.secondBootloaderLength = info[5] as UInt
|
||||
this.secondBootloaderOffset = info[6] as UInt
|
||||
this.tagsOffset = info[7] as UInt
|
||||
this.pageSize = info[8] as UInt
|
||||
this.headerVersion = info[9] as UInt
|
||||
val osNPatch = info[10] as UInt
|
||||
if (0U != osNPatch) { //treated as 'reserved' in this boot image
|
||||
this.osVersion = Common.parseOsVersion(osNPatch.toInt() shr 11)
|
||||
this.osPatchLevel = Common.parseOsPatchLevel((osNPatch and 0x7ff.toUInt()).toInt())
|
||||
}
|
||||
this.board = info[11] as String
|
||||
this.cmdline = (info[12] as String) + (info[14] as String)
|
||||
this.hash = info[13] as ByteArray
|
||||
|
||||
if (this.headerVersion > 0U) {
|
||||
this.recoveryDtboLength = info[15] as UInt
|
||||
this.recoveryDtboOffset = info[16] as ULong
|
||||
}
|
||||
|
||||
this.headerSize = info[17] as UInt
|
||||
assert(this.headerSize.toInt() in intArrayOf(BOOT_IMAGE_HEADER_V2_SIZE,
|
||||
BOOT_IMAGE_HEADER_V1_SIZE, BOOT_IMAGE_HEADER_V0_SIZE)) {
|
||||
"header size ${this.headerSize} illegal"
|
||||
}
|
||||
|
||||
if (this.headerVersion > 1U) {
|
||||
this.dtbLength = info[18] as UInt
|
||||
this.dtbOffset = info[19] as ULong
|
||||
}
|
||||
}
|
||||
|
||||
private fun get_recovery_dtbo_offset(): UInt {
|
||||
return Helper.round_to_multiple(this.headerSize, pageSize) +
|
||||
Helper.round_to_multiple(this.kernelLength, pageSize) +
|
||||
Helper.round_to_multiple(this.ramdiskLength, pageSize) +
|
||||
Helper.round_to_multiple(this.secondBootloaderLength, pageSize)
|
||||
}
|
||||
|
||||
fun encode(): ByteArray {
|
||||
val pageSizeChoices: MutableSet<Long> = mutableSetOf<Long>().apply {
|
||||
(11..14).forEach { add(2.0.pow(it).toLong()) }
|
||||
}
|
||||
assert(pageSizeChoices.contains(pageSize.toLong())) { "invalid parameter [pageSize=$pageSize], (choose from $pageSizeChoices)" }
|
||||
return Struct3(FORMAT_STRING).pack(
|
||||
magic,
|
||||
//10I
|
||||
kernelLength,
|
||||
kernelOffset,
|
||||
ramdiskLength,
|
||||
ramdiskOffset,
|
||||
secondBootloaderLength,
|
||||
secondBootloaderOffset,
|
||||
tagsOffset,
|
||||
pageSize,
|
||||
headerVersion,
|
||||
(Common.packOsVersion(osVersion) shl 11) or Common.packOsPatchLevel(osPatchLevel),
|
||||
//16s
|
||||
board,
|
||||
//512s
|
||||
cmdline.substring(0, minOf(512, cmdline.length)),
|
||||
//32b
|
||||
hash!!,
|
||||
//1024s
|
||||
if (cmdline.length > 512) cmdline.substring(512) else "",
|
||||
//I
|
||||
recoveryDtboLength,
|
||||
//Q
|
||||
if (headerVersion > 0U) recoveryDtboOffset else 0,
|
||||
//I
|
||||
when (headerVersion) {
|
||||
0U -> BOOT_IMAGE_HEADER_V0_SIZE
|
||||
1U -> BOOT_IMAGE_HEADER_V1_SIZE
|
||||
2U -> BOOT_IMAGE_HEADER_V2_SIZE
|
||||
else -> java.lang.IllegalArgumentException("headerVersion $headerVersion illegal")
|
||||
},
|
||||
//I
|
||||
dtbLength,
|
||||
//Q
|
||||
if (headerVersion > 1U) dtbOffset else 0
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val log = LoggerFactory.getLogger(BootHeaderV2::class.java)
|
||||
const val magic = "ANDROID!"
|
||||
const val FORMAT_STRING = "8s" + //"ANDROID!"
|
||||
"10I" +
|
||||
"16s" + //board name
|
||||
"512s" + //cmdline part 1
|
||||
"32b" + //hash digest
|
||||
"1024s" + //cmdline part 2
|
||||
"I" + //dtbo length [v1]
|
||||
"Q" + //dtbo offset [v1]
|
||||
"I" + //header size [v1]
|
||||
"I" + //dtb length [v2]
|
||||
"Q" //dtb offset [v2]
|
||||
const val BOOT_IMAGE_HEADER_V2_SIZE = 1660
|
||||
const val BOOT_IMAGE_HEADER_V1_SIZE = 1648
|
||||
const val BOOT_IMAGE_HEADER_V0_SIZE = 0
|
||||
|
||||
init {
|
||||
assert(BOOT_IMAGE_HEADER_V2_SIZE == Struct3(FORMAT_STRING).calcSize())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,470 @@
|
||||
package cfig.bootimg.v2
|
||||
|
||||
import cfig.Avb
|
||||
import cfig.Helper
|
||||
import cfig.bootimg.Common
|
||||
import cfig.bootimg.Common.Companion.deleleIfExists
|
||||
import cfig.bootimg.Common.Slice
|
||||
import cfig.bootimg.Signer
|
||||
import cfig.packable.VBMetaParser
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import de.vandermeer.asciitable.AsciiTable
|
||||
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.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
data class BootV2(
|
||||
var info: MiscInfo = MiscInfo(),
|
||||
var kernel: CommArgs = CommArgs(),
|
||||
var ramdisk: CommArgs = CommArgs(),
|
||||
var secondBootloader: CommArgs? = null,
|
||||
var recoveryDtbo: CommArgsLong? = null,
|
||||
var dtb: CommArgsLong? = null
|
||||
) {
|
||||
data class MiscInfo(
|
||||
var output: String = "",
|
||||
var json: String = "",
|
||||
var headerVersion: UInt = 0U,
|
||||
var headerSize: UInt = 0U,
|
||||
var loadBase: UInt = 0U,
|
||||
var tagsOffset: UInt = 0U,
|
||||
var board: String? = null,
|
||||
var pageSize: UInt = 0U,
|
||||
var cmdline: String = "",
|
||||
var osVersion: String? = null,
|
||||
var osPatchLevel: String? = null,
|
||||
var hash: ByteArray? = byteArrayOf(),
|
||||
var verify: String = "",
|
||||
var imageSize: Long = 0)
|
||||
|
||||
data class CommArgs(
|
||||
var file: String? = null,
|
||||
var position: UInt = 0U,
|
||||
var size: Int = 0,
|
||||
var loadOffset: UInt = 0U)
|
||||
|
||||
data class CommArgsLong(
|
||||
var file: String? = null,
|
||||
var position: UInt = 0U,
|
||||
var size: UInt = 0U,
|
||||
var loadOffset: ULong = 0U)
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(BootV2::class.java)
|
||||
private val workDir = Helper.prop("workDir")
|
||||
|
||||
fun parse(fileName: String): BootV2 {
|
||||
val ret = BootV2()
|
||||
FileInputStream(fileName).use { fis ->
|
||||
val bh2 = BootHeaderV2(fis)
|
||||
ret.info.let { theInfo ->
|
||||
theInfo.output = File(fileName).name
|
||||
theInfo.json = File(fileName).name.removeSuffix(".img") + ".json"
|
||||
theInfo.pageSize = bh2.pageSize
|
||||
theInfo.headerSize = bh2.headerSize
|
||||
theInfo.headerVersion = bh2.headerVersion
|
||||
theInfo.board = bh2.board
|
||||
theInfo.cmdline = bh2.cmdline
|
||||
theInfo.imageSize = File(fileName).length()
|
||||
theInfo.tagsOffset = bh2.tagsOffset
|
||||
theInfo.hash = bh2.hash
|
||||
theInfo.osVersion = bh2.osVersion
|
||||
theInfo.osPatchLevel = bh2.osPatchLevel
|
||||
if (Avb.hasAvbFooter(fileName)) {
|
||||
theInfo.verify = "VB2.0"
|
||||
Avb.verifyAVBIntegrity(fileName, Helper.prop("avbtool"))
|
||||
} else {
|
||||
theInfo.verify = "VB1.0"
|
||||
}
|
||||
}
|
||||
ret.kernel.let { theKernel ->
|
||||
theKernel.file = "${workDir}kernel"
|
||||
theKernel.size = bh2.kernelLength.toInt()
|
||||
theKernel.loadOffset = bh2.kernelOffset
|
||||
theKernel.position = ret.getKernelPosition()
|
||||
}
|
||||
ret.ramdisk.let { theRamdisk ->
|
||||
theRamdisk.size = bh2.ramdiskLength.toInt()
|
||||
theRamdisk.loadOffset = bh2.ramdiskOffset
|
||||
theRamdisk.position = ret.getRamdiskPosition()
|
||||
if (bh2.ramdiskLength > 0U) {
|
||||
theRamdisk.file = "${workDir}ramdisk.img.gz"
|
||||
}
|
||||
}
|
||||
if (bh2.secondBootloaderLength > 0U) {
|
||||
ret.secondBootloader = CommArgs()
|
||||
ret.secondBootloader!!.size = bh2.secondBootloaderLength.toInt()
|
||||
ret.secondBootloader!!.loadOffset = bh2.secondBootloaderOffset
|
||||
ret.secondBootloader!!.file = "${workDir}second"
|
||||
ret.secondBootloader!!.position = ret.getSecondBootloaderPosition()
|
||||
}
|
||||
if (bh2.recoveryDtboLength > 0U) {
|
||||
ret.recoveryDtbo = CommArgsLong()
|
||||
ret.recoveryDtbo!!.size = bh2.recoveryDtboLength
|
||||
ret.recoveryDtbo!!.loadOffset = bh2.recoveryDtboOffset //Q
|
||||
ret.recoveryDtbo!!.file = "${workDir}recoveryDtbo"
|
||||
ret.recoveryDtbo!!.position = ret.getRecoveryDtboPosition().toUInt()
|
||||
}
|
||||
if (bh2.dtbLength > 0U) {
|
||||
ret.dtb = CommArgsLong()
|
||||
ret.dtb!!.size = bh2.dtbLength
|
||||
ret.dtb!!.loadOffset = bh2.dtbOffset //Q
|
||||
ret.dtb!!.file = "${workDir}dtb"
|
||||
ret.dtb!!.position = ret.getDtbPosition()
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHeaderSize(pageSize: UInt): UInt {
|
||||
val pad = (pageSize - (1648U and (pageSize - 1U))) and (pageSize - 1U)
|
||||
return pad + 1648U
|
||||
}
|
||||
|
||||
private fun getKernelPosition(): UInt {
|
||||
return getHeaderSize(info.pageSize)
|
||||
}
|
||||
|
||||
private fun getRamdiskPosition(): UInt {
|
||||
return (getKernelPosition() + kernel.size.toUInt() +
|
||||
Common.getPaddingSize(kernel.size.toUInt(), info.pageSize))
|
||||
}
|
||||
|
||||
private fun getSecondBootloaderPosition(): UInt {
|
||||
return getRamdiskPosition() + ramdisk.size.toUInt() +
|
||||
Common.getPaddingSize(ramdisk.size.toUInt(), info.pageSize)
|
||||
}
|
||||
|
||||
private fun getRecoveryDtboPosition(): UInt {
|
||||
return if (this.secondBootloader == null) {
|
||||
getSecondBootloaderPosition()
|
||||
} else {
|
||||
getSecondBootloaderPosition() + secondBootloader!!.size.toUInt() +
|
||||
Common.getPaddingSize(secondBootloader!!.size.toUInt(), info.pageSize)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDtbPosition(): UInt {
|
||||
return if (this.recoveryDtbo == null) {
|
||||
getRecoveryDtboPosition()
|
||||
} else {
|
||||
getRecoveryDtboPosition() + recoveryDtbo!!.size +
|
||||
Common.getPaddingSize(recoveryDtbo!!.size, info.pageSize)
|
||||
}
|
||||
}
|
||||
|
||||
fun extractImages(): BootV2 {
|
||||
val workDir = Helper.prop("workDir")
|
||||
//info
|
||||
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this)
|
||||
//kernel
|
||||
Common.dumpKernel(Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!))
|
||||
//ramdisk
|
||||
if (this.ramdisk.size > 0) {
|
||||
Common.dumpRamdisk(Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file!!),
|
||||
"${workDir}root")
|
||||
}
|
||||
//second bootloader
|
||||
secondBootloader?.let {
|
||||
Helper.extractFile(info.output,
|
||||
secondBootloader!!.file!!,
|
||||
secondBootloader!!.position.toLong(),
|
||||
secondBootloader!!.size)
|
||||
}
|
||||
//recovery dtbo
|
||||
recoveryDtbo?.let {
|
||||
Helper.extractFile(info.output,
|
||||
recoveryDtbo!!.file!!,
|
||||
recoveryDtbo!!.position.toLong(),
|
||||
recoveryDtbo!!.size.toInt())
|
||||
}
|
||||
//dtb
|
||||
this.dtb?.let { _ ->
|
||||
Common.dumpDtb(Slice(info.output, dtb!!.position.toInt(), dtb!!.size.toInt(), dtb!!.file!!))
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun extractVBMeta(): BootV2 {
|
||||
Avb().parseVbMeta(info.output)
|
||||
if (File("vbmeta.img").exists()) {
|
||||
log.warn("Found vbmeta.img, parsing ...")
|
||||
VBMetaParser().unpack("vbmeta.img")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun printSummary(): BootV2 {
|
||||
val workDir = Helper.prop("workDir")
|
||||
val tableHeader = AsciiTable().apply {
|
||||
addRule()
|
||||
addRow("What", "Where")
|
||||
addRule()
|
||||
}
|
||||
val tab = AsciiTable().let {
|
||||
it.addRule()
|
||||
it.addRow("image info", workDir + info.output.removeSuffix(".img") + ".json")
|
||||
if (this.info.verify == "VB2.0") {
|
||||
it.addRule()
|
||||
it.addRow("AVB info", Avb.getJsonFileName(info.output))
|
||||
}
|
||||
//kernel
|
||||
it.addRule()
|
||||
it.addRow("kernel", this.kernel.file)
|
||||
File(Helper.prop("kernelVersionFile")).let { kernelVersionFile ->
|
||||
if (kernelVersionFile.exists()) {
|
||||
it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path)
|
||||
}
|
||||
}
|
||||
File(Helper.prop("kernelConfigFile")).let { kernelConfigFile ->
|
||||
if (kernelConfigFile.exists()) {
|
||||
it.addRow("\\-- config", kernelConfigFile.path)
|
||||
}
|
||||
}
|
||||
//ramdisk
|
||||
if (this.ramdisk.size > 0) {
|
||||
it.addRule()
|
||||
it.addRow("ramdisk", this.ramdisk.file)
|
||||
it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root")
|
||||
}
|
||||
//second
|
||||
this.secondBootloader?.let { theSecondBootloader ->
|
||||
if (theSecondBootloader.size > 0) {
|
||||
it.addRule()
|
||||
it.addRow("second bootloader", theSecondBootloader.file)
|
||||
}
|
||||
}
|
||||
//dtbo
|
||||
this.recoveryDtbo?.let { theDtbo ->
|
||||
if (theDtbo.size > 0U) {
|
||||
it.addRule()
|
||||
it.addRow("recovery dtbo", theDtbo.file)
|
||||
}
|
||||
}
|
||||
//dtb
|
||||
this.dtb?.let { theDtb ->
|
||||
if (theDtb.size > 0u) {
|
||||
it.addRule()
|
||||
it.addRow("dtb", theDtb.file)
|
||||
if (File(theDtb.file + ".src").exists()) {
|
||||
it.addRow("\\-- decompiled dts", theDtb.file + ".src")
|
||||
}
|
||||
}
|
||||
}
|
||||
//END
|
||||
it.addRule()
|
||||
it
|
||||
}
|
||||
val tabVBMeta = AsciiTable().let {
|
||||
if (File("vbmeta.img").exists()) {
|
||||
it.addRule()
|
||||
it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))
|
||||
it.addRule()
|
||||
"\n" + it.render()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
log.info("\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}",
|
||||
tableHeader.render(), tab.render(), tabVBMeta)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toHeader(): BootHeaderV2 {
|
||||
return BootHeaderV2(
|
||||
kernelLength = kernel.size.toUInt(),
|
||||
kernelOffset = kernel.loadOffset,
|
||||
ramdiskLength = ramdisk.size.toUInt(),
|
||||
ramdiskOffset = ramdisk.loadOffset,
|
||||
secondBootloaderLength = if (secondBootloader != null) secondBootloader!!.size.toUInt() else 0U,
|
||||
secondBootloaderOffset = if (secondBootloader != null) secondBootloader!!.loadOffset else 0U,
|
||||
recoveryDtboLength = if (recoveryDtbo != null) recoveryDtbo!!.size.toUInt() else 0U,
|
||||
recoveryDtboOffset = if (recoveryDtbo != null) recoveryDtbo!!.loadOffset else 0U,
|
||||
dtbLength = if (dtb != null) dtb!!.size else 0U,
|
||||
dtbOffset = if (dtb != null) dtb!!.loadOffset else 0U,
|
||||
tagsOffset = info.tagsOffset,
|
||||
pageSize = info.pageSize,
|
||||
headerSize = info.headerSize,
|
||||
headerVersion = info.headerVersion,
|
||||
board = info.board.toString(),
|
||||
cmdline = info.cmdline,
|
||||
hash = info.hash,
|
||||
osVersion = info.osVersion,
|
||||
osPatchLevel = info.osPatchLevel
|
||||
)
|
||||
}
|
||||
|
||||
fun pack(): BootV2 {
|
||||
//refresh kernel size
|
||||
this.kernel.size = File(this.kernel.file!!).length().toInt()
|
||||
//refresh ramdisk size
|
||||
if (this.ramdisk.file.isNullOrBlank()) {
|
||||
ramdisk.file = null
|
||||
ramdisk.loadOffset = 0U
|
||||
} else {
|
||||
if (File(this.ramdisk.file!!).exists() && !File(workDir + "root").exists()) {
|
||||
//do nothing if we have ramdisk.img.gz but no /root
|
||||
log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}")
|
||||
} else {
|
||||
File(this.ramdisk.file!!).deleleIfExists()
|
||||
File(this.ramdisk.file!!.removeSuffix(".gz")).deleleIfExists()
|
||||
Common.packRootfs("${workDir}/root", this.ramdisk.file!!)
|
||||
}
|
||||
this.ramdisk.size = File(this.ramdisk.file!!).length().toInt()
|
||||
}
|
||||
//refresh second bootloader size
|
||||
secondBootloader?.let { theSecond ->
|
||||
theSecond.size = File(theSecond.file!!).length().toInt()
|
||||
}
|
||||
//refresh recovery dtbo size
|
||||
recoveryDtbo?.let { theDtbo ->
|
||||
theDtbo.size = File(theDtbo.file!!).length().toUInt()
|
||||
theDtbo.loadOffset = getRecoveryDtboPosition().toULong()
|
||||
log.warn("using fake recoveryDtboOffset ${theDtbo.loadOffset} (as is in AOSP avbtool)")
|
||||
}
|
||||
//refresh dtb size
|
||||
dtb?.let { theDtb ->
|
||||
theDtb.size = File(theDtb.file!!).length().toUInt()
|
||||
}
|
||||
//refresh image hash
|
||||
info.hash = when (info.headerVersion) {
|
||||
0U -> {
|
||||
Common.hashFileAndSize(kernel.file, ramdisk.file, secondBootloader?.file)
|
||||
}
|
||||
1U -> {
|
||||
Common.hashFileAndSize(kernel.file, ramdisk.file,
|
||||
secondBootloader?.file, recoveryDtbo?.file)
|
||||
}
|
||||
2U -> {
|
||||
Common.hashFileAndSize(kernel.file, ramdisk.file,
|
||||
secondBootloader?.file, recoveryDtbo?.file, dtb?.file)
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("headerVersion ${info.headerVersion} illegal")
|
||||
}
|
||||
}
|
||||
|
||||
val encodedHeader = this.toHeader().encode()
|
||||
|
||||
//write
|
||||
FileOutputStream("${info.output}.clear", false).use { fos ->
|
||||
fos.write(encodedHeader)
|
||||
fos.write(ByteArray((Helper.round_to_multiple(encodedHeader.size.toUInt(), info.pageSize) - encodedHeader.size.toUInt()).toInt()))
|
||||
}
|
||||
|
||||
log.info("Writing data ...")
|
||||
val bytesV2 = ByteBuffer.allocate(1024 * 1024 * 64)//assume total SIZE small than 64MB
|
||||
.let { bf ->
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
Common.writePaddedFile(bf, kernel.file!!, info.pageSize)
|
||||
if (ramdisk.size > 0) {
|
||||
Common.writePaddedFile(bf, ramdisk.file!!, info.pageSize)
|
||||
}
|
||||
secondBootloader?.let {
|
||||
Common.writePaddedFile(bf, secondBootloader!!.file!!, info.pageSize)
|
||||
}
|
||||
recoveryDtbo?.let {
|
||||
Common.writePaddedFile(bf, recoveryDtbo!!.file!!, info.pageSize)
|
||||
}
|
||||
dtb?.let {
|
||||
Common.writePaddedFile(bf, dtb!!.file!!, info.pageSize)
|
||||
}
|
||||
bf
|
||||
}
|
||||
//write
|
||||
FileOutputStream("${info.output}.clear", true).use { fos ->
|
||||
fos.write(bytesV2.array(), 0, bytesV2.position())
|
||||
}
|
||||
|
||||
this.toCommandLine().apply {
|
||||
addArgument("${info.output}.google")
|
||||
log.info(this.toString())
|
||||
DefaultExecutor().execute(this)
|
||||
}
|
||||
|
||||
Common.assertFileEquals("${info.output}.clear", "${info.output}.google")
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toCommandLine(): CommandLine {
|
||||
val ret = CommandLine(Helper.prop("mkbootimg"))
|
||||
ret.addArgument(" --header_version ")
|
||||
ret.addArgument(info.headerVersion.toString())
|
||||
ret.addArgument(" --base ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(0))
|
||||
ret.addArgument(" --kernel ")
|
||||
ret.addArgument(kernel.file!!)
|
||||
ret.addArgument(" --kernel_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(kernel.loadOffset.toInt()))
|
||||
if (this.ramdisk.size > 0) {
|
||||
ret.addArgument(" --ramdisk ")
|
||||
ret.addArgument(ramdisk.file)
|
||||
}
|
||||
ret.addArgument(" --ramdisk_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(ramdisk.loadOffset.toInt()))
|
||||
if (secondBootloader != null) {
|
||||
ret.addArgument(" --second ")
|
||||
ret.addArgument(secondBootloader!!.file!!)
|
||||
ret.addArgument(" --second_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(secondBootloader!!.loadOffset.toInt()))
|
||||
}
|
||||
if (!info.board.isNullOrBlank()) {
|
||||
ret.addArgument(" --board ")
|
||||
ret.addArgument(info.board)
|
||||
}
|
||||
if (info.headerVersion > 0U) {
|
||||
if (recoveryDtbo != null) {
|
||||
ret.addArgument(" --recovery_dtbo ")
|
||||
ret.addArgument(recoveryDtbo!!.file!!)
|
||||
}
|
||||
}
|
||||
if (info.headerVersion > 1U) {
|
||||
if (dtb != null) {
|
||||
ret.addArgument("--dtb ")
|
||||
ret.addArgument(dtb!!.file!!)
|
||||
ret.addArgument("--dtb_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(dtb!!.loadOffset.toLong()))
|
||||
}
|
||||
}
|
||||
ret.addArgument(" --pagesize ")
|
||||
ret.addArgument(Integer.toString(info.pageSize.toInt()))
|
||||
ret.addArgument(" --cmdline ")
|
||||
ret.addArgument(info.cmdline, false)
|
||||
if (!info.osVersion.isNullOrBlank()) {
|
||||
ret.addArgument(" --os_version ")
|
||||
ret.addArgument(info.osVersion)
|
||||
}
|
||||
if (!info.osPatchLevel.isNullOrBlank()) {
|
||||
ret.addArgument(" --os_patch_level ")
|
||||
ret.addArgument(info.osPatchLevel)
|
||||
}
|
||||
ret.addArgument(" --tags_offset ")
|
||||
ret.addArgument("0x" + Integer.toHexString(info.tagsOffset.toInt()))
|
||||
ret.addArgument(" --id ")
|
||||
ret.addArgument(" --output ")
|
||||
//ret.addArgument("boot.img" + ".google")
|
||||
|
||||
log.debug("To Commandline: $ret")
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
fun sign(): BootV2 {
|
||||
if (info.verify == "VB2.0") {
|
||||
Signer.signAVB(info.output, this.info.imageSize)
|
||||
log.info("Adding hash_footer with verified-boot 2.0 style")
|
||||
} else {
|
||||
Signer.signVB1(info.output + ".clear", info.output + ".signed")
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package cfig.bootimg.v3
|
||||
|
||||
import cfig.bootimg.Common
|
||||
import cfig.io.Struct3
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.InputStream
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class BootHeaderV3(
|
||||
var kernelSize: UInt = 0U,
|
||||
var ramdiskSize: UInt = 0U,
|
||||
var osVersion: String = "",
|
||||
var osPatchLevel: String = "",
|
||||
var headerSize: UInt = 0U,
|
||||
var headerVersion: UInt = 0U,
|
||||
var cmdline: String = ""
|
||||
) {
|
||||
@Throws(IllegalArgumentException::class)
|
||||
constructor(iS: InputStream?) : this() {
|
||||
if (iS == null) {
|
||||
return
|
||||
}
|
||||
log.warn("BootImgHeaderV3 constructor")
|
||||
val info = Struct3(FORMAT_STRING).unpack(iS)
|
||||
assert(11 == info.size)
|
||||
if (info[0] != magic) {
|
||||
throw IllegalArgumentException("stream doesn't look like Android Boot Image V3 Header")
|
||||
}
|
||||
this.kernelSize = info[1] as UInt
|
||||
this.ramdiskSize = info[2] as UInt
|
||||
val osNPatch = info[3] as UInt
|
||||
if (0U != osNPatch) { //treated as 'reserved' in this boot image
|
||||
this.osVersion = Common.parseOsVersion(osNPatch.toInt() shr 11)
|
||||
this.osPatchLevel = Common.parseOsPatchLevel((osNPatch and 0x7ff.toUInt()).toInt())
|
||||
}
|
||||
this.headerSize = info[4] as UInt
|
||||
//5,6,7,8 reserved
|
||||
this.headerVersion = info[9] as UInt
|
||||
|
||||
this.cmdline = info[10] as String
|
||||
|
||||
assert(this.headerSize.toInt() in intArrayOf(BOOT_IMAGE_HEADER_V3_SIZE))
|
||||
}
|
||||
|
||||
fun encode(): ByteArray {
|
||||
return Struct3(FORMAT_STRING).pack(
|
||||
magic,
|
||||
kernelSize,
|
||||
ramdiskSize,
|
||||
(Common.packOsVersion(osVersion) shl 11) or Common.packOsPatchLevel(osPatchLevel),
|
||||
headerSize,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
headerVersion,
|
||||
cmdline)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "BootImgHeaderV3(kernelSize=$kernelSize, ramdiskSize=$ramdiskSize, osVersion=$osVersion, osPatchLevel=$osPatchLevel, headerSize=$headerSize, headerVersion=$headerVersion, cmdline='$cmdline')"
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val log = LoggerFactory.getLogger(BootHeaderV3::class.java)
|
||||
const val magic = "ANDROID!"
|
||||
const val FORMAT_STRING = "8s" + //"ANDROID!"
|
||||
"4I" + //kernel size, ramdisk size, os_version/patch, header size
|
||||
"4I" + //reserved
|
||||
"I" + //header version
|
||||
"1536s" //cmdline
|
||||
private const val BOOT_IMAGE_HEADER_V3_SIZE = 1580
|
||||
val pageSize: UInt = 4096U
|
||||
|
||||
init {
|
||||
assert(BOOT_IMAGE_HEADER_V3_SIZE == Struct3(FORMAT_STRING).calcSize()) {
|
||||
"internal error: expected size $BOOT_IMAGE_HEADER_V3_SIZE "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
package cfig.bootimg.v3
|
||||
|
||||
import cfig.Avb
|
||||
import cfig.Helper
|
||||
import cfig.bootimg.Common.Companion.deleleIfExists
|
||||
import cfig.bootimg.Common.Companion.getPaddingSize
|
||||
import cfig.bootimg.Signer
|
||||
import cfig.packable.VBMetaParser
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import de.vandermeer.asciitable.AsciiTable
|
||||
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.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import cfig.bootimg.Common as C
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
data class BootV3(var info: MiscInfo = MiscInfo(),
|
||||
var kernel: CommArgs = CommArgs(),
|
||||
val ramdisk: CommArgs = CommArgs()
|
||||
) {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(BootV3::class.java)
|
||||
private val workDir = Helper.prop("workDir")
|
||||
|
||||
fun parse(fileName: String): BootV3 {
|
||||
val ret = BootV3()
|
||||
FileInputStream(fileName).use { fis ->
|
||||
val header = BootHeaderV3(fis)
|
||||
//info
|
||||
ret.info.output = File(fileName).name
|
||||
ret.info.json = File(fileName).name.removeSuffix(".img") + ".json"
|
||||
ret.info.cmdline = header.cmdline
|
||||
ret.info.headerSize = header.headerSize
|
||||
ret.info.headerVersion = header.headerVersion
|
||||
ret.info.osVersion = header.osVersion
|
||||
ret.info.osPatchLevel = header.osPatchLevel
|
||||
ret.info.pageSize = BootHeaderV3.pageSize
|
||||
//kernel
|
||||
ret.kernel.file = workDir + "kernel"
|
||||
ret.kernel.size = header.kernelSize.toInt()
|
||||
ret.kernel.position = BootHeaderV3.pageSize.toInt()
|
||||
//ramdisk
|
||||
ret.ramdisk.file = workDir + "ramdisk.img.gz"
|
||||
ret.ramdisk.size = header.ramdiskSize.toInt()
|
||||
ret.ramdisk.position = ret.kernel.position + header.kernelSize.toInt() +
|
||||
getPaddingSize(header.kernelSize, BootHeaderV3.pageSize).toInt()
|
||||
}
|
||||
ret.info.imageSize = File(fileName).length()
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
data class MiscInfo(
|
||||
var output: String = "",
|
||||
var json: String = "",
|
||||
var headerVersion: UInt = 0U,
|
||||
var headerSize: UInt = 0U,
|
||||
var pageSize: UInt = 0U,
|
||||
var cmdline: String = "",
|
||||
var osVersion: String = "",
|
||||
var osPatchLevel: String = "",
|
||||
var imageSize: Long = 0
|
||||
)
|
||||
|
||||
data class CommArgs(
|
||||
var file: String = "",
|
||||
var position: Int = 0,
|
||||
var size: Int = 0)
|
||||
|
||||
fun pack(): BootV3 {
|
||||
if (File( this.ramdisk.file).exists() && !File(workDir + "root").exists()) {
|
||||
//do nothing if we have ramdisk.img.gz but no /root
|
||||
log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}")
|
||||
} else {
|
||||
File(this.ramdisk.file).deleleIfExists()
|
||||
File(this.ramdisk.file.removeSuffix(".gz")).deleleIfExists()
|
||||
C.packRootfs("$workDir/root", this.ramdisk.file)
|
||||
}
|
||||
this.kernel.size = File(this.kernel.file).length().toInt()
|
||||
this.ramdisk.size = File( this.ramdisk.file).length().toInt()
|
||||
|
||||
//header
|
||||
FileOutputStream(this.info.output + ".clear", false).use { fos ->
|
||||
val encodedHeader = this.toHeader().encode()
|
||||
fos.write(encodedHeader)
|
||||
fos.write(ByteArray((
|
||||
Helper.round_to_multiple(encodedHeader.size.toUInt(),
|
||||
this.info.pageSize) - encodedHeader.size.toUInt()).toInt()
|
||||
))
|
||||
}
|
||||
|
||||
//data
|
||||
log.info("Writing data ...")
|
||||
val bf = ByteBuffer.allocate(1024 * 1024 * 64)//assume total SIZE small than 64MB
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
C.writePaddedFile(bf, this.kernel.file, this.info.pageSize)
|
||||
C.writePaddedFile(bf, this.ramdisk.file, this.info.pageSize)
|
||||
//write
|
||||
FileOutputStream("${this.info.output}.clear", true).use { fos ->
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
}
|
||||
|
||||
//google way
|
||||
this.toCommandLine().addArgument(this.info.output + ".google").let {
|
||||
log.info(it.toString())
|
||||
DefaultExecutor().execute(it)
|
||||
}
|
||||
|
||||
C.assertFileEquals(this.info.output + ".clear", this.info.output + ".google")
|
||||
return this
|
||||
}
|
||||
|
||||
fun sign(fileName: String): BootV3 {
|
||||
Signer.signAVB(fileName, this.info.imageSize)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toHeader(): BootHeaderV3 {
|
||||
return BootHeaderV3(
|
||||
kernelSize = kernel.size.toUInt(),
|
||||
ramdiskSize = ramdisk.size.toUInt(),
|
||||
headerVersion = info.headerVersion,
|
||||
osVersion = info.osVersion,
|
||||
osPatchLevel = info.osPatchLevel,
|
||||
headerSize = info.headerSize,
|
||||
cmdline = info.cmdline)
|
||||
}
|
||||
|
||||
fun extractImages(): BootV3 {
|
||||
val workDir = Helper.prop("workDir")
|
||||
//info
|
||||
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
|
||||
//kernel
|
||||
C.dumpKernel(C.Slice(info.output, kernel.position, kernel.size, kernel.file))
|
||||
//ramdisk
|
||||
C.dumpRamdisk(C.Slice(info.output, ramdisk.position, ramdisk.size, ramdisk.file), "${workDir}root")
|
||||
return this
|
||||
}
|
||||
|
||||
fun extractVBMeta(): BootV3 {
|
||||
Avb().parseVbMeta(info.output)
|
||||
if (File("vbmeta.img").exists()) {
|
||||
log.warn("Found vbmeta.img, parsing ...")
|
||||
VBMetaParser().unpack("vbmeta.img")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun printSummary(): BootV3 {
|
||||
val workDir = Helper.prop("workDir")
|
||||
val tableHeader = AsciiTable().apply {
|
||||
addRule()
|
||||
addRow("What", "Where")
|
||||
addRule()
|
||||
}
|
||||
val tab = AsciiTable().let {
|
||||
it.addRule()
|
||||
it.addRow("image info", workDir + info.output.removeSuffix(".img") + ".json")
|
||||
it.addRule()
|
||||
it.addRow("kernel", this.kernel.file)
|
||||
File(Helper.prop("kernelVersionFile")).let { kernelVersionFile ->
|
||||
if (kernelVersionFile.exists()) {
|
||||
it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path)
|
||||
}
|
||||
}
|
||||
File(Helper.prop("kernelConfigFile")).let { kernelConfigFile ->
|
||||
if (kernelConfigFile.exists()) {
|
||||
it.addRow("\\-- config", kernelConfigFile.path)
|
||||
}
|
||||
}
|
||||
it.addRule()
|
||||
it.addRow("ramdisk", this.ramdisk.file)
|
||||
it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root")
|
||||
it.addRule()
|
||||
it.addRow("AVB info", Avb.getJsonFileName(info.output))
|
||||
it.addRule()
|
||||
it
|
||||
}
|
||||
val tabVBMeta = AsciiTable().let {
|
||||
if (File("vbmeta.img").exists()) {
|
||||
it.addRule()
|
||||
it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))
|
||||
it.addRule()
|
||||
"\n" + it.render()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
log.info("\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}",
|
||||
tableHeader.render(), tab.render(), tabVBMeta)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toCommandLine(): CommandLine {
|
||||
return CommandLine(Helper.prop("mkbootimg")).let { ret ->
|
||||
ret.addArgument("--header_version")
|
||||
ret.addArgument(info.headerVersion.toString())
|
||||
if (kernel.size > 0) {
|
||||
ret.addArgument("--kernel")
|
||||
ret.addArgument(this.kernel.file)
|
||||
}
|
||||
if (ramdisk.size > 0) {
|
||||
ret.addArgument("--ramdisk")
|
||||
ret.addArgument(this.ramdisk.file)
|
||||
}
|
||||
if (info.cmdline.isNotBlank()) {
|
||||
ret.addArgument(" --cmdline ")
|
||||
ret.addArgument(info.cmdline, false)
|
||||
}
|
||||
if (info.osVersion.isNotBlank()) {
|
||||
ret.addArgument(" --os_version")
|
||||
ret.addArgument(info.osVersion)
|
||||
}
|
||||
if (info.osPatchLevel.isNotBlank()) {
|
||||
ret.addArgument(" --os_patch_level")
|
||||
ret.addArgument(info.osPatchLevel)
|
||||
}
|
||||
ret.addArgument(" --id ")
|
||||
ret.addArgument(" --output ")
|
||||
//ret.addArgument("boot.img" + ".google")
|
||||
|
||||
log.debug("To Commandline: $ret")
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
package cfig.bootimg.v3
|
||||
|
||||
import cfig.Avb
|
||||
import cfig.Helper
|
||||
import cfig.bootimg.Common.Companion.deleleIfExists
|
||||
import cfig.bootimg.Signer
|
||||
import cfig.packable.VBMetaParser
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import de.vandermeer.asciitable.AsciiTable
|
||||
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.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import cfig.bootimg.Common as C
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
data class VendorBoot(var info: MiscInfo = MiscInfo(),
|
||||
var ramdisk: CommArgs = CommArgs(),
|
||||
var dtb: CommArgs = CommArgs()) {
|
||||
data class CommArgs(
|
||||
var file: String = "",
|
||||
var position: UInt = 0U,
|
||||
var size: UInt = 0U,
|
||||
var loadAddr: UInt = 0U)
|
||||
|
||||
data class MiscInfo(
|
||||
var output: String = "",
|
||||
var json: String = "",
|
||||
var headerVersion: UInt = 0U,
|
||||
var product: String = "",
|
||||
var headerSize: UInt = 0U,
|
||||
var pageSize: UInt = 0U,
|
||||
var cmdline: String = "",
|
||||
var tagsLoadAddr: UInt = 0U,
|
||||
var kernelLoadAddr: UInt = 0U,
|
||||
var imageSize: Long = 0
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(VendorBoot::class.java)
|
||||
fun parse(fileName: String): VendorBoot {
|
||||
val ret = VendorBoot()
|
||||
val workDir = Helper.prop("workDir")
|
||||
FileInputStream(fileName).use { fis ->
|
||||
val header = VendorBootHeader(fis)
|
||||
ret.info.output = File(fileName).name
|
||||
ret.info.json = File(fileName).name.removeSuffix(".img") + ".json"
|
||||
ret.info.headerSize = header.headerSize
|
||||
ret.info.product = header.product
|
||||
ret.info.tagsLoadAddr = header.tagsLoadAddr
|
||||
ret.info.cmdline = header.cmdline
|
||||
ret.info.kernelLoadAddr = header.kernelLoadAddr
|
||||
ret.info.pageSize = header.pageSize
|
||||
ret.info.headerVersion = header.headerVersion
|
||||
//ramdisk
|
||||
ret.ramdisk.file = workDir + "ramdisk.img.gz"
|
||||
ret.ramdisk.size = header.vndRamdiskSize
|
||||
ret.ramdisk.loadAddr = header.ramdiskLoadAddr
|
||||
ret.ramdisk.position = Helper.round_to_multiple(
|
||||
VendorBootHeader.VENDOR_BOOT_IMAGE_HEADER_V3_SIZE,
|
||||
header.pageSize)
|
||||
//dtb
|
||||
ret.dtb.file = workDir + "dtb"
|
||||
ret.dtb.size = header.dtbSize
|
||||
ret.dtb.loadAddr = header.dtbLoadAddr.toUInt()
|
||||
ret.dtb.position = ret.ramdisk.position +
|
||||
Helper.round_to_multiple(ret.ramdisk.size, header.pageSize)
|
||||
}
|
||||
ret.info.imageSize = File(fileName).length()
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
fun pack(): VendorBoot {
|
||||
val workDir = Helper.prop("workDir")
|
||||
if (File(workDir + this.ramdisk.file).exists() && !File(workDir + "root").exists()) {
|
||||
//do nothing if we have ramdisk.img.gz but no /root
|
||||
log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}")
|
||||
} else {
|
||||
File(this.ramdisk.file).deleleIfExists()
|
||||
File(this.ramdisk.file.removeSuffix(".gz")).deleleIfExists()
|
||||
C.packRootfs("$workDir/root", this.ramdisk.file)
|
||||
}
|
||||
this.ramdisk.size = File(this.ramdisk.file).length().toUInt()
|
||||
this.dtb.size = File(this.dtb.file).length().toUInt()
|
||||
//header
|
||||
FileOutputStream(this.info.output + ".clear", false).use { fos ->
|
||||
val encodedHeader = this.toHeader().encode()
|
||||
fos.write(encodedHeader)
|
||||
fos.write(ByteArray((
|
||||
Helper.round_to_multiple(encodedHeader.size.toUInt(),
|
||||
this.info.pageSize) - encodedHeader.size.toUInt()).toInt()
|
||||
))
|
||||
}
|
||||
//data
|
||||
log.info("Writing data ...")
|
||||
val bf = ByteBuffer.allocate(1024 * 1024 * 128)//assume total SIZE small than 64MB
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
C.writePaddedFile(bf, this.ramdisk.file, this.info.pageSize)
|
||||
C.writePaddedFile(bf, this.dtb.file, this.info.pageSize)
|
||||
//write
|
||||
FileOutputStream("${this.info.output}.clear", true).use { fos ->
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
}
|
||||
|
||||
//google way
|
||||
this.toCommandLine().addArgument(this.info.output + ".google").let {
|
||||
log.info(it.toString())
|
||||
DefaultExecutor().execute(it)
|
||||
}
|
||||
|
||||
C.assertFileEquals(this.info.output + ".clear", this.info.output + ".google")
|
||||
return this
|
||||
}
|
||||
|
||||
fun sign(): VendorBoot {
|
||||
Signer.signAVB(info.output, this.info.imageSize)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toHeader(): VendorBootHeader {
|
||||
return VendorBootHeader(
|
||||
headerVersion = info.headerVersion,
|
||||
pageSize = info.pageSize,
|
||||
kernelLoadAddr = info.kernelLoadAddr,
|
||||
ramdiskLoadAddr = ramdisk.loadAddr,
|
||||
vndRamdiskSize = ramdisk.size,
|
||||
cmdline = info.cmdline,
|
||||
tagsLoadAddr = info.tagsLoadAddr,
|
||||
product = info.product,
|
||||
headerSize = info.headerSize,
|
||||
dtbSize = dtb.size,
|
||||
dtbLoadAddr = dtb.loadAddr.toULong()
|
||||
)
|
||||
}
|
||||
|
||||
fun extractImages(): VendorBoot {
|
||||
val workDir = Helper.prop("workDir")
|
||||
//header
|
||||
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(
|
||||
File(workDir + this.info.json), this)
|
||||
//ramdisk
|
||||
C.dumpRamdisk(C.Slice(info.output, ramdisk.position.toInt(), ramdisk.size.toInt(), ramdisk.file),
|
||||
"${workDir}root")
|
||||
//dtb
|
||||
C.dumpDtb(C.Slice(info.output, dtb.position.toInt(), dtb.size.toInt(), dtb.file))
|
||||
return this
|
||||
}
|
||||
|
||||
fun extractVBMeta(): VendorBoot {
|
||||
Avb().parseVbMeta(info.output)
|
||||
if (File("vbmeta.img").exists()) {
|
||||
log.warn("Found vbmeta.img, parsing ...")
|
||||
VBMetaParser().unpack("vbmeta.img")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun printSummary(): VendorBoot {
|
||||
val workDir = Helper.prop("workDir")
|
||||
val tableHeader = AsciiTable().apply {
|
||||
addRule()
|
||||
addRow("What", "Where")
|
||||
addRule()
|
||||
}
|
||||
val tab = AsciiTable().let {
|
||||
it.addRule()
|
||||
it.addRow("image info", workDir + info.output.removeSuffix(".img") + ".json")
|
||||
it.addRule()
|
||||
it.addRow("ramdisk", this.ramdisk.file)
|
||||
it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root")
|
||||
it.addRule()
|
||||
it.addRow("dtb", this.dtb.file)
|
||||
if (File(this.dtb.file + ".src").exists()) {
|
||||
it.addRow("\\-- decompiled dts", dtb.file + ".src")
|
||||
}
|
||||
it.addRule()
|
||||
it.addRow("AVB info", Avb.getJsonFileName(info.output))
|
||||
it.addRule()
|
||||
it
|
||||
}
|
||||
val tabVBMeta = AsciiTable().let {
|
||||
if (File("vbmeta.img").exists()) {
|
||||
it.addRule()
|
||||
it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))
|
||||
it.addRule()
|
||||
"\n" + it.render()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
log.info("\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}",
|
||||
tableHeader.render(), tab.render(), tabVBMeta)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toCommandLine(): CommandLine {
|
||||
return CommandLine(Helper.prop("mkbootimg"))
|
||||
.addArgument("--vendor_ramdisk")
|
||||
.addArgument(this.ramdisk.file)
|
||||
.addArgument("--dtb")
|
||||
.addArgument(this.dtb.file)
|
||||
.addArgument("--vendor_cmdline")
|
||||
.addArgument(info.cmdline, false)
|
||||
.addArgument("--header_version")
|
||||
.addArgument(info.headerVersion.toString())
|
||||
.addArgument("--vendor_boot")
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package cfig.bootimg.v3
|
||||
|
||||
import cfig.io.Struct3
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.InputStream
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class VendorBootHeader(
|
||||
var headerVersion: UInt = 0U,
|
||||
var pageSize: UInt = 0U,
|
||||
var kernelLoadAddr: UInt = 0U,
|
||||
var ramdiskLoadAddr: UInt = 0U,
|
||||
var vndRamdiskSize: UInt = 0U,
|
||||
var cmdline: String = "",
|
||||
var tagsLoadAddr: UInt = 0U,
|
||||
var product: String = "",
|
||||
var headerSize: UInt = 0U,
|
||||
var dtbSize: UInt = 0U,
|
||||
var dtbLoadAddr: ULong = 0U
|
||||
) {
|
||||
@Throws(IllegalArgumentException::class)
|
||||
constructor(iS: InputStream?) : this() {
|
||||
if (iS == null) {
|
||||
return
|
||||
}
|
||||
log.warn("VendorBootHeader constructor")
|
||||
val info = Struct3(FORMAT_STRING).unpack(iS)
|
||||
assert(12 == info.size)
|
||||
if (info[0] != magic) {
|
||||
throw IllegalArgumentException("stream doesn't look like Android Vendor Boot Image")
|
||||
}
|
||||
this.headerVersion = info[1] as UInt
|
||||
this.pageSize = info[2] as UInt
|
||||
this.kernelLoadAddr = info[3] as UInt
|
||||
this.ramdiskLoadAddr = info[4] as UInt
|
||||
this.vndRamdiskSize = info[5] as UInt
|
||||
this.cmdline = info[6] as String
|
||||
this.tagsLoadAddr = info[7] as UInt
|
||||
this.product = info[8] as String
|
||||
this.headerSize = info[9] as UInt
|
||||
this.dtbSize = info[10] as UInt
|
||||
this.dtbLoadAddr = info[11] as ULong
|
||||
|
||||
assert(this.headerSize in arrayOf(VENDOR_BOOT_IMAGE_HEADER_V3_SIZE))
|
||||
assert(this.headerVersion == 3U)
|
||||
}
|
||||
|
||||
fun encode(): ByteArray {
|
||||
return Struct3(FORMAT_STRING).pack(
|
||||
magic,
|
||||
headerVersion,
|
||||
pageSize,
|
||||
kernelLoadAddr,
|
||||
ramdiskLoadAddr,
|
||||
vndRamdiskSize,
|
||||
cmdline,
|
||||
tagsLoadAddr,
|
||||
product,
|
||||
headerSize,
|
||||
dtbSize,
|
||||
dtbLoadAddr)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(VendorBootHeader::class.java)
|
||||
const val magic = "VNDRBOOT"
|
||||
const val VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112U
|
||||
const val FORMAT_STRING = "8s" + //magic
|
||||
"I" + //header version
|
||||
"I" + //page size
|
||||
"I" + //kernel physical load addr
|
||||
"I" + //ramdisk physical load addr
|
||||
"I" + //vendor ramdisk size
|
||||
"2048s" + //cmdline
|
||||
"I" + //kernel tag load addr
|
||||
"16s" + //product name
|
||||
"I" + //header size
|
||||
"I" + //dtb size
|
||||
"Q" //dtb physical load addr
|
||||
init {
|
||||
assert(Struct3(FORMAT_STRING).calcSize().toUInt() == VENDOR_BOOT_IMAGE_HEADER_V3_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "VendorBootHeader(headerVersion=$headerVersion, pageSize=$pageSize, kernelLoadAddr=$kernelLoadAddr, ramdiskLoadAddr=$ramdiskLoadAddr, vndRamdiskSize=$vndRamdiskSize, cmdline='$cmdline', tagsLoadAddr=$tagsLoadAddr, product='$product', headerSize=$headerSize, dtbSize=$dtbSize, dtbLoadAddr=$dtbLoadAddr)"
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package cfig.packable
|
||||
|
||||
import cfig.Helper
|
||||
import cfig.bootimg.v3.VendorBoot
|
||||
import cfig.packable.BootImgParser.Companion.updateVbmeta
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
class VendorBootParser : IPackable {
|
||||
override val loopNo: Int = 0
|
||||
private val log = LoggerFactory.getLogger(VendorBootParser::class.java)
|
||||
private val workDir = Helper.prop("workDir")
|
||||
override fun capabilities(): List<String> {
|
||||
return listOf("^vendor_boot\\.img$")
|
||||
}
|
||||
|
||||
override fun unpack(fileName: String) {
|
||||
cleanUp()
|
||||
val vb = VendorBoot
|
||||
.parse(fileName)
|
||||
.extractImages()
|
||||
.extractVBMeta()
|
||||
.printSummary()
|
||||
log.debug(vb.toString())
|
||||
}
|
||||
|
||||
override fun pack(fileName: String) {
|
||||
val cfgFile = "$workDir/${fileName.removeSuffix(".img")}.json"
|
||||
log.info("Loading config from $cfgFile")
|
||||
ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java)
|
||||
.pack()
|
||||
.sign()
|
||||
updateVbmeta(fileName)
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
workDir = build/unzip_boot/
|
||||
mkbootfsBin = aosp/mkbootfs/build/exe/mkbootfs/mkbootfs
|
||||
avbtool = aosp/avb/avbtool
|
||||
kernelExtracter = aosp/build/tools/extract_kernel.py
|
||||
bootSigner = aosp/boot_signer/build/libs/boot_signer.jar
|
||||
verity_pk8 = aosp/security/verity.pk8
|
||||
verity_pem = aosp/security/verity.x509.pem
|
||||
mkbootimg = aosp/system/tools/mkbootimg/mkbootimg.py
|
||||
kernelVersionFile = build/unzip_boot/kernel_version.txt
|
||||
kernelConfigFile = build/unzip_boot/kernel_configs.txt
|
@ -1 +1 @@
|
||||
Subproject commit d8168a226d055f8372e55e3c0096d6dca70b8611
|
||||
Subproject commit 7cd84fb4f31151410d413d138e8ae1c35cd40dd9
|
Loading…
Reference in New Issue