atomicshop 2.14.1__py3-none-any.whl → 2.14.3__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.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

Files changed (28) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/etws/trace.py +5 -21
  3. atomicshop/etws/traces/trace_dns.py +17 -14
  4. atomicshop/etws/traces/trace_sysmon_process_creation.py +34 -22
  5. atomicshop/get_process_list.py +133 -0
  6. atomicshop/monitor/change_monitor.py +1 -0
  7. atomicshop/monitor/checks/dns.py +2 -1
  8. atomicshop/process.py +3 -3
  9. atomicshop/process_poller/__init__.py +0 -0
  10. atomicshop/process_poller/pollers/__init__.py +0 -0
  11. atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
  12. atomicshop/process_poller/process_pool.py +208 -0
  13. atomicshop/process_poller/simple_process_pool.py +112 -0
  14. atomicshop/process_poller/tracer_base.py +45 -0
  15. atomicshop/process_poller/tracers/__init__.py +0 -0
  16. atomicshop/process_poller/tracers/event_log.py +46 -0
  17. atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
  18. atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +106 -43
  19. atomicshop/wrappers/pywin32w/win_event_log/subscribes/{subscribe_to_process_create.py → process_create.py} +18 -4
  20. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +103 -0
  21. atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
  22. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/METADATA +1 -1
  23. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/RECORD +27 -16
  24. atomicshop/process_poller.py +0 -346
  25. /atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +0 -0
  26. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/LICENSE.txt +0 -0
  27. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/WHEEL +0 -0
  28. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,208 @@
