add 'pull' task

WIP: add initrc parser test to analyze system boot sequence
pull/20/head
cfig 7 years ago
parent 1f7476d884
commit 6505fea379
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -14,10 +14,10 @@ Also need python 2.x(required by avbtool) and java 8.
(1) Target boot.img(or recovery.img / recovery-two-step.img) MUST follows AOSP verified boot flow, either [Boot image signature](https://source.android.com/security/verifiedboot/verified-boot#signature_format) in VBoot 1.0 or [AVB HASH footer](https://android.googlesource.com/platform/external/avb/+/master/README.md#The-VBMeta-struct) in VBoot 2.0.
(2) These utilities are known to work for Nexus/Pixel (or Pixel compatible) boot.img(or recovery.img/recovery-two-step.img) for the following Android releases:
(2) These utilities are known to work for Nexus/Pixel boot.img(or recovery.img/recovery-two-step.img/vbmeta.img) for the following Android releases:
- AOSP master
- Lollipop (API Level 21,22) - Oreo (API Level 26,27)
- Lollipop (5.0) - Pi (9)
You can get a full [Android version list](https://source.android.com/source/build-numbers.html) here.

@ -28,11 +28,26 @@ class Signer {
ImgArgs.VerifyType.AVB -> {
log.info("Adding hash_footer with verified-boot 2.0 style")
val sig = readBack[2] as ImgInfo.AvbSignature
File(args.output + ".clear").copyTo(File(args.output + ".signed"))
val ai = ObjectMapper().readValue(File(Avb.getJsonFileName(args.output)), AVBInfo::class.java)
//val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())
//our signer
File(args.output + ".clear").copyTo(File(args.output + ".signed"))
Avb().add_hash_footer(args.output + ".signed",
sig.imageSize!!.toLong(),
false,
false,
salt = sig.salt,
hash_algorithm = sig.hashAlgorithm!!,
partition_name = sig.partName!!,
rollback_index = ai.header!!.rollback_index,
common_algorithm = sig.algorithm!!,
inReleaseString = ai.header!!.release_string)
//original signer
File(args.output + ".clear").copyTo(File(args.output + ".signed2"))
val signKey = Algorithms.get(sig.algorithm!!)
var cmdlineStr = "$avbtool add_hash_footer " +
"--image ${args.output}.signed " +
"--image ${args.output}.signed2 " +
"--partition_size ${sig.imageSize} " +
"--salt ${sig.salt} " +
"--partition_name ${sig.partName} " +
@ -47,19 +62,6 @@ class Signer {
cmdLine.addArgument(ai.header!!.release_string, false)
DefaultExecutor().execute(cmdLine)
verifyAVBIntegrity(args, avbtool)
File(args.output + ".clear").copyTo(File(args.output + ".signed2"))
val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())
Avb().add_hash_footer(args.output + ".signed2",
sig.imageSize!!.toLong(),
false,
false,
salt = sig.salt,
hash_algorithm = sig.hashAlgorithm!!,
partition_name = sig.partName!!,
rollback_index = ai.header!!.rollback_index,
common_algorithm = sig.algorithm!!,
inReleaseString = ai.header!!.release_string)
}
}
}
@ -80,4 +82,4 @@ class Signer {
return "{ $sb }"
}
}
}
}

