mirror of
https://github.com/mvt-project/mvt.git
synced 2024-06-18 10:29:01 +00:00
Adds partial compression support in Android Backup parsing
This commit is contained in:
parent
8eb30e3a02
commit
639c163297
|
@ -10,7 +10,7 @@ import base64
|
||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
|
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 mvt.common.module import InsufficientPrivileges
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
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.")
|
self.log.error("Extracting SMS via Android backup failed. No valid backup data found.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if header["compression"]:
|
|
||||||
self.log.error("The backup is compressed and cannot be parsed, quitting...")
|
|
||||||
return
|
|
||||||
|
|
||||||
pwd = None
|
pwd = None
|
||||||
if header["encryption"] != "none":
|
if header["encryption"] != "none":
|
||||||
pwd = getpass.getpass(prompt="Backup Password: ", stream=None)
|
pwd = getpass.getpass(prompt="Backup Password: ", stream=None)
|
||||||
|
@ -140,8 +136,12 @@ class SMS(AndroidExtraction):
|
||||||
try:
|
try:
|
||||||
self.results = parse_sms_backup(backup_output, password=pwd)
|
self.results = parse_sms_backup(backup_output, password=pwd)
|
||||||
except InvalidBackupPassword:
|
except InvalidBackupPassword:
|
||||||
self.info.log("Invalid backup password")
|
self.log.info("Invalid backup password")
|
||||||
return
|
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))
|
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
|
@ -8,7 +8,7 @@ import getpass
|
||||||
|
|
||||||
from mvt.common.module import MVTModule
|
from mvt.common.module import MVTModule
|
||||||
from mvt.common.utils import check_for_links
|
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):
|
class SMS(MVTModule):
|
||||||
|
@ -48,10 +48,7 @@ class SMS(MVTModule):
|
||||||
if not header["backup"]:
|
if not header["backup"]:
|
||||||
self.log.info("Not a valid Android Backup file, quitting...")
|
self.log.info("Not a valid Android Backup file, quitting...")
|
||||||
return
|
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
|
pwd = None
|
||||||
if header["encryption"] != "none":
|
if header["encryption"] != "none":
|
||||||
pwd = getpass.getpass(prompt="Backup Password: ", stream=None)
|
pwd = getpass.getpass(prompt="Backup Password: ", stream=None)
|
||||||
|
@ -61,6 +58,11 @@ class SMS(MVTModule):
|
||||||
except InvalidBackupPassword:
|
except InvalidBackupPassword:
|
||||||
self.log.info("Invalid password, impossible de decrypt the backup, quitting...")
|
self.log.info("Invalid password, impossible de decrypt the backup, quitting...")
|
||||||
return
|
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
|
self.results = messages
|
||||||
else:
|
else:
|
||||||
app_folder = os.path.join(self.base_folder,
|
app_folder = os.path.join(self.base_folder,
|
||||||
|
|
|
@ -16,15 +16,15 @@ from mvt.common.utils import check_for_links, convert_timestamp_to_iso
|
||||||
PBKDF2_KEY_SIZE = 32
|
PBKDF2_KEY_SIZE = 32
|
||||||
|
|
||||||
|
|
||||||
class AndroidBackupParseError(Exception):
|
class AndroidBackupParsingError(Exception):
|
||||||
"""Exception raised file parsing an android backup file"""
|
"""Exception raised file parsing an android backup file"""
|
||||||
|
|
||||||
|
|
||||||
class AndroidBackupNotImplemented(AndroidBackupParseError):
|
class AndroidBackupNotImplemented(AndroidBackupParsingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidBackupPassword(AndroidBackupParseError):
|
class InvalidBackupPassword(AndroidBackupParsingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,13 +106,11 @@ def parse_backup_file(data, password=None):
|
||||||
Inspired by https://github.com/FloatingOctothorpe/dump_android_backup
|
Inspired by https://github.com/FloatingOctothorpe/dump_android_backup
|
||||||
"""
|
"""
|
||||||
if not data.startswith(b"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)
|
[magic_header, version, is_compressed, encryption, tar_data] = data.split(b"\n", 4)
|
||||||
version = int(version)
|
version = int(version)
|
||||||
is_compressed = int(is_compressed)
|
is_compressed = int(is_compressed)
|
||||||
if is_compressed == 1:
|
|
||||||
raise AndroidBackupNotImplemented("Compression is not implemented")
|
|
||||||
|
|
||||||
if encryption != b"none":
|
if encryption != b"none":
|
||||||
if encryption != b"AES-256":
|
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))
|
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(master_key, master_iv))
|
||||||
tar_data = decrypter.feed(encrypted_data)
|
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
|
return tar_data
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,17 @@ class TestBackupParsing:
|
||||||
assert len(sms[0]["links"]) == 1
|
assert len(sms[0]["links"]) == 1
|
||||||
assert sms[0]["links"][0] == "https://google.com/"
|
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/"
|
||||||
|
|
BIN
tests/artifacts/android_backup/backup3.ab
Normal file
BIN
tests/artifacts/android_backup/backup3.ab
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user