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.
Files changed (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
atomicshop/etws/trace.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import queue
2
2
  import sys
3
- import time
3
+ import multiprocessing.managers
4
+ from datetime import datetime
4
5
 
5
6
  # Import FireEye Event Tracing library.
6
7
  import etw
@@ -8,27 +9,25 @@ import etw
8
9
  from ..print_api import print_api
9
10
  from . import sessions
10
11
  from ..process_poller import simple_process_pool
11
- from ..wrappers.psutilw import psutilw
12
-
13
-
14
- WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
15
- WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
16
12
 
17
13
 
18
14
  class EventTrace(etw.ETW):
19
15
  def __init__(
20
16
  self,
21
17
  providers: list,
22
- event_callback=None,
18
+ event_callback: callable = None,
23
19
  event_id_filters: list = None,
24
20
  session_name: str = None,
25
21
  close_existing_session_name: bool = True,
26
- enable_process_poller: bool = False
22
+ enable_process_poller: bool = False,
23
+ process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = None
27
24
  ):
28
25
  """
29
26
  :param providers: List of tuples with provider name and provider GUID.
30
27
  tuple[0] = provider name
31
28
  tuple[1] = provider GUID
29
+
30
+ Example: [('Microsoft-Windows-DNS-Client', '{1c95126e-7ee8-4e23-86b2-6e7e4a5a8e9b}')]
32
31
  :param event_callback: Reference to the callable callback function that will be called for each occurring event.
33
32
  :param event_id_filters: List of event IDs that we want to filter. If not provided, all events will be returned.
34
33
  The default in the 'etw.ETW' method is 'None'.
@@ -37,6 +36,13 @@ class EventTrace(etw.ETW):
37
36
  :param enable_process_poller: Boolean to enable process poller. Gets the process PID, Name and CommandLine.
38
37
  Since the DNS events doesn't contain the process name and command line, only PID.
39
38
  Then DNS events will be enriched with the process name and command line from the process poller.
39
+ :param process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy,
40
+ multiprocessing shared dict proxy that contains current processes.
41
+ Check the 'atomicshop\process_poller\simple_process_pool.py' SimpleProcessPool class for more information.
42
+
43
+ If None, the process poller will create a new shared dict proxy.
44
+ If provided, then the provided shared dict proxy will be used.
45
+ Off course valid only if 'enable_process_poller' is True.
40
46
 
41
47
  ------------------------------------------
42
48
 
@@ -60,6 +66,7 @@ class EventTrace(etw.ETW):
60
66
  self.event_queue = queue.Queue()
61
67
  self.close_existing_session_name: bool = close_existing_session_name
62
68
  self.enable_process_poller: bool = enable_process_poller
69
+ self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
63
70
 
64
71
  # If no callback function is provided, we will use the default one, which will put the event in the queue.
65
72
  if not event_callback:
@@ -73,8 +80,16 @@ class EventTrace(etw.ETW):
73
80
  for provider in providers:
74
81
  etw_format_providers.append(etw.ProviderInfo(provider[0], etw.GUID(provider[1])))
75
82
 
83
+ self.self_hosted_poller: bool = False
76
84
  if self.enable_process_poller:
77
- self.process_poller = simple_process_pool.SimpleProcessPool()
85
+ if self.process_pool_shared_dict_proxy is None:
86
+ self.self_hosted_poller = True
87
+ self.process_poller = simple_process_pool.SimpleProcessPool()
88
+ self.multiprocessing_manager: multiprocessing.managers.SyncManager = multiprocessing.Manager()
89
+ self.process_pool_shared_dict_proxy = self.multiprocessing_manager.dict()
90
+
91
+ self.pid_process_converter = simple_process_pool.PidProcessConverter(
92
+ process_pool_shared_dict_proxy=self.process_pool_shared_dict_proxy)
78
93
 
79
94
  super().__init__(
80
95
  providers=etw_format_providers, event_callback=function_callable, event_id_filters=event_id_filters,
@@ -82,7 +97,7 @@ class EventTrace(etw.ETW):
82
97
  )
83
98
 
84
99
  def start(self):
85
- if self.enable_process_poller:
100
+ if self.enable_process_poller and self.self_hosted_poller:
86
101
  self.process_poller.start()
87
102
 
88
103
  # Check if the session name already exists.
@@ -107,9 +122,11 @@ class EventTrace(etw.ETW):
107
122
  def stop(self):
108
123
  super().stop()
109
124
 
110
- if self.enable_process_poller:
125
+ if self.self_hosted_poller:
111
126
  self.process_poller.stop()
112
127
 
128
+ self.multiprocessing_manager.shutdown()
129
+
113
130
  def emit(self):
114
131
  """
115
132
  The Function will return the next event from the queue.
@@ -127,44 +144,25 @@ class EventTrace(etw.ETW):
127
144
  :return: etw event object.
128
145
  """
129
146
 
130
- # Get the processes first, since we need the process name and command line.
131
- # If they're not ready, we will get just pids from DNS tracing.
132
- if self.enable_process_poller:
133
- self._get_processes_from_poller()
134
-
135
147
  event: tuple = self.event_queue.get()
136
148
 
149
+ current_datetime = datetime.now()
150
+ readable_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S.%f')
151
+
137
152
  event_dict: dict = {
138
153
  'EventId': event[0],
139
154
  'EventHeader': event[1],
140
- 'pid': event[1]['EventHeader']['ProcessId']
155
+ 'timestamp': readable_time
141
156
  }
142
157
 
158
+ if 'ProcessId' not in event[1]:
159
+ event_dict['pid'] = event[1]['EventHeader']['ProcessId']
160
+ else:
161
+ event_dict['pid'] = event[1]['ProcessId']
162
+
143
163
  if self.enable_process_poller:
144
- processes = self.process_poller.get_processes()
145
- if event_dict['pid'] not in processes:
146
- counter = 0
147
- while counter < WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
148
- processes = self.process_poller.get_processes()
149
- if event_dict['pid'] not in processes:
150
- time.sleep(0.1)
151
- counter += 1
152
- else:
153
- break
154
-
155
- if counter == WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
156
- print_api(f"Error: Couldn't get the process name for PID: {event_dict['pid']}.", color='red')
157
-
158
- event_dict = psutilw.cross_single_connection_with_processes(event_dict, processes)
164
+ process_info: dict = self.pid_process_converter.get_process_by_pid(event_dict['pid'])
165
+ event_dict['name'] = process_info['name']
166
+ event_dict['cmdline'] = process_info['cmdline']
159
167
 
160
168
  return event_dict
161
-
162
- def _get_processes_from_poller(self):
163
- processes: dict = {}
164
- while not processes:
165
- processes = self.process_poller.get_processes()
166
-
167
- if isinstance(processes, BaseException):
168
- raise processes
169
-
170
- return processes
@@ -1,6 +1,8 @@
1
+ import multiprocessing.managers
2
+
1
3
  from .. import trace, const
2
4
  from ...basics import dicts
3
- from ... import dns
5
+ from ... import dns, ip_addresses
4
6
 
5
7
 
6
8
  ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopDnsTrace'
@@ -9,9 +11,6 @@ PROVIDER_NAME: str = const.ETW_DNS['provider_name']
9
11
  PROVIDER_GUID: str = const.ETW_DNS['provider_guid']
10
12
  REQUEST_RESP_EVENT_ID: int = const.ETW_DNS['event_ids']['dns_request_response']
11
13
 
12
- WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
13
- WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
14
-
15
14
 
16
15
  class DnsRequestResponseTrace:
17
16
  """DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008."""
@@ -20,7 +19,8 @@ class DnsRequestResponseTrace:
20
19
  attrs: list = None,
21
20
  session_name: str = None,
22
21
  close_existing_session_name: bool = True,
23
- skip_record_list: list = None
22
+ skip_record_list: list = None,
23
+ process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = None
24
24
  ):
25
25
  """
26
26
  :param attrs: List of attributes to return. If None, all attributes will be returned.
@@ -32,6 +32,13 @@ class DnsRequestResponseTrace:
32
32
  created. Instead, the existing session will be used. If there is a buffer from the previous session,
33
33
  you will get the events from the buffer.
34
34
  :param skip_record_list: List of DNS Records to skip emitting. Example: ['PTR', 'SRV']
35
+ :param process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy, multiprocessing shared dict proxy
36
+ that contains current processes.
37
+ Check the 'atomicshop\process_poller\simple_process_pool.py' SimpleProcessPool class for more information.
38
+
39
+ For this specific class it means that you can run the process poller outside of this class and pass the
40
+ 'process_pool_shared_dict_proxy' to this class. Then you can get the process name and command line for
41
+ the DNS events from the 'process_pool_shared_dict_proxy' and use it also in other classes.
35
42
 
36
43
  -------------------------------------------------
37
44
 
@@ -40,7 +47,7 @@ class DnsRequestResponseTrace:
40
47
 
41
48
 
42
49
  dns_trace_w = dns_trace.DnsTrace(
43
- attrs=['pid', 'name', 'cmdline', 'domain', 'query_type'],
50
+ attrs=['pid', 'name', 'cmdline', 'query', 'query_type'],
44
51
  session_name='MyDnsTrace',
45
52
  close_existing_session_name=True,
46
53
  enable_process_poller=True
@@ -53,6 +60,7 @@ class DnsRequestResponseTrace:
53
60
  """
54
61
 
55
62
  self.attrs = attrs
63
+ self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
56
64
 
57
65
  if skip_record_list:
58
66
  self.skip_record_list: list = skip_record_list
@@ -68,7 +76,8 @@ class DnsRequestResponseTrace:
68
76
  event_id_filters=[REQUEST_RESP_EVENT_ID],
69
77
  session_name=session_name,
70
78
  close_existing_session_name=close_existing_session_name,
71
- enable_process_poller=True
79
+ enable_process_poller=True,
80
+ process_pool_shared_dict_proxy=self.process_pool_shared_dict_proxy
72
81
  )
73
82
 
74
83
  def start(self):
@@ -90,13 +99,52 @@ class DnsRequestResponseTrace:
90
99
  :return: Dictionary with the event data.
91
100
  """
92
101
 
102
+ # Get the event from ETW as is.
93
103
  event = self.event_trace.emit()
94
104
 
105
+ # Get the raw query results string from the event.
106
+ query_results: str = event['EventHeader']['QueryResults']
107
+
108
+ if query_results != '':
109
+ query_results_list: list = query_results.split(';')
110
+
111
+ addresses_ips: list = list()
112
+ addresses_cnames: list = list()
113
+ for query_result in query_results_list:
114
+ # If there is a type in the query result, it means it is a cname (domain).
115
+ if 'type' in query_result:
116
+ query_result = query_result.split(' ')[-1]
117
+
118
+ # But we'll still make sure that the query result is an IP address or not.
119
+ if ip_addresses.is_ip_address(query_result):
120
+ addresses_ips.append(query_result)
121
+ # If it is not empty, then it is a cname.
122
+ elif query_result != '':
123
+ addresses_cnames.append(query_result)
124
+ # if the query results are empty, then we'll just set the addresses to empty lists.
125
+ else:
126
+ addresses_ips: list = list()
127
+ addresses_cnames: list = list()
128
+
129
+ status_id: str = str(event['EventHeader']['QueryStatus'])
130
+
131
+ # Getting the 'QueryStatus' key. If DNS Query Status is '0' then it was executed successfully.
132
+ # And if not, it means there was an error. The 'QueryStatus' indicate what number of an error it is.
133
+ if status_id == '0':
134
+ status = 'Success'
135
+ else:
136
+ status = 'Error'
137
+
95
138
  event_dict: dict = {
139
+ 'timestamp': event['timestamp'],
96
140
  'event_id': event['EventId'],
97
- 'domain': event['EventHeader']['QueryName'],
141
+ 'query': event['EventHeader']['QueryName'],
98
142
  'query_type_id': str(event['EventHeader']['QueryType']),
99
143
  'query_type': dns.TYPES_DICT[str(event['EventHeader']['QueryType'])],
144
+ 'result_ips': ','.join(addresses_ips),
145
+ 'result_cnames': ','.join(addresses_cnames),
146
+ 'status_id': status_id,
147
+ 'status': status,
100
148
  'pid': event['pid'],
101
149
  'name': event['name'],
102
150
  'cmdline': event['cmdline']
@@ -107,42 +155,6 @@ class DnsRequestResponseTrace:
107
155
  if event_dict['query_type'] in self.skip_record_list:
108
156
  return self.emit()
109
157
 
110
- # Defining list if ips and other answers, which aren't IPs.
111
- list_of_ips = list()
112
- list_of_other_domains = list()
113
- # Parse DNS results, only if 'QueryResults' key isn't empty, since many of the events are, mostly due errors.
114
- if event['EventHeader']['QueryResults']:
115
- # 'QueryResults' key contains a string with all the 'Answers' divided by type and ';' character.
116
- # Basically, we can parse each type out of string, but we need only IPs and other answers.
117
- list_of_parameters = event['EventHeader']['QueryResults'].split(';')
118
-
119
- # Iterating through all the parameters that we got from 'QueryResults' key.
120
- for parameter in list_of_parameters:
121
- # If 'type' string is present it means that entry is a domain;
122
- if 'type' in parameter:
123
- # Remove the 'type' string and get the domain name.
124
- current_iteration_parameter = parameter.rsplit(' ', maxsplit=1)[1]
125
- # Add the variable to the list of other answers.
126
- list_of_other_domains.append(current_iteration_parameter)
127
- # If 'type' string is not present it means that entry is an IP.
128
- else:
129
- # Sometimes the last parameter in the 'QueryResults' key after ';' character will be empty, skip it.
130
- if parameter:
131
- list_of_ips.append(parameter)
132
-
133
- event_dict['ips'] = list_of_ips
134
- event_dict['other_domains'] = list_of_other_domains
135
-
136
- # Getting the 'QueryStatus' key.
137
- event_dict['status_id'] = event['EventHeader']['QueryStatus']
138
-
139
- # Getting the 'QueryStatus' key. If DNS Query Status is '0' then it was executed successfully.
140
- # And if not, it means there was an error. The 'QueryStatus' indicate what number of an error it is.
141
- if event['EventHeader']['QueryStatus'] == '0':
142
- event_dict['status'] = 'Success'
143
- else:
144
- event_dict['status'] = 'Error'
145
-
146
158
  if self.attrs:
147
159
  event_dict = dicts.reorder_keys(
148
160
  event_dict, self.attrs, skip_keys_not_in_list=True)
@@ -0,0 +1,130 @@
1
+ import multiprocessing.managers
2
+
3
+ from .. import trace, providers
4
+ from ...basics import dicts
5
+
6
+
7
+ ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopTcpTrace'
8
+
9
+ PROVIDER_NAME: str = "Microsoft-Windows-TCPIP"
10
+ PROVIDER_GUID: str = '{' + providers.get_provider_guid_by_name(PROVIDER_NAME) + '}'
11
+ REQUEST_RESP_EVENT_ID: int = 1033
12
+
13
+
14
+ class TcpIpNewConnectionsTrace:
15
+ """
16
+ TcpIpNewConnectionsTrace class use to trace new connection events from Windows Event Tracing:
17
+ Provider: Microsoft-Windows-TCPIP
18
+ EventId: 1033
19
+ """
20
+ def __init__(
21
+ self,
22
+ attrs: list = None,
23
+ session_name: str = None,
24
+ close_existing_session_name: bool = True,
25
+ process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = None
26
+ ):
27
+ """
28
+ :param attrs: List of attributes to return. If None, all attributes will be returned.
29
+ :param session_name: The name of the session to create. If not provided, a UUID will be generated.
30
+ :param close_existing_session_name: Boolean to close existing session names.
31
+ True: if ETW session with 'session_name' exists, you will be notified and the session will be closed.
32
+ Then the new session with this name will be created.
33
+ False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
34
+ created. Instead, the existing session will be used. If there is a buffer from the previous session,
35
+ you will get the events from the buffer.
36
+ :param process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy, multiprocessing shared dict proxy
37
+ that contains current processes.
38
+ Check the 'atomicshop\process_poller\simple_process_pool.py' SimpleProcessPool class for more information.
39
+
40
+ For this specific class it means that you can run the process poller outside of this class and pass the
41
+ 'process_pool_shared_dict_proxy' to this class. Then you can get the process name and command line for
42
+ the DNS events from the 'process_pool_shared_dict_proxy' and use it also in other classes.
43
+
44
+ -------------------------------------------------
45
+
46
+ Usage Example:
47
+ from atomicshop.etw import tcp_trace
48
+
49
+
50
+ tcp_trace_w = tcp_trace.TcpIpNewConnectionsTrace(
51
+ attrs=['pid', 'name', 'cmdline', 'domain', 'query_type'],
52
+ session_name='MyTcpTrace',
53
+ close_existing_session_name=True,
54
+ enable_process_poller=True
55
+ )
56
+ tcp_trace_w.start()
57
+ while True:
58
+ tcp_dict = tcp_trace_w.emit()
59
+ print(tcp_dict)
60
+ tcp_trace_w.stop()
61
+ """
62
+
63
+ self.attrs = attrs
64
+ self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
65
+
66
+ if not session_name:
67
+ session_name = ETW_DEFAULT_SESSION_NAME
68
+
69
+ self.event_trace = trace.EventTrace(
70
+ providers=[(PROVIDER_NAME, PROVIDER_GUID)],
71
+ # lambda x: self.event_queue.put(x),
72
+ event_id_filters=[REQUEST_RESP_EVENT_ID],
73
+ session_name=session_name,
74
+ close_existing_session_name=close_existing_session_name,
75
+ enable_process_poller=True,
76
+ process_pool_shared_dict_proxy=self.process_pool_shared_dict_proxy
77
+ )
78
+
79
+ def start(self):
80
+ self.event_trace.start()
81
+
82
+ def stop(self):
83
+ self.event_trace.stop()
84
+
85
+ def emit(self):
86
+ """
87
+ Function that will return the next event from the queue.
88
+ The queue is blocking, so if there is no event in the queue, the function will wait until there is one.
89
+
90
+ Usage Example:
91
+ while True:
92
+ tcp_dict = tcp_trace.emit()
93
+ print(tcp_dict)
94
+
95
+ :return: Dictionary with the event data.
96
+ """
97
+
98
+ # Get the event from ETW as is.
99
+ event = self.event_trace.emit()
100
+
101
+ local_address_port: str = event['EventHeader']['LocalAddress']
102
+ remote_address_port: str = event['EventHeader']['RemoteAddress']
103
+
104
+ if 'ffff' in local_address_port:
105
+ pass
106
+
107
+ local_address, local_port = local_address_port.rsplit(':', 1)
108
+ local_address = local_address.replace('[', '').replace(']', '')
109
+
110
+ remote_address, remote_port = remote_address_port.rsplit(':', 1)
111
+ remote_address = remote_address.replace('[', '').replace(']', '')
112
+
113
+ event_dict: dict = {
114
+ 'timestamp': event['timestamp'],
115
+ 'event_id': event['EventId'],
116
+ 'local_ip': local_address,
117
+ 'local_port': local_port,
118
+ 'remote_ip': remote_address,
119
+ 'remote_port': remote_port,
120
+ 'status': event['EventHeader']['Status'],
121
+ 'pid': event['pid'],
122
+ 'name': event['name'],
123
+ 'cmdline': event['cmdline']
124
+ }
125
+
126
+ if self.attrs:
127
+ event_dict = dicts.reorder_keys(
128
+ event_dict, self.attrs, skip_keys_not_in_list=True)
129
+
130
+ return event_dict
@@ -2,11 +2,10 @@ import csv
2
2
  import io
3
3
  from typing import Tuple, List
4
4
 
5
- from .file_io import read_file_decorator
6
5
  from . import file_io
7
6
 
8
7
 
9
- @read_file_decorator
8
+ @file_io.read_file_decorator
10
9
  def read_csv_to_list_of_dicts_by_header(
11
10
  file_path: str,
12
11
  file_mode: str = 'r',
@@ -53,7 +52,7 @@ def read_csv_to_list_of_dicts_by_header(
53
52
  return csv_list, header
54
53
 
55
54
 
56
- @read_file_decorator
55
+ @file_io.read_file_decorator
57
56
  def read_csv_to_list_of_lists(
58
57
  file_path: str,
59
58
  file_mode: str = 'r',
@@ -103,7 +102,8 @@ def read_csv_to_list_of_lists(
103
102
  def write_list_to_csv(
104
103
  content_list: list,
105
104
  file_path: str,
106
- mode: str = 'w'
105
+ mode: str = 'w',
106
+ encoding: str = None
107
107
  ) -> None:
108
108
  """
109
109
  This function got dual purpose:
@@ -115,10 +115,12 @@ def write_list_to_csv(
115
115
  :param content_list: List object that each iteration contains dictionary with same keys and different values.
116
116
  :param file_path: Full file path to CSV file.
117
117
  :param mode: String, file writing mode. Default is 'w'.
118
+ :param encoding: String, encoding of the file. Default is 'None'.
119
+ Example: 'utf-8', 'utf-16', 'cp1252'.
118
120
  :return: None.
119
121
  """
120
122
 
121
- with open(file_path, mode=mode, newline='') as csv_file:
123
+ with open(file_path, mode=mode, newline='', encoding=encoding) as csv_file:
122
124
  if len(content_list) > 0 and isinstance(content_list[0], dict):
123
125
  # Treat the list as list of dictionaries.
124
126
  header = content_list[0].keys()
@@ -254,3 +256,23 @@ def escape_csv_line_to_list(csv_line: list) -> list:
254
256
  result_csv_entries.append(escape_csv_value(entry))
255
257
 
256
258
  return result_csv_entries
259
+
260
+
261
+ def get_number_of_cells_in_string_line(line: str) -> int:
262
+ """
263
+ Function to get number of cells in CSV line.
264
+
265
+ :param line: String, line of CSV file.
266
+ :return: int, number of cells in the line.
267
+ """
268
+
269
+ # Create CSV reader from 'input_file'. By default, the first row will be the header if 'fieldnames' is None.
270
+ csv_reader = csv.reader([line])
271
+
272
+ # Get the first row of the CSV file.
273
+ csv_list = list(csv_reader)
274
+
275
+ # Get the number of cells in the first row.
276
+ number_of_cells = len(csv_list[0])
277
+
278
+ return number_of_cells
@@ -12,7 +12,7 @@ def get_hyperlinks(docx_path):
12
12
  :return: list of strings, hyperlinks.
13
13
  """
14
14
 
15
- hyperlinks: list = list()
15
+ hyperlinks: list[dict] = list()
16
16
 
17
17
  try:
18
18
  doc = Document(docx_path)
@@ -26,7 +26,19 @@ def get_hyperlinks(docx_path):
26
26
  if not paragraph.hyperlinks:
27
27
  continue
28
28
  for hyperlink in paragraph.hyperlinks:
29
- hyperlinks.append(hyperlink.address)
29
+ # Hyperlinks are stored in docx (document.xml) without the fragment part.
30
+ # Fragment is the anchor of the link, for example: 'https://www.example.com#anchor'.
31
+ # So the hyperlink.address is stored as 'https://www.example.com'.
32
+ # And the fragment is stored in the hyperlink.fragment as 'anchor'.
33
+ # For the full hyperlink, we need to concatenate the address and the fragment.
34
+ # If there is no anchor in the link the fragment will be empty string ('').
35
+ # Basically, we don't need to add the fragment to the hyperlink if it's empty, we can just use the url.
36
+ # if hyperlink.fragment:
37
+ # hyperlinks.append(hyperlink.address + "#" + hyperlink.fragment)
38
+ hyperlinks.append({
39
+ 'url': hyperlink.url,
40
+ 'text': hyperlink.text
41
+ })
30
42
 
31
43
  return hyperlinks
32
44
 
@@ -66,24 +78,27 @@ def search_for_hyperlink_in_files(directory_path: str, hyperlink: str, relative_
66
78
  raise NotADirectoryError(f"Directory doesn't exist: {directory_path}")
67
79
 
68
80
  # Get all the docx files in the specified directory.
69
- files = filesystem.get_file_paths_from_directory(
70
- directory_path, file_name_check_pattern="*\.docx",
81
+ files = filesystem.get_paths_from_directory(
82
+ directory_path, get_file=True, file_name_check_pattern="*.docx",
71
83
  add_relative_directory=True, relative_file_name_as_directory=True)
72
84
 
73
- found_in_files: list = list()
85
+ found_in_files: list[dict] = list()
74
86
  for file_path in files:
75
- hyperlinks = get_hyperlinks(file_path['file_path'])
76
- if hyperlink in hyperlinks:
77
- found_in_files.append(file_path)
87
+ doc_hyperlinks = get_hyperlinks(file_path.path)
88
+ for doc_link in doc_hyperlinks:
89
+ if hyperlink in doc_link['url']:
90
+ if relative_paths:
91
+ path: str = file_path.relative_dir
92
+ else:
93
+ path: str = file_path.path
78
94
 
79
- if relative_paths:
80
- result_list = list()
81
- for found_file in found_in_files:
82
- result_list.append(found_file['relative_dir'])
83
- else:
84
- result_list = found_in_files
95
+ found_in_files.append({
96
+ 'path':path,
97
+ 'link':doc_link['url'],
98
+ 'text':doc_link['text']
99
+ })
85
100
 
86
- return result_list
101
+ return found_in_files
87
102
 
88
103
 
89
104
  def search_for_hyperlink_in_files_interface_main(script_directory: str = None):
@@ -126,10 +141,12 @@ def search_for_hyperlink_in_files_interface_main(script_directory: str = None):
126
141
  found_in_files = search_for_hyperlink_in_files(
127
142
  config['directory_path'], config['hyperlink'], relative_paths=config['relative_paths'])
128
143
 
129
- print_api(f"Found in [{len(found_in_files)}] files:", color="blue")
144
+ print_api(f"Found [{len(found_in_files)}] links:", color="blue")
130
145
 
131
146
  for index, found_file in enumerate(found_in_files):
132
147
  print_api(f"[{index+1}]", print_end="", color="green")
133
- print_api(f" {found_file}")
148
+ print_api(f" {found_file['path']}")
149
+ print_api(f" {found_file['link']}", color="cyan")
150
+ print_api(f" {found_file['text']}", color="orange")
134
151
 
135
152
  input('[*] Press [Enter] to exit...')