first version compatible with Windows 10

pull/53/head
cfig 4 years ago
parent 0dbf161ce8
commit b108110dbd
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -1,3 +1,7 @@
branches:
only:
- master
- dev
language: java
os:
- linux

@ -2,27 +2,18 @@
[![Build Status](https://travis-ci.org/cfig/Android_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Android_boot_image_editor)
[![License](http://img.shields.io/:license-apache-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html)
A tool for reverse engineering Android ROM images. (working on ![Linux](doc/linux24.png)(Ubuntu 18.04+) and ![Mac](doc/apple24.png))
A tool for reverse engineering Android ROM images.
## Getting Started
#### Installation
* install required packages
#### install required packages
```bash
sudo apt install device-tree-compiler lz4 xz zlib1g-dev
```
Mac: `brew install lz4 xz`
* get the tool
```bash
git clone https://github.com/cfig/Android_boot_image_editor.git --depth=1
```
Linux: `sudo apt install device-tree-compiler lz4 xz zlib1g-dev openjdk-11-jdk`
or clone it from mirror:
```bash
git clone https://gitee.com/cfig/Android_boot_image_editor.git --depth=1
```
Windows: Make sure you have `python3`, `JDK9+` and `openssl` properly installed.
An easy way is to install [Anaconda](https://www.anaconda.com/products/individual#windows) and [Oracle JDK 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), then run the program under anaconda PowerShell.
#### Parsing and packing

@ -63,9 +63,9 @@ class EnvironmentVerifier {
} catch (e: Exception) {
log.warn("'dtc' not installed. Please install it manually to analyze DTB files")
if (isMacOS) {
log.warn("For Mac OS: \n\n\tbrew install dtc\n")
} else {
log.warn("Like this: \n\n\t$ sudo apt install device-tree-compiler")
log.warn("For Mac OS: \n\tbrew install dtc\n")
} else if (isLinux) {
log.warn("Like this: \n\t$ sudo apt install device-tree-compiler")
}
return false
}
@ -75,6 +75,12 @@ class EnvironmentVerifier {
val isMacOS: Boolean
get() = System.getProperty("os.name").contains("Mac")
val isLinux: Boolean
get() = System.getProperty("os.name").contains("Linux")
val isWindows: Boolean
get() = System.getProperty("os.name").contains("Windows")
private fun getJavaVersion(): Int {
return System.getProperty("java.version").let { version ->
if (version.startsWith("1.")) {

@ -340,7 +340,7 @@ class Avb {
}
fun verifyAVBIntegrity(fileName: String, avbtool: String) {
val cmdline = "$avbtool verify_image --image $fileName"
val cmdline = "python $avbtool verify_image --image $fileName"
log.info(cmdline)
try {
DefaultExecutor().execute(CommandLine.parse(cmdline))

@ -14,6 +14,8 @@ import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.io.FileInputStream
import java.lang.NumberFormatException
import java.nio.ByteBuffer
@ -119,12 +121,16 @@ class Common {
Helper.extractFile(s.srcFile, s.dumpFile, s.offset.toLong(), s.length)
when {
ZipHelper.isGZ(s.dumpFile) -> {
File(s.dumpFile).renameTo(File(s.dumpFile + ".gz"))
Files.move(
Paths.get(s.dumpFile), Paths.get(s.dumpFile + ".gz"),
java.nio.file.StandardCopyOption.REPLACE_EXISTING)
ZipHelper.unGnuzipFile(s.dumpFile + ".gz", s.dumpFile)
}
ZipHelper.isLZ4(s.dumpFile) -> {
log.info("ramdisk is compressed lz4")
File(s.dumpFile).renameTo(File(s.dumpFile + ".lz4"))
Files.move(
Paths.get(s.dumpFile), Paths.get(s.dumpFile + ".lz4"),
java.nio.file.StandardCopyOption.REPLACE_EXISTING)
ZipHelper.decompressLZ4Ext(s.dumpFile + ".lz4", s.dumpFile)
ret = "lz4"
}

@ -10,6 +10,7 @@ import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory
import java.io.File
import cfig.EnvironmentVerifier
class Signer {
@OptIn(ExperimentalUnsignedTypes::class)
@ -30,7 +31,8 @@ class Signer {
partition_name = bootDesc.partition_name,
newAvbInfo = newAvbInfo)
//original signer
CommandLine.parse("$avbtool add_hash_footer").apply {
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
CommandLine.parse(cmdPrefix + "$avbtool add_hash_footer").apply {
addArguments("--image ${output}.signed2")
addArguments("--partition_size ${imageSize}")
addArguments("--salt ${Helper.toHexString(bootDesc.salt)}")

@ -1,6 +1,7 @@
package cfig.bootimg.cpio
import cfig.helper.Helper
import cfig.EnvironmentVerifier
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream
@ -37,8 +38,12 @@ class AndroidCpio {
}
}
} else { //later visit
log.debug(item.path + " ~ " + root.path)
val newOutname = item.path.substring(root.path.length + 1) //remove leading slash
val newOutname = if (EnvironmentVerifier().isWindows) {
item.path.substring(root.path.length + 1).replace("\\", "/")
} else {
item.path.substring(root.path.length + 1) //remove leading slash
}
log.debug(item.path + " ~ " + root.path + " => " + newOutname)
val entry = when {
Files.isSymbolicLink(item.toPath()) -> {
val target = Files.readSymbolicLink(Paths.get(item.path))
@ -202,23 +207,35 @@ class AndroidCpio {
when {
entry.isSymbolicLink -> {
entryInfo.note = ("LNK " + entryInfo.note)
Files.createSymbolicLink(Paths.get(outEntryName), Paths.get(String(buffer)))
if (EnvironmentVerifier().isWindows) {
File(outEntryName).writeBytes(buffer)
} else {
Files.createSymbolicLink(Paths.get(outEntryName), Paths.get(String(buffer)))
}
}
entry.isRegularFile -> {
entryInfo.note = ("REG " + entryInfo.note)
File(outEntryName).writeBytes(buffer)
Files.setPosixFilePermissions(
Paths.get(outEntryName),
Helper.modeToPermissions((entry.mode and 0xfff).toInt())
)
if (EnvironmentVerifier().isWindows) {
//Windows: Posix not supported
} else {
Files.setPosixFilePermissions(
Paths.get(outEntryName),
Helper.modeToPermissions((entry.mode and 0xfff).toInt())
)
}
}
entry.isDirectory -> {
entryInfo.note = ("DIR " + entryInfo.note)
File(outEntryName).mkdir()
Files.setPosixFilePermissions(
Paths.get(outEntryName),
Helper.modeToPermissions((entry.mode and 0xfff).toInt())
)
if (!EnvironmentVerifier().isWindows) {
Files.setPosixFilePermissions(
Paths.get(outEntryName),
Helper.modeToPermissions((entry.mode and 0xfff).toInt())
)
} else {
//Windows
}
}
else -> throw IllegalArgumentException("??? type unknown")
}

@ -404,7 +404,8 @@ data class BootV2(
}
private fun toCommandLine(): CommandLine {
val ret = CommandLine(Helper.prop("mkbootimg"))
val ret = CommandLine("python")
ret.addArgument(Helper.prop("mkbootimg"))
ret.addArgument(" --header_version ")
ret.addArgument(info.headerVersion.toString())
ret.addArgument(" --base ")
@ -467,7 +468,8 @@ data class BootV2(
}
fun sign(): BootV2 {
val avbtool = String.format(Helper.prop("avbtool"), if (Common.parseOsMajor(info.osVersion.toString()) > 10) "v1.2" else "v1.1")
//unify with v1.1/v1.2 avbtool
val avbtool = String.format(Helper.prop("avbtool"), "v1.2")
if (info.verify == "VB2.0") {
Signer.signAVB(info.output, this.info.imageSize, avbtool)
log.info("Adding hash_footer with verified-boot 2.0 style")

@ -204,7 +204,8 @@ data class BootV3(var info: MiscInfo = MiscInfo(),
}
private fun toCommandLine(): CommandLine {
return CommandLine(Helper.prop("mkbootimg")).let { ret ->
return CommandLine("python").let { ret ->
ret.addArgument(Helper.prop("mkbootimg"))
ret.addArgument("--header_version")
ret.addArgument(info.headerVersion.toString())
if (kernel.size > 0) {

@ -16,6 +16,7 @@ import java.io.FileOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import cfig.bootimg.Common as C
import cfig.EnvironmentVerifier
@OptIn(ExperimentalUnsignedTypes::class)
data class VendorBoot(var info: MiscInfo = MiscInfo(),
@ -215,17 +216,19 @@ data class VendorBoot(var info: MiscInfo = MiscInfo(),
}
private fun toCommandLine(): CommandLine {
return CommandLine(Helper.prop("mkbootimg"))
.addArgument("--vendor_ramdisk").addArgument(ramdisk.file)
.addArgument("--dtb").addArgument(dtb.file)
.addArgument("--vendor_cmdline").addArgument(info.cmdline, false)
.addArgument("--header_version").addArgument(info.headerVersion.toString())
.addArgument("--base").addArgument("0")
.addArgument("--tags_offset").addArgument(info.tagsLoadAddr.toString())
.addArgument("--kernel_offset").addArgument(info.kernelLoadAddr.toString())
.addArgument("--ramdisk_offset").addArgument(ramdisk.loadAddr.toString())
.addArgument("--dtb_offset").addArgument(dtb.loadAddr.toString())
.addArgument("--pagesize").addArgument(info.pageSize.toString())
.addArgument("--vendor_boot")
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).apply {
addArgument("--vendor_ramdisk").addArgument(ramdisk.file)
addArgument("--dtb").addArgument(dtb.file)
addArgument("--vendor_cmdline").addArgument(info.cmdline, false)
addArgument("--header_version").addArgument(info.headerVersion.toString())
addArgument("--base").addArgument("0")
addArgument("--tags_offset").addArgument(info.tagsLoadAddr.toString())
addArgument("--kernel_offset").addArgument(info.kernelLoadAddr.toString())
addArgument("--ramdisk_offset").addArgument(ramdisk.loadAddr.toString())
addArgument("--dtb_offset").addArgument(dtb.loadAddr.toString())
addArgument("--pagesize").addArgument(info.pageSize.toString())
addArgument("--vendor_boot")
}
}
}

@ -64,6 +64,7 @@ data class BootloaderMsg(//offset 0, size 2k
this.recovery = info[2] as String
this.stage = info[3] as String
this.reserved = info[4] as ByteArray
fis.close()
} else {
log.info("$miscFile missing")
}

@ -226,7 +226,10 @@ class ZipHelper {
fun isGZ(compressedFile: String): Boolean {
return try {
GZIPInputStream(FileInputStream(compressedFile)).use { }
FileInputStream(compressedFile).use { fis ->
GZIPInputStream(fis).use {
}
}
true
} catch (e: ZipException) {
false
@ -235,7 +238,10 @@ class ZipHelper {
fun isXZ(compressedFile: String): Boolean {
return try {
XZCompressorInputStream(FileInputStream(compressedFile)).use { }
FileInputStream(compressedFile).use { fis ->
XZCompressorInputStream(fis).use {
}
}
true
} catch (e: ZipException) {
false

@ -20,7 +20,8 @@ class KernelExtractor {
val ret: MutableList<String> = mutableListOf()
val kernelVersionFile = Helper.prop("kernelVersionFile")
val kernelConfigFile = Helper.prop("kernelConfigFile")
val cmd = CommandLine.parse(Helper.prop("kernelExtracter")).let {
val cmd = CommandLine.parse("python").let {
it.addArgument(Helper.prop("kernelExtracter"))
it.addArgument("--input")
it.addArgument(fileName)
it.addArgument("--output-configs")

@ -36,8 +36,11 @@ class DtboParser(val workDir: File) : IPackable {
}
execInDirectory(cmd, this.workDir)
val props = Properties()
props.load(FileInputStream(File(headerPath)))
val props = Properties().apply {
FileInputStream(File(headerPath)).use { fis ->
load(fis)
}
}
if (envv.hasDtc) {
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {
val inputDtb = "$dtbPath.$i"
@ -56,8 +59,11 @@ class DtboParser(val workDir: File) : IPackable {
}
val headerPath = File("${outDir}/dtbo.header").path
val props = Properties()
props.load(FileInputStream(File(headerPath)))
val props = Properties().apply {
FileInputStream(File(headerPath)).use { fis ->
load(fis)
}
}
val cmd = CommandLine.parse("$dtboMaker create $fileName.clear").let {
it.addArguments("--version=1")
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {

@ -11,13 +11,13 @@ import java.io.File
@OptIn(ExperimentalUnsignedTypes::class)
class BootloaderMsgTest {
private val log = LoggerFactory.getLogger(BootloaderMsgTest::class.java)
@After
fun tearDown() {
File(BootloaderMsg.miscFile).deleleIfExists()
}
private val log = LoggerFactory.getLogger(BootloaderMsgTest::class.java)
@Test
fun writeRebootBootloaderTest() {
val msg = BootloaderMsg()

@ -1,12 +1,20 @@
package init
import cfig.bootloader_message.BootloaderMsg
import cfig.init.Reboot
import org.junit.Test
import org.junit.After
import java.io.File
import java.util.*
import cfig.bootloader_message.BootloaderMsg
import cfig.init.Reboot
import cfig.bootimg.Common.Companion.deleleIfExists
@OptIn(ExperimentalUnsignedTypes::class)
class RebootTest {
@After
fun tearDown() {
File(BootloaderMsg.miscFile).deleleIfExists()
}
@Test
fun testDifferentModes() {
Reboot.handlePowerctlMessage("reboot,recovery")

@ -83,9 +83,11 @@ tasks {
if (System.getProperty("os.name").contains("Mac")) {
unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseMacos")
packTask.dependsOn("aosp:libsparse:img2simg:installReleaseMacos")
} else {
} else if (System.getProperty("os.name").contains("Linux")) {
unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseLinux")
packTask.dependsOn("aosp:libsparse:img2simg:installReleaseLinux")
} else {
logger.info("Disable C++ modules on Window$")
}
}

@ -64,3 +64,16 @@ place 'ramdisk.img.gz' in directory, delete "root/", program will use it as preb
## cpio
decompress cpio with commandline `cpio -idmv -F <file>`
### cpio on windows
* got `java.nio.file.FileSystemException` and says "A required privilege is not held by the client"
```
java.base/java.nio.file.Files.createSymbolicLink(Files.java:1058)
```
Solution:
Avoid using this feature on Windows, create regular file instead.
* File.renameTo() is problematic, use Files.move() instead.
* remember to close File streams to avoid any potential problems

3
gradlew.bat vendored

@ -21,6 +21,9 @@
@rem
@rem ##########################################################################
@rem set path for lz4
@IF EXIST tools\bin SET PATH=%PATH%;tools\bin
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@ -1,7 +1,7 @@
#!/usr/bin/env python3
import shutil, os.path, json, subprocess, hashlib, glob
import unittest, logging, sys, lzma
import unittest, logging, sys, lzma, time
successLogo = """
+----------------------------------+
@ -24,8 +24,14 @@ def hashFile(fileName):
return hasher.hexdigest()
def deleteIfExists(inFile):
if os.path.isfile(inFile):
os.remove(inFile)
for i in range(3):
try:
if os.path.isfile(inFile):
os.remove(inFile)
return
except Exception as e:
log.warn("Exception in cleaning up %s" % inFile)
time.sleep(3)
def cleanUp():
log.info("clean up ...")
@ -62,8 +68,12 @@ def verifySingleJson(jsonFile):
decompressXZ(it + ".xz", v)
else:
raise
subprocess.check_call("./gradlew unpack", shell = True)
subprocess.check_call("./gradlew pack", shell = True)
if sys.platform == "win32":
gradleWrapper = "gradlew.bat"
else:
gradleWrapper = "./gradlew"
subprocess.check_call(gradleWrapper + " unpack", shell = True)
subprocess.check_call(gradleWrapper + " pack", shell = True)
for k, v in verifyItems["hash"].items():
log.info("%s : %s" % (k, v))
unittest.TestCase().assertEqual(v, hashFile(k))
@ -81,7 +91,11 @@ def verifySingleDir(inResourceDir, inImageDir):
for pyFile in pyFiles:
cleanUp()
log.warning("calling %s" % pyFile)
subprocess.check_call(pyFile, shell = True)
if sys.platform == "win32":
theCmd = "python " + pyFile
else:
theCmd = pyFile
subprocess.check_call(theCmd, shell = True)
cleanUp()
log.info("Leave %s" % os.path.join(resDir, imgDir))
@ -91,6 +105,14 @@ def decompressXZ(inFile, outFile):
with open(outFile, "wb") as f2:
f2.write(file_content)
def seekedCopy(inFile, outFile, offset):
print(inFile + " -> " + outFile)
with open(inFile, "rb") as reader:
reader.seek(offset)
content = reader.read()
with open(outFile, "wb") as writer:
writer.write(content)
def main():
# from volunteers
verifySingleDir(resDir, "recovery_image_from_s-trace")
@ -102,11 +124,13 @@ def main():
verifySingleDir(resDir, "6.0.0_bullhead_mda89e")
# 7.0 special boot
cleanUp()
subprocess.check_call("dd if=%s/7.1.1_volantis_n9f27m/boot.img of=boot.img bs=256 skip=1" % resDir, shell = True)
verifySingleJson("%s/7.1.1_volantis_n9f27m/boot.json" % resDir)
#subprocess.check_call("dd if=%s/7.1.1_volantis_n9f27m/boot.img of=boot.img bs=256 skip=1" % resDir, shell = True)
seekedCopy(os.path.join(resDir, "7.1.1_volantis_n9f27m", "boot.img"), "boot.img", 256)
verifySingleJson(resDir + "/7.1.1_volantis_n9f27m/boot.json")
# 7.0 special recovery
cleanUp()
subprocess.check_call("dd if=%s/7.1.1_volantis_n9f27m/recovery.img of=recovery.img bs=256 skip=1" % resDir, shell = True)
#subprocess.check_call("dd if=%s/7.1.1_volantis_n9f27m/recovery.img of=recovery.img bs=256 skip=1" % resDir, shell = True)
seekedCopy(os.path.join(resDir, "7.1.1_volantis_n9f27m", "recovery.img"), "recovery.img", 256)
verifySingleJson("%s/7.1.1_volantis_n9f27m/recovery.json" % resDir)
# 8.0
verifySingleDir(resDir, "8.0.0_fugu_opr2.170623.027")

@ -1 +1 @@
Subproject commit 5ff0d54252367d6bae8c8448dc0100c98823a212
Subproject commit 35fd7989555769a3cca47537798df28fc733479f

@ -2,7 +2,6 @@
# release.mk
# yu, 2020-12-20 00:19
#
define gw
#!/usr/bin/env sh\n
if [ "x$$1" = "xassemble" ]; then\n
@ -20,7 +19,12 @@ fi\n
java -jar bbootimg/bbootimg.jar $$*
endef
export gw
define gw_win
@IF EXIST tools\\bin SET PATH=%PATH%;tools\\bin\n
@java -jar bbootimg/bbootimg.jar %*
endef
export gw gw_win
all:
cd ../bbootimg && gradle build
cp ../bbootimg/build/libs/bbootimg.jar .
@ -33,7 +37,9 @@ all:
mkdir ../bbootimg && mv bbootimg.jar ../bbootimg/
echo $$gw > gradlew
chmod 755 gradlew
echo $$gw_win > gradlew.bat
mv gradlew ../
mv gradlew.bat ../
# vim:ft=make
#

Loading…
Cancel
Save