• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eronnen / procmon-parser / 4264299274

pending completion
4264299274

Pull #23

github

GitHub
<a href="https://github.com/eronnen/procmon-parser/commit/<a class=hub.com/eronnen/procmon-parser/commit/<a class="double-link" href="https://git"><a class=hub.com/eronnen/procmon-parser/commit/<a class="double-link" href="https://git"><a class=hub.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d60d95271">d39c29706<a href="https://github.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d60d95271">&quot;&gt;&amp;lt;a href=&amp;quot;https://github.com/eronnen/procmon-parser/commit/&lt;/a&gt;&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/eronnen/procmon-parser/commit/&amp;lt;a class=&amp;quot;double-link&amp;quot; href=&amp;quot;https://git&quot;&gt;&amp;lt;a class=&lt;/a&gt;hub.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d60d95271&quot;&gt;d39c29706&lt;/a&gt;&lt;a href=&quot;https://github.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d60d95271&quot;&gt;&amp;lt;a href=&amp;quot;https://github.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d&lt;/a&gt;60d95271&quot;>&quot;&gt;Merge &lt;/a&gt;&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/eronnen/procmon-parser/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/eronnen/procmon-parser/commit/<a class="double-link" href="https://github.com/eronnen/procmon-parser/commit/cf82b9dbafd27aee38b23f9c970128177856469c">cf82b9dba</a><a href="https://github.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d60d95271">&amp;quot;&amp;gt;cf82b9dba&amp;lt;/a&amp;gt;&amp;quot;&amp;gt;cf82b9dba&amp;lt;/a&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/eronnen/procmon-parser/commit/d39c29706745f6ac90f63de2553ec56d60d95&lt;/a&gt;271&quot;&gt; into &lt;/a&gt;&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/eronnen/procmon-parser/commit/<a class="double-link" href="https://github.com/eronnen/procmon-parser/commit/ec8666638">ec8666638">ec8666638</a>
Pull Request #23: [WIP] Symbolic stack traces

498 of 498 new or added lines in 8 files covered. (100.0%)

