atomicshop 2.11.47__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/addons/process_list/compile.cmd +7 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
- 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/{addons/mains → a_mains}/msi_unpacker.py +3 -1
- 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/appointment_management.py +5 -3
- 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/dicts.py +12 -0
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/list_of_dicts.py +69 -5
- atomicshop/basics/lists.py +14 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/package_module.py +10 -0
- atomicshop/basics/strings.py +160 -7
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -4
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +12 -7
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +98 -0
- atomicshop/diff_check.py +340 -40
- atomicshop/dns.py +128 -12
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/const.py +38 -0
- atomicshop/etws/providers.py +21 -0
- atomicshop/etws/sessions.py +43 -0
- atomicshop/etws/trace.py +168 -0
- atomicshop/etws/traces/trace_dns.py +162 -0
- atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +222 -24
- atomicshop/file_io/docxs.py +35 -18
- atomicshop/file_io/file_io.py +35 -19
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +864 -293
- atomicshop/get_process_list.py +133 -0
- atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
- 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 -74
- 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 +257 -166
- atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
- atomicshop/monitor/change_monitor.py +96 -120
- atomicshop/monitor/checks/dns.py +139 -70
- atomicshop/monitor/checks/file.py +77 -0
- atomicshop/monitor/checks/network.py +81 -77
- atomicshop/monitor/checks/process_running.py +33 -34
- atomicshop/monitor/checks/url.py +94 -0
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +205 -0
- 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 -41
- atomicshop/process.py +63 -17
- atomicshop/process_poller/__init__.py +0 -0
- atomicshop/process_poller/pollers/__init__.py +0 -0
- atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
- atomicshop/process_poller/process_pool.py +207 -0
- atomicshop/process_poller/simple_process_pool.py +311 -0
- atomicshop/process_poller/tracer_base.py +45 -0
- atomicshop/process_poller/tracers/__init__.py +0 -0
- atomicshop/process_poller/tracers/event_log.py +46 -0
- atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/question_answer_engine.py +2 -2
- atomicshop/scheduling.py +24 -5
- atomicshop/sound.py +4 -2
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/startup/__init__.py +0 -0
- atomicshop/startup/win/__init__.py +0 -0
- atomicshop/startup/win/startup_folder.py +53 -0
- atomicshop/startup/win/task_scheduler.py +119 -0
- atomicshop/system_resource_monitor.py +61 -46
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/timer.py +30 -11
- atomicshop/urls.py +41 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +110 -25
- atomicshop/web_apis/__init__.py +0 -0
- 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/__init__.py +0 -0
- atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
- 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 +13 -9
- atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
- 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/factw/postgresql/firmware.py +4 -6
- atomicshop/wrappers/githubw.py +583 -51
- atomicshop/wrappers/loggingw/consts.py +49 -0
- atomicshop/wrappers/loggingw/filters.py +102 -0
- atomicshop/wrappers/loggingw/formatters.py +58 -71
- atomicshop/wrappers/loggingw/handlers.py +459 -40
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +1010 -178
- atomicshop/wrappers/loggingw/reading.py +344 -19
- atomicshop/wrappers/mongodbw/__init__.py +0 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
- 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 +81 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/psutilw/psutilw.py +9 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/console.py +34 -0
- atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
- atomicshop/wrappers/pywin32w/winshell.py +19 -0
- atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
- 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 +500 -173
- 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 +14 -9
- 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 +157 -0
- 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.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- 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/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/addons/process_list/compile.cmd +0 -2
- atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- 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/etw/dns_trace.py +0 -118
- atomicshop/etw/etw.py +0 -61
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -240
- atomicshop/monitor/checks/hash.py +0 -44
- atomicshop/monitor/checks/hash_checks/file.py +0 -55
- atomicshop/monitor/checks/hash_checks/url.py +0 -62
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -110
- atomicshop/process_poller.py +0 -237
- 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/loggingw/checks.py +0 -20
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.11.47.dist-info/RECORD +0 -251
- /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/mains → 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/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
- /atomicshop/{archiver → etws}/__init__.py +0 -0
- /atomicshop/{etw → etws/traces}/__init__.py +0 -0
- /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
- /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import queue
|
|
3
|
+
from ctypes.wintypes import ULONG
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from . import const
|
|
8
|
+
from ....etws import providers
|
|
9
|
+
|
|
10
|
+
class ETWSessionExists(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Convert the GUID string to a GUID structure
|
|
15
|
+
def _string_to_guid(guid_string):
|
|
16
|
+
guid_string = guid_string.strip('{}') # Remove curly braces
|
|
17
|
+
parts = guid_string.split('-')
|
|
18
|
+
return const.GUID(
|
|
19
|
+
Data1=int(parts[0], 16),
|
|
20
|
+
Data2=int(parts[1], 16),
|
|
21
|
+
Data3=int(parts[2], 16),
|
|
22
|
+
Data4=(ctypes.c_ubyte * 8)(*[
|
|
23
|
+
int(parts[3][i:i+2], 16) for i in range(0, 4, 2)
|
|
24
|
+
] + [
|
|
25
|
+
int(parts[4][i:i+2], 16) for i in range(0, 12, 2)
|
|
26
|
+
])
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Set up the ETW session
|
|
31
|
+
def start_etw_session(
|
|
32
|
+
session_name: str,
|
|
33
|
+
provider_guid_list: list = None,
|
|
34
|
+
provider_name_list: list = None,
|
|
35
|
+
verbosity_mode: int = 4,
|
|
36
|
+
maximum_buffers: int = 38
|
|
37
|
+
) -> const.TRACEHANDLE:
|
|
38
|
+
"""
|
|
39
|
+
Start an ETW session and enable the specified provider.
|
|
40
|
+
|
|
41
|
+
:param session_name: The name of the session to start.
|
|
42
|
+
:param provider_guid_list: The GUID list of the providers to enable.
|
|
43
|
+
:param provider_name_list: The name list of the providers to enable.
|
|
44
|
+
:param verbosity_mode: The verbosity level of the events to capture.
|
|
45
|
+
0 - Always: Capture all events. This is typically used for critical events that should always be logged.
|
|
46
|
+
1 - Critical: Capture critical events that indicate a severe problem.
|
|
47
|
+
2 - Error: Capture error events that indicate a problem but are not critical.
|
|
48
|
+
3 - Warning: Capture warning events that indicate a potential problem.
|
|
49
|
+
4 - Information: Capture informational events that are not indicative of problems.
|
|
50
|
+
5 - Verbose: Capture detailed trace events for diagnostic purposes.
|
|
51
|
+
:param maximum_buffers: The maximum number of buffers to use.
|
|
52
|
+
0 or 16: The default value of ETW class. If you put 0, it will be converted to 16 by default by ETW itself.
|
|
53
|
+
38: The maximum number of buffers that can be used.
|
|
54
|
+
|
|
55
|
+
Event Handling Capacity:
|
|
56
|
+
16 Buffers: With fewer buffers, the session can handle a smaller volume of events before needing to flush
|
|
57
|
+
the buffers to the log file or before a real-time consumer needs to process them. If the buffers fill up
|
|
58
|
+
quickly and cannot be processed in time, events might be lost.
|
|
59
|
+
38 Buffers: With more buffers, the session can handle a higher volume of events. This reduces the
|
|
60
|
+
likelihood of losing events in high-traffic scenarios because more events can be held in memory before
|
|
61
|
+
they need to be processed or written to a log file.
|
|
62
|
+
Performance Considerations:
|
|
63
|
+
16 Buffers: Requires less memory, but may be prone to event loss under heavy load if the buffers fill up
|
|
64
|
+
faster than they can be processed.
|
|
65
|
+
38 Buffers: Requires more memory, but can improve reliability in capturing all events under heavy load by
|
|
66
|
+
providing more buffer space. However, it can also increase the memory footprint of the application or
|
|
67
|
+
system running the ETW session.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
if not provider_guid_list and not provider_name_list:
|
|
71
|
+
raise ValueError("Either 'provider_guid_list' or 'provider_name_list' must be provided")
|
|
72
|
+
elif provider_guid_list and provider_name_list:
|
|
73
|
+
raise ValueError("Only one of 'provider_guid_list' or 'provider_name_list' must be provided")
|
|
74
|
+
|
|
75
|
+
if provider_name_list:
|
|
76
|
+
provider_guid_list = []
|
|
77
|
+
for provider_name in provider_name_list:
|
|
78
|
+
provider_guid_list.append(providers.get_provider_guid_by_name(provider_name))
|
|
79
|
+
|
|
80
|
+
# (1) Allocate a buffer large enough for EVENT_TRACE_PROPERTIES + space for 2 strings
|
|
81
|
+
# The typical approach is to allow enough space for:
|
|
82
|
+
# - The structure
|
|
83
|
+
# - The "LoggerName" string
|
|
84
|
+
# - The "LogFileName" string (even if we don't use it, we must leave offset space)
|
|
85
|
+
#
|
|
86
|
+
props_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
|
|
87
|
+
# Add space for 2 x 1024 wide-chars (one for LoggerName, one for LogFileName)
|
|
88
|
+
# Each wide char = ctypes.sizeof(ctypes.c_wchar).
|
|
89
|
+
props_size += (2 * 1024 * ctypes.sizeof(ctypes.c_wchar)) # for logger name
|
|
90
|
+
props_size += (2 * 1024 * ctypes.sizeof(ctypes.c_wchar)) # for log file name
|
|
91
|
+
|
|
92
|
+
properties_buffer = ctypes.create_string_buffer(props_size)
|
|
93
|
+
properties_ptr = ctypes.cast(properties_buffer, ctypes.POINTER(const.EVENT_TRACE_PROPERTIES))
|
|
94
|
+
props = properties_ptr.contents
|
|
95
|
+
|
|
96
|
+
# (2) Fill in basic fields
|
|
97
|
+
props.Wnode.BufferSize = props_size
|
|
98
|
+
props.Wnode.Flags = const.WNODE_FLAG_TRACED_GUID # 0x00020000
|
|
99
|
+
props.Wnode.ClientContext = 1 # QPC clock
|
|
100
|
+
props.BufferSize = 1024
|
|
101
|
+
props.MinimumBuffers = 1
|
|
102
|
+
props.MaximumBuffers = maximum_buffers
|
|
103
|
+
props.MaximumFileSize = 0
|
|
104
|
+
props.LogFileMode = const.EVENT_TRACE_REAL_TIME_MODE # real-time
|
|
105
|
+
props.FlushTimer = 1
|
|
106
|
+
props.EnableFlags = 0
|
|
107
|
+
|
|
108
|
+
# (3) Indicate where in this allocated buffer the strings should go
|
|
109
|
+
struct_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
|
|
110
|
+
props.LoggerNameOffset = struct_size
|
|
111
|
+
props.LogFileNameOffset = struct_size + (2 * 1024 * ctypes.sizeof(ctypes.c_wchar))
|
|
112
|
+
|
|
113
|
+
# (4) Copy the session name into the LoggerName space
|
|
114
|
+
logger_name_address = ctypes.addressof(properties_buffer) + props.LoggerNameOffset
|
|
115
|
+
session_name_wchar = ctypes.create_unicode_buffer(session_name)
|
|
116
|
+
ctypes.memmove(
|
|
117
|
+
logger_name_address,
|
|
118
|
+
session_name_wchar,
|
|
119
|
+
len(session_name) * ctypes.sizeof(ctypes.c_wchar)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# (5) Start the session
|
|
123
|
+
session_handle = const.TRACEHANDLE(0)
|
|
124
|
+
status = const.StartTrace(
|
|
125
|
+
ctypes.byref(session_handle),
|
|
126
|
+
session_name, # The session name as an LPCWSTR
|
|
127
|
+
properties_ptr # pointer to EVENT_TRACE_PROPERTIES
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if status != 0:
|
|
131
|
+
# 183 => ERROR_ALREADY_EXISTS
|
|
132
|
+
if status == 183:
|
|
133
|
+
raise ETWSessionExists(f"ETW session [{session_name}] already exists")
|
|
134
|
+
else:
|
|
135
|
+
raise Exception(f"StartTraceW failed with error code {status}")
|
|
136
|
+
|
|
137
|
+
# (6) If we have providers to enable, enable each
|
|
138
|
+
if provider_guid_list:
|
|
139
|
+
for guid_str in provider_guid_list:
|
|
140
|
+
guid_struct = _string_to_guid(guid_str)
|
|
141
|
+
|
|
142
|
+
# Typically you'd do something like:
|
|
143
|
+
# advapi32.EnableTraceEx2(TRACEHANDLE, PGUID, CONTROL_CODE, LEVEL, KW, KW, TIMEOUT, FILTER)
|
|
144
|
+
|
|
145
|
+
enable_status = const.EnableTraceEx2(
|
|
146
|
+
session_handle, # The session handle
|
|
147
|
+
ctypes.byref(guid_struct), # The provider GUID
|
|
148
|
+
const.EVENT_CONTROL_CODE_ENABLE_PROVIDER, # 1 => enable
|
|
149
|
+
verbosity_mode, # level
|
|
150
|
+
0xFFFFFFFFFFFFFFFF, # matchAnyKeyword
|
|
151
|
+
0, # matchAllKeyword
|
|
152
|
+
0, # timeout
|
|
153
|
+
None # enableParameters (optional)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if enable_status != 0:
|
|
157
|
+
raise Exception(
|
|
158
|
+
f"EnableTraceEx2 failed for provider {guid_str} (error={enable_status})"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
print(f"ETW session '{session_name}' started successfully.")
|
|
162
|
+
return session_handle
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# Function to stop and delete ETW session
|
|
166
|
+
def stop_and_delete_etw_session(session_name: str) -> tuple[bool, int]:
|
|
167
|
+
"""
|
|
168
|
+
Stop and delete ETW session.
|
|
169
|
+
|
|
170
|
+
:param session_name: The name of the session to stop and delete.
|
|
171
|
+
:return: A tuple containing a boolean indicating success and an integer status code.
|
|
172
|
+
True, 0: If the session was stopped and deleted successfully.
|
|
173
|
+
False, <status>: If the session could not be stopped and deleted and the status code, why it failed.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
session_name_unicode = ctypes.create_unicode_buffer(session_name)
|
|
177
|
+
properties_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES) + 1024 # Adjust buffer size if needed
|
|
178
|
+
properties = ctypes.create_string_buffer(properties_size)
|
|
179
|
+
|
|
180
|
+
trace_properties = ctypes.cast(properties, ctypes.POINTER(const.EVENT_TRACE_PROPERTIES)).contents
|
|
181
|
+
trace_properties.Wnode.BufferSize = properties_size
|
|
182
|
+
trace_properties.Wnode.Flags = const.WNODE_FLAG_TRACED_GUID
|
|
183
|
+
trace_properties.Wnode.Guid = const.GUID() # Ensure a GUID is provided if necessary
|
|
184
|
+
trace_properties.LoggerNameOffset = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
|
|
185
|
+
|
|
186
|
+
ctypes.memmove(ctypes.addressof(properties) + trace_properties.LoggerNameOffset,
|
|
187
|
+
session_name_unicode, ctypes.sizeof(session_name_unicode))
|
|
188
|
+
|
|
189
|
+
status = const.advapi32.ControlTraceW(
|
|
190
|
+
None, session_name_unicode, ctypes.byref(trace_properties), const.EVENT_TRACE_CONTROL_STOP)
|
|
191
|
+
|
|
192
|
+
if status != 0:
|
|
193
|
+
# print(f"Failed to stop and delete ETW session: {status}")
|
|
194
|
+
return False, status
|
|
195
|
+
else:
|
|
196
|
+
# print("ETW session stopped and deleted successfully.")
|
|
197
|
+
return True, status
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@const.EVENT_CALLBACK_TYPE
|
|
201
|
+
def _default_callback(
|
|
202
|
+
event_record_ptr
|
|
203
|
+
):
|
|
204
|
+
"""
|
|
205
|
+
This function will be called by Windows for every incoming ETW event.
|
|
206
|
+
'event_record_ptr' is a pointer to an EVENT_RECORD structure.
|
|
207
|
+
"""
|
|
208
|
+
# Convert pointer to a Python EVENT_RECORD object
|
|
209
|
+
event_record = event_record_ptr.contents
|
|
210
|
+
|
|
211
|
+
# Do something with event_record (e.g., parse via TDH)
|
|
212
|
+
print("Received an ETW event!", event_record.EventHeader.ProviderId)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def start_etw_consumer(
|
|
216
|
+
session_name: str,
|
|
217
|
+
record_queue: queue.Queue
|
|
218
|
+
):
|
|
219
|
+
"""
|
|
220
|
+
Attach to an existing real-time ETW session by 'session_name'
|
|
221
|
+
using the "new" EVENT_RECORD callback approach.
|
|
222
|
+
This call blocks until the ETW session is stopped or an error occurs.
|
|
223
|
+
"""
|
|
224
|
+
# 1) Create a closure callback that references the queue
|
|
225
|
+
# We define an inner function that sees 'record_queue' from outer scope.
|
|
226
|
+
# Then we wrap that in EVENT_CALLBACK_TYPE.
|
|
227
|
+
if record_queue is not None:
|
|
228
|
+
@const.EVENT_CALLBACK_TYPE
|
|
229
|
+
def _queue_callback(event_record_ptr):
|
|
230
|
+
event_record = event_record_ptr.contents
|
|
231
|
+
# # Example: put a simple dict with the provider ID & possibly more info
|
|
232
|
+
# record_queue.put({
|
|
233
|
+
# "provider_id": str(event_record.EventHeader.ProviderId),
|
|
234
|
+
# "process_id": event_record.EventHeader.ProcessId,
|
|
235
|
+
# "thread_id": event_record.EventHeader.ThreadId,
|
|
236
|
+
# # ... more fields or parse them with TdhGetEventInformation, etc.
|
|
237
|
+
# })
|
|
238
|
+
print("Received an ETW event!", event_record.EventHeader.ProviderId)
|
|
239
|
+
record_queue.put(event_record)
|
|
240
|
+
else:
|
|
241
|
+
_queue_callback = _default_callback
|
|
242
|
+
|
|
243
|
+
# Keep a reference to the callback so Python doesn't GC it.
|
|
244
|
+
start_etw_consumer._callback_ref = _queue_callback
|
|
245
|
+
|
|
246
|
+
# Prepare the EVENT_TRACE_LOGFILE structure
|
|
247
|
+
logfile = const.EVENT_TRACE_LOGFILE()
|
|
248
|
+
# You can also do: logfile.LoggerName = session_name
|
|
249
|
+
# If you assign session_name (a Python str), ctypes automatically converts it to a temporary c_wchar_p under the hood.
|
|
250
|
+
# logfile.LoggerName = ctypes.c_wchar_p(session_name)
|
|
251
|
+
logfile.LoggerName = session_name
|
|
252
|
+
logfile.LogFileName = None # Real-time, not from a file
|
|
253
|
+
logfile.ProcessTraceMode = (const.PROCESS_TRACE_MODE_REAL_TIME | const.PROCESS_TRACE_MODE_EVENT_RECORD)
|
|
254
|
+
|
|
255
|
+
# Point to our Python callback
|
|
256
|
+
logfile.EventRecordCallback = const.EVENT_RECORD_CALLBACK(_default_callback)
|
|
257
|
+
|
|
258
|
+
# Open the trace
|
|
259
|
+
trace_handle = const.OpenTrace(ctypes.byref(logfile))
|
|
260
|
+
if trace_handle == const.INVALID_PROCESSTRACE_HANDLE:
|
|
261
|
+
error_code = ctypes.get_last_error()
|
|
262
|
+
raise OSError(f"OpenTrace failed with error code: {error_code}")
|
|
263
|
+
|
|
264
|
+
# trace_handle is actually an unsigned long, but in 64-bit it's a 64-bit handle.
|
|
265
|
+
# We'll create an array of one handle for ProcessTrace:
|
|
266
|
+
trace_handle_array = (ctypes.c_uint64 * 1)(trace_handle)
|
|
267
|
+
|
|
268
|
+
# Blocking call - will not return until the session is stopped (or error).
|
|
269
|
+
status = const.ProcessTrace(
|
|
270
|
+
trace_handle_array,
|
|
271
|
+
1, # HandleCount = 1
|
|
272
|
+
None, # StartTime = None
|
|
273
|
+
None # EndTime = None
|
|
274
|
+
)
|
|
275
|
+
if status != 0:
|
|
276
|
+
raise OSError(f"ProcessTrace failed with error code: {status}")
|
|
277
|
+
|
|
278
|
+
print("ProcessTrace returned. ETW consumer finished.")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def start_etw_consumer_in_thread(
|
|
282
|
+
session_name: str,
|
|
283
|
+
record_queue: queue.Queue
|
|
284
|
+
):
|
|
285
|
+
"""
|
|
286
|
+
Start an ETW consumer in a separate thread.
|
|
287
|
+
"""
|
|
288
|
+
import threading
|
|
289
|
+
|
|
290
|
+
def _start_etw_consumer():
|
|
291
|
+
start_etw_consumer(session_name, record_queue)
|
|
292
|
+
|
|
293
|
+
thread = threading.Thread(target=_start_etw_consumer)
|
|
294
|
+
thread.start()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_all_providers(key_as: Literal['name', 'guid'] = 'name') -> dict:
|
|
298
|
+
"""
|
|
299
|
+
Get all ETW providers available on the system.
|
|
300
|
+
|
|
301
|
+
:param key_as: The key to use in the dictionary, either 'name' or 'guid'.
|
|
302
|
+
'name': The provider name is used as the key, the guid as the value.
|
|
303
|
+
'guid': The provider guid is used as the key, the name as the value.
|
|
304
|
+
:return: dict containing the provider name and GUID.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
if key_as not in ['name', 'guid']:
|
|
308
|
+
raise ValueError("key_as must be either 'name' or 'guid'")
|
|
309
|
+
|
|
310
|
+
providers_info_size = ULONG(0)
|
|
311
|
+
status = const.tdh.TdhEnumerateProviders(None, ctypes.byref(providers_info_size))
|
|
312
|
+
|
|
313
|
+
# Initial allocation
|
|
314
|
+
buffer = (ctypes.c_byte * providers_info_size.value)()
|
|
315
|
+
providers_info = ctypes.cast(buffer, ctypes.POINTER(const.PROVIDER_ENUMERATION_INFO))
|
|
316
|
+
|
|
317
|
+
# Loop to handle resizing
|
|
318
|
+
while True:
|
|
319
|
+
status = const.tdh.TdhEnumerateProviders(providers_info, ctypes.byref(providers_info_size))
|
|
320
|
+
|
|
321
|
+
if status == 0:
|
|
322
|
+
break
|
|
323
|
+
elif status == 0x8007007A: # ERROR_INSUFFICIENT_BUFFER
|
|
324
|
+
buffer = (ctypes.c_byte * providers_info_size.value)()
|
|
325
|
+
providers_info = ctypes.cast(buffer, ctypes.POINTER(const.PROVIDER_ENUMERATION_INFO))
|
|
326
|
+
else:
|
|
327
|
+
raise ctypes.WinError(status)
|
|
328
|
+
|
|
329
|
+
provider_count = providers_info.contents.NumberOfProviders
|
|
330
|
+
provider_array = ctypes.cast(
|
|
331
|
+
ctypes.addressof(providers_info.contents) + ctypes.sizeof(const.PROVIDER_ENUMERATION_INFO),
|
|
332
|
+
ctypes.POINTER(const.PROVIDER_INFORMATION * provider_count))
|
|
333
|
+
|
|
334
|
+
providers: dict = {}
|
|
335
|
+
for i in range(provider_count):
|
|
336
|
+
provider = provider_array.contents[i]
|
|
337
|
+
provider_name_offset = provider.ProviderNameOffset
|
|
338
|
+
provider_name_ptr = ctypes.cast(
|
|
339
|
+
ctypes.addressof(providers_info.contents) + provider_name_offset, ctypes.c_wchar_p)
|
|
340
|
+
provider_name = provider_name_ptr.value
|
|
341
|
+
provider_guid = uuid.UUID(bytes_le=bytes(provider.ProviderId))
|
|
342
|
+
provider_guid_string = str(provider_guid)
|
|
343
|
+
|
|
344
|
+
if key_as == 'name':
|
|
345
|
+
providers[provider_name] = provider_guid_string
|
|
346
|
+
elif key_as == 'guid':
|
|
347
|
+
providers[provider_guid_string] = provider_name
|
|
348
|
+
|
|
349
|
+
return providers
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def list_etw_sessions() -> list[dict]:
|
|
353
|
+
"""
|
|
354
|
+
List all running ETW sessions.
|
|
355
|
+
|
|
356
|
+
:return: A list of dictionaries containing the names of all running ETW sessions and their log files.
|
|
357
|
+
"""
|
|
358
|
+
# Create an array of EVENT_TRACE_PROPERTIES pointers
|
|
359
|
+
PropertiesArrayType = ctypes.POINTER(const.EVENT_TRACE_PROPERTIES) * const.MAXIMUM_LOGGERS
|
|
360
|
+
properties_array = PropertiesArrayType()
|
|
361
|
+
for i in range(const.MAXIMUM_LOGGERS):
|
|
362
|
+
properties = const.EVENT_TRACE_PROPERTIES()
|
|
363
|
+
properties.Wnode.BufferSize = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
|
|
364
|
+
properties_array[i] = ctypes.pointer(properties)
|
|
365
|
+
|
|
366
|
+
# Define the number of loggers variable
|
|
367
|
+
logger_count = ULONG(const.MAXIMUM_LOGGERS)
|
|
368
|
+
|
|
369
|
+
# Call QueryAllTraces
|
|
370
|
+
status = const.QueryAllTraces(properties_array, const.MAXIMUM_LOGGERS, ctypes.byref(logger_count))
|
|
371
|
+
if status != 0:
|
|
372
|
+
raise Exception(f"QueryAllTraces failed, error code: {status}")
|
|
373
|
+
|
|
374
|
+
# Extract session names
|
|
375
|
+
session_list: list = []
|
|
376
|
+
for i in range(logger_count.value):
|
|
377
|
+
logger_name = None
|
|
378
|
+
logfile_path = None
|
|
379
|
+
|
|
380
|
+
properties = properties_array[i].contents
|
|
381
|
+
if properties.LoggerNameOffset != 0:
|
|
382
|
+
logger_name_address = ctypes.addressof(properties) + properties.LoggerNameOffset
|
|
383
|
+
logger_name = ctypes.wstring_at(logger_name_address)
|
|
384
|
+
if properties.LogFileNameOffset != 0:
|
|
385
|
+
logfile_name_address = ctypes.addressof(properties) + properties.LogFileNameOffset
|
|
386
|
+
logfile_path = ctypes.wstring_at(logfile_name_address)
|
|
387
|
+
|
|
388
|
+
session_list.append({
|
|
389
|
+
'session_name': logger_name,
|
|
390
|
+
'log_file': logfile_path
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
return session_list
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import ctypes
|
|
3
|
+
import pefile
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_file_properties(file_path: str) -> dict:
|
|
7
|
+
"""
|
|
8
|
+
Retrieve file version properties using ctypes.
|
|
9
|
+
|
|
10
|
+
:param file_path: Full path to the file.
|
|
11
|
+
:return: Dictionary with file properties.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def query_value(name):
|
|
15
|
+
r = ctypes.c_void_p()
|
|
16
|
+
l = ctypes.c_uint()
|
|
17
|
+
ctypes.windll.version.VerQueryValueW(
|
|
18
|
+
res, f"\\StringFileInfo\\040904b0\\{name}", ctypes.byref(r), ctypes.byref(l))
|
|
19
|
+
return ctypes.wstring_at(r) if r.value else "N/A"
|
|
20
|
+
|
|
21
|
+
properties = {
|
|
22
|
+
"FileDescription": "N/A",
|
|
23
|
+
"FileVersion": "N/A",
|
|
24
|
+
"ProductName": "N/A",
|
|
25
|
+
"ProductVersion": "N/A",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if not os.path.isfile(file_path):
|
|
29
|
+
return properties
|
|
30
|
+
|
|
31
|
+
# Load version information
|
|
32
|
+
size = ctypes.windll.version.GetFileVersionInfoSizeW(file_path, None)
|
|
33
|
+
if size == 0:
|
|
34
|
+
return properties
|
|
35
|
+
|
|
36
|
+
res = ctypes.create_string_buffer(size)
|
|
37
|
+
ctypes.windll.version.GetFileVersionInfoW(file_path, None, size, res)
|
|
38
|
+
|
|
39
|
+
properties["FileDescription"] = query_value("FileDescription")
|
|
40
|
+
properties["FileVersion"] = query_value("FileVersion")
|
|
41
|
+
properties["ProductName"] = query_value("ProductName")
|
|
42
|
+
properties["ProductVersion"] = query_value("ProductVersion")
|
|
43
|
+
|
|
44
|
+
# Fallback to pefile if ctypes fails or returns, pefile is much slower but more reliable, so we only use it as a fallback.
|
|
45
|
+
if properties["FileDescription"] == "N/A" or properties["FileVersion"] == "N/A":
|
|
46
|
+
pe = pefile.PE(file_path)
|
|
47
|
+
version_info = pe.VS_FIXEDFILEINFO
|
|
48
|
+
if version_info:
|
|
49
|
+
# If version_info is a list, take the first valid entry
|
|
50
|
+
if isinstance(version_info, list):
|
|
51
|
+
version_info = version_info[0]
|
|
52
|
+
|
|
53
|
+
properties["FileVersion"] = f"{version_info.FileVersionMS >> 16}.{version_info.FileVersionMS & 0xFFFF}.{version_info.FileVersionLS >> 16}.{version_info.FileVersionLS & 0xFFFF}"
|
|
54
|
+
# Attempt to get additional metadata
|
|
55
|
+
for entry in pe.FileInfo or []:
|
|
56
|
+
for structure in entry:
|
|
57
|
+
if hasattr(structure, "StringTable"):
|
|
58
|
+
for string_table in structure.StringTable:
|
|
59
|
+
for key, value in string_table.entries.items():
|
|
60
|
+
if key == b"FileDescription":
|
|
61
|
+
properties["FileDescription"] = value.decode("utf-8", errors="ignore")
|
|
62
|
+
elif key == b"ProductName":
|
|
63
|
+
properties["ProductName"] = value.decode("utf-8", errors="ignore")
|
|
64
|
+
elif key == b"ProductVersion":
|
|
65
|
+
properties["ProductVersion"] = value.decode("utf-8", errors="ignore")
|
|
66
|
+
|
|
67
|
+
return properties
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import sys
|
|
3
2
|
import argparse
|
|
4
3
|
|
|
4
|
+
from dkarchiver.arch_wrappers import sevenz_app_w
|
|
5
|
+
|
|
5
6
|
from .base import msi
|
|
6
7
|
from . import base, tables, cabs
|
|
7
8
|
from ... import olefilew
|
|
8
9
|
from ....print_api import print_api
|
|
9
|
-
from ....archiver import sevenz_app_w
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
# Directory names.
|
|
@@ -80,26 +80,28 @@ def extract_files_from_msi_main(
|
|
|
80
80
|
|
|
81
81
|
# If not provided, raise an error.
|
|
82
82
|
if not msi_filepath:
|
|
83
|
-
print_api("The path to the MSI file is not provided.", color="red")
|
|
84
|
-
|
|
83
|
+
print_api("The path to the MSI file is not provided with [-m].", color="red")
|
|
84
|
+
return 1
|
|
85
85
|
if not main_out_directory:
|
|
86
|
-
print_api("The main output directory is not provided.", color="red")
|
|
86
|
+
print_api("The main output directory is not provided with [-o].", color="red")
|
|
87
|
+
return 1
|
|
87
88
|
if not sevenz_path:
|
|
88
89
|
print_api(
|
|
89
90
|
"The path to the 7z executable is not provided. Assuming 7z is in the PATH environment variable.",
|
|
90
91
|
color="yellow")
|
|
92
|
+
sevenz_path = "7z"
|
|
91
93
|
|
|
92
94
|
if not sevenz_app_w.is_path_contains_7z_executable(sevenz_path):
|
|
93
95
|
print_api("The path to 7z does not contain 7z executable", color="red")
|
|
94
|
-
|
|
96
|
+
return 1
|
|
95
97
|
|
|
96
|
-
if not os.path.isfile(sevenz_path):
|
|
98
|
+
if sevenz_path != "7z" and not os.path.isfile(sevenz_path):
|
|
97
99
|
print_api("The path to 7z executable doesn't exist.", color="red")
|
|
98
|
-
|
|
100
|
+
return 1
|
|
99
101
|
|
|
100
102
|
if not sevenz_app_w.is_executable_a_7z(sevenz_path):
|
|
101
103
|
print_api("Provided 7z executable is not 7z.", color="red")
|
|
102
|
-
|
|
104
|
+
return 1
|
|
103
105
|
|
|
104
106
|
# Create the main output directory.
|
|
105
107
|
os.makedirs(main_out_directory, exist_ok=True)
|
|
@@ -134,3 +136,5 @@ def extract_files_from_msi_main(
|
|
|
134
136
|
|
|
135
137
|
# Extract OLE metadata from the MSI file.
|
|
136
138
|
olefilew.extract_ole_metadata(msi_filepath, os.path.join(embedded_files_directory, OLE_METADATA))
|
|
139
|
+
|
|
140
|
+
return 0
|
|
@@ -417,3 +417,38 @@ def get_directory_table_info(db_handle):
|
|
|
417
417
|
msi.MsiCloseHandle(view_handle)
|
|
418
418
|
|
|
419
419
|
return directory_info
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _get_stream_table_info(db_handle):
|
|
423
|
+
"""
|
|
424
|
+
Get stream table info.
|
|
425
|
+
Basically this function gets all the file names and their binaries from the _Streams table.
|
|
426
|
+
All the above functions already do this in a more structured way.
|
|
427
|
+
There is nothing more in this function that you will find, unless there is a file that will not be in other tables,
|
|
428
|
+
which is very unlikely.
|
|
429
|
+
|
|
430
|
+
The only thing that may be of interest is the '\x05SummaryInformation' stream, which is a special stream that
|
|
431
|
+
contains information about the MSI package. But we already use the 'wrappers.olefilew.extract_ole_metadata'
|
|
432
|
+
function to get this information in the parsed way.
|
|
433
|
+
:param db_handle:
|
|
434
|
+
:return:
|
|
435
|
+
"""
|
|
436
|
+
query = "SELECT `Name`, `Data` FROM `_Streams`"
|
|
437
|
+
view_handle = base.create_open_execute_view_handle(db_handle, query)
|
|
438
|
+
|
|
439
|
+
stream_info = {}
|
|
440
|
+
|
|
441
|
+
while True:
|
|
442
|
+
record_handle = base.create_fetch_record_from_view_handle(view_handle)
|
|
443
|
+
if not record_handle:
|
|
444
|
+
break
|
|
445
|
+
|
|
446
|
+
stream_name = base.get_table_field_data_from_record(record_handle, field_index=1, data_type='stringw')
|
|
447
|
+
stream_data = base.get_table_field_data_from_record(record_handle, field_index=2, data_type='stream')
|
|
448
|
+
|
|
449
|
+
stream_info[stream_name] = stream_data
|
|
450
|
+
|
|
451
|
+
msi.MsiCloseHandle(record_handle)
|
|
452
|
+
msi.MsiCloseHandle(view_handle)
|
|
453
|
+
|
|
454
|
+
return stream_info
|