From 9316ac7fb0d0175008e538980ead3bf48cd49e9c Mon Sep 17 00:00:00 2001 From: itzzdev09 Date: Mon, 28 Sep 2026 02:07:03 +0530 Subject: [PATCH] Bound the ADB manager state by matching braces --- src/mvt/android/artifacts/dumpsys_adb.py | 43 +++++++++++++++++-- tests/android/test_artifact_dumpsys_adb.py | 50 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/mvt/android/artifacts/dumpsys_adb.py b/src/mvt/android/artifacts/dumpsys_adb.py index cbd80e21..f3c76461 100644 --- a/src/mvt/android/artifacts/dumpsys_adb.py +++ b/src/mvt/android/artifacts/dumpsys_adb.py @@ -34,6 +34,15 @@ class DumpsysADBArtifact(AndroidArtifact): indent = len(line) - len(line.lstrip()) if indent < cur_indent: # If the current line is less indented than the previous one, back out + if len(stack) <= 1: + # Dedenting below the outermost level means this is not the + # well-formed block the parser expects. Stop here rather than + # raise IndexError out of the module on the next line. + self.log.error( + "Unexpected indentation in ADB manager state, " + "stopping the parse of this section" + ) + break stack.pop() cur_indent = indent else: @@ -63,6 +72,12 @@ class DumpsysADBArtifact(AndroidArtifact): current_dict = stack[-1] if key == "}": + if len(stack) <= 1: + self.log.error( + "Unbalanced closing brace in ADB manager state, " + "stopping the parse of this section" + ) + break stack.pop() continue @@ -161,6 +176,26 @@ class DumpsysADBArtifact(AndroidArtifact): f"'{user_key['fingerprint']}'" ) + @staticmethod + def _find_state_end(content: bytes, open_brace: int) -> int: + """Index of the brace closing the one at ``open_brace``, or -1. + + The end of the ADB manager state is found by matching braces rather than + by looking for the last one in the output: a bug report holds many + dumpsys sections, and a brace in a later one would extend this section + past its end. + """ + depth = 0 + for index in range(open_brace, len(content)): + char = content[index : index + 1] + if char == b"{": + depth += 1 + elif char == b"}": + depth -= 1 + if depth == 0: + return index + return -1 + def parse(self, content: bytes) -> None: """ Parse the Dumpsys ADB section @@ -180,12 +215,14 @@ class DumpsysADBArtifact(AndroidArtifact): self.log.error("Unable to find ADB manager state in dumpsys output") return - end_of_json = content.rfind(b"}\n") - if end_of_json == -1 or end_of_json <= start_of_json: + end_of_json = self._find_state_end(content, start_of_json + 1) + if end_of_json == -1: self.log.error("Unable to find complete ADB manager state in dumpsys output") return - json_content = content[start_of_json + 2 : end_of_json - 2].rstrip() + # The brace that opens the state and the one that closes it are not part + # of the indented body. + json_content = content[start_of_json + 2 : end_of_json].rstrip() parsed = self.indented_dump_parser(json_content) if parsed.get("debugging_manager") is None: diff --git a/tests/android/test_artifact_dumpsys_adb.py b/tests/android/test_artifact_dumpsys_adb.py index 8d501b7d..232da076 100644 --- a/tests/android/test_artifact_dumpsys_adb.py +++ b/tests/android/test_artifact_dumpsys_adb.py @@ -131,6 +131,56 @@ class TestDumpsysADBArtifact: assert key_store_entry["last_connected"] == "1628501829898" + + ADB_STATE = ( + b"ADB MANAGER STATE (dumpsys adb):\n" + b"{\n" + b" debugging_manager={\n" + b" connected_to_adb=true\n" + b" user_keys=QUJDRA== host@example\n" + b" }\n" + b"}\n" + b"--------- 0.5s was the duration of 'dumpsys adb'\n" + ) + + def test_a_later_dumpsys_section_does_not_extend_the_adb_state(self): + # A bug report holds many sections. Looking for the last closing brace + # in the whole output pulled a later section into this one, which threw + # IndexError out of parse() and lost the ADB records entirely. + da_adb = DumpsysADBArtifact() + da_adb.parse( + self.ADB_STATE + + b"DUMP OF SERVICE other:\n" + b" debugging_manager={\n" + b" connected_to_adb=false\n" + b" user_keys=RVZJTA== attacker@host\n" + b" }\n" + b"}\n" + ) + + assert len(da_adb.results) == 1 + assert [key["user"] for key in da_adb.results[0]["user_keys"]] == [ + "host@example" + ] + assert da_adb.results[0]["connected_to_adb"] is True + + def test_unbalanced_state_is_reported_rather_than_raising(self): + da_adb = DumpsysADBArtifact() + da_adb.parse( + b"ADB MANAGER STATE (dumpsys adb):\n" + b"{\n" + b" debugging_manager={\n" + b" connected_to_adb=true\n" + b" }\n" + b" }\n" + b"}\n" + ) + + # No exception, and whatever was read before the bad line is kept. + assert len(da_adb.results) == 1 + assert da_adb.results[0]["connected_to_adb"] is True + + class TestDumpsysADBStateAlerts: def test_no_androidqf_context_preserves_existing_behavior(self): module = DumpsysADBState(