Add Unix and Windows application plugins
This PR adds Unix (snapd and .desktop) and Windows (registry) application plugins to dissect.
Fixes #884, fixes #885 and fixes #886.
As mentioned in #852. Please also add the record field documentation to their respective exported functions
In case you missed it, could you please add this to the exported functions still?
In case you missed it, could you please add this to the exported functions still?
I missed that, thanks for reminding me. Added in f354b97.
Codecov Report
Attention: Patch coverage is 89.21569% with 11 lines in your changes missing coverage. Please review.
Project coverage is 76.85%. Comparing base (
d0fa985) to head (b989f78). Report is 1 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #851 +/- ##
==========================================
+ Coverage 76.75% 76.85% +0.10%
==========================================
Files 315 318 +3
Lines 27130 27231 +101
==========================================
+ Hits 20823 20929 +106
+ Misses 6307 6302 -5
| Flag | Coverage Δ | |
|---|---|---|
| unittests | 76.85% <89.21%> (+0.10%) |
:arrow_up: |
Flags with carried forward coverage won't be shown. Click here to find out more.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
The tests seem to be failing on the Windows applications plugin. Looks like an invalid timestamp is passed. Can you try fix this?
The tests seem to be failing on the Windows applications plugin. Looks like an invalid timestamp is passed. Can you try fix this?
I cannot reproduce that locally, could you share stdout/err?
Upon looking a little closer the the Windows application plugin fails from Python (and PyPy) version 3.9 up to and including 3.10 on Ubuntu and Windows.
Errors across Python versions and OSes are similar of nature (PyPy 3.10 on Windows):
cls = <class 'flow.record.fieldtypes.datetime'>, date_string = '20240301'
@classmethod
def fromisoformat(cls, date_string):
"""Construct a datetime from the output of datetime.isoformat()."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
# Split this at the separator
dstr = date_string[0:10]
tstr = date_string[11:]
try:
> date_components = _parse_isoformat_date(dstr)
C:\hostedtoolcache\windows\PyPy\3.10.14\x86\Lib\datetime.py:1752:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
dtstr = '20240301'
def _parse_isoformat_date(dtstr):
# It is assumed that this function will only be called with a
# string of length exactly 10, and (though this is not used) ASCII-only
if len(dtstr) < 10:
> raise ValueError('isoformat expects a string of length 10')
E ValueError: isoformat expects a string of length 10
C:\hostedtoolcache\windows\PyPy\3.10.14\x86\Lib\datetime.py:275: ValueError
During handling of the above exception, another exception occurred:
target_win_users = <Target D:\a\dissect.target\dissect.target\.tox\pypy3.10\tmp\test_windows_applications0\MockTarget-yc3dbmav>
hive_hklm = <VirtualHive>
def test_windows_applications(target_win_users: Target, hive_hklm: VirtualHive) -> None:
"""test if windows applications are detected correctly in the registry"""
firefox_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Mozilla Firefox 123.0.1 (x64 nl)"
firefox_key = VirtualKey(hive_hklm, firefox_name)
firefox_key.add_value("Comments", "Mozilla Firefox 123.0.1 (x64 nl)")
firefox_key.add_value("DisplayIcon", "C:\\Program Files\\Mozilla Firefox\\firefox.exe,0")
firefox_key.add_value("DisplayName", "Mozilla Firefox (x64 nl)")
firefox_key.add_value("DisplayVersion", "123.0.1")
firefox_key.add_value("EstimatedSize", 238271)
firefox_key.add_value("HelpLink", "https://support.mozilla.org/")
firefox_key.add_value("InstallLocation", "C:\\Program Files\\Mozilla Firefox")
firefox_key.add_value("NoModify", 1)
firefox_key.add_value("NoRepair", 1)
firefox_key.add_value("Publisher", "Mozilla")
firefox_key.add_value("URLInfoAbout", "https://www.mozilla.org/")
firefox_key.add_value("URLUpdateInfo", "https://www.mozilla.org/firefox/123.0.1/releasenotes")
firefox_key.add_value("UninstallString", '"C:\\Program Files\\Mozilla Firefox\\uninstall\\helper.exe"')
hive_hklm.map_key(firefox_name, firefox_key)
chrome_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{47FB91DD-98F3-3C87-A963-357B14EAC7C9}"
chrome_key = VirtualKey(hive_hklm, chrome_name)
chrome_key.add_value("DisplayVersion", "122.0.6261.95")
chrome_key.add_value("InstallDate", "20240301")
chrome_key.add_value("InstallLocation", "")
chrome_key.add_value("InstallSource", "C:\\Users\\user\\Desktop\\GoogleChromeEnterpriseBundle64\\Installers\\")
chrome_key.add_value("ModifyPath", "MsiExec.exe /X{47FB91DD-98F3-3C87-A963-357B14EAC7C9}")
chrome_key.add_value("NoModify", 1)
chrome_key.add_value("Publisher", "Google LLC")
chrome_key.add_value("EstimatedSize", 113725)
chrome_key.add_value("UninstallString", "MsiExec.exe /X{47FB91DD-98F3-3C87-A963-357B14EAC7C9}")
chrome_key.add_value("VersionMajor", 70)
chrome_key.add_value("VersionMinor", 29)
chrome_key.add_value("WindowsInstaller", 1)
chrome_key.add_value("Version", 1176322143)
chrome_key.add_value("Language", 1033)
chrome_key.add_value("DisplayName", "Google Chrome")
hive_hklm.map_key(chrome_name, chrome_key)
addressbook_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\AddressBook"
addressbook_key = VirtualKey(hive_hklm, addressbook_name)
addressbook_key.timestamp = datetime(2024, 12, 31, 13, 37, 0, tzinfo=timezone.utc)
hive_hklm.map_key(addressbook_name, addressbook_key)
msvc_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{D5D19E2F-7189-42FE-8103-92CD1FA457C2}"
msvc_key = VirtualKey(hive_hklm, msvc_name)
msvc_key.add_value("DisplayName", "Microsoft Visual C++ 2022 X64 Minimum Runtime - 14.36.32532")
msvc_key.add_value("InstallDate", "20240301")
msvc_key.add_value("DisplayVersion", "14.36.32532")
msvc_key.add_value("Publisher", "Microsoft Corporation")
msvc_key.add_value(
"InstallSource",
"C:\\ProgramData\\Package Cache\\{D5D19E2F-7189-42FE-8103-92CD1FA457C2}v14.36.32532\\packages\\vcRuntimeMinimum_amd64\\", # noqa: E501
)
msvc_key.add_value("SystemComponent", 1)
hive_hklm.map_key(msvc_name, msvc_key)
target_win_users.add_plugin(WindowsApplicationsPlugin)
> results = sorted(list(target_win_users.applications()), key=lambda r: r.name)
tests\plugins\os\windows\regf\test_applications.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
dissect\target\plugins\os\windows\regf\applications.py:49: in applications
yield WindowsApplicationRecord(
dissect\target\helpers\record.py:78: in __call__
return super().__call__(*args, **kwargs)
.tox\pypy3.10\lib\site-packages\flow\record\base.py:595: in __call__
return self.recordType(*args, **kwargs)
<string>:26: in __init__
???
.tox\pypy3.10\lib\site-packages\flow\record\base.py:195: in __setattr__
v = field_type(v)
.tox\pypy3.10\lib\site-packages\flow\record\fieldtypes\__init__.py:323: in __new__
obj = cls.fromisoformat(arg)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'flow.record.fieldtypes.datetime'>, date_string = '20240301'
@classmethod
def fromisoformat(cls, date_string):
"""Construct a datetime from the output of datetime.isoformat()."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
# Split this at the separator
dstr = date_string[0:10]
tstr = date_string[11:]
try:
date_components = _parse_isoformat_date(dstr)
except ValueError:
> raise ValueError(f'Invalid isoformat string: {date_string!r}')
E ValueError: Invalid isoformat string: '20240301'
C:\hostedtoolcache\windows\PyPy\3.10.14\x86\Lib\datetime.py:1754: ValueError
Python 3.9 on Ubuntu:
target_win_users = <Target /home/runner/work/dissect.target/dissect.target/.tox/3.9/tmp/test_windows_applications0/MockTarget-kj40a8z8>
hive_hklm = <VirtualHive>
def test_windows_applications(target_win_users: Target, hive_hklm: VirtualHive) -> None:
"""test if windows applications are detected correctly in the registry"""
firefox_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Mozilla Firefox 123.0.1 (x64 nl)"
firefox_key = VirtualKey(hive_hklm, firefox_name)
firefox_key.add_value("Comments", "Mozilla Firefox 123.0.1 (x64 nl)")
firefox_key.add_value("DisplayIcon", "C:\\Program Files\\Mozilla Firefox\\firefox.exe,0")
firefox_key.add_value("DisplayName", "Mozilla Firefox (x64 nl)")
firefox_key.add_value("DisplayVersion", "123.0.1")
firefox_key.add_value("EstimatedSize", 238271)
firefox_key.add_value("HelpLink", "https://support.mozilla.org/")
firefox_key.add_value("InstallLocation", "C:\\Program Files\\Mozilla Firefox")
firefox_key.add_value("NoModify", 1)
firefox_key.add_value("NoRepair", 1)
firefox_key.add_value("Publisher", "Mozilla")
firefox_key.add_value("URLInfoAbout", "https://www.mozilla.org/")
firefox_key.add_value("URLUpdateInfo", "https://www.mozilla.org/firefox/123.0.1/releasenotes")
firefox_key.add_value("UninstallString", '"C:\\Program Files\\Mozilla Firefox\\uninstall\\helper.exe"')
hive_hklm.map_key(firefox_name, firefox_key)
chrome_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{47FB91DD-98F3-3C87-A963-357B14EAC7C9}"
chrome_key = VirtualKey(hive_hklm, chrome_name)
chrome_key.add_value("DisplayVersion", "122.0.6261.95")
chrome_key.add_value("InstallDate", "20240301")
chrome_key.add_value("InstallLocation", "")
chrome_key.add_value("InstallSource", "C:\\Users\\user\\Desktop\\GoogleChromeEnterpriseBundle64\\Installers\\")
chrome_key.add_value("ModifyPath", "MsiExec.exe /X{47FB91DD-98F3-3C87-A963-357B14EAC7C9}")
chrome_key.add_value("NoModify", 1)
chrome_key.add_value("Publisher", "Google LLC")
chrome_key.add_value("EstimatedSize", 113725)
chrome_key.add_value("UninstallString", "MsiExec.exe /X{47FB91DD-98F3-3C87-A963-357B14EAC7C9}")
chrome_key.add_value("VersionMajor", 70)
chrome_key.add_value("VersionMinor", 29)
chrome_key.add_value("WindowsInstaller", 1)
chrome_key.add_value("Version", 1176322143)
chrome_key.add_value("Language", 1033)
chrome_key.add_value("DisplayName", "Google Chrome")
hive_hklm.map_key(chrome_name, chrome_key)
addressbook_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\AddressBook"
addressbook_key = VirtualKey(hive_hklm, addressbook_name)
addressbook_key.timestamp = datetime(2024, 12, 31, 13, 37, 0, tzinfo=timezone.utc)
hive_hklm.map_key(addressbook_name, addressbook_key)
msvc_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{D5D19E2F-7189-42FE-8103-92CD1FA457C2}"
msvc_key = VirtualKey(hive_hklm, msvc_name)
msvc_key.add_value("DisplayName", "Microsoft Visual C++ 2022 X64 Minimum Runtime - 14.36.32532")
msvc_key.add_value("InstallDate", "20240301")
msvc_key.add_value("DisplayVersion", "14.36.32532")
msvc_key.add_value("Publisher", "Microsoft Corporation")
msvc_key.add_value(
"InstallSource",
"C:\\ProgramData\\Package Cache\\{D5D19E2F-7189-42FE-8103-92CD1FA457C2}v14.36.32532\\packages\\vcRuntimeMinimum_amd64\\", # noqa: E501
)
msvc_key.add_value("SystemComponent", 1)
hive_hklm.map_key(msvc_name, msvc_key)
target_win_users.add_plugin(WindowsApplicationsPlugin)
> results = sorted(list(target_win_users.applications()), key=lambda r: r.name)
tests/plugins/os/windows/regf/test_applications.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
dissect/target/plugins/os/windows/regf/applications.py:49: in applications
yield WindowsApplicationRecord(
dissect/target/helpers/record.py:78: in __call__
return super().__call__(*args, **kwargs)
.tox/3.9/lib/python3.9/site-packages/flow/record/base.py:595: in __call__
return self.recordType(*args, **kwargs)
<string>:26: in __init__
???
.tox/3.9/lib/python3.9/site-packages/flow/record/base.py:195: in __setattr__
v = field_type(v)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'flow.record.fieldtypes.datetime'>, args = ('20240301',)
kwargs = {}, arg = '20240301', tstr = '20240301', tzstr = '', tzsearch = ''
tzpos = 0, microsecond_pos = 0
def __new__(cls, *args, **kwargs):
if len(args) == 1 and not kwargs:
arg = args[0]
if isinstance(arg, bytes_type):
arg = arg.decode("utf-8")
if isinstance(arg, string_type):
# If we are on Python 3.11 or newer, we can use fromisoformat() to parse the string (fast path)
#
# Else we need to do some manual parsing to fix some issues with the string format:
# - Python 3.10 and older do not support nanoseconds in fromisoformat()
# - Python 3.10 and older do not support Z as timezone info in fromisoformat()
# - Python 3.10 and older do not support +0200 as timezone info in fromisoformat()
# - Python 3.10 and older requires "T" between date and time in fromisoformat()
#
# There are other incompatibilities, but we don't care about those for now.
if not PY_311_OR_HIGHER:
# Convert Z to +00:00 so that fromisoformat() works correctly on Python 3.10 and older
if arg[-1] == "Z":
arg = arg[:-1] + "+00:00"
# Find timezone info after the date part. Possible formats, so we use the longest one:
#
# YYYYmmdd length: 8
# YYYY-mm-dd length: 10
tstr = arg
tzstr = ""
tzsearch = arg[10:]
if tzpos := tzsearch.find("+") + 1 or tzsearch.find("-") + 1:
tzstr = arg[10 + tzpos - 1 :]
tstr = arg[: 10 + tzpos - 1]
# Convert +0200 to +02:00 so that fromisoformat() works correctly on Python 3.10 and older
if len(tzstr) == 5 and tzstr[3] != ":":
tzstr = tzstr[:3] + ":" + tzstr[3:]
# Python 3.10 and older do not support nanoseconds in fromisoformat()
if microsecond_pos := arg.rfind(".") + 1:
microseconds = arg[microsecond_pos:]
tstr = arg[: microsecond_pos - 1]
if tzpos := (microseconds.find("+") + 1 or microseconds.find("-") + 1):
microseconds = microseconds[: tzpos - 1]
# Pad microseconds to 6 digits, truncate if longer
microseconds = microseconds.ljust(6, "0")[:6]
arg = tstr + "." + microseconds + tzstr
else:
arg = tstr + tzstr
> obj = cls.fromisoformat(arg)
E ValueError: Invalid isoformat string: '20240301'
.tox/3.9/lib/python3.9/site-packages/flow/record/fieldtypes/__init__.py:323: ValueError
This should now be fixed in ae15c0a.