Last active
March 31, 2025 20:02
-
-
Save thurask/6edc649c9e832bc2ab49787359c22776 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#!/usr/bin/env python3 | |
import gzip | |
import os | |
import shutil | |
import subprocess | |
from defusedxml import ElementTree | |
import yaml | |
""" | |
*Run this on Linux, first of all | |
*Python 3.5+ is needed | |
*It will ask you for your sudo password, if you have a password set for it | |
*Have simg2img, abootimg, cpio, and Java (8+) in your path (apt install simg2img abootimg cpio on *buntu) | |
*Have pyyaml and defusedxml installed via pip (pip3/pip install pyyaml defusedxml) | |
*Have the apktool jar as ~/apktool_2.4.1.jar (change in script as needed) | |
*Run this script from a terminal in the same folder as extracted OS image files (system.img, userdata.img, etc) | |
*Comment out the functions in the bottom block if necessary | |
Currently tested on: | |
*Motion (Nougat) | |
*KEYone (Nougat) | |
*DTEK60 (Marshmallow) | |
*DTEK50 (Marshmallow) | |
*Priv (Lollipop, Marshmallow) | |
""" | |
def simg2img(indir, excepts=None): | |
if excepts is None: | |
excepts = ["dummy.nevergonnahappen"] | |
exts = [os.path.join(indir, x) for x in os.listdir(indir) if x.endswith(".img") and ".raw" not in x and x not in excepts] | |
for ext in exts: | |
print("DECOMPRESSING: {0}".format(os.path.basename(ext))) | |
subprocess.run(["simg2img", ext, ext.replace(".img", ".raw.img")]) | |
def indiv_abootimg(imgfile): | |
imgdir = os.path.dirname(imgfile) | |
imgname = os.path.basename(imgfile).split(".")[0] | |
outdir = os.path.join(imgdir, "output", imgname) | |
here = os.getcwd() | |
os.chdir(outdir) | |
with open(os.devnull, "wb") as dnull: | |
subprocess.run(["abootimg", "-x", imgfile], stdout=dnull, stderr=subprocess.STDOUT) | |
for image in ["initrd.img", "stage2.img"]: | |
imagepath = os.path.join(outdir, image) | |
if not os.path.exists(imagepath): | |
pass | |
else: | |
imagedir = os.path.join(outdir, image.split(".")[0]) | |
if not os.path.exists(imagedir): | |
os.makedirs(imagedir) | |
os.chdir(imagedir) | |
try: | |
gzdata = gunzip(imagepath) | |
except OSError: | |
continue | |
else: | |
with open(os.devnull, "wb") as dnull: | |
subprocess.run(["cpio", "-id"], input=gzdata, stdout=dnull, stderr=subprocess.STDOUT) | |
os.chdir(here) | |
def gunzip(gzfile): | |
with gzip.open(gzfile) as gunfile: | |
data = gunfile.read() | |
return data | |
def boot_recovery_extract(indir): | |
prep_output_dirs(indir, ["boot", "recovery"]) | |
for img in ["boot.img", "recovery.img"]: | |
indiv_abootimg(os.path.join(indir, img)) | |
def prep_dirs(indir): | |
loopdir = os.path.join(indir, "loop") | |
outdir = os.path.join(indir, "output") | |
for dir in (loopdir, outdir): | |
if not os.path.exists(dir): | |
os.makedirs(dir) | |
def prep_output_dirs(indir, dirlist): | |
for dir in dirlist: | |
outdir = os.path.join(indir, "output", dir) | |
if not os.path.exists(dir) and not os.path.exists(outdir): | |
os.makedirs(outdir) | |
def remove_loop(indir): | |
os.rmdir(os.path.join(indir, "loop")) | |
def indiv_imgextract(imgfile, fstype="ext4"): | |
imgdir = os.path.dirname(imgfile) | |
imgname = os.path.basename(imgfile).split(".")[0] | |
subprocess.run(["sudo", "mount", "-t", fstype, "-o", "loop", os.path.join(imgdir, imgfile), os.path.join(imgdir, "loop")]) | |
with open(os.devnull, "wb") as dnull: | |
subprocess.run(["sudo", "cp", "-r", os.path.join(imgdir, "loop"), os.path.join(imgdir, "output", imgname)], stdout=dnull, stderr=subprocess.STDOUT) | |
subprocess.run(["sudo", "umount", os.path.join(imgdir, "loop")]) | |
def imgextract(indir, dirnames, fstype="ext4"): | |
for dir in dirnames: | |
print("EXTRACTING IMAGE: {0}".format(os.path.basename(dir))) | |
indiv_imgextract(os.path.join(indir, dir), fstype) | |
def get_yml_info(yamlfile): | |
with open(yamlfile, "r") as afile: | |
skiptag = next(afile) | |
ydata = yaml.load(afile.read(), Loader=yaml.FullLoader) | |
sdkinfo = ydata["sdkInfo"] | |
if sdkinfo is None: | |
sdkinfo = {"minSdkVersion": "unknown"} | |
elif "minSdkVersion" not in sdkinfo.keys(): | |
sdkinfo = {"minSdkVersion": "unknown"} | |
return sdkinfo, ydata["versionInfo"] | |
def get_xml_info(xmlfile): | |
tree = ElementTree.parse(xmlfile) | |
root = tree.getroot() | |
return root.attrib["package"] | |
def is_odexed(apkdir): | |
status = "deodexed" if "classes.dex" in os.listdir(apkdir) else "odexed" | |
return status | |
def get_apk_filename(apkdir): | |
sdkinfo, versioninfo = get_yml_info(os.path.join(apkdir, "apktool.yml")) | |
packname = get_xml_info(os.path.join(apkdir, "AndroidManifest.xml")) | |
status = is_odexed(apkdir) | |
filename = "{0}_{1}-{2}_minAPI{3}_{4}.apk".format(packname, versioninfo["versionName"], versioninfo["versionCode"], sdkinfo["minSdkVersion"], status) | |
return filename | |
def apktool(apkdir, basedir, apktoolpath=None, framedir=None): | |
if apktoolpath is None: | |
apktoolversion = "2.4.1" | |
apktoolpath = os.path.join(os.path.expanduser("~"), "apktool_{0}.jar".format(apktoolversion)) | |
apkname = "{0}.apk".format(apkdir.split(os.sep)[-1]) | |
if framedir is None: | |
framedir = os.path.join(basedir, "output", "system", "framework") | |
with open(os.devnull, "wb") as dnull: | |
subprocess.run(["java", "-jar", apktoolpath, "d", os.path.join(apkdir, apkname), "-s", "-f", "-p", framedir, "-o", os.path.join(apkdir, apkname).replace(".apk", "")], stdout=dnull, stderr=subprocess.STDOUT) | |
#subprocess.run(["java", "-jar", apktoolpath, "d", os.path.join(apkdir, apkname), "-s", "-f", "-p", framedir, "-o", os.path.join(apkdir, apkname).replace(".apk", "")]) | |
def indiv_apkextract(basedir, outdir, apkdir, apktoolpath=None, framedir=None): | |
apktool(apkdir, basedir, apktoolpath, framedir) | |
apkname = apkdir.split(os.sep)[-1] | |
try: | |
fname = get_apk_filename(os.path.join(apkdir, apkname)) | |
except FileNotFoundError: | |
pass | |
else: | |
aspl = apkdir.split(os.sep) | |
bottom = aspl[-2] | |
top = aspl[-3] | |
newdir = os.path.join(basedir, "apks", top, bottom) | |
if not os.path.exists(newdir): | |
os.makedirs(newdir) | |
shutil.copy(os.path.join(apkdir, "{0}.apk".format(apkname)), os.path.join(newdir, fname)) | |
shutil.rmtree(os.path.join(apkdir, apkname), ignore_errors=True) | |
def apk_extract(indir, apkdirs): | |
for apkdir in apkdirs: | |
aspl = apkdir.split(os.sep) | |
bottom = aspl[-1] | |
top = aspl[-2] | |
outdir = os.path.join(indir, "apks", top, bottom) | |
apklist = [os.path.join(apkdir, x) for x in os.listdir(apkdir) if are_there_apks(os.path.join(apkdir, x))] | |
for apk in apklist: | |
bname = os.path.basename(apk) | |
print("COPYING APK: {0}.apk".format(os.path.join(top, bottom, bname))) | |
indiv_apkextract(indir, outdir, apk) | |
def prep_apks(indir, apkdirs): | |
for apkdir in apkdirs: | |
aspl = apkdir.split(os.sep) | |
bottom = aspl[-1] | |
top = aspl[-2] | |
if not os.path.exists(os.path.join(indir, "apks", top, bottom)): | |
os.makedirs(os.path.join(indir, "apks", top, bottom)) | |
def are_there_apks(indir): | |
for root, dirs, files in os.walk(indir): | |
for file in files: | |
if file.lower().endswith(".apk"): | |
return True | |
return False | |
def unpack_images(indir): | |
excludes = ["boot.img", "recovery.img", "cache.img", "cache_256m.img", "bbpersist.img", "oempersist.img"] | |
simg2img(indir, excludes) | |
def dump_images(indir): | |
prep_dirs(indir) | |
raws = [os.path.join(indir, x) for x in os.listdir(indir) if ".raw.img" in x] | |
dirlist = [os.path.basename(x) for x in raws] | |
prep_output_dirs(indir, dirlist) | |
imgextract(indir, raws) | |
boot_recovery_extract(indir) | |
def dump_radios(indir): | |
rads = [os.path.join(indir, x) for x in os.listdir(indir) if "NON-HLOS" in x] | |
if rads: | |
dirlist = [os.path.basename(x) for x in rads] | |
prep_output_dirs(indir, dirlist) | |
imgextract(indir, rads, fstype="vfat") | |
if "adspso.bin" in os.listdir(indir): | |
adspsolist = [os.path.join(indir, "adspso.bin")] | |
prep_output_dirs(indir, adspsolist) | |
imgextract(indir, adspsolist, fstype="ext4") | |
def collect_appdirs(indir): | |
apklistdirs = [os.path.join(indir, "output", x) for x in os.listdir(os.path.join(indir, "output")) if are_there_apks(os.path.join(indir, "output", x))] | |
#apklistdirs = [os.path.join(indir, "output", x) for x in [os.path.join(indir, "output", "system")] if are_there_apks(os.path.join(indir, "output", x))] | |
apkdirs = [] | |
for dir in apklistdirs: | |
for tail in ("app", "priv-app", "removeable-app"): | |
if os.path.exists(os.path.join(dir, tail)) and are_there_apks(os.path.join(dir, tail)): | |
apkdirs.append(os.path.join(dir, tail)) | |
return apkdirs | |
def dump_apks(indir): | |
apkdirs = collect_appdirs(indir) | |
prep_apks(indir, apkdirs) | |
apk_extract(indir, apkdirs) | |
if __name__ == "__main__": | |
indir = os.path.abspath(os.getcwd()) | |
print("DECOMPRESSING IMAGES...") | |
unpack_images(indir) | |
print("EXTRACTING IMAGES...") | |
dump_images(indir) | |
print("EXTRACTING RADIO FILES...") | |
dump_radios(indir) | |
print("EXTRACTING APKS...") | |
dump_apks(indir) | |
remove_loop(indir) | |
print("EXTRACTION COMPLETE!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment