OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """Parser for Windows Scheduled Task job files.""" | 2 """Parser for Windows Scheduled Task job files.""" |
3 | 3 |
4 from __future__ import unicode_literals | 4 from __future__ import unicode_literals |
5 | 5 |
6 import construct | |
7 | |
8 from dfdatetime import definitions as dfdatetime_definitions | 6 from dfdatetime import definitions as dfdatetime_definitions |
9 from dfdatetime import systemtime as dfdatetime_systemtime | 7 from dfdatetime import systemtime as dfdatetime_systemtime |
10 from dfdatetime import time_elements as dfdatetime_time_elements | 8 from dfdatetime import time_elements as dfdatetime_time_elements |
11 | 9 |
12 from plaso.containers import events | 10 from plaso.containers import events |
13 from plaso.containers import time_events | 11 from plaso.containers import time_events |
14 from plaso.lib import binary | |
15 from plaso.lib import errors | 12 from plaso.lib import errors |
16 from plaso.lib import definitions | 13 from plaso.lib import definitions |
17 from plaso.parsers import interface | 14 from plaso.parsers import dtfabric_parser |
18 from plaso.parsers import manager | 15 from plaso.parsers import manager |
19 | 16 |
20 | 17 |
21 class WinJobEventData(events.EventData): | 18 class WinJobEventData(events.EventData): |
22 """Windows Scheduled Task event data. | 19 """Windows Scheduled Task event data. |
23 | 20 |
24 Attributes: | 21 Attributes: |
25 application (str): path to job executable. | 22 application (str): path to job executable. |
26 description (str): description of the scheduled task. | 23 description (str): description of the scheduled task. |
27 parameters (str): application command line parameters. | 24 parameters (str): application command line parameters. |
28 trigger_type (int): trigger type. | 25 trigger_type (int): trigger type. |
29 username (str): username that scheduled the task. | 26 username (str): username that scheduled the task. |
30 working_directory (str): working directory of the scheduled task. | 27 working_directory (str): working directory of the scheduled task. |
31 """ | 28 """ |
32 | 29 |
33 DATA_TYPE = 'windows:tasks:job' | 30 DATA_TYPE = 'windows:tasks:job' |
34 | 31 |
35 def __init__(self): | 32 def __init__(self): |
36 """Initializes event data.""" | 33 """Initializes event data.""" |
37 super(WinJobEventData, self).__init__(data_type=self.DATA_TYPE) | 34 super(WinJobEventData, self).__init__(data_type=self.DATA_TYPE) |
38 self.application = None | 35 self.application = None |
39 self.comment = None | 36 self.comment = None |
40 self.parameters = None | 37 self.parameters = None |
41 self.trigger_type = None | 38 self.trigger_type = None |
42 self.username = None | 39 self.username = None |
43 self.working_directory = None | 40 self.working_directory = None |
44 | 41 |
45 | 42 |
46 class WinJobParser(interface.FileObjectParser): | 43 class WinJobParser(dtfabric_parser.DtFabricBaseParser): |
47 """Parse Windows Scheduled Task files for job events.""" | 44 """Parse Windows Scheduled Task files for job events.""" |
48 | 45 |
49 NAME = 'winjob' | 46 NAME = 'winjob' |
50 DESCRIPTION = 'Parser for Windows Scheduled Task job (or At-job) files.' | 47 DESCRIPTION = 'Parser for Windows Scheduled Task job (or At-job) files.' |
51 | 48 |
| 49 _DEFINITION_FILE = 'winjob.yaml' |
| 50 |
52 _EMPTY_SYSTEM_TIME_TUPLE = (0, 0, 0, 0, 0, 0, 0, 0) | 51 _EMPTY_SYSTEM_TIME_TUPLE = (0, 0, 0, 0, 0, 0, 0, 0) |
53 | 52 |
54 _PRODUCT_VERSIONS = { | 53 _PRODUCT_VERSIONS = { |
55 0x0400: 'Windows NT 4.0', | 54 0x0400: 'Windows NT 4.0', |
56 0x0500: 'Windows 2000', | 55 0x0500: 'Windows 2000', |
57 0x0501: 'Windows XP', | 56 0x0501: 'Windows XP', |
58 0x0600: 'Windows Vista', | 57 0x0600: 'Windows Vista', |
59 0x0601: 'Windows 7', | 58 0x0601: 'Windows 7', |
60 0x0602: 'Windows 8', | 59 0x0602: 'Windows 8', |
61 0x0603: 'Windows 8.1', | 60 0x0603: 'Windows 8.1', |
62 0x0a00: 'Windows 10', | 61 0x0a00: 'Windows 10', |
63 } | 62 } |
64 | 63 |
65 _JOB_FIXED_LENGTH_SECTION_STRUCT = construct.Struct( | 64 def _ParseEventData(self, variable_length_section): |
66 'job_fixed_length_section', | 65 """Parses the event data form a variable-length data section. |
67 construct.ULInt16('product_version'), | |
68 construct.ULInt16('format_version'), | |
69 construct.Bytes('job_uuid', 16), | |
70 construct.ULInt16('application_length_offset'), | |
71 construct.ULInt16('trigger_offset'), | |
72 construct.ULInt16('error_retry_count'), | |
73 construct.ULInt16('error_retry_interval'), | |
74 construct.ULInt16('idle_deadline'), | |
75 construct.ULInt16('idle_wait'), | |
76 construct.ULInt32('priority'), | |
77 construct.ULInt32('max_run_time'), | |
78 construct.ULInt32('exit_code'), | |
79 construct.ULInt32('status'), | |
80 construct.ULInt32('flags'), | |
81 construct.Struct( | |
82 'last_run_time', | |
83 construct.ULInt16('year'), | |
84 construct.ULInt16('month'), | |
85 construct.ULInt16('weekday'), | |
86 construct.ULInt16('day'), | |
87 construct.ULInt16('hours'), | |
88 construct.ULInt16('minutes'), | |
89 construct.ULInt16('seconds'), | |
90 construct.ULInt16('milliseconds'))) | |
91 | 66 |
92 # Using Construct's utf-16 encoding here will create strings with their | 67 Args: |
93 # null terminators exposed. Instead, we'll read these variables raw and | 68 variable_length_section (job_fixed_length_data_section): a variable-length |
94 # convert them using Plaso's ReadUTF16() for proper formatting. | 69 data section. |
95 _JOB_VARIABLE_STRUCT = construct.Struct( | |
96 'job_variable_length_section', | |
97 construct.ULInt16('running_instance_count'), | |
98 construct.ULInt16('application_length'), | |
99 construct.String( | |
100 'application', | |
101 lambda ctx: ctx.application_length * 2), | |
102 construct.ULInt16('parameter_length'), | |
103 construct.String( | |
104 'parameter', | |
105 lambda ctx: ctx.parameter_length * 2), | |
106 construct.ULInt16('working_directory_length'), | |
107 construct.String( | |
108 'working_directory', | |
109 lambda ctx: ctx.working_directory_length * 2), | |
110 construct.ULInt16('username_length'), | |
111 construct.String( | |
112 'username', | |
113 lambda ctx: ctx.username_length * 2), | |
114 construct.ULInt16('comment_length'), | |
115 construct.String( | |
116 'comment', | |
117 lambda ctx: ctx.comment_length * 2), | |
118 construct.ULInt16('userdata_length'), | |
119 construct.String( | |
120 'userdata', | |
121 lambda ctx: ctx.userdata_length), | |
122 construct.ULInt16('reserved_length'), | |
123 construct.String( | |
124 'reserved', | |
125 lambda ctx: ctx.reserved_length), | |
126 construct.ULInt16('number_of_triggers')) | |
127 | 70 |
128 _TRIGGER_STRUCT = construct.Struct( | 71 Returns: |
129 'trigger', | 72 WinJobEventData: event data of the job file. |
130 construct.ULInt16('size'), | 73 """ |
131 construct.ULInt16('reserved1'), | 74 event_data = WinJobEventData() |
132 construct.ULInt16('start_year'), | 75 event_data.application = ( |
133 construct.ULInt16('start_month'), | 76 variable_length_section.application_name.rstrip('\x00')) |
134 construct.ULInt16('start_day'), | 77 event_data.comment = variable_length_section.comment.rstrip('\x00') |
135 construct.ULInt16('end_year'), | 78 event_data.parameters = ( |
136 construct.ULInt16('end_month'), | 79 variable_length_section.parameters.rstrip('\x00')) |
137 construct.ULInt16('end_day'), | 80 event_data.username = variable_length_section.author.rstrip('\x00') |
138 construct.ULInt16('start_hour'), | 81 event_data.working_directory = ( |
139 construct.ULInt16('start_minute'), | 82 variable_length_section.working_directory.rstrip('\x00')) |
140 construct.ULInt32('duration'), | 83 |
141 construct.ULInt32('interval'), | 84 return event_data |
142 construct.ULInt32('trigger_flags'), | 85 |
143 construct.ULInt32('trigger_type'), | 86 def _ParseLastRunTime(self, parser_mediator, fixed_length_section): |
144 construct.ULInt16('trigger_arg0'), | 87 """Parses the last run time from a fixed-length data section. |
145 construct.ULInt16('trigger_arg1'), | 88 |
146 construct.ULInt16('trigger_arg2'), | 89 Args: |
147 construct.ULInt16('trigger_padding'), | 90 parser_mediator (ParserMediator): mediates interactions between parsers |
148 construct.ULInt16('trigger_reserved2'), | 91 and other components, such as storage and dfvfs. |
149 construct.ULInt16('trigger_reserved3')) | 92 fixed_length_section (job_fixed_length_data_section): a fixed-length |
| 93 data section. |
| 94 |
| 95 Returns: |
| 96 dfdatetime.DateTimeValues: last run date and time or None if not |
| 97 available. |
| 98 """ |
| 99 systemtime_struct = fixed_length_section.last_run_time |
| 100 system_time_tuple = ( |
| 101 systemtime_struct.year, systemtime_struct.month, |
| 102 systemtime_struct.weekday, systemtime_struct.day_of_month, |
| 103 systemtime_struct.hours, systemtime_struct.minutes, |
| 104 systemtime_struct.seconds, systemtime_struct.milliseconds) |
| 105 |
| 106 date_time = None |
| 107 if system_time_tuple != self._EMPTY_SYSTEM_TIME_TUPLE: |
| 108 try: |
| 109 date_time = dfdatetime_systemtime.Systemtime( |
| 110 system_time_tuple=system_time_tuple) |
| 111 except ValueError: |
| 112 parser_mediator.ProduceExtractionError( |
| 113 'invalid last run time: {0!s}'.format(system_time_tuple)) |
| 114 |
| 115 return date_time |
| 116 |
| 117 def _ParseTriggerEndTime(self, parser_mediator, trigger): |
| 118 """Parses the end time from a trigger. |
| 119 |
| 120 Args: |
| 121 parser_mediator (ParserMediator): mediates interactions between parsers |
| 122 and other components, such as storage and dfvfs. |
| 123 trigger (job_trigger): a trigger. |
| 124 |
| 125 Returns: |
| 126 dfdatetime.DateTimeValues: last run date and time or None if not |
| 127 available. |
| 128 """ |
| 129 time_elements_tuple = ( |
| 130 trigger.end_date.year, trigger.end_date.month, |
| 131 trigger.end_date.day_of_month, 0, 0, 0) |
| 132 |
| 133 date_time = None |
| 134 if time_elements_tuple != (0, 0, 0, 0, 0, 0): |
| 135 try: |
| 136 date_time = dfdatetime_time_elements.TimeElements( |
| 137 time_elements_tuple=time_elements_tuple) |
| 138 date_time.is_local_time = True |
| 139 # TODO: add functionality to dfdatetime to control precision. |
| 140 date_time._precision = dfdatetime_definitions.PRECISION_1_DAY # pylint:
disable=protected-access |
| 141 except ValueError: |
| 142 parser_mediator.ProduceExtractionError( |
| 143 'invalid trigger end time: {0!s}'.format(time_elements_tuple)) |
| 144 |
| 145 return date_time |
| 146 |
| 147 def _ParseTriggerStartTime(self, parser_mediator, trigger): |
| 148 """Parses the start time from a trigger. |
| 149 |
| 150 Args: |
| 151 parser_mediator (ParserMediator): mediates interactions between parsers |
| 152 and other components, such as storage and dfvfs. |
| 153 trigger (job_trigger): a trigger. |
| 154 |
| 155 Returns: |
| 156 dfdatetime.DateTimeValues: last run date and time or None if not |
| 157 available. |
| 158 """ |
| 159 time_elements_tuple = ( |
| 160 trigger.start_date.year, trigger.start_date.month, |
| 161 trigger.start_date.day_of_month, trigger.start_time.hours, |
| 162 trigger.start_time.minutes, 0) |
| 163 |
| 164 date_time = None |
| 165 if time_elements_tuple != (0, 0, 0, 0, 0, 0): |
| 166 try: |
| 167 date_time = dfdatetime_time_elements.TimeElements( |
| 168 time_elements_tuple=time_elements_tuple) |
| 169 date_time.is_local_time = True |
| 170 # TODO: add functionality to dfdatetime to control precision. |
| 171 date_time._precision = dfdatetime_definitions.PRECISION_1_MINUTE # pyli
nt: disable=protected-access |
| 172 except ValueError: |
| 173 parser_mediator.ProduceExtractionError( |
| 174 'invalid trigger start time: {0!s}'.format(time_elements_tuple)) |
| 175 |
| 176 return date_time |
150 | 177 |
151 def ParseFileObject(self, parser_mediator, file_object, **kwargs): | 178 def ParseFileObject(self, parser_mediator, file_object, **kwargs): |
152 """Parses a Windows job file-like object. | 179 """Parses a Windows job file-like object. |
153 | 180 |
154 Args: | 181 Args: |
155 parser_mediator (ParserMediator): mediates interactions between parsers | 182 parser_mediator (ParserMediator): mediates interactions between parsers |
156 and other components, such as storage and dfvfs. | 183 and other components, such as storage and dfvfs. |
157 file_object (dfvfs.FileIO): a file-like object. | 184 file_object (dfvfs.FileIO): a file-like object. |
158 | 185 |
159 Raises: | 186 Raises: |
160 UnableToParseFile: when the file cannot be parsed. | 187 UnableToParseFile: when the file cannot be parsed. |
161 """ | 188 """ |
| 189 fixed_section_data_map = self._GetDataTypeMap( |
| 190 'job_fixed_length_data_section') |
| 191 |
162 try: | 192 try: |
163 header_struct = self._JOB_FIXED_LENGTH_SECTION_STRUCT.parse_stream( | 193 fixed_length_section, file_offset = self._ReadStructureFromFileObject( |
164 file_object) | 194 file_object, 0, fixed_section_data_map) |
165 except (IOError, construct.FieldError) as exception: | 195 except (ValueError, errors.ParseError) as exception: |
166 raise errors.UnableToParseFile( | 196 raise errors.UnableToParseFile( |
167 'Unable to parse fixed-length section with error: {0!s}'.format( | 197 'Unable to parse fixed-length data section with error: {0!s}'.format( |
168 exception)) | 198 exception)) |
169 | 199 |
170 if not header_struct.product_version in self._PRODUCT_VERSIONS: | 200 if not fixed_length_section.product_version in self._PRODUCT_VERSIONS: |
171 raise errors.UnableToParseFile( | 201 raise errors.UnableToParseFile( |
172 'Unsupported product version in: 0x{0:04x}'.format( | 202 'Unsupported product version in: 0x{0:04x}'.format( |
173 header_struct.product_version)) | 203 fixed_length_section.product_version)) |
174 | 204 |
175 if not header_struct.format_version == 1: | 205 if not fixed_length_section.format_version == 1: |
176 raise errors.UnableToParseFile( | 206 raise errors.UnableToParseFile( |
177 'Unsupported format version in: {0:d}'.format( | 207 'Unsupported format version in: {0:d}'.format( |
178 header_struct.format_version)) | 208 fixed_length_section.format_version)) |
| 209 |
| 210 variable_section_data_map = self._GetDataTypeMap( |
| 211 'job_variable_length_data_section') |
179 | 212 |
180 try: | 213 try: |
181 job_variable_struct = self._JOB_VARIABLE_STRUCT.parse_stream(file_object) | 214 variable_length_section, data_size = self._ReadStructureFromFileObject( |
182 except (IOError, construct.FieldError) as exception: | 215 file_object, file_offset, variable_section_data_map) |
183 raise errors.UnableToParseFile( | 216 except (ValueError, errors.ParseError) as exception: |
184 'Unable to parse variable-length section with error: {0!s}'.format( | 217 raise errors.UnableToParseFile(( |
185 exception)) | 218 'Unable to parse variable-length data section with error: ' |
| 219 '{0!s}').format(exception)) |
186 | 220 |
187 event_data = WinJobEventData() | 221 file_offset += data_size |
188 event_data.application = binary.ReadUTF16(job_variable_struct.application) | |
189 event_data.comment = binary.ReadUTF16(job_variable_struct.comment) | |
190 event_data.parameters = binary.ReadUTF16(job_variable_struct.parameter) | |
191 event_data.username = binary.ReadUTF16(job_variable_struct.username) | |
192 event_data.working_directory = binary.ReadUTF16( | |
193 job_variable_struct.working_directory) | |
194 | 222 |
195 systemtime_struct = header_struct.last_run_time | 223 event_data = self._ParseEventData(variable_length_section) |
196 system_time_tuple = ( | |
197 systemtime_struct.year, systemtime_struct.month, | |
198 systemtime_struct.weekday, systemtime_struct.day, | |
199 systemtime_struct.hours, systemtime_struct.minutes, | |
200 systemtime_struct.seconds, systemtime_struct.milliseconds) | |
201 | 224 |
202 date_time = None | 225 date_time = self._ParseLastRunTime(parser_mediator, fixed_length_section) |
203 if system_time_tuple != self._EMPTY_SYSTEM_TIME_TUPLE: | |
204 try: | |
205 date_time = dfdatetime_systemtime.Systemtime( | |
206 system_time_tuple=system_time_tuple) | |
207 except ValueError: | |
208 parser_mediator.ProduceExtractionError( | |
209 'invalid last run time: {0!s}'.format(system_time_tuple)) | |
210 | |
211 if date_time: | 226 if date_time: |
212 event = time_events.DateTimeValuesEvent( | 227 event = time_events.DateTimeValuesEvent( |
213 date_time, definitions.TIME_DESCRIPTION_LAST_RUN) | 228 date_time, definitions.TIME_DESCRIPTION_LAST_RUN) |
214 parser_mediator.ProduceEventWithEventData(event, event_data) | 229 parser_mediator.ProduceEventWithEventData(event, event_data) |
215 | 230 |
216 for index in range(job_variable_struct.number_of_triggers): | 231 trigger_data_map = self._GetDataTypeMap('job_trigger') |
| 232 |
| 233 for trigger_index in range(0, variable_length_section.number_of_triggers): |
217 try: | 234 try: |
218 trigger_struct = self._TRIGGER_STRUCT.parse_stream(file_object) | 235 trigger, data_size = self._ReadStructureFromFileObject( |
219 except (IOError, construct.FieldError) as exception: | 236 file_object, file_offset, trigger_data_map) |
220 parser_mediator.ProduceExtractionError( | 237 except (ValueError, errors.ParseError) as exception: |
221 'unable to parse trigger: {0:d} with error: {1!s}'.format( | 238 raise errors.UnableToParseFile(( |
222 index, exception)) | 239 'Unable to parse trigger: {0:d} with error: {2!s}').format( |
223 return | 240 trigger_index, exception)) |
224 | 241 |
225 event_data.trigger_type = trigger_struct.trigger_type | 242 file_offset += data_size |
226 | 243 |
227 time_elements_tuple = ( | 244 event_data.trigger_type = trigger.trigger_type |
228 trigger_struct.start_year, trigger_struct.start_month, | |
229 trigger_struct.start_day, trigger_struct.start_hour, | |
230 trigger_struct.start_minute, 0) | |
231 | 245 |
232 if time_elements_tuple != (0, 0, 0, 0, 0, 0): | 246 date_time = self._ParseTriggerStartTime(parser_mediator, trigger) |
233 try: | 247 if date_time: |
234 date_time = dfdatetime_time_elements.TimeElements( | 248 event = time_events.DateTimeValuesEvent( |
235 time_elements_tuple=time_elements_tuple) | 249 date_time, 'Scheduled to start', time_zone=parser_mediator.timezone) |
236 date_time.is_local_time = True | 250 parser_mediator.ProduceEventWithEventData(event, event_data) |
237 # TODO: add functionality to dfdatetime to control precision. | |
238 date_time._precision = dfdatetime_definitions.PRECISION_1_MINUTE # py
lint: disable=protected-access | |
239 except ValueError: | |
240 date_time = None | |
241 parser_mediator.ProduceExtractionError( | |
242 'invalid trigger start time: {0!s}'.format(time_elements_tuple)) | |
243 | 251 |
244 if date_time: | 252 date_time = self._ParseTriggerEndTime(parser_mediator, trigger) |
245 event = time_events.DateTimeValuesEvent( | 253 if date_time: |
246 date_time, 'Scheduled to start', | 254 event = time_events.DateTimeValuesEvent( |
247 time_zone=parser_mediator.timezone) | 255 date_time, 'Scheduled to end', time_zone=parser_mediator.timezone) |
248 parser_mediator.ProduceEventWithEventData(event, event_data) | 256 parser_mediator.ProduceEventWithEventData(event, event_data) |
249 | |
250 time_elements_tuple = ( | |
251 trigger_struct.end_year, trigger_struct.end_month, | |
252 trigger_struct.end_day, 0, 0, 0) | |
253 | |
254 if time_elements_tuple != (0, 0, 0, 0, 0, 0): | |
255 try: | |
256 date_time = dfdatetime_time_elements.TimeElements( | |
257 time_elements_tuple=time_elements_tuple) | |
258 date_time.is_local_time = True | |
259 # TODO: add functionality to dfdatetime to control precision. | |
260 date_time._precision = dfdatetime_definitions.PRECISION_1_DAY # pylin
t: disable=protected-access | |
261 except ValueError: | |
262 date_time = None | |
263 parser_mediator.ProduceExtractionError( | |
264 'invalid trigger end time: {0!s}'.format(time_elements_tuple)) | |
265 | |
266 if date_time: | |
267 event = time_events.DateTimeValuesEvent( | |
268 date_time, 'Scheduled to end', | |
269 time_zone=parser_mediator.timezone) | |
270 parser_mediator.ProduceEventWithEventData(event, event_data) | |
271 | 257 |
272 # TODO: create a timeless event object if last_run_time and | 258 # TODO: create a timeless event object if last_run_time and |
273 # trigger_start_time are None? What should be the description of | 259 # trigger_start_time are None? What should be the description of |
274 # this event? | 260 # this event? |
275 | 261 |
276 | 262 |
277 manager.ParsersManager.RegisterParser(WinJobParser) | 263 manager.ParsersManager.RegisterParser(WinJobParser) |
OLD | NEW |