forked from GrapheneOS-Archive/android-prepare-vendor
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
From cyxx/extract_android_ota_payload#8 Commit 254cbdc9825056fd6ac49aea5a4fc9bcefe0b3d0 "Support selective extraction of partitions" Signed-off-by: spezi77 <[email protected]>
- Loading branch information
1 parent
a5e28e5
commit 940193a
Showing
5 changed files
with
828 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# extract_android_ota_payload.py | ||
|
||
Extract Android firmware images from an OTA payload.bin file. | ||
|
||
With the introduction of the A/B system update, the OTA file format changed. | ||
This tool allows to extract and decompress the firmware images packed using the 'brillo' toolset. | ||
|
||
Incremental firmware images are not supported (source_copy, source_bsdiff operations). | ||
|
||
## Usage | ||
|
||
``` | ||
$ extract_android_ota_payload.py <payload.bin> [target_dir] [partition.img] | ||
<payload.bin> : file extracted from the OTA zip file or the OTA zip file | ||
<target_dir> : output directory for the extracted file | ||
<partition_img> : name of partition to be extracted | ||
``` | ||
|
||
## Example | ||
|
||
``` | ||
$ python extract_android_ota_payload.py marlin-ota-opm4.171019.021.d1-fd6998a5.zip /tmp/ | ||
Extracting 'boot.img' | ||
Extracting 'system.img' | ||
Extracting 'vendor.img' | ||
... | ||
Extracting 'modem.img' | ||
``` | ||
|
||
## Dependencies | ||
|
||
``` | ||
python-protobuf | ||
``` |
161 changes: 161 additions & 0 deletions
161
scripts/extract_android_ota_payload/extract_android_ota_payload.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/usr/bin/env python | ||
|
||
import hashlib | ||
import os | ||
import os.path | ||
import shutil | ||
import struct | ||
import subprocess | ||
import sys | ||
import zipfile | ||
|
||
# protobufs compiled with protoc 2.5.0 are not compatible with python3 | ||
if sys.version_info[0] != 2: | ||
raise Exception("Python 2.x is required") | ||
|
||
# from https://android.googlesource.com/platform/system/update_engine/+/refs/heads/master/scripts/update_payload/ | ||
import update_metadata_pb2 | ||
|
||
PROGRAMS = [ 'bzcat', 'xzcat' ] | ||
|
||
BRILLO_MAJOR_PAYLOAD_VERSION = 2 | ||
|
||
class PayloadError(Exception): | ||
pass | ||
|
||
class Payload(object): | ||
class _PayloadHeader(object): | ||
_MAGIC = 'CrAU' | ||
|
||
def __init__(self): | ||
self.version = None | ||
self.manifest_len = None | ||
self.metadata_signature_len = None | ||
self.size = None | ||
|
||
def ReadFromPayload(self, payload_file): | ||
magic = payload_file.read(4) | ||
if magic != self._MAGIC: | ||
raise PayloadError('Invalid payload magic: %s' % magic) | ||
self.version = struct.unpack('>Q', payload_file.read(8))[0] | ||
self.manifest_len = struct.unpack('>Q', payload_file.read(8))[0] | ||
self.size = 20 | ||
self.metadata_signature_len = 0 | ||
if self.version != BRILLO_MAJOR_PAYLOAD_VERSION: | ||
raise PayloadError('Unsupported payload version (%d)' % self.version) | ||
self.size += 4 | ||
self.metadata_signature_len = struct.unpack('>I', payload_file.read(4))[0] | ||
|
||
def __init__(self, payload_file): | ||
self.payload_file = payload_file | ||
self.header = None | ||
self.manifest = None | ||
self.data_offset = None | ||
self.metadata_signature = None | ||
self.metadata_size = None | ||
|
||
def _ReadManifest(self): | ||
return self.payload_file.read(self.header.manifest_len) | ||
|
||
def _ReadMetadataSignature(self): | ||
self.payload_file.seek(self.header.size + self.header.manifest_len) | ||
return self.payload_file.read(self.header.metadata_signature_len); | ||
|
||
def ReadDataBlob(self, offset, length): | ||
self.payload_file.seek(self.data_offset + offset) | ||
return self.payload_file.read(length) | ||
|
||
def Init(self): | ||
self.header = self._PayloadHeader() | ||
self.header.ReadFromPayload(self.payload_file) | ||
manifest_raw = self._ReadManifest() | ||
self.manifest = update_metadata_pb2.DeltaArchiveManifest() | ||
self.manifest.ParseFromString(manifest_raw) | ||
metadata_signature_raw = self._ReadMetadataSignature() | ||
if metadata_signature_raw: | ||
self.metadata_signature = update_metadata_pb2.Signatures() | ||
self.metadata_signature.ParseFromString(metadata_signature_raw) | ||
self.metadata_size = self.header.size + self.header.manifest_len | ||
self.data_offset = self.metadata_size + self.header.metadata_signature_len | ||
|
||
def decompress_payload(command, data, size, hash): | ||
p = subprocess.Popen([command, '-'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) | ||
r = p.communicate(data)[0] | ||
if len(r) != size: | ||
print("Unexpected size %d %d" % (len(r), size)) | ||
elif hashlib.sha256(data).digest() != hash: | ||
print("Hash mismatch") | ||
return r | ||
|
||
def parse_payload(payload_f, partition, out_f): | ||
BLOCK_SIZE = 4096 | ||
for operation in partition.operations: | ||
e = operation.dst_extents[0] | ||
data = payload_f.ReadDataBlob(operation.data_offset, operation.data_length) | ||
out_f.seek(e.start_block * BLOCK_SIZE) | ||
if operation.type == update_metadata_pb2.InstallOperation.REPLACE: | ||
out_f.write(data) | ||
elif operation.type == update_metadata_pb2.InstallOperation.REPLACE_XZ: | ||
r = decompress_payload('xzcat', data, e.num_blocks * BLOCK_SIZE, operation.data_sha256_hash) | ||
out_f.write(r) | ||
elif operation.type == update_metadata_pb2.InstallOperation.REPLACE_BZ: | ||
r = decompress_payload('bzcat', data, e.num_blocks * BLOCK_SIZE, operation.data_sha256_hash) | ||
out_f.write(r) | ||
else: | ||
raise PayloadError('Unhandled operation type (%d)' % operation.type) | ||
|
||
def main(filename, output_dir, partition): | ||
if filename.endswith('.zip'): | ||
print("Extracting 'payload.bin' from OTA file...") | ||
ota_zf = zipfile.ZipFile(filename) | ||
payload_file = open(ota_zf.extract('payload.bin', output_dir)) | ||
else: | ||
payload_file = file(filename) | ||
|
||
payload = Payload(payload_file) | ||
payload.Init() | ||
|
||
for p in payload.manifest.partitions: | ||
name = p.partition_name + '.img' | ||
if (partition is not None and name != partition): | ||
continue | ||
print("Extracting '%s'" % name) | ||
fname = os.path.join(output_dir, name) | ||
out_f = open(fname, 'w') | ||
try: | ||
parse_payload(payload, p, out_f) | ||
except PayloadError as e: | ||
print('Failed: %s' % e) | ||
out_f.close() | ||
os.unlink(fname) | ||
|
||
if __name__ == '__main__': | ||
try: | ||
filename = sys.argv[1] | ||
except: | ||
print('Usage: %s payload.bin [output_dir] [partition.img]' % sys.argv[0]) | ||
sys.exit() | ||
|
||
try: | ||
output_dir = sys.argv[2] | ||
except IndexError: | ||
output_dir = os.getcwd() | ||
|
||
try: | ||
partition = sys.argv[3] | ||
except IndexError: | ||
try: | ||
partition = sys.argv[2] | ||
except IndexError: | ||
partition = None | ||
|
||
if partition is not None and not partition.endswith('.img'): | ||
partition = None | ||
|
||
if output_dir.endswith('.img'): | ||
output_dir = os.getcwd() | ||
|
||
if not os.path.exists(output_dir): | ||
os.makedirs(output_dir) | ||
|
||
main(filename, output_dir, partition) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
protobuf>=2.5.0 |
Oops, something went wrong.