src: refactor windows hive code

Remove usage of hivexget as a subprocess and use Python hivex to
inspect the Windows Registry.

Use registry path constants defined in src.utils.winreg

Remove windows_is64bit() funcion as the code to identify the
architecture relies on a broken Registry query. Fixing the query
proved to be a challenge and the only implication is the removal
of the string "64 bits" at the end of the listed Windows OS
installed in each partition.

Use utility function in src.utils.winreg to make the software
inventory code more compact.

Rewrite onliner in _fill_package_set function and parse the
registry with a for loop.
master
Alejandro Sirgo Rica 2024-12-10 17:00:13 +01:00
parent aa570e66e6
commit 855768e144
2 changed files with 45 additions and 76 deletions

View File

@ -12,6 +12,7 @@ import platform
import logging import logging
import sys import sys
from src.utils.winreg import *
from enum import Enum from enum import Enum
from subprocess import PIPE from subprocess import PIPE
@ -45,35 +46,23 @@ def getlinuxversion(osrelease):
def getwindowsversion(winreghives): def getwindowsversion(winreghives):
""" """
Tries to obtain windows version information by Try to obtain windows version information by querying the SOFTWARE registry
querying the SOFTWARE registry hive. Registry hive to fetch ProductName and ReleaseId.
hives path is a required parameter. Return a generic "Microsoft Windows" string if something fails.
Runs hivexget(1) to fetch ProductName and
ReleaseId. If something fails (hivexget is
not installed, or registry is not found) it
returns a generic "Microsoft Windows" string.
""" """
# XXX: 3.6 friendly
try: try:
proc_prodname = subprocess.run(['hivexget', hivepath = f'{winreghives}/SOFTWARE'
f'{winreghives}/SOFTWARE', hive = hive_handler_open(hivepath, write = False)
'microsoft\windows nt\currentversion', root_node = hive.root()
'ProductName'], stdout=PIPE) version_node = get_node_child_from_path(hive, root_node, 'Microsoft/Windows NT/CurrentVersion')
proc_releaseid = subprocess.run(['hivexget',
f'{winreghives}/SOFTWARE',
'microsoft\windows nt\currentversion',
'ReleaseId'], stdout=PIPE)
prodname = proc_prodname.stdout.decode().replace('\n', '') prodname = get_value_from_node(hive, version_node, 'ProductName')
releaseid = proc_releaseid.stdout.decode().replace('\n', '') releaseid = get_value_from_node(hive, version_node, 'ReleaseId')
bits = ' 64 bits' if windows_is64bit(winreghives) else ''
if proc_prodname.returncode == 0 and proc_releaseid.returncode == 0: return f'{prodname} {releaseid}'
return f'{prodname} {releaseid}{bits}' except (RuntimeError, OgError) as e:
except FileNotFoundError: # hivexget command not found logging.error(f'Hivex was not able to operate over {hivepath}. Reported: {e}')
pass
return 'Microsoft Windows' return 'Microsoft Windows'
@ -81,28 +70,6 @@ def interpreter_is64bit():
return sys.maxsize > 2**32 return sys.maxsize > 2**32
def windows_is64bit(winreghives):
"""
Check for 64 bit Windows by means of retrieving the value of
ProgramW6432Dir. This key is set if Windows is running 64 bit.
If set returns True.
If not set or hivexget exits with non-zero, returns False.
"""
try:
proc_hivexget = subprocess.run(['hivexget',
f'{winreghives}/SOFTWARE',
'Microsoft\Windows\CurrentVersion',
'ProgramW6432Dir'], stdout=PIPE)
stdout = proc_hivexget.stdout.decode().replace('\n', '')
if proc_hivexget.returncode == 0 and stdout:
return True
except FileNotFoundError: # hivexget command not found
pass
return False
def linux_is64bit(mountpoint): def linux_is64bit(mountpoint):
""" """
If /sbin/init is detected, check if compiled for 64-bit machine. If /sbin/init is detected, check if compiled for 64-bit machine.
@ -174,7 +141,7 @@ def os_probe(mountpoint):
Returns a string depending on the OS it detects. Returns a string depending on the OS it detects.
""" """
winreghives = f'{mountpoint}/Windows/System32/config' winreghives = f'{mountpoint}{WINDOWS_HIVES_PATH}'
osrelease = f'{mountpoint}/etc/os-release' osrelease = f'{mountpoint}/etc/os-release'
if os.path.exists(osrelease): if os.path.exists(osrelease):

View File

