monkeytoolbox 0.1.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.
- CHANGELOG.md +15 -0
- README.md +14 -0
- monkeytoolbox/__init__.py +34 -0
- monkeytoolbox/code_utils.py +160 -0
- monkeytoolbox/decorators.py +66 -0
- monkeytoolbox/environment.py +38 -0
- monkeytoolbox/file_utils.py +78 -0
- monkeytoolbox/network_utils.py +53 -0
- monkeytoolbox/secure_directory.py +88 -0
- monkeytoolbox/secure_file.py +85 -0
- monkeytoolbox/threading.py +140 -0
- monkeytoolbox/windows_permissions.py +52 -0
- monkeytoolbox-0.1.0.dist-info/LICENSE +677 -0
- monkeytoolbox-0.1.0.dist-info/METADATA +36 -0
- monkeytoolbox-0.1.0.dist-info/RECORD +16 -0
- monkeytoolbox-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import threading
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from itertools import count
|
|
5
|
+
from threading import Lock, Thread
|
|
6
|
+
from typing import Any, Callable, Iterable, Iterator, Optional, Tuple, TypeVar
|
|
7
|
+
|
|
8
|
+
from monkeytypes import Event
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_worker_threads(
|
|
14
|
+
target: Callable[..., None],
|
|
15
|
+
name_prefix: str,
|
|
16
|
+
args: Tuple = (),
|
|
17
|
+
num_workers: int = 2,
|
|
18
|
+
):
|
|
19
|
+
worker_threads = []
|
|
20
|
+
counter = run_worker_threads.counters.setdefault(name_prefix, count(start=1))
|
|
21
|
+
for i in range(0, num_workers):
|
|
22
|
+
name = f"{name_prefix}-{next(counter):02d}"
|
|
23
|
+
t = create_daemon_thread(target=target, name=name, args=args)
|
|
24
|
+
t.start()
|
|
25
|
+
worker_threads.append(t)
|
|
26
|
+
|
|
27
|
+
for t in worker_threads:
|
|
28
|
+
t.join()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
run_worker_threads.counters = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_daemon_thread(target: Callable[..., None], name: str, args: Tuple = ()) -> Thread:
|
|
35
|
+
return Thread(target=target, name=name, args=args, daemon=True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def interruptible_iter(
|
|
39
|
+
iterator: Iterable,
|
|
40
|
+
interrupt: Event,
|
|
41
|
+
log_message: Optional[str] = None,
|
|
42
|
+
log_level: int = logging.DEBUG,
|
|
43
|
+
) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Wraps an iterator so that the iterator can be interrupted if the `interrupt` event is set. This
|
|
46
|
+
is a convinient way to make loops interruptible and avoids the need to add an `if` to each and
|
|
47
|
+
every loop.
|
|
48
|
+
:param Iterable iterator: An iterator that will be made interruptible.
|
|
49
|
+
:param Event interrupt: An `Event` that, if set, will prevent the remainder of the
|
|
50
|
+
iterator's items from being processed.
|
|
51
|
+
:param str log_message: A message to be logged if the iterator is interrupted. If `log_message`
|
|
52
|
+
is `None` (default), then no message is logged.
|
|
53
|
+
:param int log_level: The log level at which to log `log_message`, defaults to `logging.DEBUG`.
|
|
54
|
+
"""
|
|
55
|
+
for i in iterator:
|
|
56
|
+
if interrupt.is_set():
|
|
57
|
+
if log_message:
|
|
58
|
+
logger.log(log_level, log_message)
|
|
59
|
+
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
yield i
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def interruptible_function(*, msg: Optional[str] = None, default_return_value: Any = None):
|
|
66
|
+
"""
|
|
67
|
+
This decorator allows a function to be skipped if an interrupt (`Event`) is set. This is
|
|
68
|
+
useful for interrupting running code without introducing duplicate `if` checks at the beginning
|
|
69
|
+
of each function.
|
|
70
|
+
|
|
71
|
+
Note: It is required that the decorated function accept a keyword-only argument named
|
|
72
|
+
"interrupt".
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
def run_algorithm(*inputs, interrupt: Event):
|
|
76
|
+
return_value = do_action_1(inputs[1], interrupt=interrupt)
|
|
77
|
+
return_value = do_action_2(return_value + inputs[2], interrupt=interrupt)
|
|
78
|
+
return_value = do_action_3(return_value + inputs[3], interrupt=interrupt)
|
|
79
|
+
|
|
80
|
+
return return_value
|
|
81
|
+
|
|
82
|
+
@interruptible_function(msg="Interrupt detected, skipping action 1", default_return_value=0)
|
|
83
|
+
def do_action_1(input, *, interrupt: Event):
|
|
84
|
+
# Process input
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
@interruptible_function(msg="Interrupt detected, skipping action 2", default_return_value=0)
|
|
88
|
+
def do_action_2(input, *, interrupt: Event):
|
|
89
|
+
# Process input
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
@interruptible_function(msg="Interrupt detected, skipping action 2", default_return_value=0)
|
|
93
|
+
def do_action_2(input, *, interrupt: Event):
|
|
94
|
+
# Process input
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
:param str msg: A message to log at the debug level if an interrupt is detected. Defaults to
|
|
98
|
+
None.
|
|
99
|
+
:param Any default_return_value: A value to return if the wrapped function is not run. Defaults
|
|
100
|
+
to None.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def _decorator(fn):
|
|
104
|
+
@wraps(fn)
|
|
105
|
+
def _wrapper(*args, interrupt: Event, **kwargs):
|
|
106
|
+
if interrupt.is_set():
|
|
107
|
+
if msg:
|
|
108
|
+
logger.debug(msg)
|
|
109
|
+
return default_return_value
|
|
110
|
+
|
|
111
|
+
return fn(*args, interrupt=interrupt, **kwargs)
|
|
112
|
+
|
|
113
|
+
return _wrapper
|
|
114
|
+
|
|
115
|
+
return _decorator
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class InterruptableThreadMixin:
|
|
119
|
+
def __init__(self):
|
|
120
|
+
self._interrupted = threading.Event()
|
|
121
|
+
|
|
122
|
+
def stop(self):
|
|
123
|
+
"""Stop a running thread."""
|
|
124
|
+
self._interrupted.set()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
T = TypeVar("T")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ThreadSafeIterator(Iterator[T]):
|
|
131
|
+
"""Provides a thread-safe iterator that wraps another iterator"""
|
|
132
|
+
|
|
133
|
+
def __init__(self, iterator: Iterator[T]):
|
|
134
|
+
self._lock = Lock()
|
|
135
|
+
self._iterator = iterator
|
|
136
|
+
|
|
137
|
+
def __next__(self) -> T:
|
|
138
|
+
while True:
|
|
139
|
+
with self._lock:
|
|
140
|
+
return next(self._iterator)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Any, Mapping
|
|
2
|
+
|
|
3
|
+
import ntsecuritycon
|
|
4
|
+
import win32api
|
|
5
|
+
import win32security
|
|
6
|
+
|
|
7
|
+
ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS
|
|
8
|
+
ACCESS_PERMISSIONS_FULL_CONTROL = ntsecuritycon.FILE_ALL_ACCESS
|
|
9
|
+
INHERITANCE_OBJECT_AND_CONTAINER = (
|
|
10
|
+
win32security.CONTAINER_INHERIT_ACE | win32security.OBJECT_INHERIT_ACE
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_security_descriptor_for_owner_only_permissions():
|
|
15
|
+
user_sid = _get_user_pySID_object()
|
|
16
|
+
entries = [_get_ace_for_owner_only_permissions(user_sid)]
|
|
17
|
+
|
|
18
|
+
dacl = win32security.ACL()
|
|
19
|
+
dacl.SetEntriesInAcl(entries)
|
|
20
|
+
|
|
21
|
+
security_descriptor = win32security.SECURITY_DESCRIPTOR()
|
|
22
|
+
security_descriptor.SetSecurityDescriptorControl(
|
|
23
|
+
win32security.SE_DACL_PROTECTED, win32security.SE_DACL_PROTECTED
|
|
24
|
+
)
|
|
25
|
+
security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0)
|
|
26
|
+
|
|
27
|
+
return security_descriptor
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_user_pySID_object():
|
|
31
|
+
# Note: We do this using the process handle and token instead of by account name, as some SIDs
|
|
32
|
+
# have no corresponding account name, such as a logon SID that identifies a logon session. This
|
|
33
|
+
# is particularly an issue when using the SMB exploiter. See issue #3173.
|
|
34
|
+
process_handle = win32api.GetCurrentProcess()
|
|
35
|
+
token_handle = win32security.OpenProcessToken(process_handle, win32security.TOKEN_ALL_ACCESS)
|
|
36
|
+
|
|
37
|
+
sid, _ = win32security.GetTokenInformation(token_handle, win32security.TokenUser)
|
|
38
|
+
|
|
39
|
+
return sid
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_ace_for_owner_only_permissions(user_sid) -> Mapping[str, Any]:
|
|
43
|
+
return {
|
|
44
|
+
"AccessMode": ACCESS_MODE_GRANT_ACCESS,
|
|
45
|
+
"AccessPermissions": ACCESS_PERMISSIONS_FULL_CONTROL,
|
|
46
|
+
"Inheritance": INHERITANCE_OBJECT_AND_CONTAINER,
|
|
47
|
+
"Trustee": {
|
|
48
|
+
"TrusteeType": win32security.TRUSTEE_IS_USER,
|
|
49
|
+
"TrusteeForm": win32security.TRUSTEE_IS_SID,
|
|
50
|
+
"Identifier": user_sid,
|
|
51
|
+
},
|
|
52
|
+
}
|