Skip to content

Commit cf67c35

Browse files
rick-slinjoachimmetz
authored andcommitted
refactored ips_plugins/interface.py, added IPS parser plugin for stacks files.
1 parent 72ddaf1 commit cf67c35

File tree

10 files changed

+14107
-80
lines changed

10 files changed

+14107
-80
lines changed

data/formatters/macos.yaml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ message:
66
- 'Application Name: {application_name}'
77
- 'Application Version: {application_version}'
88
- 'Bug Type: {bug_type}'
9+
- 'Crash Reporter_key: {crash_reporter_key}'
910
- 'Device Model: {device_model}'
1011
- 'Exception Type: {exception_type}'
1112
- 'Incident Identifier: {incident_identifier}'
@@ -21,7 +22,29 @@ short_message:
2122
- 'Incident Identifier :{incident_identifier}'
2223
- 'OS version :{os_version}'
2324
short_source: 'RecoveryLogd'
24-
source: 'Apple Recovery Logd'
25+
source: 'Apple Recovery IPS'
26+
---
27+
type: 'conditional'
28+
data_type: 'apple:ips_stacks'
29+
message:
30+
- 'Bug Type: {bug_type}'
31+
- 'Crash Reporter_key: {crash_reporter_key}'
32+
- 'Device Model: {device_model}'
33+
- 'Event Time: {event_time}'
34+
- 'Kernel Version: {kernel_version}'
35+
- 'Incident Identifier: {incident_identifier}'
36+
- 'Process List: {process_list}'
37+
- 'OS Version: {os_version}'
38+
- 'Reason: {reason}'
39+
short_message:
40+
- 'Bug Type: {bug_type}'
41+
- 'Crash Reporter_key: {crash_reporter_key}'
42+
- 'Device Model: {device_model}'
43+
- 'Incident Identifier: {incident_identifier}'
44+
- 'OS Version: {os_version}'
45+
- 'Reason: {reason}'
46+
short_source: 'StacksIPS'
47+
source: 'Apple Stacks IPS'
2548
---
2649
type: 'conditional'
2750
data_type: 'imessage:event:chat'

data/timeliner.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ attributes_mapping:
101101
description: 'Event Time'
102102
place_holder_event: true
103103
---
104+
data_type: 'apple:ips_stacks'
105+
attributes_mapping:
106+
- name: 'event_time'
107+
description: 'Event Time'
108+
place_holder_event: true
109+
---
104110
data_type: 'av:defender:detection_history'
105111
attribute_mappings:
106112
- name: 'recorded_time'

plaso/parsers/ips_plugins/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
"""Imports for the ips log parser plugins."""
33

44
from plaso.parsers.ips_plugins import recovery_logd
5+
from plaso.parsers.ips_plugins import stacks_ips

plaso/parsers/ips_plugins/interface.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# -*- coding: utf-8 -*-
22
"""Interface for IPS log file parser plugins."""
3+
34
import abc
5+
import pyparsing
6+
7+
from dfdatetime import time_elements as dfdatetime_time_elements
48

59
from plaso.parsers import plugins
610

@@ -11,9 +15,75 @@ class IPSPlugin(plugins.BasePlugin):
1115
NAME = 'ips_plugin'
1216
DATA_FORMAT = 'ips log file'
1317

18+
ENCODING = 'utf-8'
19+
1420
REQUIRED_HEADER_KEYS = []
1521
REQUIRED_CONTENT_KEYS = []
1622