@ -16,33 +16,39 @@ from collections import namedtuple
import hivex import hivex
from src.utils.probe import os_probe from src.utils.probe import os_probe
from src.utils.winreg import *
Package = namedtuple('Package', ['name', 'version']) Package = namedtuple('Package', ['name', 'version'])
Package.__str__ = lambda pkg: f'{pkg.name} {pkg.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' DPKG_STATUS_PATH = '/var/lib/dpkg/status'
OSRELEASE_PATH = '/etc/os-release' OSRELEASE_PATH = '/etc/os-release'
def _fill_package_set(h, key, pkg_set): def _fill_package_set(hive, key, pkg_set):
""" """
Fill the package set looking for entries at the current registry Fill the package set looking for entries at the current registry
node childs. node childs.
Any valid node child must have "DisplayVersion" or "DisplayName" keys. Any valid node child must have "DisplayVersion" or "DisplayName" keys.
""" """
childs = h.node_children(key) childs = hive.node_children(key)
valid_childs = [h.node_get_child(key, h.node_name(child)) valid_childs = []
for child in childs for child in childs:
for value in h.node_values(child) if h.value_key(value) == 'DisplayVersion'] child_name = hive.node_name(child)
values = hive.node_values(child)
for value in values:
if hive.value_key(value) == 'DisplayVersion':
valid_child = hive.node_get_child(key, child_name)
valid_childs.append(valid_child)
for ch in valid_childs: for ch in valid_childs:
try: try:
name = h.value_string(h.node_get_value(ch, 'DisplayName')) name = hive.value_string(hive.node_get_value(ch, 'DisplayName'))
value = h.node_get_value(ch, 'DisplayVersion') value = hive.node_get_value(ch, 'DisplayVersion')
version = h.value_string(value) version = hive.value_string(value)
pkg = Package(name, version) pkg = Package(name, version)
pkg_set.add(pkg) pkg_set.add(pkg)
except RuntimeError: except RuntimeError:
@ -50,22 +56,19 @@ def _fill_package_set(h, key, pkg_set):
pass pass
def _fill_package_set_1(h, pkg_set): def _fill_package_set_1(hive, pkg_set):
""" """
Looks for entries in registry path Looks for entries in registry path
/Microsoft/Windows/CurrentVersion/Uninstall /Microsoft/Windows/CurrentVersion/Uninstall
Fills the given set with Package instances for each program found. Fills the given set with Package instances for each program found.
""" """
key = h.root() root_node = hive.root()
key = h.node_get_child(key, 'Microsoft') key = get_node_child_from_path(hive, root_node, 'Microsoft/Windows/CurrentVersion/Uninstall')
key = h.node_get_child(key, 'Windows') _fill_package_set(hive, key, pkg_set)
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): def _fill_package_set_32_bit_compat(hive, pkg_set):
""" """
Looks for entries in registry path Looks for entries in registry path
/Wow6432Node/Microsoft/Windows/CurrentVersion/Uninstall /Wow6432Node/Microsoft/Windows/CurrentVersion/Uninstall
@ -73,23 +76,22 @@ def _fill_package_set_2(h, pkg_set):
Fills the given set with Package instances for each program found. Fills the given set with Package instances for each program found.
""" """
key = h.root() root_node = hive.root()
key = h.node_get_child(key, 'Wow6432Node') key = get_node_child_from_path(hive, root_node, 'Wow6432Node/Windows/CurrentVersion/Uninstall')
key = h.node_get_child(key, 'Microsoft') _fill_package_set(hive, key, pkg_set)
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): def _get_package_set_windows(hivepath):
packages = set() packages = set()
try: try:
h = hivex.Hivex(hivepath) h = hive_handler_open(hivepath, write = False)
_fill_package_set_1(h, packages) _fill_package_set_1(h, packages)
_fill_package_set_2(h, packages) except (RuntimeError, OgError) as e:
except RuntimeError as e:
logging.error(f'Hivex was not able to operate over {hivepath}. Reported: {e}') logging.error(f'Hivex was not able to operate over {hivepath}. Reported: {e}')
try:
_fill_package_set_32_bit_compat(h, packages)
except (RuntimeError, OgError) as e:
pass
return packages return packages
@ -119,7 +121,7 @@ def _get_package_set_dpkg(dpkg_status_path):
def get_package_set(mountpoint): def get_package_set(mountpoint):
dpkg_status_path = f'{mountpoint}{DPKG_STATUS_PATH}' dpkg_status_path = f'{mountpoint}{DPKG_STATUS_PATH}'
softwarehive = f'{mountpoint}{WINDOWS_HIVES_SOFTWARE}' softwarehive = f'{mountpoint}{WINDOWS_HIVE_SOFTWARE}'
if os.path.exists(softwarehive): if os.path.exists(softwarehive):
pkgset = _get_package_set_windows(softwarehive) pkgset = _get_package_set_windows(softwarehive)
elif os.path.exists(dpkg_status_path): elif os.path.exists(dpkg_status_path):