@ -0,0 +1,321 @@
import org.junit.Test
import java.io.File
import java.util.regex.Matcher
import java.util.regex.Pattern
class ReadTest {
data class Trigger(
var trigger: String = "",
var actions: MutableList<String> = mutableListOf()
)
data class Import(
var initrc: String = ""
)
data class Service(
var name: String = "",
var cmd: String = "",
var theClass: String = "default",
var theUser: String? = null,
var theGroup: String? = null,
var theSeclabel: String? = null,
var theMiscAttr: MutableList<String> = mutableListOf(),
var theCaps: MutableList<String> = mutableListOf(),
var theSocket: String? = null,
var theWritePid: String? = null,
var theKeycodes: String? = null,
var thePriority: Int? = null,
var theIOPriority: String? = null,
var theOnRestart: MutableList<String> = mutableListOf()
)
fun parseConfig(inRootDir: String, inPath: String,
triggers: MutableList<Trigger>,
services: MutableList<Service>) {
if (!File(inRootDir + inPath).exists()) {
println("Parsing " + inPath + " fail: 404");
}
if (File(inRootDir + inPath).isFile()) {
parseConfigFile(inRootDir, inPath, triggers, services)
} else if (File(inRootDir + inPath).isDirectory()) {
parseConfigDir(inRootDir, inPath, triggers, services)
}
}
fun parseConfigDir(inRootDir: String, inPath: String,
triggers: MutableList<Trigger>,
services: MutableList<Service>) {
println("Parsing directory $inPath ...")
File(inRootDir + inPath).listFiles().forEach {
parseConfig(inRootDir,
it.path.substring(inRootDir.length - 1),
triggers, services)
}
}
fun parseConfigFile(inRootDir: String, inPath: String,
triggers: MutableList<Trigger>,
services: MutableList<Service>) {
if (!File(inRootDir + inPath).exists()) {
println("Parsing $inPath fail: 404");
return
}
println("Parsing file $inPath ...")
var imports: MutableList<Import> = mutableListOf()
var aTrigger: Trigger? = null
var aService: Service? = null
var aImport: Import? = null
var toBeContinued = false
val lines = File(inRootDir + inPath).readLines()
for (item in lines) {
val line = item.trim();
//comment
if (line.startsWith("#") || line.isEmpty()) {
continue
}
//continue
if (toBeContinued) {
if (line.endsWith("\\")) {
aService!!.cmd += " "
aService!!.cmd += line.substring(0, line.length - 1)
println(" CONTINUE:" + line.substring(0, line.length - 1))
} else {
toBeContinued = false
aService!!.cmd += " "
aService!!.cmd += line
println(" END :$line")
}
continue
}
val finderOn = Pattern.compile("^(on)\\s+(\\S+.*$)").matcher(line)
val finderService = Pattern.compile("^service\\s+(\\S+)\\s+(.*$)").matcher(line)
val finderImport = Pattern.compile("^import\\s+(\\S+)$").matcher(line)
if (finderOn.matches() || finderService.matches() || finderImport.matches()) {
//flush start >>
aTrigger?.let { /* println("[add] " + aTrigger); */ triggers.add(aTrigger!!); aTrigger = null }
aService?.let { /* println("[add] " + aService); */ services.add(aService!!); aService = null }
aImport?.let { /* println("[add] " + aImport); */ imports.add(aImport!!); aImport = null }
// << flush end
}
finderOn.reset()
finderService.reset()
finderImport.reset()
if (finderOn.find()) {
//println(" |on| " + line)
//println(" group.cnt = " + finderOn.groupCount())
//println(" " + line.substring(finderOn.start(), finderOn.end()))
//println(" >" + finderOn.group(1))
//println(" >" + finderOn.group(2))
aTrigger = Trigger(trigger = finderOn.group(2))
} else if (finderService.find()) {
aService = Service()
aService!!.name = finderService.group(1)
aService!!.cmd = finderService.group(2)
if (finderService.group(2).endsWith("\\")) { //remove trailing slash
toBeContinued = true
aService!!.cmd = aService!!.cmd.substring(0, aService!!.cmd.length - 1)
}
} else if (finderImport.find()) {
aImport = Import()
aImport!!.initrc = finderImport.group(1)
if (aImport!!.initrc.startsWith("/")) {
aImport!!.initrc = aImport!!.initrc.substring(1)
} else {
//do nothing
}
val ro_hardware = "\${ro.hardware}"
val ro_zygote = "\${ro.zygote}"
aImport!!.initrc = aImport!!.initrc.replace(ro_hardware, "sequoia")
aImport!!.initrc = aImport!!.initrc.replace(ro_zygote, "zygote32")
} else {
if (aTrigger != null) {
aTrigger!!.actions.add(line)
} else if (aService != null) {
//class
var bParsed = false
lateinit var mm: Matcher
if (!bParsed) {
mm = Pattern.compile("^class\\s+(.*)").matcher(line)
if (mm.matches()) {
aService!!.theClass = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//user
mm = Pattern.compile("^user\\s+(.*)").matcher(line)
if (mm.matches()) {
aService!!.theUser = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//capabilities
mm = Pattern.compile("^capabilities\\s+(.*)").matcher(line)
if (mm.matches()) {
aService!!.theCaps.add(mm.group(1))
bParsed = true
}
}
if (!bParsed) {
//group
mm = Pattern.compile("^group\\s+(.*)").matcher(line)
if (mm.matches()) {
aService!!.theGroup = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//seclabel
mm = Pattern.compile("^seclabel\\s+(.*)").matcher(line)
if (mm.matches()) {
aService!!.theSeclabel = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//writepid
mm = Pattern.compile("^writepid\\s+(.*)$").matcher(line)
if (mm.matches()) {
aService!!.theWritePid = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//onrestart
mm = Pattern.compile("^onrestart\\s+(.*)$").matcher(line)
if (mm.matches()) {
aService!!.theOnRestart.add(mm.group(1))
bParsed = true
}
}
if (!bParsed) {
//socket
mm = Pattern.compile("^socket\\s+(.*)$").matcher(line)
if (mm.matches()) {
aService!!.theSocket = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//ioprio
mm = Pattern.compile("^ioprio\\s+(.*)$").matcher(line)
if (mm.matches()) {
aService!!.theIOPriority = mm.group(1)
bParsed = true
}
}
if (!bParsed) {
//priority
mm = Pattern.compile("^priority\\s+(\\S+)$").matcher(line)
if (mm.matches()) {
aService!!.thePriority = Integer.parseInt(mm.group(1))
bParsed = true
}
}
if (!bParsed) {
//check space
mm = Pattern.compile("^\\S+$").matcher(line)
if (mm.matches()) {
aService!!.theMiscAttr.add(line)
bParsed = true
}
}
if (!bParsed) {
println("<< Dangling << $line")
}
} else {
println("<< Dangling << $line")
}
}
}
//flush start >>
aTrigger?.let { /* println("[add] " + aTrigger); */ triggers.add(aTrigger!!); aTrigger = null }
aService?.let { /* println("[add] " + aService); */ services.add(aService!!); aService = null }
aImport?.let { /* println("[add] " + aImport); */ imports.add(aImport!!); aImport = null }
// << flush end
imports.forEach { println(it) }
//parse imports again
var iteratorImport: Iterator<Import> = imports.iterator()
while (iteratorImport.hasNext()) {
val item: Import = iteratorImport.next()
parseConfigFile(inRootDir, item.initrc, triggers, services)
}
println("Parsing file $inPath done")
}
fun queueEventTrigger(inServices: MutableList<Service>,
inTriggers: List<Trigger>, inTriggerName: String,
inIndent: String = "") {
val aPre = inIndent
inTriggers.filter { it.trigger == inTriggerName }.forEach { aTrigger ->
println(aPre + " (on+${aTrigger.trigger})")
aTrigger.actions.forEach { aAction ->
aAction.executeCmd(inServices, inTriggers, aPre + " ")
}
}
}
fun String.executeCmd(inServices: MutableList<Service>,
inTriggers: List<Trigger>, inIndent: String) {
val aPre = inIndent + " "
if (this.startsWith("trigger ")) {
println(aPre + "|-- " + this)
queueEventTrigger(inServices, inTriggers, this.substring(8).trim(), aPre + "| ")
} else if (this.startsWith("chmod")) {
} else if (this.startsWith("chown")) {
} else if (this.startsWith("mkdir")) {
} else if (this.startsWith("write")) {
} else if (Pattern.compile("class_start\\s+\\S+").matcher(this).find()) {
println(aPre + "|-- " + this)
val m = Pattern.compile("class_start\\s+(\\S+)$").matcher(this)
if (m.find()) {
inServices
.filter {
it.theClass != null
&& it.theClass!!.split(" ").contains(m.group(1))
}
.forEach {
println(aPre + "| \\-- Starting " + it.name + "...")
}
} else {
println("error")
}
} else if (this.startsWith("start")) {
println(aPre + "|-- " + this)
println(aPre + "| \\-- Starting " + this.substring(5).trim() + "...")
} else {
println(aPre + "|-- " + this)
}
}
@Test
fun parseTest() {
System.out.println(System.getProperty("user.dir"))
var gTriggers: MutableList<Trigger> = mutableListOf()
var gServices: MutableList<Service> = mutableListOf()
parseConfig("__temp/", "/init.rc", gTriggers, gServices)
parseConfig("__temp/", "/system/etc/init", gTriggers, gServices)
parseConfig("__temp/", "/vendor/etc/init", gTriggers, gServices)
parseConfig("__temp/", "/odm/etc/init", gTriggers, gServices)
gTriggers.forEach { println(it) }
gServices.forEach { println(it) }
println("Trigger count:" + gTriggers.size)
println("Service count:" + gServices.size)
queueEventTrigger(gServices, gTriggers, "early-init")
queueEventTrigger(gServices, gTriggers, "init")
queueEventTrigger(gServices, gTriggers, "late-init")
// println(">> mount_all() returned 0, trigger nonencrypted")
// queueEventTrigger(gServices, gTriggers, "nonencrypted")
}
}

@ -11,7 +11,7 @@ subprojects {
// ----------------------------------------------------------------------------
// global
// ----------------------------------------------------------------------------
def workdir='build/unzip_boot'
def workdir = 'build/unzip_boot'
project.ext.rootWorkDir = new File(workdir).getAbsolutePath()
String activeImg = "boot.img"
String activePath = "/boot"
@ -80,6 +80,35 @@ task _setup(type: Copy) {
into '.'
}
task pull() {
doFirst {
println("Pulling ...")
}
doLast {
if (project.findProperty("group")) {
println("Pull: $group")
} else {
println("Pull /boot, /recovery, /vbmeta")
pullDefault()
}
}
}
void pullDefault() {
Run(["adb", "shell", "dd if=/dev/block/by-name/boot of=/cache/boot.img"])
Run(["adb", "shell", "dd if=/dev/block/by-name/recovery of=/cache/recovery.img"])
Run(["adb", "shell", "dd if=/dev/block/by-name/vbmeta of=/cache/vbmeta.img"])
Run(["adb", "pull", "/cache/boot.img"])
Run(["adb", "pull", "/cache/recovery.img"])
Run(["adb", "pull", "/cache/vbmeta.img"])
Run(["adb", "shell", "rm /cache/boot.img"])
Run(["adb", "shell", "rm /cache/recovery.img"])
Run(["adb", "shell", "rm /cache/vbmeta.img"])
}
void Run(List<String> inCmd, String inWorkdir = null) {
println("CMD:" + inCmd)
if (inWorkdir == null) {
@ -89,7 +118,7 @@ void Run(List<String> inCmd, String inWorkdir = null) {
.directory(new File(inWorkdir))
.redirectErrorStream(true);
Process p = pb.start()
p.inputStream.eachLine {println it}
p.inputStream.eachLine { println it }
p.waitFor();
assert 0 == p.exitValue()
}
@ -117,7 +146,7 @@ void updateBootImage(String activeImg) {
}
Run("adb root")
Run("adb push " + activeImg + ".signed /cache/")
List<String> cmd2 = ["adb", "shell", "dd if=/cache/" + activeImg + ".signed of=" + flashTarget];
List<String> cmd2 = ["adb", "shell", "dd if=/cache/" + activeImg + ".signed of=" + flashTarget]
Run(cmd2)
cmd2 = ["adb", "shell", "rm -f /cache/" + activeImg + ".signed"];
Run(cmd2)

@ -0,0 +1,34 @@
Enable developer debugging for AVB-enabled devices
### modify build/unzip_boot/vbmeta.img.avb.json
* disable dm-verity hashtree verification
header -> flags: set to 1(AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1) to disable dm-verity hashtree(system/vendor etc.)
```diff
"descriptors_size" : 1384,
"rollback_index" : 0,
- "flags" : 0,
+ "flags" : 1,
"release_string" : "avbtool 1.0.0"
},
"authBlob" : {
```
* disable all AVB verification
header -> flags: set to 2(AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED = 2) to disable all verification, including AVB hash_footer(boot/recovery etc.) and dm-verity hashtree(system/vendor etc.)
```diff
"descriptors_size" : 1384,
"rollback_index" : 0,
- "flags" : 0,
+ "flags" : 2,
"release_string" : "avbtool 1.0.0"
},
"authBlob" : {
```
### unlock bootloader
'unlock' state will pass flag AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR when verifying images, then you can disable
Loading…
Cancel
Save