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
|
@@ -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
|
|
@@ -6,8 +6,15 @@ from ctypes.wintypes import ULONG
|
|
|
6
6
|
# Constants
|
|
7
7
|
EVENT_TRACE_CONTROL_STOP = 1
|
|
8
8
|
WNODE_FLAG_TRACED_GUID = 0x00020000
|
|
9
|
+
EVENT_TRACE_REAL_TIME_MODE = 0x00000100
|
|
10
|
+
EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1
|
|
9
11
|
|
|
10
12
|
MAXIMUM_LOGGERS = 64
|
|
13
|
+
ULONG64 = ctypes.c_uint64
|
|
14
|
+
|
|
15
|
+
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
|
|
16
|
+
TRACEHANDLE = ULONG64
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
|
|
13
20
|
"""
|
|
@@ -43,7 +50,6 @@ class WNODE_HEADER(ctypes.Structure):
|
|
|
43
50
|
]
|
|
44
51
|
|
|
45
52
|
|
|
46
|
-
# Define EVENT_TRACE_PROPERTIES structure
|
|
47
53
|
class EVENT_TRACE_PROPERTIES(ctypes.Structure):
|
|
48
54
|
_fields_ = [
|
|
49
55
|
("Wnode", WNODE_HEADER),
|
|
@@ -70,29 +76,32 @@ class EVENT_TRACE_PROPERTIES(ctypes.Structure):
|
|
|
70
76
|
]
|
|
71
77
|
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
class EVENT_TRACE_LOGFILE(ctypes.Structure):
|
|
79
|
+
class TRACE_LOGFILE_HEADER(ctypes.Structure):
|
|
75
80
|
_fields_ = [
|
|
76
|
-
("LogFileName", wintypes.LPWSTR),
|
|
77
|
-
("LoggerName", wintypes.LPWSTR),
|
|
78
|
-
("CurrentTime", wintypes.LARGE_INTEGER),
|
|
79
|
-
("BuffersRead", wintypes.ULONG),
|
|
80
|
-
("ProcessTraceMode", wintypes.ULONG),
|
|
81
|
-
("EventRecordCallback", wintypes.LPVOID),
|
|
82
81
|
("BufferSize", wintypes.ULONG),
|
|
83
|
-
("
|
|
84
|
-
("
|
|
85
|
-
("
|
|
86
|
-
("
|
|
87
|
-
("
|
|
88
|
-
("
|
|
82
|
+
("Version", wintypes.ULONG),
|
|
83
|
+
("ProviderVersion", wintypes.ULONG),
|
|
84
|
+
("NumberOfProcessors", wintypes.ULONG),
|
|
85
|
+
("EndTime", wintypes.LARGE_INTEGER),
|
|
86
|
+
("TimerResolution", wintypes.ULONG),
|
|
87
|
+
("MaximumFileSize", wintypes.ULONG),
|
|
89
88
|
("LogFileMode", wintypes.ULONG),
|
|
90
|
-
("
|
|
91
|
-
("
|
|
89
|
+
("BuffersWritten", wintypes.ULONG),
|
|
90
|
+
("StartBuffers", wintypes.ULONG),
|
|
91
|
+
("PointerSize", wintypes.ULONG),
|
|
92
|
+
("EventsLost", wintypes.ULONG),
|
|
93
|
+
("CpuSpeedInMHz", wintypes.ULONG),
|
|
94
|
+
("LoggerName", wintypes.WCHAR * 256),
|
|
95
|
+
("LogFileName", wintypes.WCHAR * 256),
|
|
96
|
+
("TimeZone", wintypes.LPVOID),
|
|
97
|
+
("BootTime", wintypes.LARGE_INTEGER),
|
|
98
|
+
("PerfFreq", wintypes.LARGE_INTEGER),
|
|
99
|
+
("StartTime", wintypes.LARGE_INTEGER),
|
|
100
|
+
("ReservedFlags", wintypes.ULONG),
|
|
101
|
+
("BuffersLost", wintypes.ULONG)
|
|
92
102
|
]
|
|
93
103
|
|
|
94
104
|
|
|
95
|
-
# Define the EVENT_TRACE_HEADER structure
|
|
96
105
|
class EVENT_TRACE_HEADER(ctypes.Structure):
|
|
97
106
|
_fields_ = [
|
|
98
107
|
("Size", wintypes.USHORT),
|
|
@@ -116,10 +125,67 @@ class EVENT_TRACE_HEADER(ctypes.Structure):
|
|
|
116
125
|
]
|
|
117
126
|
|
|
118
127
|
|
|
119
|
-
|
|
128
|
+
class EVENT_TRACE(ctypes.Structure):
|
|
129
|
+
_fields_ = [
|
|
130
|
+
("Header", EVENT_TRACE_HEADER),
|
|
131
|
+
("InstanceId", wintypes.DWORD),
|
|
132
|
+
("ParentInstanceId", wintypes.DWORD),
|
|
133
|
+
("ParentGuid", GUID),
|
|
134
|
+
("MofData", ctypes.c_void_p),
|
|
135
|
+
("MofLength", wintypes.ULONG),
|
|
136
|
+
("ClientContext", wintypes.ULONG)
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class EVENT_TRACE_LOGFILEW(ctypes.Structure):
|
|
141
|
+
_fields_ = [
|
|
142
|
+
("LogFileName", ctypes.c_wchar_p),
|
|
143
|
+
("LoggerName", ctypes.c_wchar_p),
|
|
144
|
+
("CurrentTime", wintypes.LARGE_INTEGER),
|
|
145
|
+
("BuffersRead", wintypes.ULONG),
|
|
146
|
+
("ProcessTraceMode", wintypes.ULONG),
|
|
147
|
+
("CurrentEvent", EVENT_TRACE),
|
|
148
|
+
("LogfileHeader", TRACE_LOGFILE_HEADER),
|
|
149
|
+
("BufferCallback", ctypes.c_void_p), # Placeholder for buffer callback
|
|
150
|
+
("BufferSize", wintypes.ULONG),
|
|
151
|
+
("Filled", wintypes.ULONG),
|
|
152
|
+
("EventsLost", wintypes.ULONG),
|
|
153
|
+
("EventCallback", ctypes.CFUNCTYPE(None, ctypes.POINTER(EVENT_TRACE))),
|
|
154
|
+
("Context", ULONG64)
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class EVENT_DESCRIPTOR(ctypes.Structure):
|
|
159
|
+
_fields_ = [
|
|
160
|
+
("Id", wintypes.USHORT),
|
|
161
|
+
("Version", wintypes.BYTE),
|
|
162
|
+
("Channel", wintypes.BYTE),
|
|
163
|
+
("Level", wintypes.BYTE),
|
|
164
|
+
("Opcode", wintypes.BYTE),
|
|
165
|
+
("Task", wintypes.USHORT),
|
|
166
|
+
("Keyword", ULONG64),
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class EVENT_HEADER(ctypes.Structure):
|
|
171
|
+
_fields_ = [
|
|
172
|
+
("Size", wintypes.USHORT),
|
|
173
|
+
("HeaderType", wintypes.USHORT),
|
|
174
|
+
("Flags", wintypes.USHORT),
|
|
175
|
+
("EventProperty", wintypes.USHORT),
|
|
176
|
+
("ThreadId", wintypes.ULONG),
|
|
177
|
+
("ProcessId", wintypes.ULONG),
|
|
178
|
+
("TimeStamp", wintypes.LARGE_INTEGER),
|
|
179
|
+
("ProviderId", GUID),
|
|
180
|
+
("EventDescriptor", EVENT_DESCRIPTOR),
|
|
181
|
+
("ProcessorTime", ULONG64),
|
|
182
|
+
("ActivityId", GUID),
|
|
183
|
+
("RelatedActivityId", GUID),
|
|
184
|
+
]
|
|
185
|
+
|
|
120
186
|
class EVENT_RECORD(ctypes.Structure):
|
|
121
187
|
_fields_ = [
|
|
122
|
-
("EventHeader",
|
|
188
|
+
("EventHeader", EVENT_HEADER),
|
|
123
189
|
("BufferContext", wintypes.ULONG),
|
|
124
190
|
("ExtendedDataCount", wintypes.USHORT),
|
|
125
191
|
("UserDataLength", wintypes.USHORT),
|
|
@@ -129,6 +195,50 @@ class EVENT_RECORD(ctypes.Structure):
|
|
|
129
195
|
]
|
|
130
196
|
|
|
131
197
|
|
|
198
|
+
# class EVENT_TRACE_LOGFILE(ctypes.Structure):
|
|
199
|
+
# _fields_ = [
|
|
200
|
+
# ("LogFileName", wintypes.LPWSTR),
|
|
201
|
+
# ("LoggerName", wintypes.LPWSTR),
|
|
202
|
+
# ("CurrentTime", wintypes.LARGE_INTEGER),
|
|
203
|
+
# ("BuffersRead", wintypes.ULONG),
|
|
204
|
+
# ("ProcessTraceMode", wintypes.ULONG),
|
|
205
|
+
# ("EventRecordCallback", wintypes.LPVOID),
|
|
206
|
+
# ("BufferSize", wintypes.ULONG),
|
|
207
|
+
# ("Filled", wintypes.ULONG),
|
|
208
|
+
# ("EventsLost", wintypes.ULONG),
|
|
209
|
+
# ("BuffersLost", wintypes.ULONG),
|
|
210
|
+
# ("RealTimeBuffersLost", wintypes.ULONG),
|
|
211
|
+
# ("LogBuffersLost", wintypes.ULONG),
|
|
212
|
+
# ("BuffersWritten", wintypes.ULONG),
|
|
213
|
+
# ("LogFileMode", wintypes.ULONG),
|
|
214
|
+
# ("IsKernelTrace", wintypes.ULONG),
|
|
215
|
+
# ("Context", wintypes.ULONG) # Placeholder for context pointer
|
|
216
|
+
# ]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class EVENT_TRACE_LOGFILE(ctypes.Structure):
|
|
220
|
+
_fields_ = [
|
|
221
|
+
("LogFileName", wintypes.LPWSTR),
|
|
222
|
+
("LoggerName", wintypes.LPWSTR),
|
|
223
|
+
("CurrentTime", wintypes.LARGE_INTEGER),
|
|
224
|
+
("BuffersRead", wintypes.ULONG),
|
|
225
|
+
("ProcessTraceMode", wintypes.ULONG),
|
|
226
|
+
("CurrentEvent", EVENT_RECORD),
|
|
227
|
+
("LogfileHeader", TRACE_LOGFILE_HEADER),
|
|
228
|
+
("BufferCallback", wintypes.LPVOID),
|
|
229
|
+
("BufferSize", wintypes.ULONG),
|
|
230
|
+
("Filled", wintypes.ULONG),
|
|
231
|
+
("EventsLost", wintypes.ULONG),
|
|
232
|
+
("EventCallback", ctypes.c_void_p),
|
|
233
|
+
("IsKernelTrace", wintypes.ULONG),
|
|
234
|
+
("Context", wintypes.LPVOID)
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Define the callback type for processing events
|
|
239
|
+
EVENT_CALLBACK_TYPE = ctypes.WINFUNCTYPE(None, ctypes.POINTER(EVENT_RECORD))
|
|
240
|
+
|
|
241
|
+
|
|
132
242
|
class PROVIDER_ENUMERATION_INFO(ctypes.Structure):
|
|
133
243
|
_fields_ = [
|
|
134
244
|
("NumberOfProviders", ULONG),
|