diff --git a/src/mvt/android/artifacts/dumpsys_adb.py b/src/mvt/android/artifacts/dumpsys_adb.py index 2bc9abe..11f6f78 100644 --- a/src/mvt/android/artifacts/dumpsys_adb.py +++ b/src/mvt/android/artifacts/dumpsys_adb.py @@ -21,12 +21,22 @@ class DumpsysADBArtifact(AndroidArtifact): stack = [res] cur_indent = 0 in_multiline = False - for line in dump_data.strip(b"\n").split(b"\n"): + # Normalize line endings to handle both Unix (\n) and Windows (\r\n) + normalized_data = dump_data.replace(b"\r\n", b"\n").replace(b"\r", b"\n") + for line in normalized_data.strip(b"\n").split(b"\n"): + # Skip completely empty lines + if not line.strip(): + continue + # Track the level of indentation indent = len(line) - len(line.lstrip()) if indent < cur_indent: # If the current line is less indented than the previous one, back out - stack.pop() + while len(stack) > 1 and indent < cur_indent: + stack.pop() + # Check if we were in multiline mode and need to exit it + if in_multiline and not isinstance(stack[-1], list): + in_multiline = False cur_indent = indent else: cur_indent = indent @@ -38,12 +48,30 @@ class DumpsysADBArtifact(AndroidArtifact): # Annoyingly, some values are multiline and don't have a key on each line if in_multiline: - if key == "": + if key == "" and len(vals) < 2: # If the line is empty, it's the terminator for the multiline value in_multiline = False stack.pop() + current_dict = stack[-1] + elif len(vals) >= 2 and (key in self.multiline_fields or key == "}" or vals[1] == b"{"): + # If we encounter a new field while in multiline mode, exit multiline mode + # and process this line as a new field + in_multiline = False + stack.pop() + current_dict = stack[-1] + # Don't continue here - let the line be processed as a new field else: - current_dict.append(line.lstrip()) + # When in multiline mode, the top of stack should be a list + if isinstance(stack[-1], list): + stack[-1].append(line.lstrip()) + else: + # Something went wrong with the stack, exit multiline mode + in_multiline = False + current_dict = stack[-1] + continue + + # Skip lines that don't have a value after '=' + if len(vals) < 2: continue if key == "}": @@ -133,7 +161,16 @@ class DumpsysADBArtifact(AndroidArtifact): # TODO: Parse AdbDebuggingManager line in output. start_of_json = content.find(b"\n{") + 2 - end_of_json = content.rfind(b"}\n") - 2 + + # Handle both Unix (\n) and Windows (\r\n) line endings + end_of_json = content.rfind(b"}\n") + if end_of_json == -1: + end_of_json = content.rfind(b"}\r\n") + if end_of_json == -1: + self.log.error("Unable to find end of JSON block in dumpsys output") + return + + end_of_json -= 2 json_content = content[start_of_json:end_of_json].rstrip() parsed = self.indented_dump_parser(json_content) diff --git a/src/mvt/android/modules/androidqf/files.py b/src/mvt/android/modules/androidqf/files.py index 22b832c..4a7550e 100644 --- a/src/mvt/android/modules/androidqf/files.py +++ b/src/mvt/android/modules/androidqf/files.py @@ -112,10 +112,18 @@ class Files(AndroidQFModule): def run(self) -> None: if timezone := self._get_device_timezone(): - device_timezone = zoneinfo.ZoneInfo(timezone) + try: + device_timezone = zoneinfo.ZoneInfo(timezone) + except zoneinfo.ZoneInfoNotFoundError: + self.log.warning("Device timezone '%s' not found, using UTC", timezone) + device_timezone = datetime.timezone.utc else: self.log.warning("Unable to determine device timezone, using UTC") - device_timezone = zoneinfo.ZoneInfo("UTC") + try: + device_timezone = zoneinfo.ZoneInfo("UTC") + except zoneinfo.ZoneInfoNotFoundError: + # Fallback for Windows systems where zoneinfo might not have UTC + device_timezone = datetime.timezone.utc for file in self._get_files_by_pattern("*/files.json"): rawdata = self._get_file_content(file).decode("utf-8", errors="ignore") diff --git a/src/mvt/common/indicators.py b/src/mvt/common/indicators.py index e23a996..eca236e 100644 --- a/src/mvt/common/indicators.py +++ b/src/mvt/common/indicators.py @@ -654,7 +654,8 @@ class Indicators: return None for ioc in self.get_iocs("processes"): - parts = file_path.split("/") + # Use os-agnostic path splitting to handle both Windows (\) and Unix (/) separators + parts = file_path.replace("\\", "/").split("/") if ioc["value"] in parts: self.log.warning( "Found known suspicious process name mentioned in file at "