atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -24,22 +24,23 @@ def convert_der_x509_bytes_to_pem_string(certificate) -> str:
|
|
|
24
24
|
return ssl.DER_cert_to_PEM_cert(certificate)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def is_tls(
|
|
27
|
+
def is_tls(
|
|
28
|
+
client_socket,
|
|
29
|
+
timeout: float = None
|
|
30
|
+
) -> Optional[Tuple[str, str]]:
|
|
28
31
|
"""
|
|
29
32
|
Return protocol type of the incoming socket after 'accept()'.
|
|
30
33
|
:param client_socket: incoming socket after 'accept()'.
|
|
34
|
+
:param timeout: optional timeout for receiving/peeking the first bytes.
|
|
31
35
|
:return: tuple with content type, protocol type + version.
|
|
32
36
|
If the length of the first bytes is less than 3, return None.
|
|
33
37
|
"""
|
|
34
38
|
|
|
35
|
-
first_bytes = receiver.peek_first_bytes(client_socket, bytes_amount=3)
|
|
39
|
+
first_bytes = receiver.peek_first_bytes(client_socket, bytes_amount=3, timeout=timeout)
|
|
36
40
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# Extract the content type and version
|
|
42
|
-
content_type, version_major, version_minor = first_bytes
|
|
41
|
+
# Sometimes only one byte is available, so we need to handle that case.
|
|
42
|
+
# convert to a tuple of ints, add three Nones, and keep only the first 3 items.
|
|
43
|
+
content_type, version_major, version_minor = (tuple(first_bytes) + (None, None, None))[:3]
|
|
43
44
|
|
|
44
45
|
# Map TLS content types to their string representation.
|
|
45
46
|
content_type_map = {
|
|
@@ -52,19 +53,19 @@ def is_tls(client_socket) -> Optional[Tuple[str, str]]:
|
|
|
52
53
|
|
|
53
54
|
# Map TLS version bytes to their string representation.
|
|
54
55
|
version_map = {
|
|
55
|
-
(0x03, 0x00): "
|
|
56
|
-
(0x03, 0x01): "
|
|
57
|
-
(0x03, 0x02): "
|
|
58
|
-
(0x03, 0x03): "
|
|
56
|
+
(0x03, 0x00): "SSLv3.0",
|
|
57
|
+
(0x03, 0x01): "TLSv1.0",
|
|
58
|
+
(0x03, 0x02): "TLSv1.1",
|
|
59
|
+
(0x03, 0x03): "TLSv1.2/1.3"
|
|
59
60
|
# Remember, you can't definitively differentiate 1.2 and 1.3 just from these bytes
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
# Get the tuple of the type and version as strings.
|
|
63
|
-
tls_content_and_version_tuple: tuple
|
|
64
|
+
tls_content_and_version_tuple: tuple[str, str] = \
|
|
64
65
|
content_type_map.get(content_type), version_map.get((version_major, version_minor))
|
|
65
66
|
|
|
66
67
|
# If both parts of the tuple are not None, return the protocol type.
|
|
67
|
-
if tls_content_and_version_tuple[0]
|
|
68
|
+
if tls_content_and_version_tuple[0]:
|
|
68
69
|
return tls_content_and_version_tuple
|
|
69
70
|
else:
|
|
70
71
|
return None
|
|
@@ -1,23 +1,154 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import multiprocessing
|
|
2
3
|
|
|
3
|
-
from
|
|
4
|
+
from ..loggingw import loggingw
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
LOGGER_NAME: str = 'statistics'
|
|
8
|
+
STATISTICS_HEADER: str = (
|
|
9
|
+
'request_time_sent,thread_id,engine,source_host,source_ip,tls,protocol,protocol2,protocol3,dest_port,host,path,command,status_code,request_size_bytes,'
|
|
10
|
+
'response_size_bytes,file_path,process_cmd,action,error')
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
|
|
13
|
+
class StatisticsCSVWriter(loggingw.CsvLogger):
|
|
14
|
+
"""
|
|
15
|
+
Class to write statistics to CSV file.
|
|
16
|
+
This can be initiated at the main, and then passed to the thread worker function.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
logger_name: str = LOGGER_NAME,
|
|
21
|
+
directory_path: str = None,
|
|
22
|
+
log_queue: multiprocessing.Queue = None,
|
|
23
|
+
add_queue_handler_start_listener_multiprocessing: bool = False,
|
|
24
|
+
add_queue_handler_no_listener_multiprocessing: bool = False
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the StatisticsCSVWriter with the directory path for the statistics CSV file.
|
|
28
|
+
:param directory_path: str, the directory path where the statistics CSV file will be created.
|
|
29
|
+
:param log_queue: multiprocessing.Queue, the queue to use for logging in multiprocessing.
|
|
30
|
+
:param add_queue_handler_start_listener_multiprocessing: bool, whether to add a queue handler that will use
|
|
31
|
+
the 'logger_queue' and start the queue listener with the same 'logger_queue' for multiprocessing.
|
|
32
|
+
:param add_queue_handler_no_listener_multiprocessing: bool, whether to add a queue handler that will use
|
|
33
|
+
the 'logger_queue' but will not start the queue listener for multiprocessing. This is useful when you
|
|
34
|
+
already started the queue listener and want to add more handlers to the logger without
|
|
35
|
+
starting a new listener.
|
|
36
|
+
|
|
37
|
+
If you don't set any of 'add_queue_handler_start_listener_multiprocessing' or
|
|
38
|
+
'add_queue_handler_no_listener_multiprocessing', the logger will be created without a queue handler.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
super().__init__(
|
|
42
|
+
logger_name=logger_name,
|
|
43
|
+
directory_path=directory_path,
|
|
44
|
+
log_queue=log_queue,
|
|
45
|
+
add_queue_handler_start_listener_multiprocessing=add_queue_handler_start_listener_multiprocessing,
|
|
46
|
+
add_queue_handler_no_listener_multiprocessing=add_queue_handler_no_listener_multiprocessing,
|
|
47
|
+
custom_header=STATISTICS_HEADER
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def write_row(
|
|
51
|
+
self,
|
|
52
|
+
thread_id: str,
|
|
53
|
+
engine: str,
|
|
54
|
+
source_host: str,
|
|
55
|
+
source_ip: str,
|
|
56
|
+
host: str,
|
|
57
|
+
tls_type: str,
|
|
58
|
+
tls_version: str,
|
|
59
|
+
protocol: str,
|
|
60
|
+
protocol2: str,
|
|
61
|
+
protocol3: str,
|
|
62
|
+
dest_port: str,
|
|
63
|
+
path: str,
|
|
64
|
+
status_code: str,
|
|
65
|
+
command: str,
|
|
66
|
+
request_size_bytes: str,
|
|
67
|
+
response_size_bytes: str,
|
|
68
|
+
recorded_file_path: str = None,
|
|
69
|
+
process_cmd: str = None,
|
|
70
|
+
error: str = None,
|
|
71
|
+
action: str = None,
|
|
72
|
+
timestamp=None,
|
|
73
|
+
):
|
|
74
|
+
if not timestamp:
|
|
75
|
+
timestamp = datetime.datetime.now()
|
|
76
|
+
|
|
77
|
+
if not tls_type and not tls_version:
|
|
78
|
+
tls_info = ''
|
|
79
|
+
else:
|
|
80
|
+
tls_info = f'{tls_type}|{tls_version}'
|
|
81
|
+
|
|
82
|
+
row_of_cols: list = [
|
|
83
|
+
timestamp,
|
|
84
|
+
thread_id,
|
|
85
|
+
engine,
|
|
86
|
+
source_host,
|
|
87
|
+
source_ip,
|
|
88
|
+
tls_info,
|
|
89
|
+
protocol,
|
|
90
|
+
protocol2,
|
|
91
|
+
protocol3,
|
|
92
|
+
dest_port,
|
|
93
|
+
host,
|
|
94
|
+
path,
|
|
95
|
+
command,
|
|
96
|
+
status_code,
|
|
97
|
+
request_size_bytes,
|
|
98
|
+
response_size_bytes,
|
|
99
|
+
recorded_file_path,
|
|
100
|
+
process_cmd,
|
|
101
|
+
action,
|
|
102
|
+
error
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
super().write(row_of_cols)
|
|
106
|
+
|
|
107
|
+
def write_accept_error(
|
|
108
|
+
self,
|
|
109
|
+
engine: str,
|
|
110
|
+
source_ip: str,
|
|
111
|
+
source_host: str,
|
|
112
|
+
error_message: str,
|
|
113
|
+
dest_port: str,
|
|
114
|
+
host: str,
|
|
115
|
+
process_name: str,
|
|
116
|
+
thread_id: str = str()
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Write the error message to the statistics CSV file.
|
|
120
|
+
This is used for easier execution, since most of the parameters will be empty on accept.
|
|
121
|
+
|
|
122
|
+
:param engine: string, engine name.
|
|
123
|
+
:param source_ip: string, source IP address.
|
|
124
|
+
:param source_host: string, source host name.
|
|
125
|
+
:param error_message: string, error message.
|
|
126
|
+
:param dest_port: string, destination port.
|
|
127
|
+
:param host: string, host, the domain or IP address.
|
|
128
|
+
:param process_name: process name, the command line of the process.
|
|
129
|
+
:param thread_id: integer, the id of the thread.
|
|
130
|
+
:return:
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
self.write_row(
|
|
134
|
+
thread_id=thread_id,
|
|
135
|
+
engine=engine,
|
|
136
|
+
source_host=source_host,
|
|
137
|
+
source_ip=source_ip,
|
|
138
|
+
tls_type='',
|
|
139
|
+
tls_version='',
|
|
140
|
+
protocol='',
|
|
141
|
+
protocol2='',
|
|
142
|
+
protocol3='',
|
|
143
|
+
dest_port=dest_port,
|
|
144
|
+
host=host,
|
|
145
|
+
path='',
|
|
146
|
+
status_code='',
|
|
147
|
+
command='',
|
|
148
|
+
request_size_bytes='',
|
|
149
|
+
response_size_bytes='',
|
|
150
|
+
recorded_file_path='',
|
|
151
|
+
process_cmd=process_name,
|
|
152
|
+
action='client_accept',
|
|
153
|
+
error=error_message
|
|
17
154
|
)
|
|
18
|
-
except UnboundLocalError:
|
|
19
|
-
pass
|
|
20
|
-
except Exception:
|
|
21
|
-
message = "Undocumented exception after accept on building statistics."
|
|
22
|
-
print_api(message, error_type=True, logger_method='error', traceback_string=True, oneline=True, **print_kwargs)
|
|
23
|
-
pass
|
atomicshop/wrappers/sysmonw.py
CHANGED
|
@@ -49,7 +49,7 @@ def is_sysmon_running():
|
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
51
|
process_list: list = process.get_running_processes_by_cmdline_pattern(
|
|
52
|
-
pattern=SYSMON_FILE_NAME, first=True,
|
|
52
|
+
pattern=SYSMON_FILE_NAME, first=True, cmdline_case_insensitive=True)
|
|
53
53
|
|
|
54
54
|
if process_list:
|
|
55
55
|
return True
|
|
@@ -4,23 +4,64 @@ import subprocess
|
|
|
4
4
|
import shutil
|
|
5
5
|
import time
|
|
6
6
|
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
7
9
|
from ..print_api import print_api
|
|
8
|
-
from .. import
|
|
10
|
+
from ..permissions import ubuntu_permissions
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
9
14
|
|
|
10
15
|
|
|
11
|
-
def install_packages(
|
|
16
|
+
def install_packages(
|
|
17
|
+
package_list: list[str],
|
|
18
|
+
timeout_seconds: int = 0,
|
|
19
|
+
):
|
|
12
20
|
"""
|
|
13
21
|
Function installs a package using apt-get.
|
|
14
22
|
:param package_list: list of strings, package names to install.
|
|
23
|
+
:param timeout_seconds: int, if the 'apt-get' command is busy at the moment, the function will wait for
|
|
24
|
+
'timeout_seconds' seconds before raising an error.
|
|
25
|
+
'-1' means wait indefinitely.
|
|
15
26
|
:return:
|
|
16
27
|
"""
|
|
17
28
|
|
|
18
29
|
# Construct the command with the package list
|
|
19
|
-
command = ["sudo", "apt
|
|
30
|
+
command = ["sudo", "apt", "install", "-y"] + package_list
|
|
31
|
+
|
|
32
|
+
if timeout_seconds != 0:
|
|
33
|
+
command.extend(["-o", f"DPkg::Lock::Timeout={str(timeout_seconds)}"])
|
|
20
34
|
|
|
21
35
|
subprocess.check_call(command)
|
|
22
36
|
|
|
23
37
|
|
|
38
|
+
def remove_packages(
|
|
39
|
+
package_list: list[str],
|
|
40
|
+
remove_config_files: bool = False,
|
|
41
|
+
remove_dependencies: bool = False,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Function removes a list of packages.
|
|
45
|
+
:param package_list: list of strings, package names to remove. Regular removal is through 'apt remove'.
|
|
46
|
+
:param remove_config_files: bool, if True, the config files will be removed also through 'apt purge'.
|
|
47
|
+
:param remove_dependencies: bool, if True, the dependencies will be removed also through 'apt autoremove'.
|
|
48
|
+
:return:
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Construct the command with the package list
|
|
52
|
+
command = ["sudo", "apt", "remove", "-y"] + package_list
|
|
53
|
+
|
|
54
|
+
# If remove_config_files is True, add 'purge' to the command
|
|
55
|
+
if remove_config_files:
|
|
56
|
+
command.insert(2, "purge")
|
|
57
|
+
|
|
58
|
+
subprocess.check_call(command)
|
|
59
|
+
|
|
60
|
+
# If remove_dependencies is True, remove the dependencies
|
|
61
|
+
if remove_dependencies:
|
|
62
|
+
subprocess.check_call(["sudo", "apt", "autoremove", "-y"])
|
|
63
|
+
|
|
64
|
+
|
|
24
65
|
def is_package_installed(package: str) -> bool:
|
|
25
66
|
"""
|
|
26
67
|
Function checks if a package is installed.
|
|
@@ -74,7 +115,7 @@ def update_system_packages():
|
|
|
74
115
|
Function updates the system packages.
|
|
75
116
|
:return:
|
|
76
117
|
"""
|
|
77
|
-
subprocess.check_call(['sudo', 'apt
|
|
118
|
+
subprocess.check_call(['sudo', 'apt', 'update'])
|
|
78
119
|
|
|
79
120
|
|
|
80
121
|
def upgrade_system_packages(apt_update: bool = True):
|
|
@@ -88,7 +129,7 @@ def upgrade_system_packages(apt_update: bool = True):
|
|
|
88
129
|
if apt_update:
|
|
89
130
|
update_system_packages()
|
|
90
131
|
|
|
91
|
-
subprocess.check_call(['sudo', 'apt
|
|
132
|
+
subprocess.check_call(['sudo', 'apt', 'upgrade', '-y'])
|
|
92
133
|
|
|
93
134
|
|
|
94
135
|
def is_service_running(service_name: str, user_mode: bool = False, return_false_on_error: bool = False) -> bool:
|
|
@@ -175,28 +216,25 @@ def start_service(service_name: str, sudo: bool = False, user_mode: bool = False
|
|
|
175
216
|
def start_enable_service_check_availability(
|
|
176
217
|
service_name: str,
|
|
177
218
|
wait_time_seconds: float = 30,
|
|
178
|
-
exit_on_error: bool = True,
|
|
179
219
|
start_service_bool: bool = True,
|
|
180
220
|
enable_service_bool: bool = True,
|
|
181
221
|
check_service_running: bool = True,
|
|
182
222
|
user_mode: bool = False,
|
|
183
|
-
sudo: bool = True
|
|
184
|
-
|
|
185
|
-
):
|
|
223
|
+
sudo: bool = True
|
|
224
|
+
) -> int:
|
|
186
225
|
"""
|
|
187
226
|
Function starts and enables a service and checks its availability.
|
|
188
227
|
|
|
189
228
|
:param service_name: str, the service name.
|
|
190
229
|
:param wait_time_seconds: float, the time to wait after starting the service before checking the service
|
|
191
230
|
availability.
|
|
192
|
-
:param exit_on_error: bool, if True, the function will exit the program if the service is not available.
|
|
193
231
|
:param start_service_bool: bool, if True, the service will be started.
|
|
194
232
|
:param enable_service_bool: bool, if True, the service will be enabled.
|
|
195
233
|
:param check_service_running: bool, if True, the function will check if the service is running.
|
|
196
234
|
:param user_mode: bool, if True, the service will be started and enabled in user mode.
|
|
197
235
|
:param sudo: bool, if True, the command will be executed with sudo.
|
|
198
|
-
|
|
199
|
-
:return:
|
|
236
|
+
|
|
237
|
+
:return: int, 0 if the service is running, 1 if the service is not running.
|
|
200
238
|
"""
|
|
201
239
|
|
|
202
240
|
if not start_service_bool and not enable_service_bool:
|
|
@@ -209,18 +247,19 @@ def start_enable_service_check_availability(
|
|
|
209
247
|
enable_service(service_name, user_mode=user_mode,sudo=sudo)
|
|
210
248
|
|
|
211
249
|
if check_service_running:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
250
|
+
print(f"Waiting up to {str(wait_time_seconds)} seconds for the program to start.")
|
|
251
|
+
count: int = 0
|
|
252
|
+
while not is_service_running(service_name, user_mode=user_mode) and count < wait_time_seconds:
|
|
253
|
+
count += 1
|
|
254
|
+
time.sleep(1)
|
|
216
255
|
|
|
217
256
|
if not is_service_running(service_name, user_mode=user_mode):
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if exit_on_error:
|
|
221
|
-
sys.exit(1)
|
|
257
|
+
console.print(f"[{service_name}] service failed to start.", style='red', markup=False)
|
|
258
|
+
return 1
|
|
222
259
|
else:
|
|
223
|
-
|
|
260
|
+
console.print(f"[{service_name}] service is running.", style='green', markup=False)
|
|
261
|
+
|
|
262
|
+
return 0
|
|
224
263
|
|
|
225
264
|
|
|
226
265
|
def add_path_to_bashrc(as_regular_user: bool = False):
|
|
@@ -231,11 +270,11 @@ def add_path_to_bashrc(as_regular_user: bool = False):
|
|
|
231
270
|
|
|
232
271
|
if as_regular_user:
|
|
233
272
|
# Get the current non-sudo user
|
|
234
|
-
with
|
|
273
|
+
with ubuntu_permissions.temporary_regular_permissions():
|
|
235
274
|
current_non_sudo_user = os.getlogin()
|
|
236
275
|
|
|
237
276
|
# Get the home path of the current non-sudo user
|
|
238
|
-
user_bashrc_path =
|
|
277
|
+
user_bashrc_path = ubuntu_permissions.expand_user_path(current_non_sudo_user, home_path_bashrc)
|
|
239
278
|
else:
|
|
240
279
|
user_bashrc_path = os.path.expanduser(home_path_bashrc)
|
|
241
280
|
|
|
@@ -258,11 +297,11 @@ def add_line_to_bashrc(line: str, as_regular_user: bool = False):
|
|
|
258
297
|
|
|
259
298
|
if as_regular_user:
|
|
260
299
|
# Get the current non-sudo user
|
|
261
|
-
with
|
|
300
|
+
with ubuntu_permissions.temporary_regular_permissions():
|
|
262
301
|
current_non_sudo_user = os.getlogin()
|
|
263
302
|
|
|
264
303
|
# Get the home path of the current non-sudo user
|
|
265
|
-
user_bashrc_path =
|
|
304
|
+
user_bashrc_path = ubuntu_permissions.expand_user_path(current_non_sudo_user, home_path_bashrc)
|
|
266
305
|
else:
|
|
267
306
|
user_bashrc_path = os.path.expanduser(home_path_bashrc)
|
|
268
307
|
|
|
@@ -293,7 +332,7 @@ def get_command_execution_as_sudo_executer(command: str, add_bash_exec: bool = F
|
|
|
293
332
|
|
|
294
333
|
command = command.replace('sudo ', '').strip()
|
|
295
334
|
|
|
296
|
-
sudo_executer_username: str =
|
|
335
|
+
sudo_executer_username: str = ubuntu_permissions.get_sudo_executer_username()
|
|
297
336
|
|
|
298
337
|
if sudo_executer_username:
|
|
299
338
|
if add_bash_exec:
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import winreg
|
|
3
|
+
|
|
4
|
+
from ..print_api import print_api
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
AUDITING_REG_PATH: str = r"Software\Microsoft\Windows\CurrentVersion\Policies\System\Audit"
|
|
8
|
+
PROCESS_CREATION_INCLUDE_CMDLINE_VALUE: str = "ProcessCreationIncludeCmdLine_Enabled"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def enable_command_line_auditing(print_kwargs: dict = None):
|
|
12
|
+
"""
|
|
13
|
+
Enable the 'Include command line in process creation events' policy.
|
|
14
|
+
|
|
15
|
+
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit" /v ProcessCreationIncludeCmdLine_Enabled /t REG_DWORD /d 1 /f
|
|
16
|
+
|
|
17
|
+
:param print_kwargs: Optional keyword arguments for the print function.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
if is_command_line_auditing_enabled():
|
|
21
|
+
print_api(
|
|
22
|
+
"[Include command line in process creation events] is already enabled.", color='blue',
|
|
23
|
+
**(print_kwargs or {}))
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# Open the registry key
|
|
28
|
+
with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, AUDITING_REG_PATH) as reg_key:
|
|
29
|
+
# Set the value
|
|
30
|
+
winreg.SetValueEx(reg_key, PROCESS_CREATION_INCLUDE_CMDLINE_VALUE, 0, winreg.REG_DWORD, 1)
|
|
31
|
+
|
|
32
|
+
print_api(
|
|
33
|
+
"Successfully enabled [Include command line in process creation events].",
|
|
34
|
+
color='green', **(print_kwargs or {}))
|
|
35
|
+
except WindowsError as e:
|
|
36
|
+
print_api(
|
|
37
|
+
f"Failed to enable [Include command line in process creation events]: {e}", error_type=True,
|
|
38
|
+
color='red', **(print_kwargs or {}))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_command_line_auditing_enabled():
|
|
42
|
+
try:
|
|
43
|
+
# Open the registry key
|
|
44
|
+
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, AUDITING_REG_PATH, 0, winreg.KEY_READ) as reg_key:
|
|
45
|
+
# Query the value
|
|
46
|
+
value, reg_type = winreg.QueryValueEx(reg_key, PROCESS_CREATION_INCLUDE_CMDLINE_VALUE)
|
|
47
|
+
# Check if the value is 1 (enabled)
|
|
48
|
+
return value == 1
|
|
49
|
+
except FileNotFoundError:
|
|
50
|
+
# Key or value not found, assume it's not enabled
|
|
51
|
+
return False
|
|
52
|
+
except WindowsError as e:
|
|
53
|
+
print(f"Failed to read the 'Include command line in process creation events' setting: {e}")
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def enable_audit_process_creation(print_kwargs: dict = None):
|
|
58
|
+
"""
|
|
59
|
+
Enable the 'Audit Process Creation' policy.
|
|
60
|
+
Log: Security
|
|
61
|
+
Event ID: 4688 - A new process has been created.
|
|
62
|
+
|
|
63
|
+
auditpol /set /subcategory:"Process Creation" /success:enable /failure:enable
|
|
64
|
+
|
|
65
|
+
:param print_kwargs: Optional keyword arguments for the print function.
|
|
66
|
+
"""
|
|
67
|
+
if is_audit_process_creation_enabled():
|
|
68
|
+
print_api("Audit Process Creation is already enabled.", color='blue', **(print_kwargs or {}))
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# Enable "Audit Process Creation" policy
|
|
72
|
+
audit_policy_command = [
|
|
73
|
+
"auditpol", "/set", "/subcategory:Process Creation", "/success:enable", "/failure:enable"
|
|
74
|
+
]
|
|
75
|
+
try:
|
|
76
|
+
subprocess.run(audit_policy_command, check=True)
|
|
77
|
+
print_api("Successfully enabled 'Audit Process Creation'.", color='green', **(print_kwargs or {}))
|
|
78
|
+
except subprocess.CalledProcessError as e:
|
|
79
|
+
print_api(f"Failed to enable 'Audit Process Creation': {e}", error_type=True, color='red', **(print_kwargs or {}))
|
|
80
|
+
raise e
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def is_audit_process_creation_enabled(print_kwargs: dict = None) -> bool:
|
|
84
|
+
"""
|
|
85
|
+
Check if the 'Audit Process Creation' policy is enabled.
|
|
86
|
+
|
|
87
|
+
:param print_kwargs: Optional keyword arguments for the print function.
|
|
88
|
+
"""
|
|
89
|
+
# Command to check the "Audit Process Creation" policy
|
|
90
|
+
audit_policy_check_command = [
|
|
91
|
+
"auditpol", "/get", "/subcategory:Process Creation"
|
|
92
|
+
]
|
|
93
|
+
try:
|
|
94
|
+
result = subprocess.run(audit_policy_check_command, check=True, capture_output=True, text=True)
|
|
95
|
+
output = result.stdout
|
|
96
|
+
# print_api(output) # Print the output for inspection
|
|
97
|
+
|
|
98
|
+
if "Process Creation" in output and "Success and Failure" in output:
|
|
99
|
+
# print_api(
|
|
100
|
+
# "'Audit Process Creation' is enabled for both success and failure.",
|
|
101
|
+
# color='green', **(print_kwargs or {}))
|
|
102
|
+
return True
|
|
103
|
+
else:
|
|
104
|
+
# print_api(output, **(print_kwargs or {}))
|
|
105
|
+
# print_api(
|
|
106
|
+
# "'Audit Process Creation' is not fully enabled. Check the output above for details.",
|
|
107
|
+
# color='yellow', **(print_kwargs or {}))
|
|
108
|
+
return False
|
|
109
|
+
except subprocess.CalledProcessError as e:
|
|
110
|
+
print_api(f"Failed to check 'Audit Process Creation': {e}", color='red', error_type=True, **(print_kwargs or {}))
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def enable_audit_process_termination(print_kwargs: dict = None):
|
|
115
|
+
"""
|
|
116
|
+
Enable the 'Audit Process Termination' policy.
|
|
117
|
+
|
|
118
|
+
:param print_kwargs: Optional keyword arguments for the print function.
|
|
119
|
+
"""
|
|
120
|
+
if is_audit_process_termination_enabled():
|
|
121
|
+
print_api("Audit Process Termination is already enabled.", color='blue', **(print_kwargs or {}))
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
audit_policy_command = [
|
|
125
|
+
"auditpol", "/set", "/subcategory:Process Termination", "/success:enable", "/failure:enable"
|
|
126
|
+
]
|
|
127
|
+
try:
|
|
128
|
+
subprocess.run(audit_policy_command, check=True)
|
|
129
|
+
print_api("Successfully enabled 'Audit Process Termination'.", color='green', **(print_kwargs or {}))
|
|
130
|
+
except subprocess.CalledProcessError as e:
|
|
131
|
+
print_api(f"Failed to enable 'Audit Process Termination': {e}", error_type=True, color='red', **(print_kwargs or {}))
|
|
132
|
+
raise e
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def is_audit_process_termination_enabled(print_kwargs: dict = None) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
Check if the 'Audit Process Termination' policy is enabled.
|
|
138
|
+
|
|
139
|
+
:param print_kwargs: Optional keyword arguments for the print function.
|
|
140
|
+
"""
|
|
141
|
+
# Command to check the "Audit Process Creation" policy
|
|
142
|
+
audit_policy_check_command = [
|
|
143
|
+
"auditpol", "/get", "/subcategory:Process Termination"
|
|
144
|
+
]
|
|
145
|
+
try:
|
|
146
|
+
result = subprocess.run(audit_policy_check_command, check=True, capture_output=True, text=True)
|
|
147
|
+
output = result.stdout
|
|
148
|
+
# print_api(output) # Print the output for inspection
|
|
149
|
+
|
|
150
|
+
if "Process Termination" in output and "Success and Failure" in output:
|
|
151
|
+
# print_api(
|
|
152
|
+
# "'Audit Process Termination' is enabled for both success and failure.",
|
|
153
|
+
# color='green', **(print_kwargs or {}))
|
|
154
|
+
return True
|
|
155
|
+
else:
|
|
156
|
+
# print_api(output, **(print_kwargs or {}))
|
|
157
|
+
# print_api(
|
|
158
|
+
# "'Audit Process Termination' is not fully enabled. Check the output above for details.",
|
|
159
|
+
# color='yellow', **(print_kwargs or {}))
|
|
160
|
+
return False
|
|
161
|
+
except subprocess.CalledProcessError as e:
|
|
162
|
+
print_api(f"Failed to check 'Audit Process Termination': {e}", color='red', error_type=True, **(print_kwargs or {}))
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def enable_audit_filtering_platform_connection(print_kwargs: dict = None):
|
|
167
|
+
"""
|
|
168
|
+
Enable the 'Filtering Platform Connection' policy.
|
|
169
|
+
This enables you to fetch connection creations and deletions from the Windows Security Event Log.
|
|
170
|
+
Log: Security
|
|
171
|
+
Event IDs:
|
|
172
|
+
5156 - The Windows Filtering Platform has permitted a connection.
|
|
173
|
+
5158 - The Windows Filtering Platform has blocked a connection.
|
|
174
|
+
Events include information about source and destination IP addresses and ports.
|
|
175
|
+
|
|
176
|
+
auditpol /set /subcategory:"Filtering Platform Connection" /success:enable /failure:enable
|
|
177
|
+
|
|
178
|
+
:param print_kwargs: Optional keyword arguments for the print function.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
audit_policy_command = [
|
|
182
|
+
"auditpol", "/set", '/subcategory:"Filtering Platform Connection"', "/success:enable", "/failure:enable"
|
|
183
|
+
]
|
|
184
|
+
try:
|
|
185
|
+
subprocess.run(audit_policy_command, check=True)
|
|
186
|
+
print_api("Successfully enabled 'Audit Filtering Platform Connection'.", color='green', **(print_kwargs or {}))
|
|
187
|
+
except subprocess.CalledProcessError as e:
|
|
188
|
+
print_api(f"Failed to enable 'Audit Filtering Platform Connection': {e}", error_type=True, color='red', **(print_kwargs or {}))
|
|
189
|
+
raise e
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import winreg
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_installed_software() -> list[dict]:
|
|
6
|
+
registry_path: str = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
|
|
7
|
+
data: list[dict] = []
|
|
8
|
+
|
|
9
|
+
# Open the specified registry path
|
|
10
|
+
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path) as reg_key:
|
|
11
|
+
i = 0
|
|
12
|
+
while True:
|
|
13
|
+
try:
|
|
14
|
+
# Enumerate all sub-keys
|
|
15
|
+
subkey_name = winreg.EnumKey(reg_key, i)
|
|
16
|
+
subkey_path = os.path.join(registry_path, subkey_name)
|
|
17
|
+
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, subkey_path) as subkey:
|
|
18
|
+
try:
|
|
19
|
+
# Fetch DisplayName and DisplayVersion if they exist
|
|
20
|
+
display_name, _ = winreg.QueryValueEx(subkey, "DisplayName")
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
display_name = "N/A"
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
display_version, _ = winreg.QueryValueEx(subkey, "DisplayVersion")
|
|
26
|
+
except FileNotFoundError:
|
|
27
|
+
display_version = "N/A"
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
install_date, _ = winreg.QueryValueEx(subkey, "InstallDate")
|
|
31
|
+
except FileNotFoundError:
|
|
32
|
+
install_date = "N/A"
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
install_location, _ = winreg.QueryValueEx(subkey, "InstallLocation")
|
|
36
|
+
except FileNotFoundError:
|
|
37
|
+
install_location = "N/A"
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
install_source, _ = winreg.QueryValueEx(subkey, "InstallSource")
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
install_source = "N/A"
|
|
43
|
+
|
|
44
|
+
if display_name != "N/A":
|
|
45
|
+
data.append({
|
|
46
|
+
"DisplayName": display_name,
|
|
47
|
+
"DisplayVersion": display_version,
|
|
48
|
+
"InstallDate": install_date,
|
|
49
|
+
"SubkeyName": subkey_name,
|
|
50
|
+
"InstallLocation": install_location,
|
|
51
|
+
"InstallSource": install_source
|
|
52
|
+
})
|
|
53
|
+
except OSError:
|
|
54
|
+
break # No more subkeys
|
|
55
|
+
|
|
56
|
+
i += 1
|
|
57
|
+
|
|
58
|
+
return data
|