1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
#
# Copyright (C) 2023 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
# Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
import platform
import re
import os
from collections import namedtuple
import hivex
from src.utils.probe import getwindowsversion, getlinuxversion
Package = namedtuple('Package', ['name', 'version'])
Package.__str__ = lambda pkg: f'{pkg.name} {pkg.version}'
WINDOWS_HIVES_PATH = '/Windows/System32/config'
WINDOWS_HIVES_SOFTWARE = f'{WINDOWS_HIVES_PATH}/SOFTWARE'
DPKG_STATUS_PATH = '/var/lib/dpkg/status'
OSRELEASE_PATH = '/etc/os-release'
def _fill_package_set(h, key, pkg_set):
"""
Fill the package set looking for entries at the current registry
node childs.
Any valid node child must have "DisplayVersion" or "DisplayName" keys.
"""
childs = h.node_children(key)
valid_childs = [h.node_get_child(key, h.node_name(child))
for child in childs
for value in h.node_values(child) if h.value_key(value) == 'DisplayVersion']
for ch in valid_childs:
name = h.value_string(h.node_get_value(ch, 'DisplayName'))
value = h.node_get_value(ch, 'DisplayVersion')
version = h.value_string(value)
pkg = Package(name, version)
pkg_set.add(pkg)
def _fill_package_set_1(h, pkg_set):
"""
Looks for entries in registry path
/Microsoft/Windows/CurrentVersion/Uninstall
Fills the given set with Package instances for each program found.
"""
key = h.root()
key = h.node_get_child(key, 'Microsoft')
key = h.node_get_child(key, 'Windows')
key = h.node_get_child(key, 'CurrentVersion')
key = h.node_get_child(key, 'Uninstall')
_fill_package_set(h, key, pkg_set)
def _fill_package_set_2(h, pkg_set):
"""
Looks for entries in registry path
/Wow6432Node/Microsoft/Windows/CurrentVersion/Uninstall
64 bit Windows only.
Fills the given set with Package instances for each program found.
"""
key = h.root()
key = h.node_get_child(key, 'Wow6432Node')
key = h.node_get_child(key, 'Microsoft')
key = h.node_get_child(key, 'Windows')
key = h.node_get_child(key, 'CurrentVersion')
key = h.node_get_child(key, 'Uninstall')
_fill_package_set(h, key, pkg_set)
def _get_package_set_windows(hivepath):
packages = set()
h = hivex.Hivex(hivepath)
_fill_package_set_1(h, packages)
_fill_package_set_2(h, packages)
return packages
def _get_package_set_dpkg(dpkg_status_path):
regex_pkg = '(?:^Package: )(?P<name>.*)\n(Essential:.*\n)?(?:Status: install ok installed)'
regex_ver = '(?:^Version: )(?P<version>.*)'
packages = set()
with open(dpkg_status_path, 'r') as f:
# Split by empty line
for par in re.split('^\n+', f.read(), flags=re.MULTILINE):
# Search for package with "Status: install ok installed"
result = re.search(regex_pkg, par)
if result is None:
continue
else:
pkg_name = result.group('name')
# If we hit a properly installed package, search for its version
result = re.search(regex_ver, par, flags=re.MULTILINE)
if result is None:
continue
else:
pkg_version = result.group('version')
pkg = Package(pkg_name, pkg_version)
packages.add(pkg)
return packages
def get_package_set(mountpoint):
dpkg_status_path = f'{mountpoint}{DPKG_STATUS_PATH}'
winreghives = f'{mountpoint}{WINDOWS_HIVES_PATH}'
osrelease = f'{mountpoint}{OSRELEASE_PATH}'
softwarehive = f'{mountpoint}{WINDOWS_HIVES_SOFTWARE}'
if os.path.exists(softwarehive):
pkgset = _get_package_set_windows(softwarehive)
osname = getwindowsversion(winreghives)
elif os.path.exists(dpkg_status_path):
pkgset = _get_package_set_dpkg(dpkg_status_path)
osname = getlinuxversion(osrelease)
else:
raise ValueError(f'Cannot fetch software inventory at {mountpoint}')
# Legacy software inventory first element is the OS name
return [osname] + list(pkgset)
|