23+
_TWO_DIGITS = pyparsing.Word(pyparsing.nums, exact=2).setParseAction(
24+
lambda tokens: int(tokens[0], 10))
25+
26+
_FOUR_DIGITS = pyparsing.Word(pyparsing.nums, exact=4).setParseAction(
27+
lambda tokens: int(tokens[0], 10))
28+
29+
_VARYING_DIGITS = pyparsing.Word(pyparsing.nums)
30+
31+
TIMESTAMP_GRAMMAR = (
32+
_FOUR_DIGITS.setResultsName('year') + pyparsing.Suppress('-') +
33+
_TWO_DIGITS.setResultsName('month') + pyparsing.Suppress('-') +
34+
_TWO_DIGITS.setResultsName('day') +
35+
_TWO_DIGITS.setResultsName('hours') + pyparsing.Suppress(':') +
36+
_TWO_DIGITS.setResultsName('minutes') + pyparsing.Suppress(':') +
37+
_TWO_DIGITS.setResultsName('seconds') + pyparsing.Suppress('.') +
38+
_VARYING_DIGITS.setResultsName('fraction') +
39+
pyparsing.Word(
40+
pyparsing.nums + '+' + '-').setResultsName('timezone_delta'))
41+
42+
def _ParseTimestampValue(self, parser_mediator, timestamp_text):
43+
"""Parses a timestamp string.
44+
45+
Args:
46+
parser_mediator (ParserMediator): parser mediator.
47+
timestamp_text (str): the timestamp to parse.
48+
49+
Returns:
50+
dfdatetime.TimeElements: date and time
51+
or None if not available.
52+
"""
53+
# dfDateTime takes the time zone offset as number of minutes relative from
54+
# UTC. So for Easter Standard Time (EST), which is UTC-5:00 the sign needs
55+
# to be converted, to +300 minutes.
56+
57+
parsed_timestamp = self.TIMESTAMP_GRAMMAR.parseString(timestamp_text)
58+
59+
try:
60+
time_delta_hours = int(parsed_timestamp['timezone_delta'][:3], 10)
61+
time_delta_minutes = int(parsed_timestamp['timezone_delta'][3:], 10)
62+
except (TypeError, ValueError):
63+
parser_mediator.ProduceExtractionWarning(
64+
'unsupported timezone offset value')
65+
return None
66+
67+
time_zone_offset = (time_delta_hours * 60) + time_delta_minutes
68+
69+
try:
70+
fraction_float = float(f"0.{parsed_timestamp['fraction']}")
71+
milliseconds = round(fraction_float * 1000)
72+
73+
time_element_object = dfdatetime_time_elements.TimeElementsInMilliseconds(
74+
time_elements_tuple=(
75+
parsed_timestamp['year'], parsed_timestamp['month'],
76+
parsed_timestamp['day'], parsed_timestamp['hours'],
77+
parsed_timestamp['minutes'], parsed_timestamp['seconds'],
78+
milliseconds),
79+
time_zone_offset=time_zone_offset)
80+
81+
except (TypeError, ValueError):
82+
parser_mediator.ProduceExtractionWarning('unsupported date time value')
83+
return None
84+
85+
return time_element_object
86+
1787
def CheckRequiredKeys(self, ips_file):
1888
"""Checks if the ips file's header and content have the keys required by
1989
the plugin.

plaso/parsers/ips_plugins/recovery_logd.py

Lines changed: 5 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
# -*- coding: utf-8 -*-
22
"""IPS file parser plugin for Apple crash recovery report."""
33

4-
import pyparsing
5-
6-
from dfdatetime import time_elements as dfdatetime_time_elements
7-
84
from plaso.containers import events
95
from plaso.parsers import ips_parser
106
from plaso.parsers.ips_plugins import interface
@@ -17,7 +13,7 @@ class AppleRecoveryLogdEvent(events.EventData):
1713
application_name (str): name of the application.
1814
application_version (str): version of the application.
1915
bug_type (str): type of bug.
20-
crash_reporter_key (str):
16+
crash_reporter_key (str): Key of the crash reporter.
2117
device_model (str): model of the device.
2218
event_time (dfdatetime.DateTimeValues): date and time of the crash report.
2319
exception_type (str): type of the exception that caused the crash.
@@ -54,82 +50,14 @@ def __init__(self):
5450
class AppleRecoveryLogdIPSPlugin(interface.IPSPlugin):
5551
"""Parses Apple Crash logs from IPS file."""
5652

57-
NAME = 'apple_crash_log'
58-
DATA_FORMAT = 'IPS application crash log'
59-
60-
ENCODING = 'utf-8'
53+
NAME = 'apple_recovery_ips'
54+
DATA_FORMAT = 'IPS recovery logd crash log'
6155

6256
REQUIRED_HEADER_KEYS = [
6357
'app_name', 'app_version', 'bug_type', 'incident_id', 'os_version',
6458
'timestamp']
6559
REQUIRED_CONTENT_KEYS = [
66-
'captureTime', 'modelCode', 'os_version', 'pid', 'procLaunch', ]
67-
68-
_TWO_DIGITS = pyparsing.Word(pyparsing.nums, exact=2).setParseAction(
69-
lambda tokens: int(tokens[0], 10))
70-
71-
_FOUR_DIGITS = pyparsing.Word(pyparsing.nums, exact=4).setParseAction(
72-
lambda tokens: int(tokens[0], 10))
73-
74-
TIMESTAMP_GRAMMAR = (
75-
_FOUR_DIGITS.setResultsName('year') + pyparsing.Suppress('-') +
76-
_TWO_DIGITS.setResultsName('month') + pyparsing.Suppress('-') +
77-
_TWO_DIGITS.setResultsName('day') +
78-
_TWO_DIGITS.setResultsName('hours') + pyparsing.Suppress(':') +
79-
_TWO_DIGITS.setResultsName('minutes') + pyparsing.Suppress(':') +
80-
_TWO_DIGITS.setResultsName('seconds') + pyparsing.Suppress('.') +
81-
_FOUR_DIGITS.setResultsName('fraction') +
82-
pyparsing.Word(
83-
pyparsing.nums + '+' + '-').setResultsName('timezone_delta'))
84-
85-
def __init__(self):
86-
"""Initializes a text parser plugin."""
87-
super(AppleRecoveryLogdIPSPlugin, self).__init__()
88-
self._event_data = None
89-
90-
def _ParseTimestampValue(self, parser_mediator, timestamp_text):
91-
"""Parses a timestamp string.
92-
93-
Args:
94-
parser_mediator (ParserMediator): parser mediator.
95-
timestamp_text (str): the timestamp to parse.
96-
97-
Returns:
98-
dfdatetime.TimeElements: date and time
99-
or None if not available.
100-
"""
101-
# dfDateTime takes the time zone offset as number of minutes relative from
102-
# UTC. So for Easter Standard Time (EST), which is UTC-5:00 the sign needs
103-
# to be converted, to +300 minutes.
104-
105-
parsed_timestamp = self.TIMESTAMP_GRAMMAR.parseString(timestamp_text)
106-
107-
try:
108-
time_delta_hours = int(parsed_timestamp['timezone_delta'][:3], 10)
109-
time_delta_minutes = int(parsed_timestamp['timezone_delta'][3:], 10)
110-
except (TypeError, ValueError):
111-
parser_mediator.ProduceExtractionWarning(
112-
'unsupported timezone offset value')
113-
return None
114-
115-
time_zone_offset = (time_delta_hours * -60) + time_delta_minutes
116-
117-
try:
118-
milliseconds = round(parsed_timestamp['fraction']/10)
119-
120-
time_element_object = dfdatetime_time_elements.TimeElementsInMilliseconds(
121-
time_elements_tuple=(
122-
parsed_timestamp['year'], parsed_timestamp['month'],
123-
parsed_timestamp['day'], parsed_timestamp['hours'],
124-
parsed_timestamp['minutes'], parsed_timestamp['seconds'],
125-
milliseconds),
126-
time_zone_offset=time_zone_offset)
127-
128-
except (TypeError, ValueError):
129-
parser_mediator.ProduceExtractionWarning('unsupported date time value')
130-
return None
131-
132-
return time_element_object
60+
'captureTime', 'modelCode', 'pid', 'procLaunch']
13361

13462
# pylint: disable=unused-argument
13563
def Process(self, parser_mediator, ips_file=None, **unused_kwargs):
@@ -144,13 +72,11 @@ def Process(self, parser_mediator, ips_file=None, **unused_kwargs):
14472
if ips_file is None:
14573
raise ValueError('Missing ips_file value')
14674

147-
# This will raise if unhandled keyword arguments are passed.
148-
super(AppleRecoveryLogdIPSPlugin, self).Process(parser_mediator)
149-
15075
event_data = AppleRecoveryLogdEvent()
15176
event_data.application_name = ips_file.header.get('app_name')
15277
event_data.application_version = ips_file.header.get('app_version')
15378
event_data.bug_type = ips_file.header.get('bug_type')
79+
event_data.crash_reporter_key = ips_file.content.get('crashReporterKey')
15480
event_data.device_model = ips_file.content.get('modelCode')
15581
event_data.exception_type = ips_file.content.get(
15682
'exception', {}).get('type')
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# -*- coding: utf-8 -*-
2+
"""IPS file parser plugin for Apple stacks report."""
3+
4+
from plaso.containers import events
5+
from plaso.parsers import ips_parser
6+
from plaso.parsers.ips_plugins import interface
7+
8+
9+
class AppleStacksIPSEvent(events.EventData):
10+
"""Apple stacks crash report.
11+
12+
Attributes:
13+
bug_type (str): type of bug.
14+
crash_reporter_key (str): Key of the crash reporter.
15+
device_model (str): model of the device.
16+
event_time (dfdatetime.DateTimeValues): date and time of the crash report.
17+
kernel_version (str): kernel version.
18+
incident_identifier (str): uuid for crash.
19+
process_list (str): list of process names running at the time of the crash.
20+
os_version (str): version of the operating system.
21+
reason (str): reason for the crash.
22+
"""
23+
24+
DATA_TYPE = 'apple:ips_stacks'
25+
26+
def __init__(self):
27+
"""Initializes event data."""
28+
super(AppleStacksIPSEvent, self).__init__(data_type=self.DATA_TYPE)
29+
self.bug_type = None
30+
self.crash_reporter_key = None
31+
self.device_model = None
32+
self.event_time = None
33+
self.kernel_version = None
34+
self.incident_identifier = None
35+
self.process_list = None
36+
self.os_version = None
37+
self.reason = None
38+
39+
40+
class AppleStacksIPSPlugin(interface.IPSPlugin):
41+
"""Parses Apple stacks crash IPS file."""
42+
43+
NAME = 'apple_stacks_ips'
44+
DATA_FORMAT = 'IPS stacks crash log'
45+
46+
REQUIRED_HEADER_KEYS = ['bug_type', 'incident_id', 'os_version', 'timestamp']
47+
REQUIRED_CONTENT_KEYS = [
48+
'build', 'crashReporterKey', 'kernel', 'product', 'reason']
49+
50+
# pylint: disable=unused-argument
51+
def Process(self, parser_mediator, ips_file=None, **unused_kwargs):
52+
"""Extracts information from an IPS log file.
53+
This is the main method that an IPS plugin needs to implement.
54+
Args:
55+
parser_mediator (ParserMediator): parser mediator.
56+
ips_file (Optional[IpsFile]): database.
57+
Raises:
58+
ValueError: If the file value is missing.
59+
"""
60+
if ips_file is None:
61+
raise ValueError('Missing ips_file value')
62+
63+
event_data = AppleStacksIPSEvent()
64+
event_data.bug_type = ips_file.header.get('bug_type')
65+
event_data.crash_reporter_key = ips_file.content.get('crashReporterKey')
66+
event_data.device_model = ips_file.content.get('product')
67+
event_data.kernel_version = ips_file.content.get('kernel')
68+
event_data.incident_identifier = ips_file.header.get('incident_id')
69+
event_data.os_version = ips_file.header.get('os_version')
70+
event_data.reason = ips_file.content.get('reason')
71+
72+
process_list = [
73+
i.get('procname') for i in ips_file.content.get(
74+
'processByPid').values()]
75+
process_list = list(set(process_list))
76+
process_list.sort()
77+
78+
event_data.process_list = ', '.join(process_list)
79+
80+
event_timestamp = ips_file.header.get('timestamp')
81+
event_data.event_time = self._ParseTimestampValue(
82+
parser_mediator, event_timestamp)
83+
84+
parser_mediator.ProduceEventData(event_data)
85+
86+
87+
ips_parser.IPSParser.RegisterPlugin(AppleStacksIPSPlugin)

0 commit comments

Comments
 (0)