1509 of 2093 relevant lines covered (72.1%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.19
/procmon_parser/logs.py
1
"""
1✔
2
Python types that procmon logs use
3
"""
4

5
import binascii
1✔
6
import datetime
1✔
7
import enum
1✔
8

9
from six import string_types
1✔
10

11
from procmon_parser.consts import Column, EventClass, get_error_message, ProcessOperation, ColumnToOriginalName
1✔
12

13
__all__ = ['PMLError', 'Module', 'Process', 'Event', 'PMLStructReader']
1✔
14

15

16
EPOCH_AS_FILETIME = 116444736000000000  # January 1, 1970 as MS file time
1✔
17
HUNDREDS_OF_NANOSECONDS = 10000000
1✔
18

19

20
class PMLError(RuntimeError):
1✔
21
    pass
1✔
22

23

24
class Module(object):
1✔
25
    """Information about a loaded module in a process or in the kernel
1✔
26
    """
27

28
    def __init__(self, base_address=0, size=0, path="", version="", company="", description="", timestamp=0):
1✔
29
        self.base_address = base_address
1✔
30
        self.size = size
1✔
31
        self.path = path
1✔
32
        self.version = version
1✔
33
        self.company = company
1✔
34
        self.description = description
1✔
35
        self.timestamp = timestamp
1✔
36

37
    def __eq__(self, other):
1✔
38
        if type(other) is type(self):
×
39
            return self.__dict__ == other.__dict__
×
40
        return False
×
41

42
    def __ne__(self, other):
1✔
43
        return not self.__eq__(other)
×
44

45
    def __str__(self):
1✔
46
        return "\"{}\", address={}, size={}".format(
×
47
            self.path, "0x{:x}".format(self.base_address), "0x{:x}".format(self.size))
48

49
    def __repr__(self):
1✔
50
        return "Module({}, {}, \"{}\", \"{}\", \"{}\", \"{}\", {})" \
×
51
            .format(self.base_address, self.size, self.path, self.version, self.company,
52
                    self.description, self.timestamp)
53

54
    def __hash__(self):
1✔
55
        return hash((self.base_address, self.size, self.path, self.timestamp))
×
56

57

58
class Process(object):
1✔
59
    """Information about a process in the system
1✔
60
    """
61

62
    def __init__(self, pid=0, parent_pid=0, authentication_id=0, session=0, virtualized=0, is_process_64bit=False,
1✔
63
                 integrity="", user="", process_name="", image_path="", command_line="", company="", version="",
64
                 description="", start_time=None, end_time=None, modules=None):
65
        self.pid = pid
1✔
66
        self.parent_pid = parent_pid
1✔
67
        self.authentication_id = authentication_id
1✔
68
        self.session = session
1✔
69
        self.virtualized = virtualized
1✔
70
        self.is_process_64bit = bool(is_process_64bit)
1✔
71
        self.integrity = integrity
1✔
72
        self.user = user
1✔
73
        self.process_name = process_name
1✔
74
        self.image_path = image_path
1✔
75
        self.command_line = command_line
1✔
76
        self.company = company
1✔
77
        self.version = version
1✔
78
        self.description = description
1✔
79
        self.start_time = start_time
1✔
80
        self.end_time = end_time
1✔
81
        self.modules = modules or []
1✔
82

83
    def __eq__(self, other):
1✔
84
        if type(other) is type(self):
×
85
            return self.__dict__ == other.__dict__
×
86
        return False
×
87

88
    def __ne__(self, other):
1✔
89
        return not self.__eq__(other)
×
90

91
    def __str__(self):
1✔
92
        return "\"{}\", {}".format(self.image_path, self.pid)
×
93

94
    def __repr__(self):
1✔
95
        return "Process({}, {}, {}, {}, {}, \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\")" \
×
96
            .format(self.pid, self.parent_pid, self.authentication_id, self.session, self.virtualized,
97
                    self.is_process_64bit, self.integrity, self.user, self.process_name, self.image_path,
98
                    self.command_line, self.company, self.version, self.description)
99

100
    def __hash__(self):
1✔
101
        return hash((self.pid, self.parent_pid, self.image_path, self.command_line, self.start_time, self.end_time))
×
102

103

104
class Event(object):
1✔
105
    def __init__(self, process=None, tid=0, event_class=None, operation=None, duration=0,
1✔
106
                 date_filetime=None, result=0, stacktrace=None, category=None, path=None, details=None):
107
        self.process = process
1✔
108
        self.tid = tid
1✔
109
        self.event_class = EventClass[event_class] if isinstance(event_class, string_types) else EventClass(event_class)
1✔
110
        self.operation = operation.name if isinstance(operation, enum.IntEnum) else operation
1✔
111
        self.date_filetime = date_filetime
1✔
112
        self.result = result
1✔
113
        self.duration = duration
1✔
114
        self.stacktrace = stacktrace
1✔
115
        self.category = category
1✔
116
        self.path = path
1✔
117
        self.details = details
1✔
118

119
    def __eq__(self, other):
1✔
120
        if type(other) is type(self):
×
121
            return self.__dict__ == other.__dict__
×
122
        return False
×
123

124
    def __ne__(self, other):
1✔
125
        return not self.__eq__(other)
×
126

127
    def __str__(self):
1✔
128
        return "Process Name={}, Pid={}, Operation={}, Path=\"{}\", Time={}".format(
×
129
            self.process.process_name, self.process.pid, self.operation, self.path,
130
            self._strftime_date(self.date_filetime, True, True))
131

132
    def __repr__(self):
1✔
133
        return "Event({}, {}, \"{}\", \"{}\", {}, {}, {}, \"{}\", \"{}\", {})" \
×
134
            .format(self.process, self.tid, self.event_class.name, self.operation, self.duration,
135
                    self.date_filetime, self.result, self.category, self.path, self.details)
136

137
    def __hash__(self):
1✔
138
        return hash((self.process.pid, self.tid, self.operation, self.date_filetime))
×
139

140
    def date(self, is_utc=True):
1✔
141
        if self.date_filetime is not None:
1✔
142
            from_timestamp = datetime.datetime.utcfromtimestamp if is_utc else datetime.datetime.fromtimestamp
1✔
143
            return from_timestamp(
1✔
144
                (self.date_filetime - EPOCH_AS_FILETIME) // HUNDREDS_OF_NANOSECONDS) + datetime.timedelta(
145
                microseconds=((self.date_filetime % HUNDREDS_OF_NANOSECONDS) // 10))
146
        else:
147
            return None
×
148

149
    @staticmethod
1✔
150
    def _strftime_date(date_filetime, show_day=True, show_nanoseconds=False):
1✔
151
        # Actually Procmon prints it in local time instead of UTC
152
        hundred_nanoseconds = (date_filetime % HUNDREDS_OF_NANOSECONDS)
1✔
153
        d = datetime.datetime.utcfromtimestamp((date_filetime - EPOCH_AS_FILETIME) // HUNDREDS_OF_NANOSECONDS)
1✔
154

155
        if show_nanoseconds:
1✔
156
            time_of_day = d.strftime("%I:%M:%S.{:07d} %p").lstrip('0').format(hundred_nanoseconds)
1✔
157
        else:
158
            time_of_day = d.strftime("%I:%M:%S %p").lstrip('0')
1✔
159

160
        if not show_day:
1✔
161
            return time_of_day
1✔
162
        day = d.strftime("%m/%d/%Y ").lstrip('0').replace('/0', '/')
1✔
163
        return day + time_of_day
1✔
164

165
    @staticmethod
1✔
166
    def _strftime_relative_time(delta_hundred_nanosecs):
1✔
167
        secs = delta_hundred_nanosecs // HUNDREDS_OF_NANOSECONDS
1✔
168
        hundred_nanosecs = delta_hundred_nanosecs % HUNDREDS_OF_NANOSECONDS
1✔
169
        return "{:02d}:{:02d}:{:02d}.{:07d}".format(secs // 3600, (secs // 60) % 60, secs % 60, hundred_nanosecs)
1✔
170

171
    @staticmethod
1✔
172
    def _strftime_duration(duration_hundred_nanosecs):
1✔
173
        secs = duration_hundred_nanosecs // HUNDREDS_OF_NANOSECONDS
1✔
174
        hundred_nanosecs = duration_hundred_nanosecs % HUNDREDS_OF_NANOSECONDS
1✔
175
        return "{}.{:07d}".format(secs, hundred_nanosecs)
1✔
176

177
    @staticmethod
1✔
178
    def _get_bool_str(b):
1✔
179
        if isinstance(b, bool):
1✔
180
            return str(b)
×
181
        if b == 0:
1✔
182
            return str(False)
1✔
183
        elif b == 1:
1✔
184
            return str(True)
1✔
185
        return "n/a"
×
186

187
    def _get_compatible_csv_operation_name(self):
1✔
188
        if "<Unknown>" in self.operation:
1✔
189
            return "<Unknown>"
1✔
190
        if EventClass.Process == self.event_class:
1✔
191
            return self.operation.replace('_', ' ')
1✔
192
        return self.operation
1✔
193

194
    def _get_compatible_csv_detail_column(self):
1✔
195
        """Returns the detail column as a string which is compatible to Procmon's detail format in the exported csv.
196
        """
197
        if not self.details:
1✔
198
            return ""
1✔
199
        details = self.details.copy()
1✔
200
        if self.operation == ProcessOperation.Load_Image.name:
1✔
201
            details["Image Base"] = "0x{:x}".format(details["Image Base"])
1✔
202
            details["Image Size"] = "0x{:x}".format(details["Image Size"])
1✔
203
        elif self.operation == ProcessOperation.Thread_Exit.name:
1✔
204
            details["User Time"] = Event._strftime_duration(details["User Time"])
1✔
205
            details["Kernel Time"] = Event._strftime_duration(details["Kernel Time"])
1✔
206
        elif self.operation == ProcessOperation.Process_Start.name:
1✔
207
            details["Environment"] = "\n;\t" + "\n;\t".join(details["Environment"])
1✔
208
        elif EventClass.Registry == self.event_class:
1✔
209
            commas_formatted_keys = ["Length", "SubKeys", "Values", "Index"]
1✔
210
            for key in commas_formatted_keys:
1✔
211
                if key in details:
1✔
212
                    details[key] = '{:,}'.format(details[key])
1✔
213

214
            hexa_formatted_keys = ["HandleTags", "UserFlags", "Wow64Flags"]
1✔
215
            for key in hexa_formatted_keys:
1✔
216
                if key in details:
1✔
217
                    details[key] = "0x{:x}".format(details[key])
1✔
218

219
            removed_keys = ["TitleIndex", "MaxNameLen", "MaxValueNameLen", "MaxValueDataLen",
1✔
220
                            "ClassOffset", "ClassLength", "MaxClassLen"]
221
            for key in removed_keys:
1✔
222
                if key in details:
1✔
223
                    del details[key]
1✔
224
            if "LastWriteTime" in details:
1✔
225
                if self.operation == "RegSetInfoKey":
1✔
226
                    details["LastWriteTime"] = self._strftime_date(details["LastWriteTime"])
×
227
                else:
228
                    del details["LastWriteTime"]
1✔
229

230
            if details.get("Type", '') == "REG_BINARY" and "Data" in details:
1✔
231
                binary_ascii = binascii.b2a_hex(details["Data"]).decode('ascii').upper()
1✔
232
                binary_ascii_formatted = ' '.join(binary_ascii[i:i+2] for i in range(0, len(binary_ascii), 2))
1✔
233
                details["Data"] = binary_ascii_formatted
1✔
234
            elif details.get("Type", '') == "REG_QWORD" and "Data" in details:
1✔
235
                details["Data"] = ''  # Procmon doesnt print qword in csv, I don't know why
1✔
236
            elif details.get("Type", '') == "REG_MULTI_SZ" and "Data" in details:
1✔
237
                details["Data"] = ', '.join(details["Data"])
1✔
238
            elif "Data" in details and isinstance(details["Data"], string_types):
1✔
239
                details["Data"] = "\n;".join(details["Data"].split('\r\n'))  # They add ; before a new line
1✔
240

241
            if self.operation == "RegQueryValue" and "Name" in details:
1✔
242
                del details["Name"]
1✔
243
            elif self.operation == "RegQueryKey" and details["Query"] == "Name" and "Name" in details:
1✔
244
                del details["Name"]
1✔
245
        elif EventClass.File_System == self.event_class:
1✔
246
            commas_formatted_keys = ["AllocationSize", "Offset", "Length"]
1✔
247
            for key in commas_formatted_keys:
1✔
248
                if key in details and int == type(details[key]):
1✔
249
                    details[key] = '{:,}'.format(details[key])
1✔
250

251
        return ", ".join("{}: {}".format(k, v) for k, v in details.items())
1✔
252

253
    def get_compatible_csv_info(self, first_event_date_filetime=None):
1✔
254
        """Returns data for every Procmon column in compatible format to the exported csv by procmon
255
        """
256
        first_event_date_filetime = first_event_date_filetime if first_event_date_filetime else self.date_filetime
1✔
257
        record = {
1✔
258
            Column.DATE_AND_TIME: Event._strftime_date(self.date_filetime, True, False),
259
            Column.PROCESS_NAME: self.process.process_name,
260
            Column.PID: str(self.process.pid),
261
            Column.OPERATION: self._get_compatible_csv_operation_name(),
262
            Column.RESULT: get_error_message(self.result),
263
            Column.DETAIL: self._get_compatible_csv_detail_column(),
264
            Column.SEQUENCE: 'n/a',  # They do it too
265
            Column.COMPANY: self.process.company,
266
            Column.DESCRIPTION: self.process.description,
267
            Column.COMMAND_LINE: self.process.command_line,
268
            Column.USER: self.process.user,
269
            Column.IMAGE_PATH: self.process.image_path,
270
            Column.SESSION: str(self.process.session),
271
            Column.PATH: self.path,
272
            Column.TID: str(self.tid),
273
            Column.RELATIVE_TIME: Event._strftime_relative_time(self.date_filetime - first_event_date_filetime),
274
            Column.DURATION:
275
                Event._strftime_duration(self.duration) if get_error_message(self.result) != "" else "",
276
            Column.TIME_OF_DAY: Event._strftime_date(self.date_filetime, False, True),
277
            Column.VERSION: self.process.version,
278
            Column.EVENT_CLASS: self.event_class.name.replace('_', ' '),
279
            Column.AUTHENTICATION_ID:
280
                "{:08x}:{:08x}".format(self.process.authentication_id >> 32,
281
                                       self.process.authentication_id & 0xFFFFFFFF),
282
            Column.VIRTUALIZED: Event._get_bool_str(self.process.virtualized),
283
            Column.INTEGRITY: self.process.integrity,
284
            Column.CATEGORY: self.category,
285
            Column.PARENT_PID: str(self.process.parent_pid),
286
            Column.ARCHITECTURE: "64-bit" if self.process.is_process_64bit else "32-bit",
287
            Column.COMPLETION_TIME:
288
                Event._strftime_date(self.date_filetime + self.duration, False, True)
289
                if get_error_message(self.result) != "" else "",
290
        }
291

292
        compatible_record = {ColumnToOriginalName[k]: v for k, v in record.items()}
1✔
293
        return compatible_record
1✔
294

295

296
class PMLStructReader(object):
1✔
297
    @property
1✔
298
    def header(self):
1✔
299
        raise NotImplementedError()
×
300

301
    @property
1✔
302
    def events_offsets(self):
1✔
303
        raise NotImplementedError()
×
304

305
    def get_event_at_offset(self, offset):
1✔
306
        raise NotImplementedError()
×
307

308
    @property
1✔
309
    def number_of_events(self):
1✔
310
        return self.header.number_of_events
1✔
311

312
    @property
1✔
313
    def maximum_application_address(self):
1✔
314
        """Return the highest possible user land address.
315
        """
316
        return self.header.maximum_application_address
×
317

318
    def processes(self):
1✔
319
        """Return a list of all the known processes in the log file
320
        """
321
        raise NotImplementedError()
×
322

323
    def __getitem__(self, index):
1✔
324
        if isinstance(index, slice):
1✔
325
            return [self.get_event_at_offset(offset) for offset in self.events_offsets[index]]
×
326
        elif isinstance(index, int):
1✔
327
            return self.get_event_at_offset(self.events_offsets[index])
1✔
328

329
        raise TypeError("Bad index")
×
330

331
    def _get_os_name(self):
1✔
332
        windows_names = {
1✔
333
            (6, 0): "Windows Vista",
334
            (6, 1): "Windows 7",
335
            (6, 2): "Windows 8",
336
            (6, 3): "Windows 8.1",
337
            (10, 0): "Windows 10",
338
        }
339

340
        windows_name = windows_names[(self.header.windows_major_number, self.header.windows_minor_number)]
1✔
341
        if self.header.service_pack_name:
1✔
342
            windows_name += ", {}".format(self.header.service_pack_name)
1✔
343

344
        return "{} (build {}.{})".format(windows_name, self.header.windows_build_number,
1✔
345
                                         self.header.windows_build_number_after_decimal_point)
346

347
    def system_details(self):
1✔
348
        """Return the system details of the computer which captured the logs (like Tools -> System Details in Procmon)
349
        """
350
        return {
1✔
351
            "Computer Name": self.header.computer_name,
352
            "Operating System": self._get_os_name(),
353
            "System Root": self.header.system_root,
354
            "Logical Processors": self.header.number_of_logical_processors,
355
            "Memory (RAM)": "{} GB".format((self.header.ram_memory_size / (1024.0 ** 3)) // 0.01 / 100),
356
            "System Type": "64-bit" if self.header.is_64bit else "32-bit"
357
        }
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc