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.

Files changed (35) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/datetimes.py +3 -2
  3. atomicshop/etws/providers.py +18 -2
  4. atomicshop/etws/sessions.py +1 -1
  5. atomicshop/etws/trace.py +5 -22
  6. atomicshop/etws/traces/trace_dns.py +17 -14
  7. atomicshop/etws/traces/trace_sysmon_process_creation.py +34 -22
  8. atomicshop/file_io/csvs.py +1 -1
  9. atomicshop/get_process_list.py +133 -0
  10. atomicshop/mitm/statistic_analyzer.py +376 -3
  11. atomicshop/monitor/change_monitor.py +1 -0
  12. atomicshop/monitor/checks/dns.py +2 -1
  13. atomicshop/process.py +3 -3
  14. atomicshop/process_poller/__init__.py +0 -0
  15. atomicshop/process_poller/pollers/__init__.py +0 -0
  16. atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
  17. atomicshop/process_poller/process_pool.py +208 -0
  18. atomicshop/process_poller/simple_process_pool.py +112 -0
  19. atomicshop/process_poller/tracer_base.py +45 -0
  20. atomicshop/process_poller/tracers/__init__.py +0 -0
  21. atomicshop/process_poller/tracers/event_log.py +46 -0
  22. atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
  23. atomicshop/wrappers/ctyping/etw_winapi/const.py +130 -20
  24. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +141 -5
  25. atomicshop/wrappers/loggingw/reading.py +20 -19
  26. atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +0 -2
  27. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -24
  28. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +103 -0
  29. {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/METADATA +1 -1
  30. {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/RECORD +34 -24
  31. atomicshop/process_poller.py +0 -345
  32. /atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +0 -0
  33. {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/LICENSE.txt +0 -0
  34. {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/WHEEL +0 -0
  35. {atomicshop-2.14.2.dist-info → atomicshop-2.14.4.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.14.2'
4
+ __version__ = '2.14.4'
atomicshop/datetimes.py CHANGED
@@ -47,7 +47,7 @@ class MonthToNumber:
47
47
  'דצמבר': '12'}
48
48
 
49
49
 
50
- def get_datetime_from_complex_string_by_pattern(complex_string: str, date_pattern: str):
50
+ def get_datetime_from_complex_string_by_pattern(complex_string: str, date_pattern: str) -> tuple[datetime, str, float]:
51
51
  """
52
52
  Function will get datetime object from a complex string by pattern.
53
53
 
@@ -65,7 +65,8 @@ def get_datetime_from_complex_string_by_pattern(complex_string: str, date_patter
65
65
  if date_str:
66
66
  # Convert the date string to a datetime object based on the given pattern
67
67
  date_obj = datetime.datetime.strptime(date_str.group(), date_pattern)
68
- return date_obj
68
+ date_timestamp = date_obj.timestamp()
69
+ return date_obj, date_str.group(), date_timestamp
69
70
  else:
70
71
  raise ValueError("No valid date found in the string")
71
72
 
@@ -1,5 +1,21 @@
1
+ from typing import Literal
2
+
1
3
  from ..wrappers.ctyping.etw_winapi import etw_functions
2
4
 
3
5
 
4
- def get_providers():
5
- return etw_functions.get_all_providers()
6
+ def get_providers(key_as: Literal['name', 'guid'] = 'name'):
7
+ return etw_functions.get_all_providers(key_as=key_as)
8
+
9
+
10
+ def get_provider_guid_by_name(provider_name):
11
+ providers = get_providers(key_as='name')
12
+
13
+ try:
14
+ provider_guid = providers[provider_name]
15
+ except KeyError:
16
+ provider_guid = None
17
+
18
+ if not provider_guid:
19
+ raise ValueError(f"Provider '{provider_name}' not found")
20
+
21
+ return provider_guid
@@ -1,7 +1,7 @@
1
1
  from ..wrappers.ctyping.etw_winapi import etw_functions
2
2
 
3
3
 
4
- def stop_and_delete(session_name) -> tuple[bool, int]:
4
+ def stop_and_delete(session_name: str) -> tuple[bool, int]:
5
5
  """
6
6
  Stop and delete ETW session.
7
7
 
atomicshop/etws/trace.py CHANGED
@@ -1,19 +1,16 @@
1
1
  import queue
2
2
  import sys
3
3
  import time
4
- from typing import Literal
5
4
 
6
5
  # Import FireEye Event Tracing library.
7
6
  import etw
8
7
 
9
8
  from ..print_api import print_api
10
9
  from . import sessions
11
- from .. import process_poller
10
+ from ..process_poller import simple_process_pool
12
11
  from ..wrappers.psutilw import psutilw
13
12
 
14
13
 
15
- PROCESS_POLLER_ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopProcessTrace'
16
-
17
14
  WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
18
15
  WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
19
16
 
@@ -26,8 +23,7 @@ class EventTrace(etw.ETW):
26
23
  event_id_filters: list = None,
27
24
  session_name: str = None,
28
25
  close_existing_session_name: bool = True,
29
- enable_process_poller: bool = False,
30
- process_poller_method: Literal['psutil', 'pywin32', 'process_dll', 'sysmon_etw', 'event_log'] = 'event_log'
26
+ enable_process_poller: bool = False
31
27
  ):
32
28
  """
33
29
  :param providers: List of tuples with provider name and provider GUID.
@@ -41,13 +37,6 @@ class EventTrace(etw.ETW):
41
37
  :param enable_process_poller: Boolean to enable process poller. Gets the process PID, Name and CommandLine.
42
38
  Since the DNS events doesn't contain the process name and command line, only PID.
43
39
  Then DNS events will be enriched with the process name and command line from the process poller.
44
- :param process_poller_method: The method to get the process information. For more information, see the
45
- 'process_poller.ProcessPollerPool' class. Summary:
46
- 'psutil': Uses 'psutil' library to get the process information.
47
- 'pywin32': Uses 'pywin32' library to get the process information.
48
- 'process_dll': Uses 'process' custom DLL to get the process information.
49
- 'sysmon_etw': Uses 'sysmon_etw' uses sysmon and ETW to get the process information.
50
- 'event_log': Uses Security Windows EVent Log channel (event id 4688) to get the process information.
51
40
 
52
41
  ------------------------------------------
53
42
 
@@ -84,14 +73,8 @@ class EventTrace(etw.ETW):
84
73
  for provider in providers:
85
74
  etw_format_providers.append(etw.ProviderInfo(provider[0], etw.GUID(provider[1])))
86
75
 
87
- process_poller_etw_session_name = None
88
- if process_poller_method == 'sysmon_etw':
89
- process_poller_etw_session_name = PROCESS_POLLER_ETW_DEFAULT_SESSION_NAME
90
-
91
76
  if self.enable_process_poller:
92
- self.process_poller = process_poller.ProcessPollerPool(
93
- operation='process', poller_method=process_poller_method,
94
- sysmon_etw_session_name=process_poller_etw_session_name)
77
+ self.process_poller = simple_process_pool.SimpleProcessPool()
95
78
 
96
79
  super().__init__(
97
80
  providers=etw_format_providers, event_callback=function_callable, event_id_filters=event_id_filters,
@@ -152,8 +135,8 @@ class EventTrace(etw.ETW):
152
135
  event: tuple = self.event_queue.get()
153
136
 
154
137
  event_dict: dict = {
155
- 'event_id': event[0],
156
- 'event': event[1],
138
+ 'EventId': event[0],
139
+ 'EventHeader': event[1],
157
140
  'pid': event[1]['EventHeader']['ProcessId']
158
141
  }
159
142
 
@@ -1,10 +1,5 @@
1
- import time
2
-
3
1
  from .. import trace, const
4
- from ...wrappers.psutilw import psutilw
5
2
  from ...basics import dicts
6
- from ...process_poller import ProcessPollerPool
7
- from ...print_api import print_api
8
3
  from ... import dns
9
4
 
10
5
 
@@ -24,7 +19,8 @@ class DnsRequestResponseTrace:
24
19
  self,
25
20
  attrs: list = None,
26
21
  session_name: str = None,
27
- close_existing_session_name: bool = True
22
+ close_existing_session_name: bool = True,
23
+ skip_record_list: list = None
28
24
  ):
29
25
  """
30
26
  :param attrs: List of attributes to return. If None, all attributes will be returned.
@@ -35,6 +31,7 @@ class DnsRequestResponseTrace:
35
31
  False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
36
32
  created. Instead, the existing session will be used. If there is a buffer from the previous session,
37
33
  you will get the events from the buffer.
34
+ :param skip_record_list: List of DNS Records to skip emitting. Example: ['PTR', 'SRV']
38
35
 
39
36
  -------------------------------------------------
40
37
 
@@ -56,6 +53,7 @@ class DnsRequestResponseTrace:
56
53
  """
57
54
 
58
55
  self.attrs = attrs
56
+ self.skip_record_list = skip_record_list
59
57
 
60
58
  if not session_name:
61
59
  session_name = ETW_DEFAULT_SESSION_NAME
@@ -91,23 +89,28 @@ class DnsRequestResponseTrace:
91
89
  event = self.event_trace.emit()
92
90
 
93
91
  event_dict: dict = {
94
- 'event_id': event['event_id'],
95
- 'domain': event['event']['QueryName'],
96
- 'query_type_id': str(event['event']['QueryType']),
97
- 'query_type': dns.TYPES_DICT[str(event['event']['QueryType'])],
92
+ 'event_id': event['EventId'],
93
+ 'domain': event['EventHeader']['QueryName'],
94
+ 'query_type_id': str(event['EventHeader']['QueryType']),
95
+ 'query_type': dns.TYPES_DICT[str(event['EventHeader']['QueryType'])],
98
96
  'pid': event['pid'],
99
97
  'name': event['name'],
100
98
  'cmdline': event['cmdline']
101
99
  }
102
100
 
101
+ # Skip emitting the record if it is in the 'skip_record_list'.
102
+ # Just recall the function to get the next event and return it.
103
+ if event_dict['query_type'] in self.skip_record_list:
104
+ return self.emit()
105
+
103
106
  # Defining list if ips and other answers, which aren't IPs.
104
107
  list_of_ips = list()
105
108
  list_of_other_domains = list()
106
109
  # Parse DNS results, only if 'QueryResults' key isn't empty, since many of the events are, mostly due errors.
107
- if event['event']['QueryResults']:
110
+ if event['EventHeader']['QueryResults']:
108
111
  # 'QueryResults' key contains a string with all the 'Answers' divided by type and ';' character.
109
112
  # Basically, we can parse each type out of string, but we need only IPs and other answers.
110
- list_of_parameters = event['event']['QueryResults'].split(';')
113
+ list_of_parameters = event['EventHeader']['QueryResults'].split(';')
111
114
 
112
115
  # Iterating through all the parameters that we got from 'QueryResults' key.
113
116
  for parameter in list_of_parameters:
@@ -127,11 +130,11 @@ class DnsRequestResponseTrace:
127
130
  event_dict['other_domains'] = list_of_other_domains
128
131
 
129
132
  # Getting the 'QueryStatus' key.
130
- event_dict['status_id'] = event['event']['QueryStatus']
133
+ event_dict['status_id'] = event['EventHeader']['QueryStatus']
131
134
 
132
135
  # Getting the 'QueryStatus' key. If DNS Query Status is '0' then it was executed successfully.
133
136
  # And if not, it means there was an error. The 'QueryStatus' indicate what number of an error it is.
134
- if event['event']['QueryStatus'] == '0':
137
+ if event['EventHeader']['QueryStatus'] == '0':
135
138
  event_dict['status'] = 'Success'
136
139
  else:
137
140
  event_dict['status'] = 'Error'
@@ -3,6 +3,8 @@ from ...wrappers import sysmonw
3
3
  from ...basics import dicts
4
4
 
5
5
 
6
+ DEFAULT_SESSION_NAME: str = 'AtomicShopSysmonProcessCreationTrace'
7
+
6
8
  PROVIDER_NAME: str = const.ETW_SYSMON['provider_name']
7
9
  PROVIDER_GUID: str = const.ETW_SYSMON['provider_guid']
8
10
  PROCESS_CREATION_EVENT_ID: int = const.ETW_SYSMON['event_ids']['process_create']
@@ -53,6 +55,9 @@ class SysmonProcessCreationTrace:
53
55
  self.attrs = attrs
54
56
  self.sysmon_directory: str = sysmon_directory
55
57
 
58
+ if not session_name:
59
+ session_name = DEFAULT_SESSION_NAME
60
+
56
61
  self.event_trace = trace.EventTrace(
57
62
  providers=[(PROVIDER_NAME, PROVIDER_GUID)],
58
63
  # lambda x: self.event_queue.put(x),
@@ -80,32 +85,39 @@ class SysmonProcessCreationTrace:
80
85
  print(dns_dict)
81
86
 
82
87
  :return: Dictionary with the event data.
88
+
89
+ -----------------------------------------------
90
+
91
+ Structure of the returned dictionary:
92
+ {
93
+ 'event_id': int,
94
+ 'ProcessId': int,
95
+ 'ProcessGuid': str,
96
+ 'Image': str,
97
+ 'FileVersion': str,
98
+ 'Product': str,
99
+ 'Company': str,
100
+ 'OriginalFileName': str,
101
+ 'CommandLine': str,
102
+ 'CurrentDirectory': str,
103
+ 'User': str,
104
+ 'LogonId': str,
105
+ 'LogonGuid': str,
106
+ 'TerminalSessionId': int,
107
+ 'IntegrityLevel': str,
108
+ 'Hashes': dict,
109
+ 'ParentProcessGuid': str,
110
+ 'ParentProcessId': int,
111
+ 'ParentImage': str,
112
+ 'ParentCommandLine': str
113
+ }
114
+
83
115
  """
84
116
 
85
117
  event = self.event_trace.emit()
86
118
 
87
- event_dict: dict = {
88
- 'event_id': event['event_id'],
89
- 'pid': event['event']['ProcessId'],
90
- 'process_guid': event['event']['ProcessGuid'],
91
- 'image': event['event']['Image'],
92
- 'file_version': event['event']['FileVersion'],
93
- 'product': event['event']['Product'],
94
- 'company': event['event']['Company'],
95
- 'original_file_name': event['event']['OriginalFileName'],
96
- 'command_line': event['event']['CommandLine'],
97
- 'current_directory': event['event']['CurrentDirectory'],
98
- 'user': event['event']['User'],
99
- 'logon_id': event['event']['LogonId'],
100
- 'logon_guid': event['event']['LogonGuid'],
101
- 'terminal_session_id': event['event']['TerminalSessionId'],
102
- 'integrity_level': event['event']['IntegrityLevel'],
103
- 'hashes': event['event']['Hashes'],
104
- 'parent_process_guid': event['event']['ParentProcessGuid'],
105
- 'parent_process_id': event['event']['ParentProcessId'],
106
- 'parent_image': event['event']['ParentImage'],
107
- 'parent_command_line': event['event']['ParentCommandLine']
108
- }
119
+ event_dict = {'EventId': event['EventId']}
120
+ event_dict.update(event['EventHeader'])
109
121
 
110
122
  if self.attrs:
111
123
  event_dict = dicts.reorder_keys(
@@ -36,7 +36,7 @@ def read_csv_to_list_of_dicts_by_header(
36
36
  All the lines of the CSV file will be considered as content.
37
37
  :param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
38
38
  statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
39
- :return: list.
39
+ :return: tuple(list of entries, header(list of cell names)).
40
40
  """
41
41
 
42
42
  # The header fields will be separated to list of "csv_reader.fieldnames".
@@ -0,0 +1,133 @@
1
+ from typing import Union, Literal
2
+
3
+ from .wrappers.pywin32w import wmi_win32process
4
+ from .wrappers.psutilw import psutilw
5
+ from .basics import dicts
6
+ from . import get_process_name_cmd_dll
7
+
8
+
9
+ class GetProcessList:
10
+ """
11
+ The class is responsible for getting the list of running processes.
12
+
13
+ Example of one time polling with 'pywin32' method:
14
+ from atomicshop import process_poller
15
+ process_list: dict = \
16
+ process_poller.GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
17
+ """
18
+ def __init__(
19
+ self,
20
+ get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
21
+ connect_on_init: bool = False
22
+ ):
23
+ """
24
+ :param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
25
+ 'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
26
+ 'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
27
+ 'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
28
+ :param connect_on_init: bool, if True, will connect to the service on init. 'psutil' don't need to connect.
29
+ """
30
+ self.get_method = get_method
31
+ self.process_polling_instance = None
32
+
33
+ self.connected = False
34
+
35
+ if self.get_method == 'psutil':
36
+ self.process_polling_instance = psutilw.PsutilProcesses()
37
+ self.connected = True
38
+ elif self.get_method == 'pywin32':
39
+ self.process_polling_instance = wmi_win32process.Pywin32Processes()
40
+ elif self.get_method == 'process_dll':
41
+ self.process_polling_instance = get_process_name_cmd_dll.ProcessNameCmdline()
42
+
43
+ if connect_on_init:
44
+ self.connect()
45
+
46
+ def connect(self):
47
+ """
48
+ Connect to the service. 'psutil' don't need to connect.
49
+ """
50
+
51
+ # If poller method is none of the allowed methods.
52
+ if self.get_method not in ['psutil', 'pywin32', 'process_dll']:
53
+ raise ValueError(f"Method '{self.get_method}' is not allowed.")
54
+
55
+ # If the service is not connected yet. Since 'psutil' don't need to connect.
56
+ if not self.connected:
57
+ if self.get_method == 'pywin32':
58
+ self.process_polling_instance.connect()
59
+ self.connected = True
60
+ elif self.get_method == 'process_dll':
61
+ self.process_polling_instance.load()
62
+ self.connected = True
63
+
64
+ def get_processes(self, as_dict: bool = True) -> Union[list, dict]:
65
+ """
66
+ The function will get the list of opened processes and return it as a list of dicts.
67
+
68
+ :return: dict while key is pid or list of dicts, of opened processes (depending on 'as_dict' setting).
69
+ """
70
+
71
+ if as_dict:
72
+ if self.get_method == 'psutil':
73
+ return self.process_polling_instance.get_processes_as_dict(
74
+ attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
75
+ elif self.get_method == 'pywin32':
76
+ processes = self.process_polling_instance.get_processes_as_dict(
77
+ attrs=['ProcessId', 'Name', 'CommandLine'])
78
+
79
+ # Convert the keys from WMI to the keys that are used in 'psutil'.
80
+ converted_process_dict = dict()
81
+ for pid, process_info in processes.items():
82
+ converted_process_dict[pid] = dicts.convert_key_names(
83
+ process_info, {'Name': 'name', 'CommandLine': 'cmdline'})
84
+
85
+ return converted_process_dict
86
+ elif self.get_method == 'process_dll':
87
+ return self.process_polling_instance.get_process_details(as_dict=True)
88
+ else:
89
+ if self.get_method == 'psutil':
90
+ return self.process_polling_instance.get_processes_as_list_of_dicts(
91
+ attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
92
+ elif self.get_method == 'pywin32':
93
+ processes = self.process_polling_instance.get_processes_as_list_of_dicts(
94
+ attrs=['ProcessId', 'Name', 'CommandLine'])
95
+
96
+ # Convert the keys from WMI to the keys that are used in 'psutil'.
97
+ for process_index, process_info in enumerate(processes):
98
+ processes[process_index] = dicts.convert_key_names(
99
+ process_info, {'ProcessId': 'pid', 'Name': 'name', 'CommandLine': 'cmdline'})
100
+
101
+ return processes
102
+ elif self.get_method == 'process_dll':
103
+ return self.process_polling_instance.get_process_details(as_dict=as_dict)
104
+
105
+
106
+ def get_process_time_tester(
107
+ get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
108
+ times_to_test: int = 50
109
+ ):
110
+ """
111
+ The function will test the time it takes to get the list of processes with different methods and cycles.
112
+
113
+ :param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
114
+ 'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
115
+ 'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
116
+ 'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64
117
+ :param times_to_test: int, how many times to test the function.
118
+ """
119
+
120
+ import timeit
121
+
122
+ setup_code = '''
123
+ from atomicshop import process_poller
124
+ get_process_list = process_poller.GetProcessList(get_method=get_method, connect_on_init=True)
125
+ '''
126
+
127
+ test_code = '''
128
+ test = get_process_list.get_processes()
129
+ '''
130
+
131
+ # globals need to be specified, otherwise the setup_code won't work with passed variables.
132
+ times = timeit.timeit(setup=setup_code, stmt=test_code, number=times_to_test, globals=locals())
133
+ print(f'Execution time: {times}')