-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathdumpsys_adb.py
128 lines (107 loc) · 4.35 KB
/
dumpsys_adb.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import base64
import hashlib
from .artifact import AndroidArtifact
class DumpsysADBArtifact(AndroidArtifact):
multiline_fields = ["user_keys"]
def indented_dump_parser(self, dump_data):
"""
Parse the indented dumpsys output, generated by DualDumpOutputStream in Android.
"""
res = {}
stack = [res]
cur_indent = 0
in_multiline = False
for line in dump_data.strip(b"\n").split(b"\n"):
# 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()
cur_indent = indent
else:
cur_indent = indent
# Split key and value by '='
vals = line.lstrip().split(b"=", 1)
key = vals[0].decode("utf-8")
current_dict = stack[-1]
# Annoyingly, some values are multiline and don't have a key on each line
if in_multiline:
if key == "":
# If the line is empty, it's the terminator for the multiline value
in_multiline = False
stack.pop()
else:
current_dict.append(line.lstrip())
continue
if key == "}":
stack.pop()
continue
if vals[1] == b"{":
# If the value is a new dictionary, add it to the stack
current_dict[key] = {}
stack.append(current_dict[key])
# Handle continue multiline values
elif key in self.multiline_fields:
current_dict[key] = []
current_dict[key].append(vals[1])
in_multiline = True
stack.append(current_dict[key])
else:
# If the value something else, store it in the current dictionary
current_dict[key] = vals[1]
return res
@staticmethod
def calculate_key_info(user_key: bytes) -> str:
key_base64, user = user_key.split(b" ", 1)
key_raw = base64.b64decode(key_base64)
key_fingerprint = hashlib.md5(key_raw).hexdigest().upper()
key_fingerprint_colon = ":".join(
[key_fingerprint[i : i + 2] for i in range(0, len(key_fingerprint), 2)]
)
return {
"user": user.decode("utf-8"),
"fingerprint": key_fingerprint_colon,
"key": key_base64,
}
def check_indicators(self) -> None:
if not self.results:
return
for entry in self.results:
for user_key in entry.get("user_keys", []):
self.log.debug(
f"Found trusted ADB key for user '{user_key['user']}' with fingerprint "
f"'{user_key['fingerprint']}'"
)
def parse(self, content: bytes) -> None:
"""
Parse the Dumpsys ADB section
Adds results to self.results (List[Dict[str, str]])
:param content: content of the ADB section (string)
"""
if not content or b"Can't find service: adb" in content:
self.log.error(
"Could not load ADB data from dumpsys. "
"It may not be supported on this device."
)
return
# TODO: Parse AdbDebuggingManager line in output.
start_of_json = content.find(b"\n{") + 2
end_of_json = content.rfind(b"}\n") - 2
json_content = content[start_of_json:end_of_json].rstrip()
parsed = self.indented_dump_parser(json_content)
if parsed.get("debugging_manager") is None:
self.log.error("Unable to find expected ADB entries in dumpsys output") # noqa
return
else:
parsed = parsed["debugging_manager"]
# Calculate key fingerprints for better readability
key_info = []
for user_key in parsed.get("user_keys", []):
user_info = self.calculate_key_info(user_key)
key_info.append(user_info)
parsed["user_keys"] = key_info
self.results = [parsed]