1
+ import threading
2
+ import multiprocessing
3
+ import time
4
+ from typing import Literal, Union
5
+
6
+ from ..print_api import print_api
7
+ from .tracers import sysmon_etw, event_log
8
+ from .pollers import psutil_pywin32wmi_dll
9
+ from ..wrappers.pywin32w.win_event_log.subscribes import process_terminate
10
+
11
+
12
+ POLLER_SETTINGS_SYSMON_ETW: dict = {
13
+ 'etw_session_name': None, # Default will be used, check 'trace_sysmon_process_creation' for details.
14
+ 'sysmon_directory': None # Directory, where sysmon.exe is located. Default will be used.
15
+ }
16
+
17
+
18
+ class ProcessPool:
19
+ """
20
+ The class is responsible for getting processes and storing them in a pool.
21
+ THE POOL OF PROCESSES IS NOT REAL TIME!!!
22
+ There can be several moments delay (less than a second) + you can add delay before pid removal from the pool.
23
+ This is needed only to correlate PIDs to process names and command lines of other events you get on Windows.
24
+ """
25
+ def __init__(
26
+ self,
27
+ operation: Literal['thread', 'process'] = 'thread',
28
+ interval_seconds: Union[int, float] = 0,
29
+ process_get_method: Literal[
30
+ 'poll_psutil',
31
+ 'poll_pywin32',
32
+ 'poll_process_dll',
33
+ 'trace_sysmon_etw',
34
+ 'trace_event_log'
35
+ ] = 'trace_event_log',
36
+ poller_settings: dict = None,
37
+ process_terminator: bool = True,
38
+ wait_before_pid_remove_seconds: float = 5
39
+ ):
40
+ """
41
+ :param operation: str, 'thread' or 'process'. Default is 'process'.
42
+ 'process': The polling will be done in a new process.
43
+ 'thread': The polling will be done in a new thread.
44
+
45
+ Python is slow, if you are going to use 'thread' all other operations inside this thread will be very slow.
46
+ You can even get exceptions, if you're using process polling for correlations of PIDs and process names.
47
+ It is advised to use the 'process' operation, which will not affect other operations in the thread.
48
+ :param interval_seconds: works only for pollers, float, how many seconds to wait between each cycle.
49
+ Default is 0, which means that the polling will be as fast as possible.
50
+
51
+ Basically, you want it to be '0' if you want to get the most recent processes.
52
+ Any polling by itself takes time, so if you want to get the most recent processes, you want to do it as fast
53
+ as possible.
54
+ :param process_get_method: str. Default is 'process_dll'. Available:
55
+ 'poll_psutil': Poller, Get the list of processes by 'psutil' library. Resource intensive and slow.
56
+ 'poll_pywin32': Poller, processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
57
+ 'poll_process_dll'. Poller, Not resource intensive and fast. Probably works only in Windows 10 x64.
58
+ 'trace_sysmon_etw': Tracer, Get the list of processes with running SysMon by ETW - Event Tracing.
59
+ In this case 'interval_seconds' is irrelevant, since the ETW is real-time.
60
+ Steps we take:
61
+ 1. Check if SysMon is Running. If not, check if the executable exists in specified
62
+ location and start it as a service.
63
+ 2. Start the "Microsoft-Windows-Sysmon" ETW session.
64
+ 3. Take a snapshot of current processes and their CMDs with psutil and store it in a dict.
65
+ 4. Each new process creation from ETW updates the dict.
66
+ 'trace_event_log': Get the list of processes by subscribing to the Windows Event Log.
67
+ Log Channel: Security, Event ID: 4688.
68
+ We enable the necessary prerequisites in registry and subscribe to the event.
69
+ :param poller_settings: dict, settings for the poller method.
70
+ 'sysmon_etw': If not set 'POLLER_SETTINGS_SYSMON_ETW' will be used.
71
+ :param process_terminator: bool, if True, process terminator will run in the background and monitor
72
+ the processes for termination. If the process is terminated it will be removed from the pool.
73
+ :param wait_before_pid_remove_seconds: float, how many seconds to wait before the process is removed from
74
+ the pool after process termination event is received for that pid.
75
+ ---------------------------------------------
76
+ If there is an exception, ProcessPollerPool.processes will be set to the exception.
77
+ While getting the processes you can use this to execute the exception:
78
+
79
+ processes = ProcessPollerPool.processes
80
+
81
+ if isinstance(processes, BaseException):
82
+ raise processes
83
+ """
84
+
85
+ self.operation: str = operation
86
+ self.interval_seconds: float = interval_seconds
87
+ self.process_get_method = process_get_method
88
+ self.process_terminator: bool = process_terminator
89
+ self.wait_before_pid_remove_seconds: float = wait_before_pid_remove_seconds
90
+ self.poller_settings: dict = poller_settings
91
+
92
+ if self.poller_settings is None:
93
+ if process_get_method == 'sysmon_etw':
94
+ self.poller_settings: dict = POLLER_SETTINGS_SYSMON_ETW
95
+
96
+ # Current process pool.
97
+ self._processes: dict = dict()
98
+
99
+ # The variable is responsible to stop the thread if it is running.
100
+ self._running: bool = False
101
+
102
+ self._process_queue = multiprocessing.Queue()
103
+ self._running_state_queue = multiprocessing.Queue()
104
+
105
+ def start(self):
106
+ if self.operation == 'thread':
107
+ self._start_thread()
108
+ elif self.operation == 'process':
109
+ self._start_process()
110
+ else:
111
+ raise ValueError(f'Invalid operation type [{self.operation}]')
112
+
113
+ thread_get_queue = threading.Thread(target=self._thread_get_queue)
114
+ thread_get_queue.daemon = True
115
+ thread_get_queue.start()
116
+
117
+ if self.process_terminator:
118
+ thread_process_termination = threading.Thread(target=self._thread_process_termination)
119
+ thread_process_termination.daemon = True
120
+ thread_process_termination.start()
121
+
122
+ def stop(self):
123
+ self._running = False
124
+ self._running_state_queue.put(False)
125
+
126
+ def get_processes(self):
127
+ return self._processes
128
+
129
+ def _get_args_for_worker(self):
130
+ return self.process_get_method, self.interval_seconds, self._process_queue, self.poller_settings
131
+
132
+ def _start_thread(self):
133
+ self._running = True
134
+
135
+ thread = threading.Thread(target=_worker, args=(self._get_args_for_worker()))
136
+ thread.daemon = True
137
+ thread.start()
138
+
139
+ def _start_process(self):
140
+ self._running = True
141
+ multiprocessing.Process(target=_worker, args=(self._get_args_for_worker())).start()
142
+
143
+ def _thread_get_queue(self):
144
+ while True:
145
+ self._processes = self._process_queue.get()
146
+
147
+ def _thread_process_termination(self):
148
+ process_terminate_instance = process_terminate.ProcessTerminateSubscriber()
149
+ process_terminate_instance.start()
150
+
151
+ while True:
152
+ termination_event = process_terminate_instance.emit()
153
+ process_id = termination_event['ProcessIdInt']
154
+
155
+ removal_thread = threading.Thread(target=self._remove_pid, args=(process_id,))
156
+ removal_thread.daemon = True
157
+ removal_thread.start()
158
+
159
+ def _remove_pid(self, process_id):
160
+ # We need to wait a bit before we remove the process.
161
+ # This is because termination event can come sooner than the creation and the process
162
+ # is not yet in the pool.
163
+ # This happens mostly when the process is terminated immediately after the creation.
164
+ # Example: ping example.c
165
+ # 'example.c' is not a valid address, so the process is terminated immediately after the creation.
166
+ counter = 0
167
+ while counter < 30:
168
+ if process_id in self._processes:
169
+ break
170
+ counter += 1
171
+ time.sleep(0.1)
172
+
173
+ if counter == 30:
174
+ # print_api(f'Process [{process_id}] not found in the pool.', color='yellow')
175
+ return
176
+
177
+ # time.sleep(1)
178
+ # if process_id not in self._processes:
179
+ # print_api(f'Process [{process_id}] not found in the pool.', color='red')
180
+ # return
181
+
182
+ time.sleep(self.wait_before_pid_remove_seconds)
183
+ _ = self._processes.pop(process_id, None)
184
+ # print_api(f'Process [{process_id}] removed from the pool.', color='yellow')
185
+
186
+
187
+ def _worker(
188
+ poller_method,
189
+ interval_seconds,
190
+ process_queue,
191
+ poller_settings
192
+ ):
193
+
194
+ if poller_method == 'trace_sysmon_etw':
195
+ get_instance = sysmon_etw.TracerSysmonEtw(settings=poller_settings, process_queue=process_queue)
196
+ elif poller_method == 'trace_event_log':
197
+ get_instance = event_log.TracerEventlog(process_queue=process_queue)
198
+ elif 'poll_' in poller_method:
199
+ get_instance = psutil_pywin32wmi_dll.PollerPsutilPywin32Dll(
200
+ interval_seconds=interval_seconds, process_get_method=poller_method, process_queue=process_queue)
201
+ else:
202
+ raise ValueError(f'Invalid poller method [{poller_method}]')
203
+
204
+ # We must initiate the connection inside the thread/process, because it is not thread-safe.
205
+ get_instance.start()
206
+
207
+ while True:
208
+ time.sleep(1)
@@ -0,0 +1,112 @@
1
+ import threading
2
+ from pathlib import Path
3
+ import time
4
+
5
+ from ..wrappers.pywin32w.win_event_log.subscribes import process_create, process_terminate
6
+ from .. import get_process_list
7
+ from ..print_api import print_api
8
+
9
+
10
+ WAIT_BEFORE_PROCESS_TERMINATION_CHECK_SECONDS: float = 3
11
+ WAIT_BEFORE_PROCESS_TERMINATION_CHECK_COUNTS: float = WAIT_BEFORE_PROCESS_TERMINATION_CHECK_SECONDS * 10
12
+
13
+
14
+ class SimpleProcessPool:
15
+ """
16
+ THE POOL OF PROCESSES IS NOT REAL TIME!!!
17
+ There can be several moments delay (less than a second) + you can add delay before pid removal from the pool.
18
+ This is needed only to correlate PIDs to process names and command lines of other events you get on Windows.
19
+ The idea is similar to the process_poller.process_pool.ProcessPool class, but this class is simpler and uses
20
+ only the pywin32 tracing of the Windows Event Log Process Creation and Process Termination events.
21
+ The simple process pool is used to get things simpler than the process_pool.ProcessPool class.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ wait_before_pid_remove_seconds: float = 5
27
+ ):
28
+ """
29
+ :param wait_before_pid_remove_seconds: float, how many seconds to wait before the process is removed from
30
+ the pool after process termination event is received for that pid.
31
+ """
32
+
33
+ self.wait_before_pid_remove_seconds: float = wait_before_pid_remove_seconds
34
+
35
+ self._processes: dict = dict()
36
+ self._running: bool = False
37
+
38
+ def start(self):
39
+ self._running = True
40
+
41
+ self._processes = get_process_list.GetProcessList(
42
+ get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
43
+
44
+ thread_get_queue = threading.Thread(target=self._start_main_thread)
45
+ thread_get_queue.daemon = True
46
+ thread_get_queue.start()
47
+
48
+ thread_process_termination = threading.Thread(target=self._thread_process_termination)
49
+ thread_process_termination.daemon = True
50
+ thread_process_termination.start()
51
+
52
+ def stop(self):
53
+ self._running = False
54
+
55
+ def get_processes(self):
56
+ return self._processes
57
+
58
+ def _start_main_thread(self):
59
+ get_instance = process_create.ProcessCreateSubscriber()
60
+ get_instance.start()
61
+
62
+ while self._running:
63
+ event = get_instance.emit()
64
+ process_id = event['NewProcessIdInt']
65
+ process_name = Path(event['NewProcessName']).name
66
+ command_line = event['CommandLine']
67
+
68
+ self._processes[process_id] = {
69
+ 'name': process_name,
70
+ 'cmdline': command_line
71
+ }
72
+
73
+ # print_api(f'Process [{process_id}] added to the pool.', color='blue')
74
+
75
+ def _thread_process_termination(self):
76
+ process_terminate_instance = process_terminate.ProcessTerminateSubscriber()
77
+ process_terminate_instance.start()
78
+
79
+ while self._running:
80
+ termination_event = process_terminate_instance.emit()
81
+ process_id = termination_event['ProcessIdInt']
82
+
83
+ removal_thread = threading.Thread(target=self._remove_pid, args=(process_id,))
84
+ removal_thread.daemon = True
85
+ removal_thread.start()
86
+
87
+ def _remove_pid(self, process_id):
88
+ # We need to wait a bit before we remove the process.
89
+ # This is because termination event can come sooner than the creation and the process
90
+ # is not yet in the pool.
91
+ # This happens mostly when the process is terminated immediately after the creation.
92
+ # Example: ping example.c
93
+ # 'example.c' is not a valid address, so the process is terminated immediately after the creation.
94
+ counter = 0
95
+ while counter < WAIT_BEFORE_PROCESS_TERMINATION_CHECK_COUNTS:
96
+ if process_id in self._processes:
97
+ break
98
+ counter += 1
99
+ time.sleep(0.1)
100
+
101
+ if counter == WAIT_BEFORE_PROCESS_TERMINATION_CHECK_COUNTS:
102
+ # print_api(f'Process [{process_id}] not found in the pool.', color='yellow')
103
+ return
104
+
105
+ # time.sleep(1)
106
+ # if process_id not in self._processes:
107
+ # print_api(f'Process [{process_id}] not found in the pool.', color='red')
108
+ # return
109
+
110
+ time.sleep(self.wait_before_pid_remove_seconds)
111
+ _ = self._processes.pop(process_id, None)
112
+ # print_api(f'Process [{process_id}] removed from the pool.', color='yellow')
@@ -0,0 +1,45 @@
1
+ import threading
2
+
3
+ from .. import get_process_list
4
+
5
+
6
+ class Tracer:
7
+ """
8
+ Main tracer class for getting the list of processes.
9
+ """
10
+
11
+ def __init__(self):
12
+ self.tracer_instance = None
13
+ self.process_queue = None
14
+
15
+ self._cycle_callable = None
16
+ self._processes = {}
17
+
18
+ def start(self):
19
+ """
20
+ Start the tracer.
21
+ """
22
+
23
+ self._processes = get_process_list.GetProcessList(
24
+ get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
25
+
26
+ self.process_queue.put(self._processes)
27
+
28
+ self.tracer_instance.start()
29
+
30
+ thread = threading.Thread(target=self.update_queue)
31
+ thread.daemon = True
32
+ thread.start()
33
+
34
+ def update_queue(self):
35
+ """
36
+ Update the list of processes.
37
+ """
38
+
39
+ while True:
40
+ # Get subclass specific get process cycle.
41
+ current_processes = self._cycle_callable()
42
+
43
+ self._processes.update(current_processes)
44
+
45
+ self.process_queue.put(self._processes)
File without changes
@@ -0,0 +1,46 @@
1
+ from pathlib import Path
2
+
3
+ from ...wrappers.pywin32w.win_event_log.subscribes import process_create, process_terminate
4
+ from .. import tracer_base
5
+
6
+
7
+ class TracerEventlog(tracer_base.Tracer):
8
+ """
9
+ Tracer subclass for getting the list of processes with Windows Event Log Subscription.
10
+ """
11
+ def __init__(
12
+ self,
13
+ process_queue
14
+ ):
15
+ """
16
+ :param process_queue: Queue. The queue to put the processes in. If None, the processes will not be put in the
17
+ queue.
18
+ """
19
+ super().__init__()
20
+
21
+ self.tracer_instance = process_create.ProcessCreateSubscriber()
22
+
23
+ self.process_queue = process_queue
24
+
25
+ # This function is used in the thread start in the main Tracer class, 'start' function.
26
+ self._cycle_callable = self.emit_cycle
27
+
28
+ def start(self):
29
+ """
30
+ Start the tracer.
31
+ """
32
+ super().start()
33
+
34
+ def emit_cycle(self):
35
+ """
36
+ Get the list of processes.
37
+ """
38
+
39
+ # Get the current processes and reinitialize the instance of the dict.
40
+ current_cycle: dict = self.tracer_instance.emit()
41
+ current_processes: dict = {int(current_cycle['NewProcessIdInt']): {
42
+ 'name': Path(current_cycle['NewProcessName']).name,
43
+ 'cmdline': current_cycle['CommandLine']}
44
+ }
45
+
46
+ return current_processes
@@ -0,0 +1,68 @@
1
+ from .. import tracer_base
2
+ from ...etws.traces import trace_sysmon_process_creation
3
+
4
+
5
+ class TracerSysmonEtw(tracer_base.Tracer):
6
+ """
7
+ Tracer subclass for getting the list of processes with SysMon ETW.
8
+ """
9
+ def __init__(
10
+ self,
11
+ attrs: list = None,
12
+ settings: dict = None,
13
+ process_queue=None
14
+ ):
15
+ """
16
+ :param attrs: list. Default is ['pid', 'original_file_name', 'command_line'].
17
+ The list of attributes to get from the sysmon trace output. Check SysmonProcessCreationTrace class for
18
+ available attributes.
19
+ :settings: dict. The settings dictionary:
20
+ 'sysmon_etw_session_name': str. The Sysmon ETW session name. If None, the default from
21
+ 'trace_sysmon_process_creation' will be used.
22
+ 'sysmon_directory': str. The directory where Sysmon.exe resides. If None, the default from
23
+ 'trace_sysmon_process_creation' will be used.
24
+ :param process_queue: Queue. The queue to put the processes in. If None, the processes will not be put in the
25
+ queue.
26
+ """
27
+ super().__init__()
28
+
29
+ if not attrs:
30
+ attrs = ['ProcessId', 'OriginalFileName', 'CommandLine']
31
+
32
+ if not settings:
33
+ settings = {
34
+ 'sysmon_etw_session_name': None,
35
+ 'sysmon_directory': None
36
+ }
37
+
38
+ self.tracer_instance = trace_sysmon_process_creation.SysmonProcessCreationTrace(
39
+ attrs=attrs,
40
+ session_name=settings.get('sysmon_etw_session_name'),
41
+ close_existing_session_name=True,
42
+ sysmon_directory=settings.get('sysmon_directory')
43
+ )
44
+
45
+ self.process_queue = process_queue
46
+
47
+ # This function is used in the thread start in the main Tracer class, 'start' function.
48
+ self._cycle_callable = self.emit_cycle
49
+
50
+ def start(self):
51
+ """
52
+ Start the tracer.
53
+ """
54
+ super().start()
55
+
56
+ def emit_cycle(self):
57
+ """
58
+ Get the list of processes.
59
+ """
60
+
61
+ # Get the current processes and reinitialize the instance of the dict.
62
+ current_cycle: dict = self.tracer_instance.emit()
63
+ current_processes: dict = {int(current_cycle['ProcessId']): {
64
+ 'name': current_cycle['OriginalFileName'],
65
+ 'cmdline': current_cycle['CommandLine']}
66
+ }
67
+
68
+ return current_processes