From 3e5449af04b8f06ae6872523fe93a3730680d4e5 Mon Sep 17 00:00:00 2001 From: cfig Date: Mon, 20 Jun 2016 22:43:48 +0800 Subject: [PATCH] Squashed commit of all strict checkings 1. make my groovy code more java like add @groovy.transform.TypeChecked and @groovy.transform.CompileStatic to eliminate runtime surprise 2. add strict checking before 'pack' task 3. all boot.img.clear* will be hashed and the hash code will be compared to assert equation 4. upgrade gradle and groovy version --- README.md | 16 +++--- abootimg/build.gradle | 2 +- .../src/main/groovy/cfig/bootimg/CArgs.groovy | 1 + .../main/groovy/cfig/bootimg/CImgInfo.groovy | 44 ++++++++++++++++- .../main/groovy/cfig/bootimg/Packer.groovy | 15 +++--- .../main/groovy/cfig/bootimg/Parser.groovy | 49 +------------------ build.gradle | 49 ++++++++++++++++++- gradle/wrapper/gradle-wrapper.properties | 4 +- 8 files changed, 115 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index a8476ba..cf13f93 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Nexus_boot_image_editor -[![Build Status](https://travis-ci.org/cfig/Nexus_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Nexus_boot_image_editor) +# Android_boot_image_editor +[![Build Status](https://travis-ci.org/cfig/Android_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Android_boot_image_editor) -Utilies for editing Nexus(or Nexus compatible) devices boot.img , then you don't need full Android source code to edit your boot images. +Utilies for editing Android boot.img. ## Prerequisite #### Host OS requirement: @@ -10,7 +10,7 @@ Linux or Mac. #### Target Android requirement: -(1) Targeted boot.img MUST follows AOSP [verified boot flow](https://source.android.com/security/verifiedboot/index.html), which means it packs linux kernel and rootfs together, then sign it with OEM/USER keys. +(1) Targeted boot.img MUST follows AOSP [verified boot flow](https://source.android.com/security/verifiedboot/index.html), which means it packs linux kernel, rootfs , and a optional second state bootloader, then sign it with OEM/USER keys. (2) These utilities are known to work for Nexus (or Nexus compatible) boot.img for the following Android releases: @@ -23,8 +23,8 @@ You can get a full [Android version list](https://source.android.com/source/buil ## Usage Get tools via git: - git clone https://github.com/cfig/Nexus_boot_image_editor.git - cd Nexus_boot_image_editor + git clone https://github.com/cfig/Android_boot_image_editor.git + cd Android_boot_image_editor Then put your boot.img at **$(CURDIR)/boot.img**, then start gradle 'unpack' task: @@ -50,3 +50,7 @@ You get the repacked boot.img at $(CURDIR): ## example & test An example boot.img has been placed at **src/test/resources/boot.img**, which is extracted from Nexus 5x(code: bullhead) factory images from [Google](https://dl.google.com/dl/android/aosp/bullhead-mda89e-factory-29247942.tgz), you can take it as a quick start. + +## boot.img layout +Read [layout](https://github.com/cfig/Android_boot_image_editor/blob/master/README.expert.md) of Android boot.img. +We now support **os\_version** and **os\_patch\_level**. diff --git a/abootimg/build.gradle b/abootimg/build.gradle index 33f9682..8c2ea83 100644 --- a/abootimg/build.gradle +++ b/abootimg/build.gradle @@ -8,7 +8,7 @@ repositories { } dependencies { - compile 'org.codehaus.groovy:groovy-all:2.3.11' + compile 'org.codehaus.groovy:groovy-all:2.4.7' compile group: 'net.sf.jopt-simple', name: 'jopt-simple', version: '5.0.2' testCompile group: 'junit', name: 'junit', version: '4.11' } diff --git a/abootimg/src/main/groovy/cfig/bootimg/CArgs.groovy b/abootimg/src/main/groovy/cfig/bootimg/CArgs.groovy index 94043e3..8f5d3ff 100644 --- a/abootimg/src/main/groovy/cfig/bootimg/CArgs.groovy +++ b/abootimg/src/main/groovy/cfig/bootimg/CArgs.groovy @@ -5,6 +5,7 @@ import groovy.transform.ToString /** * Created by yu at 09:57 on 2016-06-17 */ +@groovy.transform.TypeChecked @ToString(includeNames = true, includeFields = true, excludes = "toCommandLine, CArgs") class CArgs { public String kernel; diff --git a/abootimg/src/main/groovy/cfig/bootimg/CImgInfo.groovy b/abootimg/src/main/groovy/cfig/bootimg/CImgInfo.groovy index 7d66d32..c02cc40 100644 --- a/abootimg/src/main/groovy/cfig/bootimg/CImgInfo.groovy +++ b/abootimg/src/main/groovy/cfig/bootimg/CImgInfo.groovy @@ -1,6 +1,7 @@ package cfig.bootimg import groovy.json.JsonSlurper +import groovy.json.JsonBuilder import groovy.transform.ToString /** @@ -16,7 +17,7 @@ class CImgInfo extends CArgs { public int second_pos; public byte[] hash; - static CImgInfo fromJson(String outFile, String workDir) { + public static CImgInfo fromJson(String outFile, String workDir) { CImgInfo aArg = new CImgInfo(); //preset info aArg.kernel = workDir + File.separator + aArg.kernel; @@ -54,4 +55,45 @@ class CImgInfo extends CArgs { return aArg; } + + private String bytes2String(byte[] inData) { + StringBuilder sb = new StringBuilder(""); + for (int i = 0; i < inData.length; i++) { + sb.append(Integer.toString((inData[i] & 0xff) + 0x100, 16).substring(1)); + } + return sb.toString(); + } + + public void toJson() { + JsonBuilder jb = new JsonBuilder(); + String hashString = bytes2String(this.hash); + jb.bootimg { + args { + base "0x" + Integer.toHexString(this.base); + kernel_offset "0x" + Integer.toHexString(this.kernel_offset); + ramdisk_offset "0x" + Integer.toHexString(this.ramdisk_offset); + second_offset "0x" + Integer.toHexString(this.second_offset); + tags_offset "0x" + Integer.toHexString(this.tags_offset); + pagesize this.pagesize; + board this.board; + cmdline this.cmdline; + os_version this.os_version; + os_patch_level this.os_patch_level; + id this.id; + } + img { + kernel_pos this.kernel_pos; + kernel_len this.kernel_len; + ramdisk_pos this.ramdisk_pos; + ramdisk_len this.ramdisk_len; + second_pos this.second_pos; + second_len this.second_len; + hash hashString; + } + } + FileWriter fw = new FileWriter(this.cfg); + fw.write(jb.toPrettyString()); + fw.flush(); + fw.close(); + } } diff --git a/abootimg/src/main/groovy/cfig/bootimg/Packer.groovy b/abootimg/src/main/groovy/cfig/bootimg/Packer.groovy index d8a6420..91db29b 100644 --- a/abootimg/src/main/groovy/cfig/bootimg/Packer.groovy +++ b/abootimg/src/main/groovy/cfig/bootimg/Packer.groovy @@ -12,6 +12,7 @@ import java.security.MessageDigest; /** * Created by yu at 10:52 on 2016-06-18 */ +@groovy.transform.CompileStatic class Packer { CArgs parse_cmdline(String[] inArgs) { OptionParser parser = new OptionParser(); @@ -51,19 +52,19 @@ class Packer { ret.id = options.has("id") if (options.has("base")) { - ret.base = Integer.decode(options.valueOf("base")) + ret.base = Integer.decode(String.valueOf(options.valueOf("base"))) } else { ret.base = 0x10000000; } if (options.has("kernel_offset")) { - ret.kernel_offset = Integer.decode(options.valueOf("kernel_offset")) + ret.kernel_offset = Integer.decode(String.valueOf(options.valueOf("kernel_offset"))) } else { ret.kernel_offset = 0x00008000; } if (options.has("ramdisk_offset")) { - ret.ramdisk_offset = Integer.decode(options.valueOf("ramdisk_offset")) + ret.ramdisk_offset = Integer.decode(String.valueOf(options.valueOf("ramdisk_offset"))) } else { ret.ramdisk_offset = 0x01000000 } @@ -73,25 +74,25 @@ class Packer { ret.os_patch_level = options.valueOf("os_patch_level") if (options.has("second_offset")) { - ret.second_offset = Integer.decode(options.valueOf("second_offset")) + ret.second_offset = Integer.decode(String.valueOf(options.valueOf("second_offset"))) } else { ret.second_offset = 0x00f00000 } if (options.has("tags_offset")) { - ret.tags_offset = Integer.decode(options.valueOf("tags_offset")) + ret.tags_offset = Integer.decode(String.valueOf(options.valueOf("tags_offset"))) } else { ret.tags_offset = 0x00000100 } if (options.has("pagesize")) { - ret.pagesize = Integer.decode(options.valueOf("pagesize")) + ret.pagesize = Integer.decode(String.valueOf(options.valueOf("pagesize"))) } else { ret.pagesize = 2048 } if (options.has("cmdline")) { - ret.cmdline = options.valueOf("cmdline") + ret.cmdline = String.valueOf(options.valueOf("cmdline")) } else { ret.cmdline = "" } diff --git a/abootimg/src/main/groovy/cfig/bootimg/Parser.groovy b/abootimg/src/main/groovy/cfig/bootimg/Parser.groovy index a92ddfd..b05a72c 100644 --- a/abootimg/src/main/groovy/cfig/bootimg/Parser.groovy +++ b/abootimg/src/main/groovy/cfig/bootimg/Parser.groovy @@ -1,13 +1,13 @@ package cfig.bootimg import groovy.json.JsonBuilder - import java.nio.ByteBuffer import java.nio.ByteOrder /** * Created by yu at 10:58 on 2016-06-18 */ +@groovy.transform.CompileStatic class Parser { int readInt(InputStream is) { @@ -52,14 +52,6 @@ class Parser { return (pagesize - (position & (pagesize - 1))) & (pagesize - 1); } - String bytes2String(byte[] inData) { - StringBuilder sb = new StringBuilder(""); - for (int i = 0; i < inData.length; i++) { - sb.append(Integer.toString((inData[i] & 0xff) + 0x100, 16).substring(1)); - } - return sb.toString(); - } - void parse_header(String fileName, CImgInfo inImgInfo) { InputStream is = new FileInputStream(new File(fileName)) assert Arrays.equals(readBytes(is, 8), "ANDROID!".getBytes()) @@ -102,43 +94,6 @@ class Parser { } } - void extract_img_header(CImgInfo inImgInfo) { - JsonBuilder jb = new JsonBuilder(); - String hashString = bytes2String(inImgInfo.hash); - jb.bootimg { - args { -// kernel inImgInfo.kernel; -// ramdisk inImgInfo.ramdisk; -// second inImgInfo.second; -// output inImgInfo.output; - base "0x" + Integer.toHexString(inImgInfo.base); - kernel_offset "0x" + Integer.toHexString(inImgInfo.kernel_offset); - ramdisk_offset "0x" + Integer.toHexString(inImgInfo.ramdisk_offset); - second_offset "0x" + Integer.toHexString(inImgInfo.second_offset); - tags_offset "0x" + Integer.toHexString(inImgInfo.tags_offset); - pagesize inImgInfo.pagesize; - board inImgInfo.board; - cmdline inImgInfo.cmdline; - os_version inImgInfo.os_version; - os_patch_level inImgInfo.os_patch_level; - id inImgInfo.id; - } - img { - kernel_pos inImgInfo.kernel_pos; - kernel_len inImgInfo.kernel_len; - ramdisk_pos inImgInfo.ramdisk_pos; - ramdisk_len inImgInfo.ramdisk_len; - second_pos inImgInfo.second_pos; - second_len inImgInfo.second_len; - hash hashString; - } - } - FileWriter fw = new FileWriter(inImgInfo.cfg); - fw.write(jb.toPrettyString()); - fw.flush(); - fw.close(); - } - void extract_img_data(String inBootImg, String outImgName, int offset, int length) { if (0 == length) { return; @@ -180,6 +135,6 @@ class Parser { extract_img_data(fileName, imgInfo.kernel, imgInfo.kernel_pos, imgInfo.kernel_len) extract_img_data(fileName, imgInfo.ramdisk, imgInfo.ramdisk_pos, imgInfo.ramdisk_len) extract_img_data(fileName, imgInfo.second, imgInfo.second_pos, imgInfo.second_len) - extract_img_header(imgInfo); + imgInfo.toJson(); } } diff --git a/build.gradle b/build.gradle index 57b1bf2..5ebbffa 100644 --- a/build.gradle +++ b/build.gradle @@ -5,11 +5,14 @@ apply plugin: 'groovy' import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.lang.RuntimeException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.security.MessageDigest; // ---------------------------------------------------------------------------- model { @@ -165,7 +168,11 @@ void gnuZipFile(String compressedFile, String decompressedFile) throws IOExcepti } } -task pack(type: JavaExec, dependsOn: ['abootimg:pack_clear', 'abootimg:pack_clear2', 'abootimg:pack_clear3', 'boot_signer:jar']) { +task verify_clear(dependsOn: ['abootimg:pack_clear', 'abootimg:pack_clear2', 'abootimg:pack_clear3']) << { + verifyClearImages(activeImg + ".clear", activeImg + ".clear2", activeImg + ".clear3"); +} + +task pack(type: JavaExec, dependsOn: [verify_clear, 'boot_signer:jar']) { main = 'com.android.verity.BootSignature' classpath = files("boot_signer/build/libs/boot_signer.jar") maxHeapSize '512m' @@ -304,3 +311,43 @@ if (null == activeImg) { unpack_bootimg.enabled = false unpack_ramdisk_gz.enabled = false } + +if (!new File(workdir + File.separator + "root").exists()) { + pack_ramdisk_and_gz.enabled = false +} + +byte[] hashFile(String inFile) { + MessageDigest md = MessageDigest.getInstance("SHA1") + ByteBuffer itemBF = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + InputStream is = new FileInputStream(new File(inFile)) + int byteRead; + byte[] dataRead = new byte[128] + while (true) { + byteRead = is.read(dataRead) + if (-1 == byteRead) { + break; + } + md.update(dataRead, 0, byteRead) + } + is.close(); + md.update(itemBF.putInt((int) new File(inFile).length()).array()) + + return md.digest(); +} + +void verifyClearImages(String... inFiles) { + assert (inFiles.length > 1) + byte[] ref; + for (String item : inFiles) { + if (null == ref) { + print(" Hashing " + item + "..."); + ref = hashFile(item); + println("DONE"); + } else { + print("Verifying " + item + "..."); + byte[] tgt = hashFile(item); + assert Arrays.equals(ref, tgt); + println("PASSED"); + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 43fbf27..1f13a87 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Apr 15 23:02:44 CST 2016 +#Mon Jun 20 13:41:04 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip