From 639c163297310b47b89dc78d26a8904e36a7e094 Mon Sep 17 00:00:00 2001 From: tek Date: Wed, 23 Feb 2022 16:18:45 +0100 Subject: [PATCH] Adds partial compression support in Android Backup parsing --- mvt/android/modules/adb/sms.py | 12 ++++++------ mvt/android/modules/backup/sms.py | 12 +++++++----- mvt/android/parsers/backup.py | 16 ++++++++++------ tests/android/test_backup_parser.py | 18 +++++++++++++----- tests/artifacts/android_backup/backup3.ab | Bin 0 -> 1393 bytes 5 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 tests/artifacts/android_backup/backup3.ab diff --git a/mvt/android/modules/adb/sms.py b/mvt/android/modules/adb/sms.py index b8f9333..10000db 100644 --- a/mvt/android/modules/adb/sms.py +++ b/mvt/android/modules/adb/sms.py @@ -10,7 +10,7 @@ import base64 import getpass from mvt.common.utils import check_for_links, convert_timestamp_to_iso -from mvt.android.parsers.backup import parse_ab_header, parse_sms_backup, InvalidBackupPassword +from mvt.android.parsers.backup import parse_ab_header, parse_sms_backup, InvalidBackupPassword, AndroidBackupParsingError from mvt.common.module import InsufficientPrivileges from .base import AndroidExtraction @@ -128,10 +128,6 @@ class SMS(AndroidExtraction): self.log.error("Extracting SMS via Android backup failed. No valid backup data found.") return - if header["compression"]: - self.log.error("The backup is compressed and cannot be parsed, quitting...") - return - pwd = None if header["encryption"] != "none": pwd = getpass.getpass(prompt="Backup Password: ", stream=None) @@ -140,8 +136,12 @@ class SMS(AndroidExtraction): try: self.results = parse_sms_backup(backup_output, password=pwd) except InvalidBackupPassword: - self.info.log("Invalid backup password") + self.log.info("Invalid backup password") return + except AndroidBackupParsingError: + self.log.info("Impossible to read SMS from the Android Backup, please extract the SMS and try extracting it with Android Backup Extractor") + return + log.info("Extracted a total of %d SMS messages containing links", len(self.results)) def run(self): diff --git a/mvt/android/modules/backup/sms.py b/mvt/android/modules/backup/sms.py index 12d3384..71247d4 100644 --- a/mvt/android/modules/backup/sms.py +++ b/mvt/android/modules/backup/sms.py @@ -8,7 +8,7 @@ import getpass from mvt.common.module import MVTModule from mvt.common.utils import check_for_links -from mvt.android.parsers.backup import parse_sms_file, parse_sms_backup, parse_ab_header, InvalidBackupPassword +from mvt.android.parsers.backup import parse_sms_file, parse_sms_backup, parse_ab_header, InvalidBackupPassword, AndroidBackupParsingError class SMS(MVTModule): @@ -48,10 +48,7 @@ class SMS(MVTModule): if not header["backup"]: self.log.info("Not a valid Android Backup file, quitting...") return - if header["compression"]: - self.log.info("MVT does not support compressed backups, either regenerate the backup with the -nocompress option or use ANdroid Backup Extractor to convert it to a tar file") - self.log.info("Quitting...") - return + pwd = None if header["encryption"] != "none": pwd = getpass.getpass(prompt="Backup Password: ", stream=None) @@ -61,6 +58,11 @@ class SMS(MVTModule): except InvalidBackupPassword: self.log.info("Invalid password, impossible de decrypt the backup, quitting...") return + except AndroidBackupParsingError: + self.log.info("Impossible to extract data from this Android Backup, please regenerate the backup using the -nocompress option or extract it using Android Backup Extractor instead.") + self.log.info("Quitting...") + return + self.results = messages else: app_folder = os.path.join(self.base_folder, diff --git a/mvt/android/parsers/backup.py b/mvt/android/parsers/backup.py index 9a83b84..180e287 100644 --- a/mvt/android/parsers/backup.py +++ b/mvt/android/parsers/backup.py @@ -16,15 +16,15 @@ from mvt.common.utils import check_for_links, convert_timestamp_to_iso PBKDF2_KEY_SIZE = 32 -class AndroidBackupParseError(Exception): +class AndroidBackupParsingError(Exception): """Exception raised file parsing an android backup file""" -class AndroidBackupNotImplemented(AndroidBackupParseError): +class AndroidBackupNotImplemented(AndroidBackupParsingError): pass -class InvalidBackupPassword(AndroidBackupParseError): +class InvalidBackupPassword(AndroidBackupParsingError): pass @@ -106,13 +106,11 @@ def parse_backup_file(data, password=None): Inspired by https://github.com/FloatingOctothorpe/dump_android_backup """ if not data.startswith(b"ANDROID BACKUP"): - raise AndroidBackupParseError("Invalid file header") + raise AndroidBackupParsingError("Invalid file header") [magic_header, version, is_compressed, encryption, tar_data] = data.split(b"\n", 4) version = int(version) is_compressed = int(is_compressed) - if is_compressed == 1: - raise AndroidBackupNotImplemented("Compression is not implemented") if encryption != b"none": if encryption != b"AES-256": @@ -153,6 +151,12 @@ def parse_backup_file(data, password=None): decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(master_key, master_iv)) tar_data = decrypter.feed(encrypted_data) + if is_compressed == 1: + try: + tar_data = zlib.decompress(tar_data) + except zlib.error: + raise AndroidBackupParsingError("Impossible to decompress the file") + return tar_data diff --git a/tests/android/test_backup_parser.py b/tests/android/test_backup_parser.py index ca754cb..766875c 100644 --- a/tests/android/test_backup_parser.py +++ b/tests/android/test_backup_parser.py @@ -42,9 +42,17 @@ class TestBackupParsing: assert len(sms[0]["links"]) == 1 assert sms[0]["links"][0] == "https://google.com/" + def test_parsing_compression(self): + file = get_artifact("android_backup/backup3.ab") + with open(file, "rb") as f: + data = f.read() + ddata = parse_backup_file(data) - - - - - + m = hashlib.sha256() + m.update(ddata) + assert m.hexdigest() == "33e73df2ede9798dcb3a85c06200ee41c8f52dd2f2e50ffafcceb0407bc13e3a" + sms = parse_tar_for_sms(ddata) + assert isinstance(sms, list) == True + assert len(sms) == 1 + assert len(sms[0]["links"]) == 1 + assert sms[0]["links"][0] == "https://google.com/" diff --git a/tests/artifacts/android_backup/backup3.ab b/tests/artifacts/android_backup/backup3.ab new file mode 100644 index 0000000000000000000000000000000000000000..6ab7bc9ff05efa9790fc24cc3efed106bb1a25d5 GIT binary patch literal 1393 zcmV-%1&;bbPDD~qNkkw*K|@PbPzp5)F$!*PZecy*R;3Vk*J%mOL3ZkKj6chv!4I~N*5}~4+LP67iaW$c@81D**cnCfw5<4 zx9)-~v;XJdxr-EkPRjI~qdA^)l!biv%Xe3s5;+oLoI-`D<645I(x|KU=JJvOZwwppB z7oFhXKp%c~OU+}W#@xO|mBGLWbXB7ed@pg&3B4XwyiCRx5SQ7bvR?9~|$ z85KE?i7aIsD41|SYrzvAR3u66wG>6vI^{BGb50WvL(-d9=_pOmQhm$0qWsp8JlBH7 z7Gvy@hBGU^`9e7dt!?Cv8G^S-@tBfncP5>BRZ|2zm2>jEm|a-dgCs^o~ZZ!E-1j8{C9TE?1g9?Z~vY zFr{Ner`jwx;-cu5DJqqL#*`L@)($ek_ag$e8J*x0M{xs=cC;OmH^S(#bBUp+3ct+v zCf;LZNhZU})L&yvO&Fy4n1baf9@GO+as~*d7H*~1LEJ)2>ZPDJJ)Wz8BYHw>XsVgb zun9r6#{ftIp|MP0PYft*UPi8^3)BsrlygbeB9fw9Kt)Ke)@r2#tc3+VFnp1Su0Xy( zu)b0Cpj_V&KlhNE45S@v~fBWFE!`t`V|J>f&HlJL3^uXg^-g3u_`}RJ3 z)AJwiKlS^MU%d4Gov)w#`o#CY?w2D!9NKcjoAl9|O?O{YZhZAqdgYrfYrZ1B_2h>K z%jfsp{@JGOTdzHS@77<|u3vcL@U|m=p4fZrg+C5HS&zSW-_dkn-}>;#YwHglx{F`_ z?$rF+cYofz@aWq=UH9$H;hovlab?F}00030{~86OU=)m800000|NjF3#3t;$eha*e literal 0 HcmV?d00001