phoenix_firmware_dumper/utils/extract-dtb.py

145 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Copyright 2017 Pablo Castellano
extract-dtb is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
extract-dtb is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with extract-dtb. If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import absolute_import
from __future__ import print_function
import argparse
import os
import string
__version__ = "1.3"
DTB_HEADER = b"\xd0\x0d\xfe\xed"
def dump_file(filename, content):
with open(filename, "wb") as fp:
fp.write(content)
def safe_output_path(output_dir, dtb_filename_new):
""" Safely combines the output folder with the relative path of the dtb
(which may contain subfolders) and creates the necessary folder
structure.
:returns: the resulting file name
"""
if "../" in dtb_filename_new + "/":
raise RuntimeException("DTB file path points outside of extraction"
" directory: " + dtb_filename_new)
ret = os.path.join(args.output_dir, dtb_filename_new)
os.makedirs(os.path.dirname(ret), exist_ok=True)
return ret
def split(args):
""" Reads a file and looks for DTB_HEADER occurrences (beginning of each DTB)
Then extract each one. If possible, use the device model as filename.
"""
positions = []
with open(args.filename, "rb") as fp:
content = fp.read()
dtb_next = content.find(DTB_HEADER)
while dtb_next != -1:
positions.append(dtb_next)
dtb_next = content.find(DTB_HEADER, dtb_next + 1)
if len(positions) == 0:
print("No appended dtbs found")
return
if args.extract:
os.makedirs(args.output_dir, exist_ok=True)
begin_pos = 0
for n, pos in enumerate(positions, 0):
dtb_filename = get_dtb_filename(n)
filepath = os.path.join(args.output_dir, dtb_filename)
dump_file(filepath, content[begin_pos:pos])
if n > 0:
dtb_name = get_dtb_model(filepath)
if dtb_name:
dtb_filename_new = get_dtb_filename(n, dtb_name)
dtb_filename_new_full = safe_output_path(args.output_dir,
dtb_filename_new)
os.rename(filepath, dtb_filename_new_full)
dtb_filename = dtb_filename_new
print("Dumped {0}, start={1} end={2}"
.format(dtb_filename, begin_pos, pos))
begin_pos = pos
# Last chunk
dtb_filename = get_dtb_filename(n + 1)
filepath = os.path.join(args.output_dir, dtb_filename)
dump_file(filepath, content[begin_pos:])
dtb_name = get_dtb_model(filepath)
if dtb_name:
dtb_filename_new = get_dtb_filename(n + 1, dtb_name)
os.rename(os.path.join(filepath),
os.path.join(args.output_dir, dtb_filename_new))
dtb_filename = dtb_filename_new
print("Dumped {0}, start={1} end={2}"
.format(dtb_filename, begin_pos, len(content)))
print("Extracted {0} appended dtbs + kernel to {1}"
.format(len(positions), args.output_dir))
else:
print("Found {0} appended dtbs".format(len(positions)))
def get_dtb_filename(n, suffix=""):
if n == 0:
return "00_kernel"
n = str(n).zfill(2)
basename = "{0}_dtbdump".format(n)
if suffix != "":
basename += "_" + suffix.replace(" ", "_").replace("/", "_")
return basename + ".dtb"
def get_dtb_model(filename, min_length=4):
""" Finds the first printable string in a file with length greater
than min_length. Replaces spaces with underscores.
"""
with open(filename, errors="ignore") as f:
result = ""
for c in f.read():
if c in string.printable:
result += c
continue
if len(result) >= min_length:
return result.replace(" ", "_")
result = ""
if len(result) >= min_length: # catch result at EOF
return result.replace(" ", "_")
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Extract dtbs from kernel images.")
parser.add_argument("filename", help="Android kernel image")
parser.add_argument("-o", dest="output_dir", default="dtb",
required=False, help="Output directory")
parser.add_argument("-n", dest="extract", action="store_false", default=True,
required=False, help="Do not extract, just output information")
parser.add_argument("-V", "--version", action="version", version=__version__)
args = parser.parse_args()
split(args)