-
Notifications
You must be signed in to change notification settings - Fork 6
/
sru.py
167 lines (130 loc) · 5.64 KB
/
sru.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
from __future__ import annotations
import argparse
from typing import BinaryIO, Iterator, Optional
from dissect.util.sid import read_sid
from dissect.util.ts import oatimestamp, wintimestamp
from dissect.esedb.c_esedb import RecordValue
from dissect.esedb.esedb import EseDB
from dissect.esedb.record import Record, serialise_record_column_values
from dissect.esedb.table import Table
NATIVE_TYPE_MAP = {
"{DD6636C4-8929-4683-974E-22C046A43763}": {"ConnectStartTime": wintimestamp},
"{5C8CF1C7-7257-4F13-B223-970EF5939312}": {"EndTime": wintimestamp},
}
SKIP_TABLES = [
"MSysObjects",
"MSysObjectsShadow",
"MSysObjids",
"MSysLocales",
"SruDbIdMapTable",
"SruDbCheckpointTable",
]
NAME_TO_GUID_MAP = {
"network_data": "{973F5D5C-1D90-4944-BE8E-24B94231A174}",
"network_connectivity": "{DD6636C4-8929-4683-974E-22C046A43763}",
"energy_estimator": "{DA73FB89-2BEA-4DDC-86B8-6E048C6DA477}",
"energy_usage": "{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}",
"energy_usage_lt": "{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}LT",
"application": "{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}",
"push_notifications": "{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}",
"application_timeline": "{5C8CF1C7-7257-4F13-B223-970EF5939312}",
"vfu": "{7ACBBAA3-D029-4BE4-9A7A-0885927F1D8F}",
# Refs:
# - http://dfir.pro/index.php?link_id=92259 and
# - HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SRUM\Extensions
"sdp_volume_provider": "{17F4D97B-F26A-5E79-3A82-90040A47D13D}",
"sdp_physical_disk_provider": "{841A7317-3805-518B-C2EA-AD224CB4AF84}",
"sdp_cpu_provider": "{DC3D3B50-BB90-5066-FA4E-A5F90DD8B677}",
"sdp_network_provider": "{EEE2F477-0659-5C47-EF03-6D6BEFD441B3}",
# not seen in the wild
"sdp_perf_count_provider": "{38AD6548-9313-58F8-45C7-D293BAFDC879}",
"sdp_event_log_provider": "{CDF8EBF6-7C0F-5AC2-158F-DBFBEE981152}",
}
class SRU:
def __init__(self, fh: BinaryIO):
self.esedb = EseDB(fh)
id_map_table = self.esedb.table("SruDbIdMapTable")
self.id_map = {r.get("IdIndex"): r for r in id_map_table.records()}
def get_table(self, table_name: str = None, table_guid: str = None) -> Optional[Table]:
if all((table_name, table_guid)) or not any((table_name, table_guid)):
raise ValueError("Either table_name or table_guid must be provided")
if table_name and table_name not in NAME_TO_GUID_MAP:
raise ValueError(f"Unknown table name: {table_name}")
table_guid = table_guid or NAME_TO_GUID_MAP[table_name]
try:
return self.esedb.table(table_guid)
except KeyError:
return None
def entries(self) -> Iterator[Entry]:
for t in self.esedb.tables():
if t.name in SKIP_TABLES:
continue
yield from self.get_table_entries(table=t)
__iter__ = entries
def get_table_entries(self, table: Table = None, table_name: str = None, table_guid: str = None) -> Iterator[Entry]:
table = table or self.get_table(table_name=table_name, table_guid=table_guid)
if not table:
return
for record in table.records():
yield Entry(self, table, record)
def resolve_id(self, value: int) -> Optional[str]:
try:
record = self.id_map[value]
except KeyError:
raise IndexError(value)
if not record.get("IdBlob"):
return None
if record.get("IdType") in (0, 1, 2):
return record.get("IdBlob").decode("utf-16-le").rstrip("\x00")
else:
return read_sid(record.get("IdBlob"))
class Entry:
def __init__(self, sru: SRU, table: Table, record: Record):
self.sru = sru
self.table = table
self.record = record
self.provider = table.name
def _get(self, attr: str) -> RecordValue:
value = self.record.get(attr)
if value is None:
return value
if attr in ("AppId", "UserId"):
value = self.sru.resolve_id(value)
if attr == "TimeStamp":
value = oatimestamp(value)
type_map = NATIVE_TYPE_MAP.get(self.table.name, None)
if type_map and attr in type_map:
value = type_map[attr](value)
# Application timeline ({5C8CF1C7-7257-4F13-B223-970EF5939312}) seems to have a weird null value
# (******** in decimal)
if self.table.name == "{5C8CF1C7-7257-4F13-B223-970EF5939312}" and value in (0x2A2A2A2A2A2A2A2A, 0x2A2A2A2A):
value = None
return value
def __getitem__(self, attr: str) -> RecordValue:
return self._get(attr)
def __getattr__(self, attr: str) -> RecordValue:
try:
return self._get(attr)
except Exception:
return object.__getattribute__(self, attr)
def __repr__(self) -> str:
column_values = serialise_record_column_values(self.record)
return f"<Entry provider={self.table.name} {column_values}>"
def main():
parser = argparse.ArgumentParser(description="dissect.esedb SRU parser")
parser.add_argument("input", help="SRU database to read")
parser.add_argument("-p", "--provider", help="filter records from this provider")
args = parser.parse_args()
with open(args.input, "rb") as fh:
parser = SRU(fh)
if args.provider in NAME_TO_GUID_MAP:
for e in parser.get_table_entries(table_name=args.provider):
print(e)
elif args.provider:
for e in parser.get_table_entries(table_guid=args.provider):
print(e)
else:
for e in parser.entries():
print(e)
if __name__ == "__main__":
main()