mirror of
https://github.com/eronnen/procmon-parser
synced 2026-06-08 14:05:59 +00:00
227 lines
8.9 KiB
Python
227 lines
8.9 KiB
Python
"""
|
|
Definitions For the process monitor configuration file formats.
|
|
"""
|
|
from collections import OrderedDict
|
|
from io import BytesIO
|
|
|
|
from construct import Struct, Int8ul, Int16ul, Int32ul, Bytes, PaddedString, Array, Const, Switch, Tell, Adapter, \
|
|
Rebuild, Default, Pointer, StreamError
|
|
|
|
from procmon_parser.configuration import Column, RuleAction, RuleRelation, Rule, Font
|
|
from procmon_parser.construct_helper import OriginalEnumAdapter, FixedUTF16String, FixedUTF16CString, FixedArray, \
|
|
FixedBytes, CheckCustom
|
|
|
|
# ===============================================================================
|
|
# Procmon configuration file definitions
|
|
# ===============================================================================
|
|
RuleActionType = OriginalEnumAdapter(Int8ul, RuleAction)
|
|
RuleRelationType = OriginalEnumAdapter(Int32ul, RuleRelation)
|
|
ColumnType = OriginalEnumAdapter(Int32ul, Column)
|
|
|
|
LOGFONTW = """
|
|
see https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw for documentation.
|
|
""" * Struct(
|
|
"lfHeight" / Int32ul,
|
|
"lfWidth" / Int32ul,
|
|
"lfEscapement" / Int32ul,
|
|
"lfOrientation" / Int32ul,
|
|
"lfWeight" / Int32ul,
|
|
"lfItalic" / Int8ul,
|
|
"lfUnderline" / Int8ul,
|
|
"lfStrikeOut" / Int8ul,
|
|
"lfCharSet" / Int8ul,
|
|
"lfOutPrecision" / Int8ul,
|
|
"lfClipPrecision" / Int8ul,
|
|
"lfQuality" / Int8ul,
|
|
"lfPitchAndFamily" / Int8ul,
|
|
"lfFaceName" / PaddedString(32 * 2, "UTF_16_le"),
|
|
)
|
|
|
|
|
|
class FontStructAdapter(Adapter):
|
|
def _decode(self, obj, context, path):
|
|
return Font(height=obj["lfHeight"], width=obj["lfWidth"], escapement=obj["lfEscapement"],
|
|
orientation=obj["lfOrientation"], weight=obj["lfWeight"], italic=obj["lfItalic"],
|
|
underline=obj["lfUnderline"], strikeout=obj["lfStrikeOut"], char_set=obj["lfCharSet"],
|
|
out_precision=obj["lfOutPrecision"], clip_precision=obj["lfClipPrecision"],
|
|
quality=obj["lfQuality"], pitch_and_family=obj["lfPitchAndFamily"], face_name=obj["lfFaceName"])
|
|
|
|
def _encode(self, obj, context, path):
|
|
return {"lfHeight": obj.height, "lfWidth": obj.width, "lfEscapement": obj.escapement,
|
|
"lfOrientation": obj.orientation, "lfWeight": obj.weight, "lfItalic": obj.italic,
|
|
"lfUnderline": obj.underline, "lfStrikeOut": obj.strikeout, "lfCharSet": obj.char_set,
|
|
"lfOutPrecision": obj.out_precision, "lfClipPrecision": obj.clip_precision, "lfQuality": obj.quality,
|
|
"lfPitchAndFamily": obj.pitch_and_family, "lfFaceName": obj.face_name}
|
|
|
|
|
|
FontStruct = FontStructAdapter(LOGFONTW)
|
|
|
|
|
|
def get_rule_integer_value(column, value):
|
|
if value.isdigit():
|
|
return int(value)
|
|
|
|
if column == Column.ARCHITECTURE:
|
|
return 32 if "32" in value else 64
|
|
|
|
return 0
|
|
|
|
|
|
RawRuleStruct = """
|
|
Struct that contains a single rule which can be applied on the process monitor events.
|
|
""" * Struct(
|
|
"reserved1" / Default(Bytes(3), 0) * "!!Unknown field!!",
|
|
"column" / ColumnType,
|
|
"relation" / RuleRelationType,
|
|
"action" / RuleActionType,
|
|
"value_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"value_length" / Default(Int32ul, 0),
|
|
"before_value_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"value" / FixedUTF16CString(lambda this: this.value_length, "value"),
|
|
"after_value_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"int_value" / Rebuild(Int32ul, lambda this: get_rule_integer_value(this.column, this.value)),
|
|
"reserved2" / Default(Bytes(1), 0) * "!!Unknown field!!",
|
|
|
|
# To calculate value string in build time
|
|
"value_length" / Pointer(lambda this: this.value_offset,
|
|
Default(Int32ul, lambda this: this.after_value_offset - this.before_value_offset))
|
|
)
|
|
|
|
|
|
class RuleStructAdapter(Adapter):
|
|
def _decode(self, obj, context, path):
|
|
return Rule(column=obj["column"], relation=obj["relation"], value=obj["value"], action=obj["action"])
|
|
|
|
def _encode(self, obj, context, path):
|
|
return {"column": obj.column, "relation": obj.relation, "action": obj.action, "value": obj.value}
|
|
|
|
|
|
RuleStruct = RuleStructAdapter(RawRuleStruct)
|
|
|
|
RawRulesStruct = """
|
|
Struct that contains a list of procmon rules.
|
|
""" * Struct(
|
|
"reserved1" / Const(1, Int8ul) * "!!Unknown field!!",
|
|
"rules_count" / Rebuild(Int8ul, lambda this: len(this.rules)),
|
|
"rules" / Array(lambda this: this.rules_count, RuleStruct),
|
|
"reserved1" / Default(Bytes(3), 0) * "!!Unknown field!!",
|
|
)
|
|
|
|
|
|
class RulesStructAdapter(Adapter):
|
|
def _decode(self, obj, context, path):
|
|
return list(obj["rules"])
|
|
|
|
def _encode(self, obj, context, path):
|
|
return {"rules": obj}
|
|
|
|
|
|
RulesStruct = RulesStructAdapter(RawRulesStruct)
|
|
RawRecordStruct = """
|
|
Struct that contains generic procmon configuration option.
|
|
""" * Struct(
|
|
"record_size_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"record_size" / Default(Int32ul, 0x10),
|
|
"record_header_size" / Const(0x10, Int32ul),
|
|
"record_header_and_name_size_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"record_header_and_name_size" / Default(Int32ul, 0x10),
|
|
"data_size_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"data_size" / Default(Int32ul, 0),
|
|
"before_name_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"name" / FixedUTF16CString(lambda this: this.record_header_and_name_size - this.record_header_size, "name"),
|
|
"after_name_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
"data" / Switch(lambda this: this.name, {
|
|
"Columns": FixedArray(lambda this: this.data_size, Int16ul),
|
|
"ColumnCount": Int32ul,
|
|
"ColumnMap": FixedArray(lambda this: this.data_size, ColumnType),
|
|
"DbgHelpPath": FixedUTF16String(lambda this: this.data_size),
|
|
"Logfile": FixedUTF16String(lambda this: this.data_size),
|
|
"HighlightFG": Int32ul,
|
|
"HighlightBG": Int32ul,
|
|
"LogFont": FontStruct,
|
|
"BoookmarkFont": FontStruct, # they have typo in "BoookmarkFont" lol
|
|
"AdvancedMode": Int32ul,
|
|
"Autoscroll": Int32ul,
|
|
"HistoryDepth": Int32ul,
|
|
"Profiling": Int32ul,
|
|
"DestructiveFilter": Int32ul,
|
|
"AlwaysOnTop": Int32ul,
|
|
"ResolveAddresses": Int32ul,
|
|
"SourcePath": FixedUTF16CString(lambda this: this.data_size, "data"),
|
|
"SymbolPath": FixedUTF16CString(lambda this: this.data_size, "data"),
|
|
"FilterRules": RulesStruct,
|
|
"HighlightRules": RulesStruct
|
|
}, FixedBytes(lambda this: this.data_size)),
|
|
"after_data_offset" / Tell, # NOT IN THE REAL FORMAT - USED FOR BUILDING ONLY
|
|
|
|
|
|
# For computing sizes only after we built the known types
|
|
"record_header_and_name_size" / Pointer(
|
|
lambda this: this.record_header_and_name_size_offset,
|
|
Default(Int32ul, lambda this: this.record_header_size + this.after_name_offset - this.before_name_offset)
|
|
),
|
|
|
|
"data_size" / Pointer(
|
|
lambda this: this.data_size_offset,
|
|
Default(Int32ul, lambda this: this.after_data_offset - this.after_name_offset)
|
|
),
|
|
|
|
"record_size" / Pointer(
|
|
lambda this: this.record_size_offset,
|
|
Default(Int32ul, lambda this: this.record_header_and_name_size + this.data_size)
|
|
),
|
|
|
|
CheckCustom(lambda this: this.record_size == this.record_header_and_name_size + this.data_size,
|
|
RuntimeError, "Record size is not valid")
|
|
)
|
|
|
|
|
|
class RecordStructAdapter(Adapter):
|
|
def _decode(self, obj, context, path):
|
|
return obj["name"], obj["data"]
|
|
|
|
def _encode(self, obj, context, path):
|
|
return {"name": obj[0], "data": obj[1]}
|
|
|
|
|
|
Record = RecordStructAdapter(RawRecordStruct)
|
|
|
|
|
|
def load_configuration(stream):
|
|
"""Deserialize ``stream`` (a ``.read()``-supporting file-like object) which contains PMC formatted data,
|
|
to a Python dictionary with the parsed configuration records.
|
|
"""
|
|
records = []
|
|
while True:
|
|
try:
|
|
name, data = Record.parse_stream(stream)
|
|
records.append((name, data))
|
|
except StreamError:
|
|
break
|
|
return OrderedDict(records)
|
|
|
|
|
|
def loads_configuration(data):
|
|
"""Deserialize ``data`` (a ``bytes`` object), which contains PMC formatted data,
|
|
to a Python dictionary with the parsed configuration records.
|
|
"""
|
|
stream = BytesIO(data)
|
|
return load_configuration(stream)
|
|
|
|
|
|
def dump_configuration(records, stream):
|
|
"""Serialize ``records``, a dictionary of procmon configuration records, to ``stream`` (a
|
|
``.write()``-supporting file-like object that returns the length written (for python2 use the io module)),
|
|
in the format of PMC.
|
|
"""
|
|
for name, data in records.items():
|
|
Record.build_stream((name, data), stream)
|
|
|
|
|
|
def dumps_configuration(records):
|
|
"""Serialize ``records``, a dictionary of procmon configuration records, to ``bytes`` in the format of PMC.
|
|
"""
|
|
stream = BytesIO()
|
|
dump_configuration(records, stream)
|
|
return stream.getvalue()
|