atomicshop 2.12.26__py3-none-any.whl → 2.13.0__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/basics/dicts.py +12 -0
- atomicshop/basics/package_module.py +10 -0
- atomicshop/diff_check.py +5 -4
- atomicshop/dns.py +0 -9
- atomicshop/etws/const.py +38 -0
- atomicshop/etws/traces/__init__.py +0 -0
- atomicshop/{etw → etws/traces}/trace_dns.py +66 -23
- atomicshop/etws/traces/trace_sysmon_process_creation.py +116 -0
- atomicshop/monitor/change_monitor.py +5 -1
- atomicshop/monitor/checks/dns.py +11 -11
- atomicshop/process.py +2 -2
- atomicshop/process_poller.py +134 -67
- atomicshop/wrappers/psutilw/psutilw.py +9 -0
- atomicshop/wrappers/sysmonw.py +153 -0
- {atomicshop-2.12.26.dist-info → atomicshop-2.13.0.dist-info}/METADATA +1 -1
- {atomicshop-2.12.26.dist-info → atomicshop-2.13.0.dist-info}/RECORD +24 -19
- /atomicshop/{etw → etws}/__init__.py +0 -0
- /atomicshop/{etw → etws}/providers.py +0 -0
- /atomicshop/{etw → etws}/sessions.py +0 -0
- /atomicshop/{etw → etws}/trace.py +0 -0
- {atomicshop-2.12.26.dist-info → atomicshop-2.13.0.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.12.26.dist-info → atomicshop-2.13.0.dist-info}/WHEEL +0 -0
- {atomicshop-2.12.26.dist-info → atomicshop-2.13.0.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
atomicshop/basics/dicts.py
CHANGED
|
@@ -24,6 +24,18 @@ def get_first_key_and_value_dict(input_dict: dict) -> dict:
|
|
|
24
24
|
return {first_key: input_dict[first_key]}
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def get_last_added_key_value(input_dict: dict) -> dict:
|
|
28
|
+
"""
|
|
29
|
+
The function will return the last added key and value in a dictionary.
|
|
30
|
+
|
|
31
|
+
:param input_dict: dict, the dictionary to get the last added key and value.
|
|
32
|
+
:return: dict, the last added key and value in the dictionary.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
last_key = list(input_dict.keys())[-1]
|
|
36
|
+
return {last_key: input_dict[last_key]}
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
def remove_keys(input_dict: dict, key_list: list) -> None:
|
|
28
40
|
"""
|
|
29
41
|
The function will remove a key from dictionary without raising an exception if it doesn't exist.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# Add static files to your package / module pyproject.toml:
|
|
3
|
+
[tool.setuptools.package-data]
|
|
4
|
+
"atomicshop.addons" = ["**"]
|
|
5
|
+
|
|
6
|
+
# Read relative path of your module inside your package / module script:
|
|
7
|
+
from importlib.resources import files
|
|
8
|
+
PACKAGE_DLL_PATH = 'addons/process_list/compiled/Win10x64/process_list.dll'
|
|
9
|
+
FULL_DLL_PATH = str(files(__package__).joinpath(PACKAGE_DLL_PATH))
|
|
10
|
+
"""
|
atomicshop/diff_check.py
CHANGED
|
@@ -314,10 +314,10 @@ class DiffChecker:
|
|
|
314
314
|
try:
|
|
315
315
|
if self.save_as == 'txt':
|
|
316
316
|
self.previous_content = file_io.read_file(
|
|
317
|
-
self.input_file_path, stderr=False, **(print_kwargs or {}))
|
|
317
|
+
self.input_file_path, stdout=False, stderr=False, **(print_kwargs or {}))
|
|
318
318
|
elif self.save_as == 'json':
|
|
319
319
|
self.previous_content = jsons.read_json_file(
|
|
320
|
-
self.input_file_path, stderr=False, **(print_kwargs or {}))
|
|
320
|
+
self.input_file_path, stdout=False, stderr=False, **(print_kwargs or {}))
|
|
321
321
|
except FileNotFoundError as except_object:
|
|
322
322
|
message = f"Input File [{Path(except_object.filename).name}] doesn't exist - Will create new one."
|
|
323
323
|
print_api(message, color='yellow', **(print_kwargs or {}))
|
|
@@ -433,10 +433,11 @@ class DiffChecker:
|
|
|
433
433
|
if self.input_file_path:
|
|
434
434
|
if self.save_as == 'txt':
|
|
435
435
|
# noinspection PyTypeChecker
|
|
436
|
-
file_io.write_file(self.previous_content, self.input_file_path, **(print_kwargs or {}))
|
|
436
|
+
file_io.write_file(self.previous_content, self.input_file_path, stdout=False, **(print_kwargs or {}))
|
|
437
437
|
elif self.save_as == 'json':
|
|
438
438
|
jsons.write_json_file(
|
|
439
|
-
self.previous_content, self.input_file_path, use_default_indent=True,
|
|
439
|
+
self.previous_content, self.input_file_path, use_default_indent=True, stdout=False,
|
|
440
|
+
**(print_kwargs or {}))
|
|
440
441
|
|
|
441
442
|
return result, message
|
|
442
443
|
|
atomicshop/dns.py
CHANGED
|
@@ -18,15 +18,6 @@ TYPES_DICT = {
|
|
|
18
18
|
'255': 'ANY'
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
# Event Tracing DNS info.
|
|
22
|
-
ETW_DNS_INFO = {
|
|
23
|
-
'provider_name': 'Microsoft-Windows-DNS-Client',
|
|
24
|
-
'provider_guid': '{1C95126E-7EEA-49A9-A3FE-A378B03DDB4D}',
|
|
25
|
-
# Event ID 3008 got DNS Queries and DNS Answers. Meaning, that information in ETW will arrive after DNS Response
|
|
26
|
-
# is received and not After DNS Query is sent.
|
|
27
|
-
'event_id': 3008
|
|
28
|
-
}
|
|
29
|
-
|
|
30
21
|
|
|
31
22
|
def resolve_dns_localhost(domain_name: str, dns_servers_list: list = None, print_kwargs: dict = None) -> str:
|
|
32
23
|
"""
|
atomicshop/etws/const.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
ETW_DNS = {
|
|
2
|
+
'provider_name': 'Microsoft-Windows-DNS-Client',
|
|
3
|
+
'provider_guid': '{1C95126E-7EEA-49A9-A3FE-A378B03DDB4D}',
|
|
4
|
+
'event_ids': {
|
|
5
|
+
# Event ID 3008 got DNS Queries and DNS Answers. Meaning, that information in ETW will arrive after DNS Response
|
|
6
|
+
# is received and not after DNS Query Request is sent.
|
|
7
|
+
'dns_request_response': 3008
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Event Tracing Kernel-Process.
|
|
13
|
+
ETW_KERNEL_PROCESS = {
|
|
14
|
+
'provider_name': 'Microsoft-Windows-Kernel-Process',
|
|
15
|
+
'provider_guid': '{22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716}',
|
|
16
|
+
'event_ids': {
|
|
17
|
+
'process_create': 1 # Emits all the processes that are started.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# You need SYSTEM privileges to execute this one. Try with psexec -s -i
|
|
23
|
+
ETW_SECURITY_AUDITING = {
|
|
24
|
+
'provider_name': 'Microsoft-Windows-Security-Auditing',
|
|
25
|
+
'provider_guid': '{54849625-5478-4994-A5BA-3E3B0328C30D}',
|
|
26
|
+
'event_ids': {
|
|
27
|
+
'process_create': 4688 # Emits all the processes that are started.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
ETW_SYSMON = {
|
|
33
|
+
'provider_name': 'Microsoft-Windows-Sysmon',
|
|
34
|
+
'provider_guid': '{5770385F-C22A-43E0-BF4C-06F5698FFBD9}',
|
|
35
|
+
'event_ids': {
|
|
36
|
+
'process_create': 1 # Emits all the processes that are started.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
File without changes
|
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
from .. import dns
|
|
3
|
-
from ..wrappers.psutilw import psutilw
|
|
4
|
-
from ..basics import dicts
|
|
5
|
-
from ..process_poller import ProcessPollerPool
|
|
6
|
-
from ..print_api import print_api
|
|
1
|
+
import time
|
|
7
2
|
|
|
3
|
+
from .. import trace, const
|
|
4
|
+
from ...wrappers.psutilw import psutilw
|
|
5
|
+
from ...basics import dicts
|
|
6
|
+
from ...process_poller import ProcessPollerPool
|
|
7
|
+
from ...print_api import print_api
|
|
8
|
+
from ... import dns
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopDnsTrace'
|
|
12
|
+
|
|
13
|
+
PROVIDER_NAME: str = const.ETW_DNS['provider_name']
|
|
14
|
+
PROVIDER_GUID: str = const.ETW_DNS['provider_guid']
|
|
15
|
+
REQUEST_RESP_EVENT_ID: int = const.ETW_DNS['event_ids']['dns_request_response']
|
|
16
|
+
|
|
17
|
+
WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
|
|
18
|
+
WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DnsRequestResponseTrace:
|
|
10
22
|
def __init__(
|
|
11
23
|
self,
|
|
12
|
-
enable_process_poller: bool = False,
|
|
13
24
|
attrs: list = None,
|
|
14
25
|
session_name: str = None,
|
|
15
|
-
close_existing_session_name: bool = True
|
|
26
|
+
close_existing_session_name: bool = True,
|
|
27
|
+
enable_process_poller: bool = False,
|
|
28
|
+
process_poller_etw_session_name: str = None
|
|
16
29
|
):
|
|
17
30
|
"""
|
|
18
31
|
DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008.
|
|
@@ -28,6 +41,7 @@ class DnsTrace:
|
|
|
28
41
|
False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
|
|
29
42
|
created. Instead, the existing session will be used. If there is a buffer from the previous session,
|
|
30
43
|
you will get the events from the buffer.
|
|
44
|
+
:param process_poller_etw_session_name: The name of the ETW session for tracing process creation.
|
|
31
45
|
|
|
32
46
|
-------------------------------------------------
|
|
33
47
|
|
|
@@ -35,7 +49,13 @@ class DnsTrace:
|
|
|
35
49
|
from atomicshop.etw import dns_trace
|
|
36
50
|
|
|
37
51
|
|
|
38
|
-
dns_trace_w = dns_trace.DnsTrace(
|
|
52
|
+
dns_trace_w = dns_trace.DnsTrace(
|
|
53
|
+
attrs=['pid', 'name', 'cmdline', 'domain', 'query_type'],
|
|
54
|
+
session_name='MyDnsTrace',
|
|
55
|
+
close_existing_session_name=True,
|
|
56
|
+
enable_process_poller=True,
|
|
57
|
+
process_poller_etw_session_name='MyProcessTrace'
|
|
58
|
+
)
|
|
39
59
|
dns_trace_w.start()
|
|
40
60
|
while True:
|
|
41
61
|
dns_dict = dns_trace_w.emit()
|
|
@@ -46,16 +66,21 @@ class DnsTrace:
|
|
|
46
66
|
self.enable_process_poller = enable_process_poller
|
|
47
67
|
self.attrs = attrs
|
|
48
68
|
|
|
69
|
+
if not session_name:
|
|
70
|
+
session_name = ETW_DEFAULT_SESSION_NAME
|
|
71
|
+
|
|
49
72
|
self.event_trace = trace.EventTrace(
|
|
50
|
-
providers=[(
|
|
73
|
+
providers=[(PROVIDER_NAME, PROVIDER_GUID)],
|
|
51
74
|
# lambda x: self.event_queue.put(x),
|
|
52
|
-
event_id_filters=[
|
|
75
|
+
event_id_filters=[REQUEST_RESP_EVENT_ID],
|
|
53
76
|
session_name=session_name,
|
|
54
77
|
close_existing_session_name=close_existing_session_name
|
|
55
78
|
)
|
|
56
79
|
|
|
57
80
|
if self.enable_process_poller:
|
|
58
|
-
self.process_poller = ProcessPollerPool(
|
|
81
|
+
self.process_poller = ProcessPollerPool(
|
|
82
|
+
operation='process', poller_method='sysmon_etw',
|
|
83
|
+
sysmon_etw_session_name=process_poller_etw_session_name)
|
|
59
84
|
|
|
60
85
|
def start(self):
|
|
61
86
|
if self.enable_process_poller:
|
|
@@ -82,6 +107,11 @@ class DnsTrace:
|
|
|
82
107
|
:return: Dictionary with the event data.
|
|
83
108
|
"""
|
|
84
109
|
|
|
110
|
+
# Get the processes first, since we need the process name and command line.
|
|
111
|
+
# If they're not ready, we will get just pids from DNS tracing.
|
|
112
|
+
if self.enable_process_poller:
|
|
113
|
+
self._get_processes_from_poller()
|
|
114
|
+
|
|
85
115
|
event = self.event_trace.emit()
|
|
86
116
|
|
|
87
117
|
event_dict: dict = {
|
|
@@ -92,12 +122,6 @@ class DnsTrace:
|
|
|
92
122
|
'query_type': dns.TYPES_DICT[str(event[1]['QueryType'])]
|
|
93
123
|
}
|
|
94
124
|
|
|
95
|
-
# # Get process name only from psutil, just in case.
|
|
96
|
-
# try:
|
|
97
|
-
# process_name = psutilw.get_process_name_by_pid(event_dict['pid'])
|
|
98
|
-
# except psutil.NoSuchProcess:
|
|
99
|
-
# process_name = str(event_dict['pid'])
|
|
100
|
-
|
|
101
125
|
# Defining list if ips and other answers, which aren't IPs.
|
|
102
126
|
list_of_ips = list()
|
|
103
127
|
list_of_other_domains = list()
|
|
@@ -135,10 +159,19 @@ class DnsTrace:
|
|
|
135
159
|
event_dict['status'] = 'Error'
|
|
136
160
|
|
|
137
161
|
if self.enable_process_poller:
|
|
138
|
-
processes = self.process_poller.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
processes = self.process_poller.get_processes()
|
|
163
|
+
if event_dict['pid'] not in processes:
|
|
164
|
+
counter = 0
|
|
165
|
+
while counter < WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
|
|
166
|
+
processes = self.process_poller.get_processes()
|
|
167
|
+
if event_dict['pid'] not in processes:
|
|
168
|
+
time.sleep(0.1)
|
|
169
|
+
counter += 1
|
|
170
|
+
else:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
if counter == WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
|
|
174
|
+
print_api(f"Error: Couldn't get the process name for PID: {event_dict['pid']}.", color='red')
|
|
142
175
|
|
|
143
176
|
event_dict = psutilw.cross_single_connection_with_processes(event_dict, processes)
|
|
144
177
|
# If it was impossible to get the process name from the process poller, get it from psutil.
|
|
@@ -150,3 +183,13 @@ class DnsTrace:
|
|
|
150
183
|
event_dict, self.attrs, skip_keys_not_in_list=True)
|
|
151
184
|
|
|
152
185
|
return event_dict
|
|
186
|
+
|
|
187
|
+
def _get_processes_from_poller(self):
|
|
188
|
+
processes: dict = {}
|
|
189
|
+
while not processes:
|
|
190
|
+
processes = self.process_poller.get_processes()
|
|
191
|
+
|
|
192
|
+
if isinstance(processes, BaseException):
|
|
193
|
+
raise processes
|
|
194
|
+
|
|
195
|
+
return processes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from .. import trace, const
|
|
2
|
+
from ...wrappers.psutilw import psutilw
|
|
3
|
+
from ...wrappers import sysmonw
|
|
4
|
+
from ...basics import dicts
|
|
5
|
+
from ...print_api import print_api
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PROVIDER_NAME: str = const.ETW_SYSMON['provider_name']
|
|
9
|
+
PROVIDER_GUID: str = const.ETW_SYSMON['provider_guid']
|
|
10
|
+
PROCESS_CREATION_EVENT_ID: int = const.ETW_SYSMON['event_ids']['process_create']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SysmonProcessCreationTrace:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
attrs: list = None,
|
|
17
|
+
session_name: str = None,
|
|
18
|
+
close_existing_session_name: bool = True,
|
|
19
|
+
sysmon_directory: str = None
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008.
|
|
23
|
+
|
|
24
|
+
:param attrs: List of attributes to return. If None, all attributes will be returned.
|
|
25
|
+
:param session_name: The name of the session to create. If not provided, a UUID will be generated.
|
|
26
|
+
:param close_existing_session_name: Boolean to close existing session names.
|
|
27
|
+
True: if ETW session with 'session_name' exists, you will be notified and the session will be closed.
|
|
28
|
+
Then the new session with this name will be created.
|
|
29
|
+
False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
|
|
30
|
+
created. Instead, the existing session will be used. If there is a buffer from the previous session,
|
|
31
|
+
you will get the events from the buffer.
|
|
32
|
+
:param sysmon_directory: The directory where Sysmon is located. If not provided, "C:\\Windows\\Sysmon" will be
|
|
33
|
+
used. If 'Sysmon.exe' is not found in the directory, it will be downloaded from the internet.
|
|
34
|
+
|
|
35
|
+
-------------------------------------------------
|
|
36
|
+
|
|
37
|
+
Usage Example:
|
|
38
|
+
from atomicshop.etw import dns_trace
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
dns_trace_w = dns_trace.DnsTrace(
|
|
42
|
+
attrs=['pid', 'name', 'cmdline', 'domain', 'query_type'],
|
|
43
|
+
session_name='MyDnsTrace',
|
|
44
|
+
close_existing_session_name=True,
|
|
45
|
+
enable_process_poller=True,
|
|
46
|
+
process_poller_etw_session_name='MyProcessTrace'
|
|
47
|
+
)
|
|
48
|
+
dns_trace_w.start()
|
|
49
|
+
while True:
|
|
50
|
+
dns_dict = dns_trace_w.emit()
|
|
51
|
+
print(dns_dict)
|
|
52
|
+
dns_trace_w.stop()
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
self.attrs = attrs
|
|
56
|
+
self.sysmon_directory: str = sysmon_directory
|
|
57
|
+
|
|
58
|
+
self.event_trace = trace.EventTrace(
|
|
59
|
+
providers=[(PROVIDER_NAME, PROVIDER_GUID)],
|
|
60
|
+
# lambda x: self.event_queue.put(x),
|
|
61
|
+
event_id_filters=[PROCESS_CREATION_EVENT_ID],
|
|
62
|
+
session_name=session_name,
|
|
63
|
+
close_existing_session_name=close_existing_session_name
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def start(self):
|
|
67
|
+
sysmonw.start_as_service(
|
|
68
|
+
installation_path=self.sysmon_directory, download_sysmon_if_not_found=True, skip_if_running=True)
|
|
69
|
+
self.event_trace.start()
|
|
70
|
+
|
|
71
|
+
def stop(self):
|
|
72
|
+
self.event_trace.stop()
|
|
73
|
+
|
|
74
|
+
def emit(self):
|
|
75
|
+
"""
|
|
76
|
+
Function that will return the next event from the queue.
|
|
77
|
+
The queue is blocking, so if there is no event in the queue, the function will wait until there is one.
|
|
78
|
+
|
|
79
|
+
Usage Example:
|
|
80
|
+
while True:
|
|
81
|
+
dns_dict = dns_trace.emit()
|
|
82
|
+
print(dns_dict)
|
|
83
|
+
|
|
84
|
+
:return: Dictionary with the event data.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
event = self.event_trace.emit()
|
|
88
|
+
|
|
89
|
+
event_dict: dict = {
|
|
90
|
+
'etw_id': event[0],
|
|
91
|
+
'pid': event[1]['ProcessId'],
|
|
92
|
+
'process_guid': event[1]['ProcessGuid'],
|
|
93
|
+
'image': event[1]['Image'],
|
|
94
|
+
'file_version': event[1]['FileVersion'],
|
|
95
|
+
'product': event[1]['Product'],
|
|
96
|
+
'company': event[1]['Company'],
|
|
97
|
+
'original_file_name': event[1]['OriginalFileName'],
|
|
98
|
+
'command_line': event[1]['CommandLine'],
|
|
99
|
+
'current_directory': event[1]['CurrentDirectory'],
|
|
100
|
+
'user': event[1]['User'],
|
|
101
|
+
'logon_id': event[1]['LogonId'],
|
|
102
|
+
'logon_guid': event[1]['LogonGuid'],
|
|
103
|
+
'terminal_session_id': event[1]['TerminalSessionId'],
|
|
104
|
+
'integrity_level': event[1]['IntegrityLevel'],
|
|
105
|
+
'hashes': event[1]['Hashes'],
|
|
106
|
+
'parent_process_guid': event[1]['ParentProcessGuid'],
|
|
107
|
+
'parent_process_id': event[1]['ParentProcessId'],
|
|
108
|
+
'parent_image': event[1]['ParentImage'],
|
|
109
|
+
'parent_command_line': event[1]['ParentCommandLine']
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if self.attrs:
|
|
113
|
+
event_dict = dicts.reorder_keys(
|
|
114
|
+
event_dict, self.attrs, skip_keys_not_in_list=True)
|
|
115
|
+
|
|
116
|
+
return event_dict
|
|
@@ -41,7 +41,8 @@ class ChangeMonitor:
|
|
|
41
41
|
'url_playwright_jpeg'],
|
|
42
42
|
None] = None,
|
|
43
43
|
object_type_settings: dict = None,
|
|
44
|
-
etw_session_name: str = None
|
|
44
|
+
etw_session_name: str = None,
|
|
45
|
+
etw_process_session_name: str = None
|
|
45
46
|
):
|
|
46
47
|
"""
|
|
47
48
|
:param object_type: string, type of object to check. The type must be one of the following:
|
|
@@ -88,6 +89,8 @@ class ChangeMonitor:
|
|
|
88
89
|
with logman and other tools: logman query -ets
|
|
89
90
|
If not provided, a default name will be generated.
|
|
90
91
|
'dns': 'AtomicShopDnsTrace'
|
|
92
|
+
:param etw_process_session_name: string, the name of the ETW session for tracing process creation.
|
|
93
|
+
This is needed to correlate the process cmd with the DNS requests PIDs.
|
|
91
94
|
|
|
92
95
|
If 'input_directory' is not specified, the 'input_file_name' is not specified, and
|
|
93
96
|
'generate_input_file_name' is False, then the input file will not be used and the object will be stored
|
|
@@ -104,6 +107,7 @@ class ChangeMonitor:
|
|
|
104
107
|
self.object_type = object_type
|
|
105
108
|
self.object_type_settings: dict = object_type_settings
|
|
106
109
|
self.etw_session_name: str = etw_session_name
|
|
110
|
+
self.etw_process_session_name: str = etw_process_session_name
|
|
107
111
|
|
|
108
112
|
# === Additional variables ========================================
|
|
109
113
|
|
atomicshop/monitor/checks/dns.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Union
|
|
3
3
|
|
|
4
|
-
from ...
|
|
4
|
+
from ...etws.traces import trace_dns
|
|
5
5
|
from ...print_api import print_api
|
|
6
6
|
from ...import diff_check
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
INPUT_FILE_DEFAULT_NAME: str = 'known_domains.json'
|
|
10
10
|
INPUT_STATISTICS_FILE_DEFAULT_NAME: str = 'dns_statistics.json'
|
|
11
|
-
ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopDnsTrace'
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class DnsCheck:
|
|
@@ -22,15 +21,16 @@ class DnsCheck:
|
|
|
22
21
|
self.diff_checker_statistics: Union[diff_check.DiffChecker, None] = None
|
|
23
22
|
self.settings: dict = change_monitor_instance.object_type_settings
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
self.etw_session_name: str = change_monitor_instance.etw_session_name
|
|
27
|
-
else:
|
|
28
|
-
self.etw_session_name: str = ETW_DEFAULT_SESSION_NAME
|
|
24
|
+
self.etw_session_name: str = change_monitor_instance.etw_session_name
|
|
29
25
|
|
|
30
|
-
self.fetch_engine:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
session_name=self.etw_session_name,
|
|
26
|
+
self.fetch_engine: trace_dns.DnsRequestResponseTrace = (
|
|
27
|
+
trace_dns.DnsRequestResponseTrace(
|
|
28
|
+
attrs=['name', 'cmdline', 'domain', 'query_type'],
|
|
29
|
+
session_name=self.etw_session_name,
|
|
30
|
+
close_existing_session_name=True,
|
|
31
|
+
enable_process_poller=True,
|
|
32
|
+
process_poller_etw_session_name=change_monitor_instance.etw_process_session_name
|
|
33
|
+
)
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
if self.settings['alert_always'] and self.settings['alert_about_missing_entries_after_learning']:
|
|
@@ -106,7 +106,7 @@ class DnsCheck:
|
|
|
106
106
|
|
|
107
107
|
def _aggregation_process(self, event_dict: dict, return_list: list, print_kwargs: dict = None):
|
|
108
108
|
self.diff_checker_aggregation.check_object = [event_dict]
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
# Check if 'known_domains' list was updated from previous cycle.
|
|
111
111
|
result, message = self.diff_checker_aggregation.check_list_of_dicts(
|
|
112
112
|
sort_by_keys=['cmdline', 'name'], print_kwargs=print_kwargs)
|
atomicshop/process.py
CHANGED
|
@@ -11,7 +11,7 @@ from .basics import strings
|
|
|
11
11
|
from .wrappers import ubuntu_terminal
|
|
12
12
|
|
|
13
13
|
if os.name == 'nt':
|
|
14
|
-
from .
|
|
14
|
+
from . import process_poller
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def is_command_exists(cmd: str) -> bool:
|
|
@@ -265,7 +265,7 @@ def match_pattern_against_running_processes_cmdlines(
|
|
|
265
265
|
"""
|
|
266
266
|
|
|
267
267
|
# Get the list of all the currently running processes.
|
|
268
|
-
get_process_list = GetProcessList(get_method='pywin32', connect_on_init=True)
|
|
268
|
+
get_process_list = process_poller.GetProcessList(get_method='pywin32', connect_on_init=True)
|
|
269
269
|
processes = get_process_list.get_processes(as_dict=False)
|
|
270
270
|
|
|
271
271
|
# Iterate through all the current process, while fetching executable file 'name' and the command line.
|
atomicshop/process_poller.py
CHANGED
|
@@ -5,8 +5,10 @@ from typing import Literal, Union
|
|
|
5
5
|
|
|
6
6
|
from .wrappers.pywin32w import wmi_win32process
|
|
7
7
|
from .wrappers.psutilw import psutilw
|
|
8
|
+
from .etws.traces import trace_sysmon_process_creation
|
|
8
9
|
from .basics import list_of_dicts, dicts
|
|
9
10
|
from .process_name_cmd import ProcessNameCmdline
|
|
11
|
+
from .print_api import print_api
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def get_process_time_tester(
|
|
@@ -40,16 +42,24 @@ test = get_process_list.get_processes()
|
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
class GetProcessList:
|
|
45
|
+
"""
|
|
46
|
+
The class is responsible for getting the list of running processes.
|
|
47
|
+
|
|
48
|
+
Example of one time polling with 'pywin32' method:
|
|
49
|
+
from atomicshop import process_poller
|
|
50
|
+
process_list: dict = \
|
|
51
|
+
process_poller.GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
|
|
52
|
+
"""
|
|
43
53
|
def __init__(
|
|
44
54
|
self,
|
|
45
|
-
get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
|
|
55
|
+
get_method: Literal['psutil', 'pywin32', 'process_dll', 'sysmon_etw'] = 'process_dll',
|
|
46
56
|
connect_on_init: bool = False
|
|
47
57
|
):
|
|
48
58
|
"""
|
|
49
59
|
:param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
|
|
50
60
|
'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
|
|
51
61
|
'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
|
|
52
|
-
'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64
|
|
62
|
+
'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
|
|
53
63
|
:param connect_on_init: bool, if True, will connect to the service on init. 'psutil' don't need to connect.
|
|
54
64
|
"""
|
|
55
65
|
self.get_method = get_method
|
|
@@ -86,7 +96,7 @@ class GetProcessList:
|
|
|
86
96
|
"""
|
|
87
97
|
The function will get the list of opened processes and return it as a list of dicts.
|
|
88
98
|
|
|
89
|
-
:return: list of dicts, of opened processes.
|
|
99
|
+
:return: dict while key is pid or list of dicts, of opened processes (depending on 'as_dict' setting).
|
|
90
100
|
"""
|
|
91
101
|
|
|
92
102
|
if as_dict:
|
|
@@ -132,19 +142,14 @@ class ProcessPollerPool:
|
|
|
132
142
|
Later, I'll find a solution to make it more efficient.
|
|
133
143
|
"""
|
|
134
144
|
def __init__(
|
|
135
|
-
self,
|
|
145
|
+
self,
|
|
136
146
|
interval_seconds: Union[int, float] = 0,
|
|
137
147
|
operation: Literal['thread', 'process'] = 'thread',
|
|
138
|
-
poller_method: Literal['psutil', 'pywin32', 'process_dll'] = '
|
|
148
|
+
poller_method: Literal['psutil', 'pywin32', 'process_dll', 'sysmon_etw'] = 'sysmon_etw',
|
|
149
|
+
sysmon_etw_session_name: str = None,
|
|
150
|
+
sysmon_directory: str = None
|
|
139
151
|
):
|
|
140
152
|
"""
|
|
141
|
-
:param store_cycles: int, how many cycles to store. Each cycle is polling processes.
|
|
142
|
-
Example: Specifying 3 will store last 3 polled cycles of processes.
|
|
143
|
-
|
|
144
|
-
Default is 200, which means that 200 latest cycles original PIDs and their process names will be stored.
|
|
145
|
-
|
|
146
|
-
You can execute the 'get_process_time_tester' function in order to find the optimal number of cycles
|
|
147
|
-
and how much time it will take.
|
|
148
153
|
:param interval_seconds: float, how many seconds to wait between each cycle.
|
|
149
154
|
Default is 0, which means that the polling will be as fast as possible.
|
|
150
155
|
|
|
@@ -162,6 +167,18 @@ class ProcessPollerPool:
|
|
|
162
167
|
'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
|
|
163
168
|
'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
|
|
164
169
|
'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
|
|
170
|
+
'sysmon_etw': Get the list of processes with running SysMon by ETW - Event Tracing for Windows.
|
|
171
|
+
In this case 'store_cycles' and 'interval_seconds' are irrelevant, since the ETW is real-time.
|
|
172
|
+
Steps we take:
|
|
173
|
+
1. Check if SysMon is Running. If not, check if the executable exists in specified
|
|
174
|
+
location and start it as a service.
|
|
175
|
+
2. Start the "Microsoft-Windows-Sysmon" ETW session.
|
|
176
|
+
3. Take a snapshot of current processes and their CMDs with psutil and store it in a dict.
|
|
177
|
+
4. Each new process creation from ETW updates the dict.
|
|
178
|
+
:param sysmon_etw_session_name: str, only for 'sysmon_etw' get_method.
|
|
179
|
+
The name of the ETW session for tracing process creation.
|
|
180
|
+
:param sysmon_directory: str, only for 'sysmon_etw' get_method.
|
|
181
|
+
The directory where the SysMon executable is located. If non-existed will be downloaded.
|
|
165
182
|
---------------------------------------------
|
|
166
183
|
If there is an exception, ProcessPollerPool.processes will be set to the exception.
|
|
167
184
|
While getting the processes you can use this to execute the exception:
|
|
@@ -172,20 +189,20 @@ class ProcessPollerPool:
|
|
|
172
189
|
raise processes
|
|
173
190
|
"""
|
|
174
191
|
|
|
175
|
-
self.store_cycles: int = store_cycles
|
|
176
192
|
self.interval_seconds: float = interval_seconds
|
|
177
193
|
self.operation: str = operation
|
|
178
194
|
self.poller_method = poller_method
|
|
179
|
-
|
|
180
|
-
self.
|
|
195
|
+
self.sysmon_etw_session_name: str = sysmon_etw_session_name
|
|
196
|
+
self.sysmon_directory: str = sysmon_directory
|
|
181
197
|
|
|
182
198
|
# Current process pool.
|
|
183
|
-
self.
|
|
199
|
+
self._processes: dict = dict()
|
|
184
200
|
|
|
185
201
|
# The variable is responsible to stop the thread if it is running.
|
|
186
|
-
self.
|
|
202
|
+
self._running: bool = False
|
|
187
203
|
|
|
188
|
-
self.
|
|
204
|
+
self._process_queue = multiprocessing.Queue()
|
|
205
|
+
self._running_state_queue = multiprocessing.Queue()
|
|
189
206
|
|
|
190
207
|
def start(self):
|
|
191
208
|
if self.operation == 'thread':
|
|
@@ -195,66 +212,116 @@ class ProcessPollerPool:
|
|
|
195
212
|
else:
|
|
196
213
|
raise ValueError(f'Invalid operation type [{self.operation}]')
|
|
197
214
|
|
|
198
|
-
|
|
199
|
-
self.running = False
|
|
200
|
-
|
|
201
|
-
def _start_thread(self):
|
|
202
|
-
self.running = True
|
|
203
|
-
# threading.Thread(target=self._worker, args=(self.process_polling_instance,)).start()
|
|
204
|
-
thread = threading.Thread(target=self._worker)
|
|
215
|
+
thread = threading.Thread(target=self._thread_get_queue)
|
|
205
216
|
thread.daemon = True
|
|
206
217
|
thread.start()
|
|
207
218
|
|
|
208
|
-
def
|
|
209
|
-
self.
|
|
210
|
-
|
|
219
|
+
def stop(self):
|
|
220
|
+
self._running = False
|
|
221
|
+
self._running_state_queue.put(False)
|
|
211
222
|
|
|
212
|
-
|
|
223
|
+
def get_processes(self):
|
|
224
|
+
return self._processes
|
|
225
|
+
|
|
226
|
+
def _start_thread(self):
|
|
227
|
+
self._running = True
|
|
228
|
+
|
|
229
|
+
thread = threading.Thread(
|
|
230
|
+
target=_worker, args=(
|
|
231
|
+
self.poller_method, self._running_state_queue, self.interval_seconds,
|
|
232
|
+
self._process_queue, self.sysmon_etw_session_name, self.sysmon_directory,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
213
235
|
thread.daemon = True
|
|
214
236
|
thread.start()
|
|
215
237
|
|
|
216
|
-
def
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
try:
|
|
224
|
-
# If the list is full (to specified 'store_cycles'), remove the first element.
|
|
225
|
-
if len(list_of_processes) == self.store_cycles:
|
|
226
|
-
del list_of_processes[0]
|
|
227
|
-
|
|
228
|
-
# Get the current processes and reinitialize the instance of the dict.
|
|
229
|
-
current_processes: dict = dict(self.get_processes_list.get_processes())
|
|
238
|
+
def _start_process(self):
|
|
239
|
+
self._running = True
|
|
240
|
+
multiprocessing.Process(
|
|
241
|
+
target=_worker, args=(
|
|
242
|
+
self.poller_method, self._running_state_queue, self.interval_seconds,
|
|
243
|
+
self._process_queue, self.sysmon_etw_session_name, self.sysmon_directory,
|
|
244
|
+
)).start()
|
|
230
245
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
current_processes[pid]['cmdline'] = str()
|
|
235
|
-
elif process_info['cmdline'] == 'Error':
|
|
236
|
-
current_processes[pid]['cmdline'] = str()
|
|
246
|
+
def _thread_get_queue(self):
|
|
247
|
+
while True:
|
|
248
|
+
self._processes = self._process_queue.get()
|
|
237
249
|
|
|
238
|
-
# Append the current processes to the list.
|
|
239
|
-
list_of_processes.append(current_processes)
|
|
240
250
|
|
|
241
|
-
|
|
242
|
-
|
|
251
|
+
def _worker(
|
|
252
|
+
poller_method, running_state_queue, interval_seconds, process_queue, sysmon_etw_session_name, sysmon_directory):
|
|
253
|
+
def _worker_to_get_running_state():
|
|
254
|
+
nonlocal running_state
|
|
255
|
+
running_state = running_state_queue.get()
|
|
243
256
|
|
|
244
|
-
|
|
245
|
-
self.queue.put(self.processes)
|
|
257
|
+
running_state: bool = True
|
|
246
258
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
exception = e
|
|
251
|
-
except Exception as e:
|
|
252
|
-
self.running = False
|
|
253
|
-
exception = e
|
|
259
|
+
thread = threading.Thread(target=_worker_to_get_running_state)
|
|
260
|
+
thread.daemon = True
|
|
261
|
+
thread.start()
|
|
254
262
|
|
|
255
|
-
|
|
256
|
-
|
|
263
|
+
if poller_method == 'sysmon_etw':
|
|
264
|
+
poller_instance = trace_sysmon_process_creation.SysmonProcessCreationTrace(
|
|
265
|
+
attrs=['pid', 'original_file_name', 'command_line'],
|
|
266
|
+
session_name=sysmon_etw_session_name,
|
|
267
|
+
close_existing_session_name=True,
|
|
268
|
+
sysmon_directory=sysmon_directory
|
|
269
|
+
)
|
|
257
270
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
# We must initiate the connection inside the thread/process, because it is not thread-safe.
|
|
272
|
+
poller_instance.start()
|
|
273
|
+
|
|
274
|
+
processes = GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
|
|
275
|
+
process_queue.put(processes)
|
|
276
|
+
else:
|
|
277
|
+
poller_instance = GetProcessList(get_method=poller_method)
|
|
278
|
+
poller_instance.connect()
|
|
279
|
+
processes = {}
|
|
280
|
+
|
|
281
|
+
exception = None
|
|
282
|
+
list_of_processes: list = list()
|
|
283
|
+
while running_state:
|
|
284
|
+
try:
|
|
285
|
+
if poller_method == 'sysmon_etw':
|
|
286
|
+
# Get the current processes and reinitialize the instance of the dict.
|
|
287
|
+
current_cycle: dict = poller_instance.emit()
|
|
288
|
+
current_processes: dict = {int(current_cycle['pid']): {
|
|
289
|
+
'name': current_cycle['original_file_name'],
|
|
290
|
+
'cmdline': current_cycle['command_line']}
|
|
291
|
+
}
|
|
292
|
+
else:
|
|
293
|
+
# Get the current processes and reinitialize the instance of the dict.
|
|
294
|
+
current_processes: dict = dict(poller_instance.get_processes())
|
|
295
|
+
|
|
296
|
+
# Remove Command lines that contains only numbers, since they are useless.
|
|
297
|
+
for pid, process_info in current_processes.items():
|
|
298
|
+
if process_info['cmdline'].isnumeric():
|
|
299
|
+
current_processes[pid]['cmdline'] = str()
|
|
300
|
+
elif process_info['cmdline'] == 'Error':
|
|
301
|
+
current_processes[pid]['cmdline'] = str()
|
|
302
|
+
|
|
303
|
+
# This loop is essential for keeping the command lines.
|
|
304
|
+
# When the process unloads from memory, the last polling will have only pid and executable name, but not
|
|
305
|
+
# the command line. This loop will keep the command line from the previous polling if this happens.
|
|
306
|
+
for pid, process_info in current_processes.items():
|
|
307
|
+
if pid in processes:
|
|
308
|
+
if processes[pid]['name'] == current_processes[pid]['name']:
|
|
309
|
+
if current_processes[pid]['cmdline'] == '':
|
|
310
|
+
current_processes[pid]['cmdline'] = processes[pid]['cmdline']
|
|
311
|
+
processes.update(current_processes)
|
|
312
|
+
|
|
313
|
+
process_queue.put(processes)
|
|
314
|
+
|
|
315
|
+
# Since ETW is a blocking operation, we don't need to sleep.
|
|
316
|
+
if poller_method != 'sysmon_etw':
|
|
317
|
+
time.sleep(interval_seconds)
|
|
318
|
+
except KeyboardInterrupt as e:
|
|
319
|
+
running_state = False
|
|
320
|
+
exception = e
|
|
321
|
+
except Exception as e:
|
|
322
|
+
running_state = False
|
|
323
|
+
exception = e
|
|
324
|
+
print_api(f'Exception in ProcessPollerPool: {e}', color='red')
|
|
325
|
+
|
|
326
|
+
if not running_state:
|
|
327
|
+
process_queue.put(exception)
|
|
@@ -164,6 +164,15 @@ def filter_processes_with_present_connections(processes) -> list:
|
|
|
164
164
|
|
|
165
165
|
|
|
166
166
|
class PsutilProcesses:
|
|
167
|
+
"""
|
|
168
|
+
Class to get all the current processes.
|
|
169
|
+
|
|
170
|
+
Example get current running processes as dicts as
|
|
171
|
+
{'<pid'>: {'name': '<process_name>', 'cmdline': '<process_cmdline>'}}:
|
|
172
|
+
from atomicshop.wrappers.psutilw import psutilw
|
|
173
|
+
processes = psutilw.PsutilProcesses().get_processes_as_dict(
|
|
174
|
+
attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
|
|
175
|
+
"""
|
|
167
176
|
def __init__(self):
|
|
168
177
|
self.processes = None
|
|
169
178
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from .. import filesystem, web, process
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Define paths and configuration
|
|
8
|
+
DEFAULT_INSTALLATION_PATH: str = 'C:\\Sysmon'
|
|
9
|
+
SYSMON_FILE_NAME: str = 'Sysmon.exe'
|
|
10
|
+
SYSINTERNALS_SYSMON_URL: str = 'https://download.sysinternals.com/files/Sysmon.zip'
|
|
11
|
+
SYSMON_CONFIG_FILE_NAME: str = 'sysmonconfig.xml'
|
|
12
|
+
SYSMON_CONFIG_FILE_PATH: str = os.path.join(DEFAULT_INSTALLATION_PATH, SYSMON_CONFIG_FILE_NAME)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigFileNotFoundError(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SymonExecutableNotFoundError(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SysmonAlreadyRunningError(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def download_sysmon(installation_path: str = None):
|
|
28
|
+
"""
|
|
29
|
+
Install Sysmon on the system.
|
|
30
|
+
|
|
31
|
+
:param installation_path: string, full path where to put the Sysmon executable.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
if not installation_path:
|
|
35
|
+
installation_path = DEFAULT_INSTALLATION_PATH
|
|
36
|
+
|
|
37
|
+
# Check if the file exists
|
|
38
|
+
if not os.path.exists(installation_path):
|
|
39
|
+
filesystem.create_directory(installation_path)
|
|
40
|
+
|
|
41
|
+
web.download_and_extract_file(SYSINTERNALS_SYSMON_URL, installation_path)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_sysmon_running():
|
|
45
|
+
"""
|
|
46
|
+
Check if Sysmon is running.
|
|
47
|
+
|
|
48
|
+
:return: boolean, True if Sysmon is running, False otherwise.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
process_list: list = process.match_pattern_against_running_processes_cmdlines(
|
|
52
|
+
pattern=SYSMON_FILE_NAME, first=True, process_name_case_insensitive=True)
|
|
53
|
+
|
|
54
|
+
if process_list:
|
|
55
|
+
return True
|
|
56
|
+
else:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def start_as_service(
|
|
61
|
+
installation_path: str = None,
|
|
62
|
+
config_file_path: str = None,
|
|
63
|
+
use_config_in_same_directory: bool = False,
|
|
64
|
+
download_sysmon_if_not_found: bool = False,
|
|
65
|
+
skip_if_running: bool = False
|
|
66
|
+
):
|
|
67
|
+
"""
|
|
68
|
+
Start Sysmon as a service. Besides starting, it installs itself as a service, meaning that on the next boot,
|
|
69
|
+
it will start automatically.
|
|
70
|
+
|
|
71
|
+
:param installation_path: string, full path where to put the Sysmon executable.
|
|
72
|
+
:param config_file_path: string, full path to the configuration file.
|
|
73
|
+
:param use_config_in_same_directory: boolean, if True, the function will use the configuration file in the same
|
|
74
|
+
directory as the Sysmon executable.
|
|
75
|
+
:param download_sysmon_if_not_found: boolean, if True, the function will download Sysmon if it is not
|
|
76
|
+
found in the 'installation_path'.
|
|
77
|
+
:param skip_if_running: boolean,
|
|
78
|
+
True, the function will not start Sysmon if it is already running.
|
|
79
|
+
False, the function will raise 'SysmonAlreadyRunningError' exception if it is already running.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# Check if sysmon already running.
|
|
83
|
+
if is_sysmon_running():
|
|
84
|
+
if skip_if_running:
|
|
85
|
+
return
|
|
86
|
+
else:
|
|
87
|
+
raise SysmonAlreadyRunningError("Sysmon is already running.")
|
|
88
|
+
|
|
89
|
+
if config_file_path and use_config_in_same_directory:
|
|
90
|
+
raise ValueError("You cannot use both 'config_file_path' and 'use_config_in_same_directory'.")
|
|
91
|
+
|
|
92
|
+
if use_config_in_same_directory:
|
|
93
|
+
config_file_path = SYSMON_CONFIG_FILE_PATH
|
|
94
|
+
|
|
95
|
+
# Check if the file exists
|
|
96
|
+
if not os.path.exists(config_file_path):
|
|
97
|
+
raise ConfigFileNotFoundError(f"Configuration file '{config_file_path}' not found.")
|
|
98
|
+
|
|
99
|
+
if not installation_path:
|
|
100
|
+
installation_path = DEFAULT_INSTALLATION_PATH
|
|
101
|
+
|
|
102
|
+
sysmon_file_path: str = os.path.join(installation_path, SYSMON_FILE_NAME)
|
|
103
|
+
|
|
104
|
+
# Check if the file exists
|
|
105
|
+
if not os.path.exists(sysmon_file_path):
|
|
106
|
+
if download_sysmon_if_not_found:
|
|
107
|
+
download_sysmon(installation_path)
|
|
108
|
+
else:
|
|
109
|
+
raise SymonExecutableNotFoundError(f"Sysmon executable '{sysmon_file_path}' not found.")
|
|
110
|
+
|
|
111
|
+
# Start Sysmon as a service.
|
|
112
|
+
subprocess.run([sysmon_file_path, '-accepteula', '-i', config_file_path])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def stop_service(installation_path: str = None):
|
|
116
|
+
"""
|
|
117
|
+
Stop Sysmon service.
|
|
118
|
+
|
|
119
|
+
:param installation_path: string, full path where to put the Sysmon executable.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
if not installation_path:
|
|
123
|
+
installation_path = DEFAULT_INSTALLATION_PATH
|
|
124
|
+
|
|
125
|
+
sysmon_file_path: str = os.path.join(installation_path, SYSMON_FILE_NAME)
|
|
126
|
+
|
|
127
|
+
# Check if the file exists
|
|
128
|
+
if not os.path.exists(sysmon_file_path):
|
|
129
|
+
raise SymonExecutableNotFoundError(f"Sysmon executable '{sysmon_file_path}' not found.")
|
|
130
|
+
|
|
131
|
+
# Stop Sysmon service.
|
|
132
|
+
subprocess.run([sysmon_file_path, '-u'])
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def change_config_on_the_fly(config_file_path: str, installation_path: str = None):
|
|
136
|
+
"""
|
|
137
|
+
Change the Sysmon configuration on the fly.
|
|
138
|
+
|
|
139
|
+
:param config_file_path: string, full path to the configuration file.
|
|
140
|
+
:param installation_path: string, full path where to put the Sysmon executable.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
if not installation_path:
|
|
144
|
+
installation_path = DEFAULT_INSTALLATION_PATH
|
|
145
|
+
|
|
146
|
+
sysmon_file_path: str = os.path.join(installation_path, SYSMON_FILE_NAME)
|
|
147
|
+
|
|
148
|
+
# Check if the file exists
|
|
149
|
+
if not os.path.exists(sysmon_file_path):
|
|
150
|
+
raise SymonExecutableNotFoundError(f"Sysmon executable '{sysmon_file_path}' not found.")
|
|
151
|
+
|
|
152
|
+
# Change the configuration on the fly.
|
|
153
|
+
subprocess.run([sysmon_file_path, '-c', config_file_path])
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=WBaPNPGU9skcNgQ8LWfW7jNFP6EyisSDfqWEliqkbZg,124
|
|
2
2
|
atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
|
|
3
3
|
atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
|
|
4
4
|
atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
|
|
@@ -9,8 +9,8 @@ atomicshop/config_init.py,sha256=z2RXD_mw9nQlAOpuGry1h9QT-2LhNscXgGAktN3dCVQ,249
|
|
|
9
9
|
atomicshop/console_output.py,sha256=AOSJjrRryE97PAGtgDL03IBtWSi02aNol8noDnW3k6M,4667
|
|
10
10
|
atomicshop/console_user_response.py,sha256=31HIy9QGXa7f-GVR8MzJauQ79E_ZqAeagF3Ks4GGdDU,3234
|
|
11
11
|
atomicshop/datetimes.py,sha256=olsL01S5tkXk4WPzucxujqgLOh198BLgJntDnGYukRU,15533
|
|
12
|
-
atomicshop/diff_check.py,sha256=
|
|
13
|
-
atomicshop/dns.py,sha256=
|
|
12
|
+
atomicshop/diff_check.py,sha256=RJvzJhyYAZyRPKVDk1dS7UwZCx0kq__WDZ6N0rNfZUY,27110
|
|
13
|
+
atomicshop/dns.py,sha256=h4uZKoz4wbBlLOOduL1GtRcTm-YpiPnGOEGxUm7hhOI,2140
|
|
14
14
|
atomicshop/domains.py,sha256=Rxu6JhhMqFZRcoFs69IoEd1PtYca0lMCG6F1AomP7z4,3197
|
|
15
15
|
atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
|
|
16
16
|
atomicshop/file_types.py,sha256=-0jzQMRlmU1AP9DARjk-HJm1tVE22E6ngP2mRblyEjY,763
|
|
@@ -25,9 +25,9 @@ atomicshop/on_exit.py,sha256=Wf1iy2e0b0Zu7oRxrct3VkLdQ_x9B32-z_JerKTt9Z0,5493
|
|
|
25
25
|
atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
|
|
26
26
|
atomicshop/permissions.py,sha256=P6tiUKV-Gw-c3ePEVsst9bqWaHJbB4ZlJB4xbDYVpEs,4436
|
|
27
27
|
atomicshop/print_api.py,sha256=DhbCQd0MWZZ5GYEk4oTu1opRFC-b31g1VWZgTGewG2Y,11568
|
|
28
|
-
atomicshop/process.py,sha256=
|
|
28
|
+
atomicshop/process.py,sha256=R1BtXWjG2g2Q3WlsyhbIlXZz0UkQeagY7fQyBOIX_DM,15951
|
|
29
29
|
atomicshop/process_name_cmd.py,sha256=CtaSp3mgxxJKCCVW8BLx6BJNx4giCklU_T7USiCEwfc,5162
|
|
30
|
-
atomicshop/process_poller.py,sha256=
|
|
30
|
+
atomicshop/process_poller.py,sha256=sEuuFu3gmUrp4tY1LVxE27L0eK5LIwNKjTbV7KtzeRM,15029
|
|
31
31
|
atomicshop/python_file_patcher.py,sha256=kd3rBWvTcosLEk-7TycNdfKW9fZbe161iVwmH4niUo0,5515
|
|
32
32
|
atomicshop/python_functions.py,sha256=zJg4ogUwECxrDD7xdDN5JikIUctITM5lsyabr_ZNsRw,4435
|
|
33
33
|
atomicshop/question_answer_engine.py,sha256=DuOn7QEgKKfqZu2cR8mVeFIfFgayfBHiW-jY2VPq_Fo,841
|
|
@@ -83,7 +83,7 @@ atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF
|
|
|
83
83
|
atomicshop/basics/booleans.py,sha256=va3LYIaSOhjdifW4ZEesnIQxBICNHyQjUAkYelzchhE,2047
|
|
84
84
|
atomicshop/basics/bytes_arrays.py,sha256=WvSRDhIGt1ywF95t-yNgpxLm1nlZUbM1Dz6QckcyE8Y,5915
|
|
85
85
|
atomicshop/basics/classes.py,sha256=EijW_g4EhdNBnKPMG3nT3HjFspTchtM7to6zm9Ad_Mk,9771
|
|
86
|
-
atomicshop/basics/dicts.py,sha256=
|
|
86
|
+
atomicshop/basics/dicts.py,sha256=DeYHIh940pMMBrFhpXt4dsigFVYzTrlqWymNo4Pq_Js,14049
|
|
87
87
|
atomicshop/basics/dicts_nested.py,sha256=StYxYnYPa0SEJr1lmEwAv5zfERWWqoULeyG8e0zRAwE,4107
|
|
88
88
|
atomicshop/basics/enumerations.py,sha256=41VVQYh_vnVapggxKg2IRU5e_EiMpZzX1n1mtxvoSzM,1364
|
|
89
89
|
atomicshop/basics/enums.py,sha256=CeV8MfqWHihK7vvV6Mbzq7rD9JykeQfrJeFdLVzfHI4,3547
|
|
@@ -96,16 +96,20 @@ atomicshop/basics/list_of_dicts.py,sha256=qI2uoYIcHjR8RSD5vtkqhpMgL6XTYRGJDcr9cb
|
|
|
96
96
|
atomicshop/basics/lists.py,sha256=I0C62vrDrNwCTNl0EjUZNa1Jsd8l0rTkp28GEx9QoEI,4258
|
|
97
97
|
atomicshop/basics/multiprocesses.py,sha256=nSskxJSlEdalPM_Uf8cc9kAYYlVwYM1GonBLAhCL2mM,18831
|
|
98
98
|
atomicshop/basics/numbers.py,sha256=ESX0z_7o_ok3sOmCKAUBoZinATklgMy2v-4RndqXlVM,1837
|
|
99
|
+
atomicshop/basics/package_module.py,sha256=fBd0uVgFce25ZCVtLq83iyowRlbwdWYFj_t4Ml7LU14,391
|
|
99
100
|
atomicshop/basics/randoms.py,sha256=DmYLtnIhDK29tAQrGP1Nt-A-v8WC7WIEB8Edi-nk3N4,282
|
|
100
101
|
atomicshop/basics/strings.py,sha256=T4MpEpwxqsiOSnXcwYkqMKB5okHiJfvUCO7t5kcRtBg,18316
|
|
101
102
|
atomicshop/basics/threads.py,sha256=xvgdDJdmgN0wmmARoZ-H7Kvl1GOcEbvgaeGL4M3Hcx8,2819
|
|
102
103
|
atomicshop/basics/timeit_template.py,sha256=fYLrk-X_dhdVtnPU22tarrhhvlggeW6FdKCXM8zkX68,405
|
|
103
104
|
atomicshop/basics/tracebacks.py,sha256=cNfh_oAwF55kSIdqtv3boHZQIoQI8TajxkTnwJwpweI,535
|
|
104
|
-
atomicshop/
|
|
105
|
-
atomicshop/
|
|
106
|
-
atomicshop/
|
|
107
|
-
atomicshop/
|
|
108
|
-
atomicshop/
|
|
105
|
+
atomicshop/etws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
|
+
atomicshop/etws/const.py,sha256=v3x_IdCYeSKbCGywiZFOZln80ldpwKW5nuMDuUe51Jg,1257
|
|
107
|
+
atomicshop/etws/providers.py,sha256=fVmWi-uGdtnsQTDpu_ty6dzx0GMhGokiST73LNBEJ38,129
|
|
108
|
+
atomicshop/etws/sessions.py,sha256=k3miewU278xn829cqDbsuH_bmZHPQE9-Zn-hINbxUSE,1330
|
|
109
|
+
atomicshop/etws/trace.py,sha256=tjBvV4RxCSn8Aoxj8NVxmqHekdECne_y4Zv2lbh11ak,5341
|
|
110
|
+
atomicshop/etws/traces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
111
|
+
atomicshop/etws/traces/trace_dns.py,sha256=wRFwfFgVgMbYJu1Sf0Yy9LPoMrf9kXejl2Xjk3PNELQ,8430
|
|
112
|
+
atomicshop/etws/traces/trace_sysmon_process_creation.py,sha256=pjb0qzNZttucEWOKv2j_BGVqaSeKjBL4O8oCsAteiGU,4785
|
|
109
113
|
atomicshop/file_io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
114
|
atomicshop/file_io/csvs.py,sha256=y8cJtnlN-NNxNupzJgSeGq9aQ4wNxYLFPX9vNNlUiIc,5830
|
|
111
115
|
atomicshop/file_io/docxs.py,sha256=6tcYFGp0vRsHR47VwcRqwhdt2DQOwrAUYhrwN996n9U,5117
|
|
@@ -135,9 +139,9 @@ atomicshop/mitm/engines/__reference_general/parser___reference_general.py,sha256
|
|
|
135
139
|
atomicshop/mitm/engines/__reference_general/recorder___reference_general.py,sha256=KENDVf9OwXD9gwSh4B1XxACCe7iHYjrvnW1t6F64wdE,695
|
|
136
140
|
atomicshop/mitm/engines/__reference_general/responder___reference_general.py,sha256=1AM49UaFTKA0AHw-k3SV3uH3QbG-o6ux0c-GoWkKNU0,6993
|
|
137
141
|
atomicshop/monitor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
138
|
-
atomicshop/monitor/change_monitor.py,sha256=
|
|
142
|
+
atomicshop/monitor/change_monitor.py,sha256=eJRP7NBnA-Y_n6Ofm_jKrR5XguJV6gEmxFdtdGMBF3k,7866
|
|
139
143
|
atomicshop/monitor/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
140
|
-
atomicshop/monitor/checks/dns.py,sha256=
|
|
144
|
+
atomicshop/monitor/checks/dns.py,sha256=_DxInUSiiMa8Bx1sYJ6fRzRl7n_eAsoaGHhUk9XgF7w,7182
|
|
141
145
|
atomicshop/monitor/checks/file.py,sha256=2tIDSlX2KZNc_9i9ji1tcOqupbFTIOj7cKXLyBEDWMk,3263
|
|
142
146
|
atomicshop/monitor/checks/network.py,sha256=CGZWl4WlQrxayZeVF9JspJXwYA-zWx8ECWTVGSlXc98,3825
|
|
143
147
|
atomicshop/monitor/checks/process_running.py,sha256=x66wd6-l466r8sbRQaIli0yswyGt1dH2DVXkGDL6O0Q,1891
|
|
@@ -163,6 +167,7 @@ atomicshop/wrappers/pipw.py,sha256=mu4jnHkSaYNfpBiLZKMZxEX_E2LqW5BVthMZkblPB_c,1
|
|
|
163
167
|
atomicshop/wrappers/process_wrapper_pbtk.py,sha256=ycPmBRnv627RWks6N8OhxJQe8Gu3h3Vwj-4HswPOw0k,599
|
|
164
168
|
atomicshop/wrappers/pycharmw.py,sha256=OHcaVlyhIqhgRioPhkeS5krDZ_NezZjpBCvyRiLjWwI,2723
|
|
165
169
|
atomicshop/wrappers/pyopensslw.py,sha256=OBWxA6EJ2vU_Qlf4M8m6ilcG3hyYB4yB0EsXUf7NhEU,6804
|
|
170
|
+
atomicshop/wrappers/sysmonw.py,sha256=5D_tl6Y0j9bHSDaoUsipe6ZZYxnXjRnOLis8e5FZ2cI,5215
|
|
166
171
|
atomicshop/wrappers/ubuntu_terminal.py,sha256=BBZD3EH6KSDORd5IZBZM-ti4U6Qh1sZwftx42s7hqB4,10917
|
|
167
172
|
atomicshop/wrappers/wslw.py,sha256=AKphiHLSddL7ErevUowr3f9Y1AgGz_R3KZ3NssW07h8,6959
|
|
168
173
|
atomicshop/wrappers/certauthw/certauth.py,sha256=hKedW0DOWlEigSNm8wu4SqHkCQsGJ1tJfH7s4nr3Bk0,12223
|
|
@@ -239,7 +244,7 @@ atomicshop/wrappers/playwrightw/waits.py,sha256=308fdOu6YDqQ7K7xywj7R27sSmFanPBQ
|
|
|
239
244
|
atomicshop/wrappers/psutilw/cpus.py,sha256=w6LPBMINqS-T_X8vzdYkLS2Wzuve28Ydp_GafTCngrc,236
|
|
240
245
|
atomicshop/wrappers/psutilw/disks.py,sha256=3ZSVoommKH1TWo37j_83frB-NqXF4Nf5q5mBCX8G4jE,9221
|
|
241
246
|
atomicshop/wrappers/psutilw/memories.py,sha256=_S0aL8iaoIHebd1vOFrY_T9aROM5Jx2D5CvDh_4j0Vc,528
|
|
242
|
-
atomicshop/wrappers/psutilw/psutilw.py,sha256=
|
|
247
|
+
atomicshop/wrappers/psutilw/psutilw.py,sha256=q3EwgprqyrR4zLCjl4l5DHFOQoukEvQMIPjNB504oQ0,21262
|
|
243
248
|
atomicshop/wrappers/psycopgw/psycopgw.py,sha256=XJvVf0oAUjCHkrYfKeFuGCpfn0Oxj3u4SbKMKA1508E,7118
|
|
244
249
|
atomicshop/wrappers/pywin32w/console.py,sha256=LstHajPLgXp9qQxFNR44QfH10nOnNp3bCJquxaTquns,1175
|
|
245
250
|
atomicshop/wrappers/pywin32w/winshell.py,sha256=i2bKiMldPU7_azsD5xGQDdMwjaM7suKJd3k0Szmcs6c,723
|
|
@@ -260,8 +265,8 @@ atomicshop/wrappers/socketw/socket_server_tester.py,sha256=AhpurHJmP2kgzHaUbq5ey
|
|
|
260
265
|
atomicshop/wrappers/socketw/socket_wrapper.py,sha256=aXBwlEIJhFT0-c4i8iNlFx2It9VpCEpsv--5Oqcpxao,11624
|
|
261
266
|
atomicshop/wrappers/socketw/ssl_base.py,sha256=k4V3gwkbq10MvOH4btU4onLX2GNOsSfUAdcHmL1rpVE,2274
|
|
262
267
|
atomicshop/wrappers/socketw/statistics_csv.py,sha256=t3dtDEfN47CfYVi0CW6Kc2QHTEeZVyYhc57IYYh5nmA,826
|
|
263
|
-
atomicshop-2.
|
|
264
|
-
atomicshop-2.
|
|
265
|
-
atomicshop-2.
|
|
266
|
-
atomicshop-2.
|
|
267
|
-
atomicshop-2.
|
|
268
|
+
atomicshop-2.13.0.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
269
|
+
atomicshop-2.13.0.dist-info/METADATA,sha256=m6jCKh9vX3yFeWZn_wgOmq5M-FK2Czr6eypskj7TVHo,10478
|
|
270
|
+
atomicshop-2.13.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
271
|
+
atomicshop-2.13.0.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
272
|
+
atomicshop-2.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|