-
Notifications
You must be signed in to change notification settings - Fork 451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Windows: Adds scheduled tasks plugin #1307
base: develop
Are you sure you want to change the base?
Windows: Adds scheduled tasks plugin #1307
Conversation
0ef2825
to
0cf298a
Compare
d8804db
to
69e824c
Compare
This adds a plugin 'ScheduledTasks' that can decode binary-encoded scheduled tasks from the Windows registry's SOFTWARE hive using a custom reader that extends the `io.BytesIO` class. Decoding operations are intended to be as fault tolerant as possible, swallowing exceptions and returning `None` to account for smear or missing data. Because each task can have mulitple triggers and multiple actions, a single entry is generated for each trigger + action pair. In the event that the either the actions could not be parsed or the triggers could not be parsed due to missing or smeared data, an entry will still be generated using the available information from the other registry value, since trigger and action data is stored separately. Much more information is decoded than is rendered, this was done intentionally to avoid overpopulating the TreeGrid with less pertinent data and to avoid an explosion of trigger and action-specific fields that may not apply to most other entries.
69e824c
to
57ef3f5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only half reviewed so far, this is a big one, but there's a few comments for you to be getting on with and I'll take another crack at it tomorrow hopefully...
return is_localized, filetime | ||
|
||
def read_filetime(self) -> Optional[datetime.datetime]: | ||
return datetime.datetime.now() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This... seems like the wrong thing? Was this intentional or just a placeholder?
format(v, "x") | ||
for v in struct.unpack( | ||
">HQ", | ||
buf[8:10] + b"\x00\x00" + buf[10:16], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow... just... wow. Microsoft do have some interesting people working for them don't they... 5:P
) | ||
|
||
|
||
class TestActionsDecoding(unittest.TestCase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be in a separate file. I'm all for test cases, but not that add huge chunks of data to the end of an already long plugin. I believe we do have a test directory. I'd be much happier if all the tests lived in there. Ideally we'd have a whole pytest setup that unit-tested each of the individual plugins and components of the framework, but I'm not going to put that on you now (and testing what is a giant parsing framework against all possible test cases isn't really feasible). However, if you could move this code somewhere appropriate with that goal in mind, I'd be very grateful...
mode_index = reader.read_u4() | ||
try: | ||
if mode_index is not None: | ||
mode = list(TimeMode)[mode_index] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like we're using the enum the wrong way? It should contain the value, and then a lookup dictionary should contain the text we want to display? Converting it to a list makes me worry that the order won't stay consisent, whereas is the enum were:
TimeMode.Once = 1
TimeMode.Dailed = 2
...
Then it should be possible to just go TimeMode(mode_index)
and then later look that up in a dictionary if we want the text for it?
data2 = reader.read_u2() | ||
data3 = reader.read_u2() | ||
|
||
reader.seek(2, io.SEEK_CUR) # pad |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the seek_relative
method, if you're always explicitly doing traditional seeks?
filetime = self.decode_filetime() | ||
if filetime is None: | ||
return None | ||
return is_localized, filetime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we know whether it's localized, could we not just return a python datetime object that includes the localization, rather than a filetime? The user might want the value changed to a timezone of their choosing, so keeping everything in one format we understand seems better than a separate flag (which appears to be ignored later on when this is called)?
This adds a plugin 'ScheduledTasks' that can decode binary-encoded scheduled tasks from the Windows registry's SOFTWARE hive using a custom reader that extends the
io.BytesIO
class. Decoding operations are intended to be as fault tolerant as possible, swallowing exceptions and returningNone
to account for smear or missing data.Because each task can have multiple triggers and multiple actions, a single entry is generated for each trigger + action pair. In the event that the either the actions or triggers could not be parsed due to missing or smeared data, an entry will still be generated using the available information from the other registry value, since trigger and action data is stored separately.
Much more information is decoded than is rendered, this was done intentionally to avoid overpopulating the TreeGrid with less pertinent data and to avoid an explosion of trigger/action-specific fields that may not apply to most other entries, while ensuring that the reader is at the correct offset.
Also documents the raised exceptions in the
RegistryHive.get_key()
docstring.Example task: