atomicshop 2.19.6__py3-none-any.whl → 2.19.8__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/a_installs/win/robocorp.py +3 -0
- atomicshop/basics/bytes_arrays.py +19 -0
- atomicshop/etws/trace.py +2 -0
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/mongodbw/mongodbw.py +81 -0
- {atomicshop-2.19.6.dist-info → atomicshop-2.19.8.dist-info}/METADATA +1 -1
- {atomicshop-2.19.6.dist-info → atomicshop-2.19.8.dist-info}/RECORD +12 -12
- {atomicshop-2.19.6.dist-info → atomicshop-2.19.8.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.19.6.dist-info → atomicshop-2.19.8.dist-info}/WHEEL +0 -0
- {atomicshop-2.19.6.dist-info → atomicshop-2.19.8.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -32,6 +32,9 @@ def main():
|
|
|
32
32
|
print_api("PIP Installing Robocorp-Recognition.")
|
|
33
33
|
subprocess.check_call(["pip", "install", "--upgrade", "rpaframework-recognition"])
|
|
34
34
|
|
|
35
|
+
print_api("PIP Installing pynput.")
|
|
36
|
+
subprocess.check_call(["pip", "install", "--upgrade", "pynput"])
|
|
37
|
+
|
|
35
38
|
print_api("Installing Playwright browsers.")
|
|
36
39
|
subprocess.check_call(["playwright", "install"])
|
|
37
40
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
def get_single_byte_from_byte_string(byte_string, index: int):
|
|
2
6
|
"""
|
|
3
7
|
Function extracts single byte as byte from byte string object.
|
|
@@ -179,3 +183,18 @@ def read_bytes_from_position(
|
|
|
179
183
|
# Read the specified number of bytes.
|
|
180
184
|
data = file.read(num_bytes)
|
|
181
185
|
return data
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def convert_bytes_to_printable_string_only(
|
|
189
|
+
byte_sequence: Union[bytes, bytearray],
|
|
190
|
+
non_printable_character: str = '.'
|
|
191
|
+
) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Convert bytes to printable string. If byte is not printable, replace it with 'non_printable_character'.
|
|
194
|
+
:param byte_sequence: bytes or bytearray, sequence of bytes.
|
|
195
|
+
:param non_printable_character: string, character to replace non-printable characters.
|
|
196
|
+
:return:
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
printable = set(string.printable)
|
|
200
|
+
return ''.join(chr(byte) if chr(byte) in printable else non_printable_character for byte in byte_sequence)
|
atomicshop/etws/trace.py
CHANGED
|
@@ -29,6 +29,8 @@ class EventTrace(etw.ETW):
|
|
|
29
29
|
:param providers: List of tuples with provider name and provider GUID.
|
|
30
30
|
tuple[0] = provider name
|
|
31
31
|
tuple[1] = provider GUID
|
|
32
|
+
|
|
33
|
+
Example: [('Microsoft-Windows-DNS-Client', '{1c95126e-7ee8-4e23-86b2-6e7e4a5a8e9b}')]
|
|
32
34
|
:param event_callback: Reference to the callable callback function that will be called for each occurring event.
|
|
33
35
|
:param event_id_filters: List of event IDs that we want to filter. If not provided, all events will be returned.
|
|
34
36
|
The default in the 'etw.ETW' method is 'None'.
|
|
@@ -11,10 +11,14 @@ EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1
|
|
|
11
11
|
|
|
12
12
|
MAXIMUM_LOGGERS = 64
|
|
13
13
|
ULONG64 = ctypes.c_uint64
|
|
14
|
+
UCHAR = ctypes.c_ubyte
|
|
14
15
|
|
|
15
16
|
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
|
|
16
17
|
TRACEHANDLE = ULONG64
|
|
17
18
|
|
|
19
|
+
PROCESS_TRACE_MODE_EVENT_RECORD = 0x10000000 # new event-record callback
|
|
20
|
+
PROCESS_TRACE_MODE_REAL_TIME = 0x00000100
|
|
21
|
+
INVALID_PROCESSTRACE_HANDLE = 0xFFFFFFFFFFFFFFFF # Often -1 in 64-bit
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
"""
|
|
@@ -69,10 +73,7 @@ class EVENT_TRACE_PROPERTIES(ctypes.Structure):
|
|
|
69
73
|
("RealTimeBuffersLost", wintypes.ULONG),
|
|
70
74
|
("LoggerThreadId", wintypes.HANDLE),
|
|
71
75
|
("LogFileNameOffset", wintypes.ULONG),
|
|
72
|
-
("LoggerNameOffset", wintypes.ULONG)
|
|
73
|
-
# Allocate space for the names at the end of the structure
|
|
74
|
-
("_LoggerName", wintypes.WCHAR * 1024),
|
|
75
|
-
("_LogFileName", wintypes.WCHAR * 1024)
|
|
76
|
+
("LoggerNameOffset", wintypes.ULONG)
|
|
76
77
|
]
|
|
77
78
|
|
|
78
79
|
|
|
@@ -183,55 +184,59 @@ class EVENT_HEADER(ctypes.Structure):
|
|
|
183
184
|
("RelatedActivityId", GUID),
|
|
184
185
|
]
|
|
185
186
|
|
|
187
|
+
|
|
188
|
+
class ETW_BUFFER_CONTEXT(ctypes.Structure):
|
|
189
|
+
_fields_ = [('ProcessorNumber', ctypes.c_ubyte),
|
|
190
|
+
('Alignment', ctypes.c_ubyte),
|
|
191
|
+
('LoggerId', ctypes.c_ushort)]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class EVENT_HEADER_EXTENDED_DATA_ITEM(ctypes.Structure):
|
|
195
|
+
_fields_ = [
|
|
196
|
+
('Reserved1', ctypes.c_ushort),
|
|
197
|
+
('ExtType', ctypes.c_ushort),
|
|
198
|
+
('Linkage', ctypes.c_ushort), # struct{USHORT :1, USHORT :15}
|
|
199
|
+
('DataSize', ctypes.c_ushort),
|
|
200
|
+
('DataPtr', ctypes.c_ulonglong)
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
|
|
186
204
|
class EVENT_RECORD(ctypes.Structure):
|
|
187
205
|
_fields_ = [
|
|
188
|
-
(
|
|
189
|
-
(
|
|
190
|
-
(
|
|
191
|
-
(
|
|
192
|
-
(
|
|
193
|
-
(
|
|
194
|
-
(
|
|
206
|
+
('EventHeader', EVENT_HEADER),
|
|
207
|
+
('BufferContext', ETW_BUFFER_CONTEXT),
|
|
208
|
+
('ExtendedDataCount', ctypes.c_ushort),
|
|
209
|
+
('UserDataLength', ctypes.c_ushort),
|
|
210
|
+
('ExtendedData', ctypes.POINTER(EVENT_HEADER_EXTENDED_DATA_ITEM)),
|
|
211
|
+
('UserData', ctypes.c_void_p),
|
|
212
|
+
('UserContext', ctypes.c_void_p)
|
|
195
213
|
]
|
|
196
214
|
|
|
197
215
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
# ]
|
|
216
|
+
class EVENT_TRACE_LOGFILE(ctypes.Structure):
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
EVENT_RECORD_CALLBACK = ctypes.WINFUNCTYPE(None, ctypes.POINTER(EVENT_RECORD))
|
|
221
|
+
EVENT_TRACE_BUFFER_CALLBACK = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.POINTER(EVENT_TRACE_LOGFILE))
|
|
217
222
|
|
|
218
223
|
|
|
219
224
|
class EVENT_TRACE_LOGFILE(ctypes.Structure):
|
|
220
225
|
_fields_ = [
|
|
221
|
-
(
|
|
222
|
-
(
|
|
223
|
-
(
|
|
224
|
-
(
|
|
225
|
-
(
|
|
226
|
-
(
|
|
227
|
-
(
|
|
228
|
-
(
|
|
229
|
-
(
|
|
230
|
-
(
|
|
231
|
-
(
|
|
232
|
-
(
|
|
233
|
-
(
|
|
234
|
-
(
|
|
226
|
+
('LogFileName', ctypes.c_wchar_p),
|
|
227
|
+
('LoggerName', ctypes.c_wchar_p),
|
|
228
|
+
('CurrentTime', ctypes.c_longlong),
|
|
229
|
+
('BuffersRead', ctypes.c_ulong),
|
|
230
|
+
('ProcessTraceMode', ctypes.c_ulong),
|
|
231
|
+
('CurrentEvent', EVENT_TRACE),
|
|
232
|
+
('LogfileHeader', TRACE_LOGFILE_HEADER),
|
|
233
|
+
('BufferCallback', EVENT_TRACE_BUFFER_CALLBACK),
|
|
234
|
+
('BufferSize', ctypes.c_ulong),
|
|
235
|
+
('Filled', ctypes.c_ulong),
|
|
236
|
+
('EventsLost', ctypes.c_ulong),
|
|
237
|
+
('EventRecordCallback', EVENT_RECORD_CALLBACK),
|
|
238
|
+
('IsKernelTrace', ctypes.c_ulong),
|
|
239
|
+
('Context', ctypes.c_void_p)
|
|
235
240
|
]
|
|
236
241
|
|
|
237
242
|
|
|
@@ -255,7 +260,7 @@ class PROVIDER_INFORMATION(ctypes.Structure):
|
|
|
255
260
|
|
|
256
261
|
|
|
257
262
|
# Load the necessary library
|
|
258
|
-
advapi32 = ctypes.WinDLL(
|
|
263
|
+
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
|
|
259
264
|
tdh = ctypes.windll.tdh
|
|
260
265
|
|
|
261
266
|
# Define necessary TDH functions
|
|
@@ -263,6 +268,46 @@ tdh.TdhEnumerateProviders.argtypes = [ctypes.POINTER(PROVIDER_ENUMERATION_INFO),
|
|
|
263
268
|
tdh.TdhEnumerateProviders.restype = ULONG
|
|
264
269
|
|
|
265
270
|
|
|
271
|
+
# Make sure StartTraceW has proper argtypes (if not set in consts)
|
|
272
|
+
StartTrace = advapi32.StartTraceW
|
|
273
|
+
StartTrace.argtypes = [
|
|
274
|
+
ctypes.POINTER(TRACEHANDLE),
|
|
275
|
+
wintypes.LPCWSTR,
|
|
276
|
+
ctypes.POINTER(EVENT_TRACE_PROPERTIES)
|
|
277
|
+
]
|
|
278
|
+
StartTrace.restype = wintypes.ULONG
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class EVENT_FILTER_DESCRIPTOR(ctypes.Structure):
|
|
282
|
+
_fields_ = [('Ptr', ctypes.c_ulonglong),
|
|
283
|
+
('Size', ctypes.c_ulong),
|
|
284
|
+
('Type', ctypes.c_ulong)]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class ENABLE_TRACE_PARAMETERS(ctypes.Structure):
|
|
288
|
+
_fields_ = [
|
|
289
|
+
('Version', ctypes.c_ulong),
|
|
290
|
+
('EnableProperty', ctypes.c_ulong),
|
|
291
|
+
('ControlFlags', ctypes.c_ulong),
|
|
292
|
+
('SourceId', GUID),
|
|
293
|
+
('EnableFilterDesc', ctypes.POINTER(EVENT_FILTER_DESCRIPTOR)),
|
|
294
|
+
('FilterDescCount', ctypes.c_ulong)
|
|
295
|
+
]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
EnableTraceEx2 = advapi32.EnableTraceEx2
|
|
299
|
+
EnableTraceEx2.argtypes = [
|
|
300
|
+
TRACEHANDLE, # TraceHandle (c_uint64)
|
|
301
|
+
ctypes.POINTER(GUID), # ProviderId
|
|
302
|
+
ctypes.c_ulong, # ControlCode
|
|
303
|
+
ctypes.c_char, # Level
|
|
304
|
+
ctypes.c_ulonglong, # MatchAnyKeyword
|
|
305
|
+
ctypes.c_ulonglong, # MatchAllKeyword
|
|
306
|
+
ctypes.c_ulong, # Timeout
|
|
307
|
+
ctypes.POINTER(ENABLE_TRACE_PARAMETERS)] # PENABLE_TRACE_PARAMETERS (optional) -> None or pointer
|
|
308
|
+
EnableTraceEx2.restype = ctypes.c_ulong
|
|
309
|
+
|
|
310
|
+
|
|
266
311
|
# Define the function prototype
|
|
267
312
|
QueryAllTraces = advapi32.QueryAllTracesW
|
|
268
313
|
QueryAllTraces.argtypes = [
|
|
@@ -277,8 +322,13 @@ OpenTrace.argtypes = [ctypes.POINTER(EVENT_TRACE_LOGFILE)]
|
|
|
277
322
|
OpenTrace.restype = wintypes.ULONG
|
|
278
323
|
|
|
279
324
|
ProcessTrace = advapi32.ProcessTrace
|
|
280
|
-
ProcessTrace.argtypes = [
|
|
281
|
-
|
|
325
|
+
ProcessTrace.argtypes = [
|
|
326
|
+
ctypes.POINTER(ctypes.c_uint64), # pointer to array of 64-bit handles
|
|
327
|
+
wintypes.ULONG, # handle count
|
|
328
|
+
ctypes.c_void_p, # LPFILETIME (start)
|
|
329
|
+
ctypes.c_void_p # LPFILETIME (end)
|
|
330
|
+
]
|
|
331
|
+
ProcessTrace.restype = wintypes.ULONG
|
|
282
332
|
|
|
283
333
|
CloseTrace = advapi32.CloseTrace
|
|
284
334
|
CloseTrace.argtypes = [wintypes.ULONG]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ctypes
|
|
2
|
-
|
|
2
|
+
import queue
|
|
3
3
|
from ctypes.wintypes import ULONG
|
|
4
4
|
import uuid
|
|
5
5
|
from typing import Literal
|
|
@@ -34,7 +34,7 @@ def start_etw_session(
|
|
|
34
34
|
provider_name_list: list = None,
|
|
35
35
|
verbosity_mode: int = 4,
|
|
36
36
|
maximum_buffers: int = 38
|
|
37
|
-
):
|
|
37
|
+
) -> const.TRACEHANDLE:
|
|
38
38
|
"""
|
|
39
39
|
Start an ETW session and enable the specified provider.
|
|
40
40
|
|
|
@@ -77,57 +77,89 @@ def start_etw_session(
|
|
|
77
77
|
for provider_name in provider_name_list:
|
|
78
78
|
provider_guid_list.append(providers.get_provider_guid_by_name(provider_name))
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
# (1) Allocate a buffer large enough for EVENT_TRACE_PROPERTIES + space for 2 strings
|
|
81
|
+
# The typical approach is to allow enough space for:
|
|
82
|
+
# - The structure
|
|
83
|
+
# - The "LoggerName" string
|
|
84
|
+
# - The "LogFileName" string (even if we don't use it, we must leave offset space)
|
|
85
|
+
#
|
|
86
|
+
props_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
|
|
87
|
+
# Add space for 2 x 1024 wide-chars (one for LoggerName, one for LogFileName)
|
|
88
|
+
# Each wide char = ctypes.sizeof(ctypes.c_wchar).
|
|
89
|
+
props_size += (2 * 1024 * ctypes.sizeof(ctypes.c_wchar)) # for logger name
|
|
90
|
+
props_size += (2 * 1024 * ctypes.sizeof(ctypes.c_wchar)) # for log file name
|
|
91
|
+
|
|
92
|
+
properties_buffer = ctypes.create_string_buffer(props_size)
|
|
93
|
+
properties_ptr = ctypes.cast(properties_buffer, ctypes.POINTER(const.EVENT_TRACE_PROPERTIES))
|
|
94
|
+
props = properties_ptr.contents
|
|
95
|
+
|
|
96
|
+
# (2) Fill in basic fields
|
|
97
|
+
props.Wnode.BufferSize = props_size
|
|
98
|
+
props.Wnode.Flags = const.WNODE_FLAG_TRACED_GUID # 0x00020000
|
|
99
|
+
props.Wnode.ClientContext = 1 # QPC clock
|
|
100
|
+
props.BufferSize = 1024
|
|
101
|
+
props.MinimumBuffers = 1
|
|
102
|
+
props.MaximumBuffers = maximum_buffers
|
|
103
|
+
props.MaximumFileSize = 0
|
|
104
|
+
props.LogFileMode = const.EVENT_TRACE_REAL_TIME_MODE # real-time
|
|
105
|
+
props.FlushTimer = 1
|
|
106
|
+
props.EnableFlags = 0
|
|
107
|
+
|
|
108
|
+
# (3) Indicate where in this allocated buffer the strings should go
|
|
109
|
+
struct_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
|
|
110
|
+
props.LoggerNameOffset = struct_size
|
|
111
|
+
props.LogFileNameOffset = struct_size + (2 * 1024 * ctypes.sizeof(ctypes.c_wchar))
|
|
112
|
+
|
|
113
|
+
# (4) Copy the session name into the LoggerName space
|
|
114
|
+
logger_name_address = ctypes.addressof(properties_buffer) + props.LoggerNameOffset
|
|
115
|
+
session_name_wchar = ctypes.create_unicode_buffer(session_name)
|
|
116
|
+
ctypes.memmove(
|
|
117
|
+
logger_name_address,
|
|
118
|
+
session_name_wchar,
|
|
119
|
+
len(session_name) * ctypes.sizeof(ctypes.c_wchar)
|
|
120
|
+
)
|
|
104
121
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Enable each provider
|
|
112
|
-
for provider_guid in provider_guid_list:
|
|
113
|
-
provider_guid_struct = _string_to_guid(provider_guid)
|
|
114
|
-
status = ctypes.windll.advapi32.EnableTraceEx2(
|
|
115
|
-
session_handle,
|
|
116
|
-
ctypes.byref(provider_guid_struct),
|
|
117
|
-
const.EVENT_CONTROL_CODE_ENABLE_PROVIDER,
|
|
118
|
-
verbosity_mode,
|
|
119
|
-
0,
|
|
120
|
-
0,
|
|
121
|
-
0,
|
|
122
|
-
None
|
|
122
|
+
# (5) Start the session
|
|
123
|
+
session_handle = const.TRACEHANDLE(0)
|
|
124
|
+
status = const.StartTrace(
|
|
125
|
+
ctypes.byref(session_handle),
|
|
126
|
+
session_name, # The session name as an LPCWSTR
|
|
127
|
+
properties_ptr # pointer to EVENT_TRACE_PROPERTIES
|
|
123
128
|
)
|
|
124
129
|
|
|
125
130
|
if status != 0:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
# 183 => ERROR_ALREADY_EXISTS
|
|
132
|
+
if status == 183:
|
|
133
|
+
raise ETWSessionExists(f"ETW session [{session_name}] already exists")
|
|
134
|
+
else:
|
|
135
|
+
raise Exception(f"StartTraceW failed with error code {status}")
|
|
136
|
+
|
|
137
|
+
# (6) If we have providers to enable, enable each
|
|
138
|
+
if provider_guid_list:
|
|
139
|
+
for guid_str in provider_guid_list:
|
|
140
|
+
guid_struct = _string_to_guid(guid_str)
|
|
141
|
+
|
|
142
|
+
# Typically you'd do something like:
|
|
143
|
+
# advapi32.EnableTraceEx2(TRACEHANDLE, PGUID, CONTROL_CODE, LEVEL, KW, KW, TIMEOUT, FILTER)
|
|
144
|
+
|
|
145
|
+
enable_status = const.EnableTraceEx2(
|
|
146
|
+
session_handle, # The session handle
|
|
147
|
+
ctypes.byref(guid_struct), # The provider GUID
|
|
148
|
+
const.EVENT_CONTROL_CODE_ENABLE_PROVIDER, # 1 => enable
|
|
149
|
+
verbosity_mode, # level
|
|
150
|
+
0xFFFFFFFFFFFFFFFF, # matchAnyKeyword
|
|
151
|
+
0, # matchAllKeyword
|
|
152
|
+
0, # timeout
|
|
153
|
+
None # enableParameters (optional)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if enable_status != 0:
|
|
157
|
+
raise Exception(
|
|
158
|
+
f"EnableTraceEx2 failed for provider {guid_str} (error={enable_status})"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
print(f"ETW session '{session_name}' started successfully.")
|
|
162
|
+
return session_handle
|
|
131
163
|
|
|
132
164
|
|
|
133
165
|
# Function to stop and delete ETW session
|
|
@@ -165,6 +197,103 @@ def stop_and_delete_etw_session(session_name: str) -> tuple[bool, int]:
|
|
|
165
197
|
return True, status
|
|
166
198
|
|
|
167
199
|
|
|
200
|
+
@const.EVENT_CALLBACK_TYPE
|
|
201
|
+
def _default_callback(
|
|
202
|
+
event_record_ptr
|
|
203
|
+
):
|
|
204
|
+
"""
|
|
205
|
+
This function will be called by Windows for every incoming ETW event.
|
|
206
|
+
'event_record_ptr' is a pointer to an EVENT_RECORD structure.
|
|
207
|
+
"""
|
|
208
|
+
# Convert pointer to a Python EVENT_RECORD object
|
|
209
|
+
event_record = event_record_ptr.contents
|
|
210
|
+
|
|
211
|
+
# Do something with event_record (e.g., parse via TDH)
|
|
212
|
+
print("Received an ETW event!", event_record.EventHeader.ProviderId)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def start_etw_consumer(
|
|
216
|
+
session_name: str,
|
|
217
|
+
record_queue: queue.Queue
|
|
218
|
+
):
|
|
219
|
+
"""
|
|
220
|
+
Attach to an existing real-time ETW session by 'session_name'
|
|
221
|
+
using the "new" EVENT_RECORD callback approach.
|
|
222
|
+
This call blocks until the ETW session is stopped or an error occurs.
|
|
223
|
+
"""
|
|
224
|
+
# 1) Create a closure callback that references the queue
|
|
225
|
+
# We define an inner function that sees 'record_queue' from outer scope.
|
|
226
|
+
# Then we wrap that in EVENT_CALLBACK_TYPE.
|
|
227
|
+
if record_queue is not None:
|
|
228
|
+
@const.EVENT_CALLBACK_TYPE
|
|
229
|
+
def _queue_callback(event_record_ptr):
|
|
230
|
+
event_record = event_record_ptr.contents
|
|
231
|
+
# # Example: put a simple dict with the provider ID & possibly more info
|
|
232
|
+
# record_queue.put({
|
|
233
|
+
# "provider_id": str(event_record.EventHeader.ProviderId),
|
|
234
|
+
# "process_id": event_record.EventHeader.ProcessId,
|
|
235
|
+
# "thread_id": event_record.EventHeader.ThreadId,
|
|
236
|
+
# # ... more fields or parse them with TdhGetEventInformation, etc.
|
|
237
|
+
# })
|
|
238
|
+
print("Received an ETW event!", event_record.EventHeader.ProviderId)
|
|
239
|
+
record_queue.put(event_record)
|
|
240
|
+
else:
|
|
241
|
+
_queue_callback = _default_callback
|
|
242
|
+
|
|
243
|
+
# Keep a reference to the callback so Python doesn't GC it.
|
|
244
|
+
start_etw_consumer._callback_ref = _queue_callback
|
|
245
|
+
|
|
246
|
+
# Prepare the EVENT_TRACE_LOGFILE structure
|
|
247
|
+
logfile = const.EVENT_TRACE_LOGFILE()
|
|
248
|
+
# You can also do: logfile.LoggerName = session_name
|
|
249
|
+
# If you assign session_name (a Python str), ctypes automatically converts it to a temporary c_wchar_p under the hood.
|
|
250
|
+
# logfile.LoggerName = ctypes.c_wchar_p(session_name)
|
|
251
|
+
logfile.LoggerName = session_name
|
|
252
|
+
logfile.LogFileName = None # Real-time, not from a file
|
|
253
|
+
logfile.ProcessTraceMode = (const.PROCESS_TRACE_MODE_REAL_TIME | const.PROCESS_TRACE_MODE_EVENT_RECORD)
|
|
254
|
+
|
|
255
|
+
# Point to our Python callback
|
|
256
|
+
logfile.EventRecordCallback = const.EVENT_RECORD_CALLBACK(_default_callback)
|
|
257
|
+
|
|
258
|
+
# Open the trace
|
|
259
|
+
trace_handle = const.OpenTrace(ctypes.byref(logfile))
|
|
260
|
+
if trace_handle == const.INVALID_PROCESSTRACE_HANDLE:
|
|
261
|
+
error_code = ctypes.get_last_error()
|
|
262
|
+
raise OSError(f"OpenTrace failed with error code: {error_code}")
|
|
263
|
+
|
|
264
|
+
# trace_handle is actually an unsigned long, but in 64-bit it's a 64-bit handle.
|
|
265
|
+
# We'll create an array of one handle for ProcessTrace:
|
|
266
|
+
trace_handle_array = (ctypes.c_uint64 * 1)(trace_handle)
|
|
267
|
+
|
|
268
|
+
# Blocking call - will not return until the session is stopped (or error).
|
|
269
|
+
status = const.ProcessTrace(
|
|
270
|
+
trace_handle_array,
|
|
271
|
+
1, # HandleCount = 1
|
|
272
|
+
None, # StartTime = None
|
|
273
|
+
None # EndTime = None
|
|
274
|
+
)
|
|
275
|
+
if status != 0:
|
|
276
|
+
raise OSError(f"ProcessTrace failed with error code: {status}")
|
|
277
|
+
|
|
278
|
+
print("ProcessTrace returned. ETW consumer finished.")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def start_etw_consumer_in_thread(
|
|
282
|
+
session_name: str,
|
|
283
|
+
record_queue: queue.Queue
|
|
284
|
+
):
|
|
285
|
+
"""
|
|
286
|
+
Start an ETW consumer in a separate thread.
|
|
287
|
+
"""
|
|
288
|
+
import threading
|
|
289
|
+
|
|
290
|
+
def _start_etw_consumer():
|
|
291
|
+
start_etw_consumer(session_name, record_queue)
|
|
292
|
+
|
|
293
|
+
thread = threading.Thread(target=_start_etw_consumer)
|
|
294
|
+
thread.start()
|
|
295
|
+
|
|
296
|
+
|
|
168
297
|
def get_all_providers(key_as: Literal['name', 'guid'] = 'name') -> dict:
|
|
169
298
|
"""
|
|
170
299
|
Get all ETW providers available on the system.
|
|
@@ -404,6 +404,33 @@ class MongoDBWrapper:
|
|
|
404
404
|
|
|
405
405
|
return count
|
|
406
406
|
|
|
407
|
+
def aggregate_entries_in_collection(
|
|
408
|
+
self,
|
|
409
|
+
collection_name: str,
|
|
410
|
+
pipeline: list[dict]
|
|
411
|
+
) -> list[dict]:
|
|
412
|
+
"""
|
|
413
|
+
Aggregate entries in a MongoDB collection by query.
|
|
414
|
+
|
|
415
|
+
:param collection_name: str, the name of the collection.
|
|
416
|
+
:param pipeline: list of dictionaries, the pipeline to search for.
|
|
417
|
+
Example, search for all entries with column name 'name' equal to 'John':
|
|
418
|
+
pipeline = [{'$match': {'name': 'John'}}]
|
|
419
|
+
Example, return all entries from collection:
|
|
420
|
+
pipeline = []
|
|
421
|
+
|
|
422
|
+
:return: list of dictionaries, the list of entries that match the query.
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
self.connect()
|
|
426
|
+
|
|
427
|
+
aggregation: list[dict] = aggregate_entries_in_collection(
|
|
428
|
+
database=self.db, collection_name=collection_name,
|
|
429
|
+
pipeline=pipeline, mongo_client=self.client, close_client=False)
|
|
430
|
+
|
|
431
|
+
return aggregation
|
|
432
|
+
|
|
433
|
+
|
|
407
434
|
def get_client(self):
|
|
408
435
|
return self.client
|
|
409
436
|
|
|
@@ -1148,6 +1175,60 @@ def count_entries_in_collection(
|
|
|
1148
1175
|
return count
|
|
1149
1176
|
|
|
1150
1177
|
|
|
1178
|
+
def aggregate_entries_in_collection(
|
|
1179
|
+
database: Union[str, pymongo.database.Database],
|
|
1180
|
+
collection_name: str,
|
|
1181
|
+
pipeline: list,
|
|
1182
|
+
mongo_client: pymongo.MongoClient = None,
|
|
1183
|
+
close_client: bool = False
|
|
1184
|
+
) -> list:
|
|
1185
|
+
"""
|
|
1186
|
+
Perform an aggregation pipeline operation on a MongoDB collection.
|
|
1187
|
+
For example, we count the number of entries with the same 'sha256' value that is provided in a list:
|
|
1188
|
+
pipeline = [
|
|
1189
|
+
{"$match": {"sha256": {"$in": ["hash1", "hash2"]}}},
|
|
1190
|
+
{"$group": {"_id": "$sha256", "count": {"$sum": 1}}}
|
|
1191
|
+
]
|
|
1192
|
+
And we will get the result:
|
|
1193
|
+
[
|
|
1194
|
+
{"_id": "hash1", "count": 1},
|
|
1195
|
+
{"_id": "hash2", "count": 1}
|
|
1196
|
+
]
|
|
1197
|
+
Meaning we will get separate counts for each 'sha256' value in the list.
|
|
1198
|
+
|
|
1199
|
+
:param database: String or the database object.
|
|
1200
|
+
str - the name of the database. In this case the database object will be created.
|
|
1201
|
+
pymongo.database.Database - the database object that will be used instead of creating a new one.
|
|
1202
|
+
:param collection_name: str, the name of the collection.
|
|
1203
|
+
:param pipeline: list, the aggregation pipeline to execute.
|
|
1204
|
+
Example:
|
|
1205
|
+
pipeline = [
|
|
1206
|
+
{"$match": {"sha256": {"$in": ["hash1", "hash2"]}}},
|
|
1207
|
+
{"$group": {"_id": "$sha256", "count": {"$sum": 1}}}
|
|
1208
|
+
]
|
|
1209
|
+
:param mongo_client: pymongo.MongoClient, the connection object.
|
|
1210
|
+
If None, a new connection will be created to default URI.
|
|
1211
|
+
:param close_client: bool, if True, the connection will be closed after the operation.
|
|
1212
|
+
|
|
1213
|
+
:return: list, the results of the aggregation pipeline.
|
|
1214
|
+
"""
|
|
1215
|
+
if not mongo_client:
|
|
1216
|
+
mongo_client = connect()
|
|
1217
|
+
close_client = True
|
|
1218
|
+
|
|
1219
|
+
db = _get_pymongo_db_from_string_or_pymongo_db(database, mongo_client)
|
|
1220
|
+
collection = db[collection_name]
|
|
1221
|
+
|
|
1222
|
+
# Perform aggregation
|
|
1223
|
+
results = collection.aggregate(pipeline)
|
|
1224
|
+
|
|
1225
|
+
if close_client:
|
|
1226
|
+
mongo_client.close()
|
|
1227
|
+
|
|
1228
|
+
# Return the results as a list
|
|
1229
|
+
return list(results)
|
|
1230
|
+
|
|
1231
|
+
|
|
1151
1232
|
def delete_all_entries_from_collection(
|
|
1152
1233
|
database: Union[str, pymongo.database.Database],
|
|
1153
1234
|
collection_name: str,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=Th-y97EjSR9zstMSKi94LqlaUJmHBUAuGgdCu-K5hrQ,123
|
|
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
|
|
@@ -57,7 +57,7 @@ atomicshop/a_installs/win/fibratus.py,sha256=TU4e9gdZ_zI73C40uueJ59pD3qmN-UFGdX5
|
|
|
57
57
|
atomicshop/a_installs/win/mongodb.py,sha256=AqyItXu19aaoe49pppDxtEkXey6PMy0PoT2Y_RmPpPE,179
|
|
58
58
|
atomicshop/a_installs/win/nodejs.py,sha256=U519Dyt4bsQPbEg_PwnZL5tsbfqDr1BbhxwoQFZsSKo,200
|
|
59
59
|
atomicshop/a_installs/win/pycharm.py,sha256=j_RSd7aDOyC3yDd-_GUTMLlQTmDrqtVFG--oUfGLiZk,140
|
|
60
|
-
atomicshop/a_installs/win/robocorp.py,sha256=
|
|
60
|
+
atomicshop/a_installs/win/robocorp.py,sha256=2E28iaRlAZROoxmXwiXv8rqTjVcdBT2UJ3B8nxrtmkc,2245
|
|
61
61
|
atomicshop/a_installs/win/wsl_ubuntu_lts.py,sha256=dZbPRLNKFeMd6MotjkE6UDY9cOiIaaclIdR1kGYWI50,139
|
|
62
62
|
atomicshop/a_mains/dns_gateway_setting.py,sha256=ncc2rFQCChxlNP59UshwmTonLqC6MWblrVAzbbz-13M,149
|
|
63
63
|
atomicshop/a_mains/msi_unpacker.py,sha256=5hrkqETYt9HIqR_3PMf32_q06kCrIcsdm_RJV9oY438,188
|
|
@@ -89,7 +89,7 @@ atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
89
89
|
atomicshop/basics/ansi_escape_codes.py,sha256=uGVRW01v2O052701sOfAdCaM_GLF_cu_jrssuYiPbzA,3216
|
|
90
90
|
atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
|
|
91
91
|
atomicshop/basics/booleans.py,sha256=V36NaMf8AffhCom_ovQeOZlYcdtGyIcQwWKki6h7O0M,1745
|
|
92
|
-
atomicshop/basics/bytes_arrays.py,sha256=
|
|
92
|
+
atomicshop/basics/bytes_arrays.py,sha256=xfFW9CBQyzf0iXNWpx-EfrxtN3-tiKzuczPzOb6cOdU,7190
|
|
93
93
|
atomicshop/basics/classes.py,sha256=UayCzPs3eynI3wOzSu-2IJSmTwOB4HwPwgVI2F-7_lQ,12648
|
|
94
94
|
atomicshop/basics/dicts.py,sha256=DeYHIh940pMMBrFhpXt4dsigFVYzTrlqWymNo4Pq_Js,14049
|
|
95
95
|
atomicshop/basics/dicts_nested.py,sha256=StYxYnYPa0SEJr1lmEwAv5zfERWWqoULeyG8e0zRAwE,4107
|
|
@@ -115,7 +115,7 @@ atomicshop/etws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
115
115
|
atomicshop/etws/const.py,sha256=v3x_IdCYeSKbCGywiZFOZln80ldpwKW5nuMDuUe51Jg,1257
|
|
116
116
|
atomicshop/etws/providers.py,sha256=CXNx8pYdjtpLIpA66IwrnE64XhY4U5ExnFBMLEb8Uzk,547
|
|
117
117
|
atomicshop/etws/sessions.py,sha256=b_KeiOvgOBJezJokN81TRlrvJiQNJlIWN4Z6UVjuxP0,1335
|
|
118
|
-
atomicshop/etws/trace.py,sha256=
|
|
118
|
+
atomicshop/etws/trace.py,sha256=QcB_NYP-KPOaafYd6jCFdPPQsBu4jzac4QicJdWJAoE,7359
|
|
119
119
|
atomicshop/etws/traces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
120
120
|
atomicshop/etws/traces/trace_dns.py,sha256=WvOZm7KNdP4r6ofkZhUGi9WjtYlkV3mUp_yxita3Qg4,6399
|
|
121
121
|
atomicshop/etws/traces/trace_sysmon_process_creation.py,sha256=OM-bkK38uYMwWLZKNOTDa0Xdk3sO6sqsxoMUIiPvm5g,4656
|
|
@@ -206,8 +206,8 @@ atomicshop/wrappers/ctyping/file_details_winapi.py,sha256=dmdnCMwx4GgVEctiUIyniV
|
|
|
206
206
|
atomicshop/wrappers/ctyping/process_winapi.py,sha256=QcXL-ETtlSSkoT8F7pYle97ubGWsjYp8cx8HxkVMgAc,2762
|
|
207
207
|
atomicshop/wrappers/ctyping/win_console.py,sha256=uTtjkz9rY559AaV0dhyZYUSSEe9cn6Du2DgurdMtX-M,1158
|
|
208
208
|
atomicshop/wrappers/ctyping/etw_winapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
209
|
-
atomicshop/wrappers/ctyping/etw_winapi/const.py,sha256=
|
|
210
|
-
atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py,sha256=
|
|
209
|
+
atomicshop/wrappers/ctyping/etw_winapi/const.py,sha256=W-NMYlJ8RrMxLRjKcatEHe6wJwNxNMGSmYbXRXrlz6o,11223
|
|
210
|
+
atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py,sha256=EMdCjGd3x6aRcHkY2NRNTlrOuC7TY4rhfqfRtTWkbYU,17225
|
|
211
211
|
atomicshop/wrappers/ctyping/msi_windows_installer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
212
212
|
atomicshop/wrappers/ctyping/msi_windows_installer/base.py,sha256=Uu9SlWLsQQ6mjE-ek-ptHcmgiI3Ruah9bdZus70EaVY,4884
|
|
213
213
|
atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py,sha256=htzwb2ROYI8yyc82xApStckPS2yCcoyaw32yC15KROs,3285
|
|
@@ -266,7 +266,7 @@ atomicshop/wrappers/mongodbw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
266
266
|
atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py,sha256=2eEOb35T259lhn5koynfTIm1hanxD02zN97ExGSBM2o,4021
|
|
267
267
|
atomicshop/wrappers/mongodbw/install_mongodb_win.py,sha256=64EUQYx7VuMC3ndO2x3nSErh5NZ_BsqMwGvPcybfC-Q,8499
|
|
268
268
|
atomicshop/wrappers/mongodbw/mongo_infra.py,sha256=IjEF0jPzQz866MpTm7rnksnyyWQeUT_B2h2DA9ryAio,2034
|
|
269
|
-
atomicshop/wrappers/mongodbw/mongodbw.py,sha256=
|
|
269
|
+
atomicshop/wrappers/mongodbw/mongodbw.py,sha256=it1TDnOF64YgDbkkBvUmUb9XGuUg6SwGnHhuqar3aHE,55929
|
|
270
270
|
atomicshop/wrappers/nodejsw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
271
271
|
atomicshop/wrappers/nodejsw/install_nodejs_ubuntu.py,sha256=wjpJdfAaY92RYl_L9esDIWuBMGeYH35RHJ5BVgMof8Y,6260
|
|
272
272
|
atomicshop/wrappers/nodejsw/install_nodejs_windows.py,sha256=WvXIcEVnKcQYD-KNwhVP094s__1tt0Ir2Y87MABl8Nc,6283
|
|
@@ -325,8 +325,8 @@ atomicshop/wrappers/socketw/statistics_csv.py,sha256=fgMzDXI0cybwUEqAxprRmY3lqbh
|
|
|
325
325
|
atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
326
326
|
atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
|
|
327
327
|
atomicshop/wrappers/winregw/winreg_network.py,sha256=AENV88H1qDidrcpyM9OwEZxX5svfi-Jb4N6FkS1xtqA,8851
|
|
328
|
-
atomicshop-2.19.
|
|
329
|
-
atomicshop-2.19.
|
|
330
|
-
atomicshop-2.19.
|
|
331
|
-
atomicshop-2.19.
|
|
332
|
-
atomicshop-2.19.
|
|
328
|
+
atomicshop-2.19.8.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
329
|
+
atomicshop-2.19.8.dist-info/METADATA,sha256=8KxTUFok-Tb489g5Kw5R07DDTmO87b3cmQC1JfAaRlY,10630
|
|
330
|
+
atomicshop-2.19.8.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
331
|
+
atomicshop-2.19.8.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
332
|
+
atomicshop-2.19.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|