atomicshop 2.14.2__py3-none-any.whl → 2.14.4__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.
- atomicshop/__init__.py +1 -1
- atomicshop/datetimes.py +3 -2
- atomicshop/etws/providers.py +18 -2
- atomicshop/etws/sessions.py +1 -1
- atomicshop/etws/trace.py +5 -22
- atomicshop/etws/traces/trace_dns.py +17 -14
- atomicshop/etws/traces/trace_sysmon_process_creation.py +34 -22
- atomicshop/file_io/csvs.py +1 -1
- atomicshop/get_process_list.py +133 -0
- atomicshop/mitm/statistic_analyzer.py +376 -3
- atomicshop/monitor/change_monitor.py +1 -0
- atomicshop/monitor/checks/dns.py +2 -1
- atomicshop/process.py +3 -3
- 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 +208 -0
- atomicshop/process_poller/simple_process_pool.py +112 -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/wrappers/ctyping/etw_winapi/const.py +130 -20
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +141 -5
- atomicshop/wrappers/loggingw/reading.py +20 -19
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +0 -2
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -24
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +103 -0
- {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/METADATA +1 -1
- {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/RECORD +34 -24
- atomicshop/process_poller.py +0 -345
- /atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +0 -0
- {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/WHEEL +0 -0
- {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/top_level.txt +0 -0
atomicshop/process_poller.py
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
import threading
|
|
2
|
-
import multiprocessing
|
|
3
|
-
import time
|
|
4
|
-
from typing import Literal, Union
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from .wrappers.pywin32w import wmi_win32process
|
|
8
|
-
from .wrappers.pywin32w.win_event_log.subscribes import process_create
|
|
9
|
-
from .wrappers.psutilw import psutilw
|
|
10
|
-
from .etws.traces import trace_sysmon_process_creation
|
|
11
|
-
from .basics import dicts
|
|
12
|
-
from .process_name_cmd import ProcessNameCmdline
|
|
13
|
-
from .print_api import print_api
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def get_process_time_tester(
|
|
17
|
-
get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
|
|
18
|
-
times_to_test: int = 50
|
|
19
|
-
):
|
|
20
|
-
"""
|
|
21
|
-
The function will test the time it takes to get the list of processes with different methods and cycles.
|
|
22
|
-
|
|
23
|
-
:param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
|
|
24
|
-
'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
|
|
25
|
-
'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
|
|
26
|
-
'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64
|
|
27
|
-
:param times_to_test: int, how many times to test the function.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
import timeit
|
|
31
|
-
|
|
32
|
-
setup_code = '''
|
|
33
|
-
from atomicshop import process_poller
|
|
34
|
-
get_process_list = process_poller.GetProcessList(get_method=get_method, connect_on_init=True)
|
|
35
|
-
'''
|
|
36
|
-
|
|
37
|
-
test_code = '''
|
|
38
|
-
test = get_process_list.get_processes()
|
|
39
|
-
'''
|
|
40
|
-
|
|
41
|
-
# globals need to be specified, otherwise the setup_code won't work with passed variables.
|
|
42
|
-
times = timeit.timeit(setup=setup_code, stmt=test_code, number=times_to_test, globals=locals())
|
|
43
|
-
print(f'Execution time: {times}')
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class GetProcessList:
|
|
47
|
-
"""
|
|
48
|
-
The class is responsible for getting the list of running processes.
|
|
49
|
-
|
|
50
|
-
Example of one time polling with 'pywin32' method:
|
|
51
|
-
from atomicshop import process_poller
|
|
52
|
-
process_list: dict = \
|
|
53
|
-
process_poller.GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
|
|
54
|
-
"""
|
|
55
|
-
def __init__(
|
|
56
|
-
self,
|
|
57
|
-
get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
|
|
58
|
-
connect_on_init: bool = False
|
|
59
|
-
):
|
|
60
|
-
"""
|
|
61
|
-
:param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
|
|
62
|
-
'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
|
|
63
|
-
'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
|
|
64
|
-
'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
|
|
65
|
-
:param connect_on_init: bool, if True, will connect to the service on init. 'psutil' don't need to connect.
|
|
66
|
-
"""
|
|
67
|
-
self.get_method = get_method
|
|
68
|
-
self.process_polling_instance = None
|
|
69
|
-
|
|
70
|
-
self.connected = False
|
|
71
|
-
|
|
72
|
-
if self.get_method == 'psutil':
|
|
73
|
-
self.process_polling_instance = psutilw.PsutilProcesses()
|
|
74
|
-
self.connected = True
|
|
75
|
-
elif self.get_method == 'pywin32':
|
|
76
|
-
self.process_polling_instance = wmi_win32process.Pywin32Processes()
|
|
77
|
-
elif self.get_method == 'process_dll':
|
|
78
|
-
self.process_polling_instance = ProcessNameCmdline()
|
|
79
|
-
|
|
80
|
-
if connect_on_init:
|
|
81
|
-
self.connect()
|
|
82
|
-
|
|
83
|
-
def connect(self):
|
|
84
|
-
"""
|
|
85
|
-
Connect to the service. 'psutil' don't need to connect.
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
# If the service is not connected yet. Since 'psutil' don't need to connect.
|
|
89
|
-
if not self.connected:
|
|
90
|
-
if self.get_method == 'pywin32':
|
|
91
|
-
self.process_polling_instance.connect()
|
|
92
|
-
self.connected = True
|
|
93
|
-
elif self.get_method == 'process_dll':
|
|
94
|
-
self.process_polling_instance.load()
|
|
95
|
-
self.connected = True
|
|
96
|
-
|
|
97
|
-
def get_processes(self, as_dict: bool = True) -> Union[list, dict]:
|
|
98
|
-
"""
|
|
99
|
-
The function will get the list of opened processes and return it as a list of dicts.
|
|
100
|
-
|
|
101
|
-
:return: dict while key is pid or list of dicts, of opened processes (depending on 'as_dict' setting).
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
if as_dict:
|
|
105
|
-
if self.get_method == 'psutil':
|
|
106
|
-
return self.process_polling_instance.get_processes_as_dict(
|
|
107
|
-
attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
|
|
108
|
-
elif self.get_method == 'pywin32':
|
|
109
|
-
processes = self.process_polling_instance.get_processes_as_dict(
|
|
110
|
-
attrs=['ProcessId', 'Name', 'CommandLine'])
|
|
111
|
-
|
|
112
|
-
# Convert the keys from WMI to the keys that are used in 'psutil'.
|
|
113
|
-
converted_process_dict = dict()
|
|
114
|
-
for pid, process_info in processes.items():
|
|
115
|
-
converted_process_dict[pid] = dicts.convert_key_names(
|
|
116
|
-
process_info, {'Name': 'name', 'CommandLine': 'cmdline'})
|
|
117
|
-
|
|
118
|
-
return converted_process_dict
|
|
119
|
-
elif self.get_method == 'process_dll':
|
|
120
|
-
return self.process_polling_instance.get_process_details(as_dict=True)
|
|
121
|
-
else:
|
|
122
|
-
if self.get_method == 'psutil':
|
|
123
|
-
return self.process_polling_instance.get_processes_as_list_of_dicts(
|
|
124
|
-
attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
|
|
125
|
-
elif self.get_method == 'pywin32':
|
|
126
|
-
processes = self.process_polling_instance.get_processes_as_list_of_dicts(
|
|
127
|
-
attrs=['ProcessId', 'Name', 'CommandLine'])
|
|
128
|
-
|
|
129
|
-
# Convert the keys from WMI to the keys that are used in 'psutil'.
|
|
130
|
-
for process_index, process_info in enumerate(processes):
|
|
131
|
-
processes[process_index] = dicts.convert_key_names(
|
|
132
|
-
process_info, {'ProcessId': 'pid', 'Name': 'name', 'CommandLine': 'cmdline'})
|
|
133
|
-
|
|
134
|
-
return processes
|
|
135
|
-
elif self.get_method == 'process_dll':
|
|
136
|
-
return self.process_polling_instance.get_process_details(as_dict=as_dict)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class ProcessPollerPool:
|
|
140
|
-
"""
|
|
141
|
-
The class is responsible for polling processes and storing them in a pool.
|
|
142
|
-
Currently, this works with 'psutil' library and takes up to 16% of CPU on my machine.
|
|
143
|
-
Because 'psutil' fetches 'cmdline' for each 'pid' dynamically, and it takes time and resources
|
|
144
|
-
Later, I'll find a solution to make it more efficient.
|
|
145
|
-
"""
|
|
146
|
-
def __init__(
|
|
147
|
-
self,
|
|
148
|
-
interval_seconds: Union[int, float] = 0,
|
|
149
|
-
operation: Literal['thread', 'process'] = 'thread',
|
|
150
|
-
poller_method: Literal['psutil', 'pywin32', 'process_dll', 'sysmon_etw', 'event_log'] = 'event_log',
|
|
151
|
-
sysmon_etw_session_name: str = None,
|
|
152
|
-
sysmon_directory: str = None
|
|
153
|
-
):
|
|
154
|
-
"""
|
|
155
|
-
:param interval_seconds: float, how many seconds to wait between each cycle.
|
|
156
|
-
Default is 0, which means that the polling will be as fast as possible.
|
|
157
|
-
|
|
158
|
-
Basically, you want it to be '0' if you want to get the most recent processes.
|
|
159
|
-
Any polling by itself takes time, so if you want to get the most recent processes, you want to do it as fast
|
|
160
|
-
as possible.
|
|
161
|
-
:param operation: str, 'thread' or 'process'. Default is 'process'.
|
|
162
|
-
'process': The polling will be done in a new process.
|
|
163
|
-
'thread': The polling will be done in a new thread.
|
|
164
|
-
|
|
165
|
-
Python is slow, if you are going to use 'thread' all other operations inside this thread will be very slow.
|
|
166
|
-
You can even get exceptions, if you're using process polling for correlations of PIDs and process names.
|
|
167
|
-
It is advised to use the 'process' operation, which will not affect other operations in the thread.
|
|
168
|
-
:param poller_method: str. Default is 'process_dll'. Available:
|
|
169
|
-
'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
|
|
170
|
-
'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
|
|
171
|
-
'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
|
|
172
|
-
'sysmon_etw': Get the list of processes with running SysMon by ETW - Event Tracing for Windows.
|
|
173
|
-
In this case 'store_cycles' and 'interval_seconds' are irrelevant, since the ETW is real-time.
|
|
174
|
-
Steps we take:
|
|
175
|
-
1. Check if SysMon is Running. If not, check if the executable exists in specified
|
|
176
|
-
location and start it as a service.
|
|
177
|
-
2. Start the "Microsoft-Windows-Sysmon" ETW session.
|
|
178
|
-
3. Take a snapshot of current processes and their CMDs with psutil and store it in a dict.
|
|
179
|
-
4. Each new process creation from ETW updates the dict.
|
|
180
|
-
'event_log': Get the list of processes by subscribing to the Windows Event Log.
|
|
181
|
-
Log Channel: Security, Event ID: 4688.
|
|
182
|
-
We enable the necessary prerequisites in registry and subscribe to the event.
|
|
183
|
-
:param sysmon_etw_session_name: str, only for 'sysmon_etw' get_method.
|
|
184
|
-
The name of the ETW session for tracing process creation.
|
|
185
|
-
:param sysmon_directory: str, only for 'sysmon_etw' get_method.
|
|
186
|
-
The directory where the SysMon executable is located. If non-existed will be downloaded.
|
|
187
|
-
---------------------------------------------
|
|
188
|
-
If there is an exception, ProcessPollerPool.processes will be set to the exception.
|
|
189
|
-
While getting the processes you can use this to execute the exception:
|
|
190
|
-
|
|
191
|
-
processes = ProcessPollerPool.processes
|
|
192
|
-
|
|
193
|
-
if isinstance(processes, BaseException):
|
|
194
|
-
raise processes
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
self.interval_seconds: float = interval_seconds
|
|
198
|
-
self.operation: str = operation
|
|
199
|
-
self.poller_method = poller_method
|
|
200
|
-
self.sysmon_etw_session_name: str = sysmon_etw_session_name
|
|
201
|
-
self.sysmon_directory: str = sysmon_directory
|
|
202
|
-
|
|
203
|
-
# Current process pool.
|
|
204
|
-
self._processes: dict = dict()
|
|
205
|
-
|
|
206
|
-
# The variable is responsible to stop the thread if it is running.
|
|
207
|
-
self._running: bool = False
|
|
208
|
-
|
|
209
|
-
self._process_queue = multiprocessing.Queue()
|
|
210
|
-
self._running_state_queue = multiprocessing.Queue()
|
|
211
|
-
|
|
212
|
-
def start(self):
|
|
213
|
-
if self.operation == 'thread':
|
|
214
|
-
self._start_thread()
|
|
215
|
-
elif self.operation == 'process':
|
|
216
|
-
self._start_process()
|
|
217
|
-
else:
|
|
218
|
-
raise ValueError(f'Invalid operation type [{self.operation}]')
|
|
219
|
-
|
|
220
|
-
thread = threading.Thread(target=self._thread_get_queue)
|
|
221
|
-
thread.daemon = True
|
|
222
|
-
thread.start()
|
|
223
|
-
|
|
224
|
-
def stop(self):
|
|
225
|
-
self._running = False
|
|
226
|
-
self._running_state_queue.put(False)
|
|
227
|
-
|
|
228
|
-
def get_processes(self):
|
|
229
|
-
return self._processes
|
|
230
|
-
|
|
231
|
-
def _start_thread(self):
|
|
232
|
-
self._running = True
|
|
233
|
-
|
|
234
|
-
thread = threading.Thread(
|
|
235
|
-
target=_worker, args=(
|
|
236
|
-
self.poller_method, self._running_state_queue, self.interval_seconds,
|
|
237
|
-
self._process_queue, self.sysmon_etw_session_name, self.sysmon_directory,
|
|
238
|
-
)
|
|
239
|
-
)
|
|
240
|
-
thread.daemon = True
|
|
241
|
-
thread.start()
|
|
242
|
-
|
|
243
|
-
def _start_process(self):
|
|
244
|
-
self._running = True
|
|
245
|
-
multiprocessing.Process(
|
|
246
|
-
target=_worker, args=(
|
|
247
|
-
self.poller_method, self._running_state_queue, self.interval_seconds,
|
|
248
|
-
self._process_queue, self.sysmon_etw_session_name, self.sysmon_directory,
|
|
249
|
-
)).start()
|
|
250
|
-
|
|
251
|
-
def _thread_get_queue(self):
|
|
252
|
-
while True:
|
|
253
|
-
self._processes = self._process_queue.get()
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def _worker(
|
|
257
|
-
poller_method, running_state_queue, interval_seconds, process_queue, sysmon_etw_session_name, sysmon_directory):
|
|
258
|
-
def _worker_to_get_running_state():
|
|
259
|
-
nonlocal running_state
|
|
260
|
-
running_state = running_state_queue.get()
|
|
261
|
-
|
|
262
|
-
running_state: bool = True
|
|
263
|
-
|
|
264
|
-
thread = threading.Thread(target=_worker_to_get_running_state)
|
|
265
|
-
thread.daemon = True
|
|
266
|
-
thread.start()
|
|
267
|
-
|
|
268
|
-
if poller_method == 'sysmon_etw':
|
|
269
|
-
poller_instance = trace_sysmon_process_creation.SysmonProcessCreationTrace(
|
|
270
|
-
attrs=['pid', 'original_file_name', 'command_line'],
|
|
271
|
-
session_name=sysmon_etw_session_name,
|
|
272
|
-
close_existing_session_name=True,
|
|
273
|
-
sysmon_directory=sysmon_directory
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
# We must initiate the connection inside the thread/process, because it is not thread-safe.
|
|
277
|
-
poller_instance.start()
|
|
278
|
-
|
|
279
|
-
processes = GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
|
|
280
|
-
process_queue.put(processes)
|
|
281
|
-
elif poller_method == 'event_log':
|
|
282
|
-
poller_instance = process_create.ProcessCreateSubscriber()
|
|
283
|
-
poller_instance.start()
|
|
284
|
-
|
|
285
|
-
processes = GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
|
|
286
|
-
process_queue.put(processes)
|
|
287
|
-
else:
|
|
288
|
-
poller_instance = GetProcessList(get_method=poller_method)
|
|
289
|
-
poller_instance.connect()
|
|
290
|
-
processes = {}
|
|
291
|
-
|
|
292
|
-
exception = None
|
|
293
|
-
while running_state:
|
|
294
|
-
try:
|
|
295
|
-
if poller_method == 'sysmon_etw':
|
|
296
|
-
# Get the current processes and reinitialize the instance of the dict.
|
|
297
|
-
current_cycle: dict = poller_instance.emit()
|
|
298
|
-
current_processes: dict = {int(current_cycle['pid']): {
|
|
299
|
-
'name': current_cycle['original_file_name'],
|
|
300
|
-
'cmdline': current_cycle['command_line']}
|
|
301
|
-
}
|
|
302
|
-
elif poller_method == 'event_log':
|
|
303
|
-
# Get the current processes and reinitialize the instance of the dict.
|
|
304
|
-
current_cycle: dict = poller_instance.emit()
|
|
305
|
-
current_processes: dict = {current_cycle['pid']: {
|
|
306
|
-
'name': Path(current_cycle['process_name']).name,
|
|
307
|
-
'cmdline': current_cycle['command_line']}
|
|
308
|
-
}
|
|
309
|
-
else:
|
|
310
|
-
# Get the current processes and reinitialize the instance of the dict.
|
|
311
|
-
current_processes: dict = dict(poller_instance.get_processes())
|
|
312
|
-
|
|
313
|
-
# Remove Command lines that contains only numbers, since they are useless.
|
|
314
|
-
for pid, process_info in current_processes.items():
|
|
315
|
-
if process_info['cmdline'].isnumeric():
|
|
316
|
-
current_processes[pid]['cmdline'] = str()
|
|
317
|
-
elif process_info['cmdline'] == 'Error':
|
|
318
|
-
current_processes[pid]['cmdline'] = str()
|
|
319
|
-
|
|
320
|
-
# This loop is essential for keeping the command lines.
|
|
321
|
-
# When the process unloads from memory, the last polling will have only pid and executable name, but not
|
|
322
|
-
# the command line. This loop will keep the command line from the previous polling if this happens.
|
|
323
|
-
for pid, process_info in current_processes.items():
|
|
324
|
-
if pid in processes:
|
|
325
|
-
if processes[pid]['name'] == current_processes[pid]['name']:
|
|
326
|
-
if current_processes[pid]['cmdline'] == '':
|
|
327
|
-
current_processes[pid]['cmdline'] = processes[pid]['cmdline']
|
|
328
|
-
processes.update(current_processes)
|
|
329
|
-
|
|
330
|
-
process_queue.put(processes)
|
|
331
|
-
|
|
332
|
-
# Since ETW is a blocking operation, we don't need to sleep in tracing pollers [sysmon_etw, event_log].
|
|
333
|
-
if poller_method not in ['sysmon_etw', 'event_log']:
|
|
334
|
-
time.sleep(interval_seconds)
|
|
335
|
-
except KeyboardInterrupt as e:
|
|
336
|
-
running_state = False
|
|
337
|
-
exception = e
|
|
338
|
-
except Exception as e:
|
|
339
|
-
running_state = False
|
|
340
|
-
exception = e
|
|
341
|
-
print_api(f'Exception in ProcessPollerPool: {e}', color='red')
|
|
342
|
-
raise
|
|
343
|
-
|
|
344
|
-
if not running_state:
|
|
345
|
-
process_queue.put(exception)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|