atomicshop 2.16.28__py3-none-any.whl → 2.16.30__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/archiver/zips.py +5 -6
- atomicshop/basics/strings.py +2 -2
- atomicshop/dns.py +8 -6
- atomicshop/file_io/file_io.py +7 -7
- atomicshop/filesystem.py +6 -7
- atomicshop/http_parse.py +82 -67
- atomicshop/mitm/connection_thread_worker.py +187 -231
- atomicshop/mitm/engines/__parent/parser___parent.py +1 -4
- atomicshop/mitm/engines/__parent/recorder___parent.py +6 -2
- atomicshop/mitm/message.py +29 -20
- atomicshop/mitm/mitm_main.py +36 -32
- atomicshop/print_api.py +13 -53
- atomicshop/system_resource_monitor.py +8 -8
- atomicshop/system_resources.py +8 -8
- atomicshop/web.py +10 -10
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/handlers.py +20 -0
- atomicshop/wrappers/loggingw/loggingw.py +130 -7
- atomicshop/wrappers/playwrightw/engine.py +6 -7
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/socketw/dns_server.py +2 -2
- atomicshop/wrappers/socketw/sender.py +56 -28
- atomicshop/wrappers/socketw/sni.py +27 -16
- atomicshop/wrappers/socketw/socket_client.py +6 -5
- atomicshop/wrappers/socketw/socket_wrapper.py +49 -14
- {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/METADATA +1 -1
- {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/RECORD +31 -31
- {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/WHEEL +0 -0
- {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/top_level.txt +0 -0
|
@@ -2,7 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
from ..wrappers.socketw import receiver, sender, socket_client, base
|
|
4
4
|
from ..http_parse import HTTPRequestParse, HTTPResponseParse
|
|
5
|
-
from ..basics
|
|
5
|
+
from ..basics import threads, tracebacks
|
|
6
6
|
from ..print_api import print_api
|
|
7
7
|
|
|
8
8
|
from .message import ClientMessage
|
|
@@ -10,9 +10,8 @@ from .initialize_engines import assign_class_by_domain
|
|
|
10
10
|
from . import config_static
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
# Thread function on client connect.
|
|
14
13
|
def thread_worker_main(
|
|
15
|
-
|
|
14
|
+
client_socket,
|
|
16
15
|
process_commandline: str,
|
|
17
16
|
is_tls: bool,
|
|
18
17
|
tls_type: str,
|
|
@@ -24,7 +23,7 @@ def thread_worker_main(
|
|
|
24
23
|
reference_module
|
|
25
24
|
):
|
|
26
25
|
def output_statistics_csv_row():
|
|
27
|
-
# If there is no '.code' attribute in HTTPResponse, this means that this is not
|
|
26
|
+
# If there is no '.code' attribute in HTTPResponse, this means that this is not an HTTP message, so there is no
|
|
28
27
|
# status code.
|
|
29
28
|
try:
|
|
30
29
|
http_status_code: str = ','.join([str(x.code) for x in client_message.response_list_of_raw_decoded])
|
|
@@ -45,6 +44,15 @@ def thread_worker_main(
|
|
|
45
44
|
|
|
46
45
|
response_size_bytes = ','.join([str(len(x)) for x in client_message.response_list_of_raw_bytes])
|
|
47
46
|
|
|
47
|
+
if statistics_error_list and len(statistics_error_list) > 1:
|
|
48
|
+
error_string = '||'.join(statistics_error_list)
|
|
49
|
+
elif statistics_error_list and len(statistics_error_list) == 1:
|
|
50
|
+
error_string = statistics_error_list[0]
|
|
51
|
+
elif not statistics_error_list:
|
|
52
|
+
error_string = str()
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(f"Error in statistics error list. Values: {statistics_error_list}")
|
|
55
|
+
|
|
48
56
|
statistics_writer.write_row(
|
|
49
57
|
host=client_message.server_name,
|
|
50
58
|
tls_type=tls_type,
|
|
@@ -58,75 +66,124 @@ def thread_worker_main(
|
|
|
58
66
|
response_size_bytes=response_size_bytes,
|
|
59
67
|
recorded_file_path=client_message.recorded_file_path,
|
|
60
68
|
process_cmd=process_commandline,
|
|
61
|
-
error=
|
|
69
|
+
error=error_string
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def record_and_statistics_write():
|
|
73
|
+
# If recorder wasn't executed before, then execute it now
|
|
74
|
+
if config_static.LogRec.enable_request_response_recordings_in_logs:
|
|
75
|
+
recorded_file = recorder(
|
|
76
|
+
class_client_message=client_message, record_path=config_static.LogRec.recordings_path).record()
|
|
77
|
+
client_message.recorded_file_path = recorded_file
|
|
78
|
+
|
|
79
|
+
# Save statistics file.
|
|
80
|
+
output_statistics_csv_row()
|
|
81
|
+
|
|
82
|
+
def parse_http():
|
|
83
|
+
nonlocal error_message
|
|
84
|
+
# Parsing the raw bytes as HTTP.
|
|
85
|
+
request_decoded, is_http_request, request_parsing_info, request_parsing_error = (
|
|
86
|
+
HTTPRequestParse(client_message.request_raw_bytes).parse())
|
|
87
|
+
|
|
88
|
+
if is_http_request:
|
|
89
|
+
client_message.protocol = 'HTTP'
|
|
90
|
+
client_message.request_raw_decoded = request_decoded
|
|
91
|
+
print_api(request_parsing_info, logger=network_logger, logger_method='info')
|
|
92
|
+
network_logger.info(f"Method: {request_decoded.command}")
|
|
93
|
+
network_logger.info(f"Path: {request_decoded.path}")
|
|
94
|
+
# If there was error - the request is really HTTP, but there's a problem with its structure.
|
|
95
|
+
else:
|
|
96
|
+
# client_message.error = request_parsing_error
|
|
97
|
+
print_api(request_parsing_error, logger=network_logger, logger_method='error', color='yellow')
|
|
98
|
+
# It doesn't matter if we have HTTP Parsing error, since the request may not be really HTTP, so it is OK
|
|
99
|
+
# not to log it into statistics.
|
|
100
|
+
# statistics_error_list.append(error_message)
|
|
101
|
+
|
|
102
|
+
def finish_thread():
|
|
103
|
+
# At this stage there could be several times that the same socket was used to the service server - we need to
|
|
104
|
+
# close this socket as well if it still opened.
|
|
105
|
+
if service_client:
|
|
106
|
+
if service_client.socket_instance:
|
|
107
|
+
service_client.close_socket()
|
|
108
|
+
|
|
109
|
+
# If client socket is still opened - close
|
|
110
|
+
if client_socket:
|
|
111
|
+
client_socket.close()
|
|
112
|
+
network_logger.info(f"Closed client socket [{client_message.client_ip}:{client_message.source_port}]...")
|
|
113
|
+
|
|
114
|
+
network_logger.info("Thread Finished. Will continue listening on the Main thread")
|
|
115
|
+
|
|
116
|
+
# Building client message object before the loop only for any exception to occurs, since we write it to
|
|
117
|
+
# recording file in its current state.
|
|
118
|
+
client_message: ClientMessage = ClientMessage()
|
|
119
|
+
# 'recorded' boolean is needed only to write the message in case of exception in the loop or before that.
|
|
120
|
+
recorded: bool = False
|
|
121
|
+
statistics_error_list: list[str] = list()
|
|
122
|
+
|
|
123
|
+
# Only protocols that are encrypted with TLS have the server name attribute.
|
|
124
|
+
if is_tls:
|
|
125
|
+
# Get current destination domain
|
|
126
|
+
server_name = client_socket.server_hostname
|
|
127
|
+
# client_message.server_name = domain_from_dns
|
|
128
|
+
# If the protocol is not TLS, then we'll use the domain from the DNS.
|
|
129
|
+
else:
|
|
130
|
+
server_name = domain_from_dns
|
|
131
|
+
client_message.server_name = server_name
|
|
132
|
+
|
|
133
|
+
thread_id = threads.current_thread_id()
|
|
134
|
+
client_message.thread_id = thread_id
|
|
135
|
+
|
|
136
|
+
# Loading parser by domain, if there is no parser for current domain - general reference parser is loaded.
|
|
137
|
+
# These should be outside any loop and initialized only once entering the thread.
|
|
138
|
+
parser, responder, recorder = assign_class_by_domain(
|
|
139
|
+
engines_usage=config_static.TCPServer.engines_usage,
|
|
140
|
+
engines_list=engines_list,
|
|
141
|
+
message_domain_name=server_name,
|
|
142
|
+
reference_module=reference_module,
|
|
143
|
+
logger=network_logger
|
|
144
|
+
)
|
|
62
145
|
|
|
63
146
|
try:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
client_message
|
|
67
|
-
request_decoded = None
|
|
68
|
-
service_client = None
|
|
147
|
+
client_ip, source_port = client_socket.getpeername()
|
|
148
|
+
client_message.client_ip = client_ip
|
|
149
|
+
client_message.source_port = source_port
|
|
69
150
|
|
|
70
|
-
|
|
71
|
-
client_message.
|
|
72
|
-
# Get client ip and port
|
|
73
|
-
client_message.client_ip, client_message.source_port = function_client_socket_object.getpeername()
|
|
74
|
-
# Get destination port
|
|
75
|
-
client_message.destination_port = function_client_socket_object.getsockname()[1]
|
|
76
|
-
# Putting the process command line.
|
|
77
|
-
client_message.process_name = process_commandline
|
|
78
|
-
|
|
79
|
-
# Only protocols that are encrypted with TLS have the server name attribute.
|
|
80
|
-
if is_tls:
|
|
81
|
-
# Get current destination domain
|
|
82
|
-
client_message.server_name = function_client_socket_object.server_hostname
|
|
83
|
-
# client_message.server_name = domain_from_dns
|
|
84
|
-
# If the protocol is not TLS, then we'll use the domain from the DNS.
|
|
85
|
-
else:
|
|
86
|
-
client_message.server_name = domain_from_dns
|
|
87
|
-
|
|
88
|
-
network_logger.info(f"Thread Created - Client [{client_message.client_ip}:{client_message.source_port}] | "
|
|
89
|
-
f"Destination service: [{client_message.server_name}:{client_message.destination_port}]")
|
|
90
|
-
|
|
91
|
-
# Loading parser by domain, if there is no parser for current domain - general reference parser is loaded.
|
|
92
|
-
# These should be outside any loop and initialized only once entering the thread.
|
|
93
|
-
parser, responder, recorder = assign_class_by_domain(
|
|
94
|
-
engines_usage=config_static.TCPServer.engines_usage,
|
|
95
|
-
engines_list=engines_list,
|
|
96
|
-
message_domain_name=client_message.server_name,
|
|
97
|
-
reference_module=reference_module,
|
|
98
|
-
logger=network_logger
|
|
99
|
-
)
|
|
151
|
+
destination_port = client_socket.getsockname()[1]
|
|
152
|
+
client_message.destination_port = destination_port
|
|
100
153
|
|
|
101
|
-
|
|
102
|
-
|
|
154
|
+
network_logger.info(f"Thread Created - Client [{client_ip}:{source_port}] | "
|
|
155
|
+
f"Destination service: [{server_name}:{destination_port}]")
|
|
103
156
|
|
|
157
|
+
service_client = None
|
|
104
158
|
# Loop while received message is not empty, if so, close socket, since other side already closed.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
159
|
+
# noinspection PyTypeChecker
|
|
160
|
+
cycle_count: int = None
|
|
161
|
+
while True:
|
|
162
|
+
# If cycle count is None, then it's the first cycle, else it's not.
|
|
163
|
+
# The cycle_count should be added 1 in the beginning of each cycle, and not in the end, since not always
|
|
164
|
+
# the cycle will be executed till the end.
|
|
165
|
+
if cycle_count is None:
|
|
166
|
+
cycle_count = 0
|
|
167
|
+
else:
|
|
168
|
+
cycle_count += 1
|
|
169
|
+
|
|
170
|
+
recorded: bool = False
|
|
171
|
+
statistics_error_list: list[str] = list()
|
|
172
|
+
|
|
173
|
+
client_message = ClientMessage()
|
|
174
|
+
client_message.thread_id = thread_id
|
|
175
|
+
client_message.client_ip = client_ip
|
|
176
|
+
client_message.source_port = source_port
|
|
177
|
+
client_message.destination_port = destination_port
|
|
178
|
+
client_message.process_name = process_commandline
|
|
179
|
+
client_message.server_name = server_name
|
|
180
|
+
# Getting current time of message received from client.
|
|
181
|
+
client_message.request_time_received = datetime.now()
|
|
182
|
+
|
|
183
|
+
network_logger.info(f"Initializing Receiver on cycle: {str(cycle_count+1)}")
|
|
127
184
|
# Getting message from the client over the socket using specific class.
|
|
128
185
|
client_received_raw_data = receiver.Receiver(
|
|
129
|
-
ssl_socket=
|
|
186
|
+
ssl_socket=client_socket, logger=network_logger).receive()
|
|
130
187
|
|
|
131
188
|
# If the message is empty, then the connection was closed already by the other side,
|
|
132
189
|
# so we can close the socket as well.
|
|
@@ -134,70 +191,21 @@ def thread_worker_main(
|
|
|
134
191
|
if client_received_raw_data:
|
|
135
192
|
# Putting the received message to the aggregating message class.
|
|
136
193
|
client_message.request_raw_bytes = client_received_raw_data
|
|
137
|
-
# Getting current time of message received from client.
|
|
138
|
-
client_message.request_time_received = datetime.now()
|
|
139
|
-
|
|
140
|
-
# HTTP Parsing section =================================================================================
|
|
141
|
-
# Parsing the raw bytes as HTTP.
|
|
142
|
-
try:
|
|
143
|
-
request_decoded = HTTPRequestParse(client_message.request_raw_bytes)
|
|
144
|
-
except Exception:
|
|
145
|
-
message = "There was an exception in HTTP Parsing module!"
|
|
146
|
-
print_api(
|
|
147
|
-
message, error_type=True, logger=network_logger, logger_method='critical',
|
|
148
|
-
traceback_string=True)
|
|
149
|
-
# Socket connection can be closed since we have a problem in current thread and break the loop
|
|
150
|
-
client_connection_boolean = False
|
|
151
|
-
break
|
|
152
|
-
|
|
153
|
-
# Getting the status of http parsing
|
|
154
|
-
request_is_http, http_parsing_reason, http_parsing_error = request_decoded.check_if_http()
|
|
155
194
|
|
|
156
|
-
|
|
157
|
-
if
|
|
158
|
-
network_logger.info(http_parsing_reason)
|
|
159
|
-
# If there was error - the request is really HTTP, but there's a problem with its structure.
|
|
160
|
-
# So, we'll stop the loop.
|
|
161
|
-
else:
|
|
162
|
-
client_message.error = http_parsing_reason
|
|
163
|
-
network_logger.critical(client_message.error)
|
|
164
|
-
break
|
|
165
|
-
|
|
166
|
-
# If the request is HTTP protocol.
|
|
167
|
-
if request_is_http:
|
|
168
|
-
client_message.protocol = 'HTTP'
|
|
169
|
-
network_logger.info(f"Method: {request_decoded.command}")
|
|
170
|
-
network_logger.info(f"Path: {request_decoded.path}")
|
|
171
|
-
# statistics.dict['path'] = request_decoded.path
|
|
172
|
-
client_message.request_raw_decoded = request_decoded
|
|
173
|
-
# HTTP Parsing section EOF =============================================================================
|
|
174
|
-
|
|
175
|
-
# Catching exceptions in the parser
|
|
176
|
-
try:
|
|
177
|
-
parser(client_message).parse()
|
|
178
|
-
except Exception:
|
|
179
|
-
message = "Exception in Parser"
|
|
180
|
-
print_api(
|
|
181
|
-
message, error_type=True, logger=parser.logger, logger_method='critical',
|
|
182
|
-
traceback_string=True)
|
|
183
|
-
print_api(
|
|
184
|
-
message, error_type=True, logger=network_logger, logger_method='critical',
|
|
185
|
-
traceback_string=True)
|
|
186
|
-
# At this point we can pass the exception and continue the script.
|
|
195
|
+
parse_http()
|
|
196
|
+
if client_message.protocol != 'HTTP':
|
|
187
197
|
pass
|
|
188
|
-
# Socket connection can be closed since we have a problem in current thread and break the loop
|
|
189
|
-
client_connection_boolean = False
|
|
190
|
-
break
|
|
191
198
|
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
# Custom parser, should parse HTTP body or the whole message if not HTTP.
|
|
200
|
+
parser_instance = parser(client_message)
|
|
201
|
+
parser_instance.parse()
|
|
202
|
+
|
|
203
|
+
# Converting body parsed to string on logging, since there is no strict rule for the parameter
|
|
204
|
+
# to be string.
|
|
205
|
+
parser_instance.logger.info(f"{str(client_message.request_body_parsed)[0: 100]}...")
|
|
199
206
|
|
|
200
207
|
# If we're in response mode, execute responder.
|
|
208
|
+
response_raw_bytes = None
|
|
201
209
|
if config_static.TCPServer.server_response_mode:
|
|
202
210
|
# Since we're in response mode, we'll record the request anyway, after the responder did its job.
|
|
203
211
|
client_message.info = "In Server Response Mode"
|
|
@@ -206,20 +214,7 @@ def thread_worker_main(
|
|
|
206
214
|
# new entries for empty list.
|
|
207
215
|
client_message.response_list_of_raw_bytes = list()
|
|
208
216
|
# Creating response for parsed message and printing
|
|
209
|
-
|
|
210
|
-
responder.create_response(client_message)
|
|
211
|
-
except Exception:
|
|
212
|
-
message = "Exception in Responder"
|
|
213
|
-
print_api(
|
|
214
|
-
message, error_type=True, logger=responder.logger, logger_method='critical',
|
|
215
|
-
traceback_string=True)
|
|
216
|
-
print_api(
|
|
217
|
-
message, error_type=True, logger=network_logger, logger_method='critical',
|
|
218
|
-
traceback_string=True)
|
|
219
|
-
pass
|
|
220
|
-
# Socket connection can be closed since we have a problem in current thread and break the loop.
|
|
221
|
-
client_connection_boolean = False
|
|
222
|
-
break
|
|
217
|
+
responder.create_response(client_message)
|
|
223
218
|
|
|
224
219
|
# Output first 100 characters of all the responses in the list.
|
|
225
220
|
for response_raw_bytes in client_message.response_list_of_raw_bytes:
|
|
@@ -254,8 +249,11 @@ def thread_worker_main(
|
|
|
254
249
|
# be passed.
|
|
255
250
|
# If there was connection error or socket close, then "ssl_socket" of the "service_client"
|
|
256
251
|
# will be empty.
|
|
257
|
-
response_raw_bytes, client_message.error, client_message.server_ip, service_ssl_socket
|
|
258
|
-
service_client.send_receive_to_service(client_message.request_raw_bytes)
|
|
252
|
+
response_raw_bytes, client_message.error, client_message.server_ip, service_ssl_socket = (
|
|
253
|
+
service_client.send_receive_to_service(client_message.request_raw_bytes))
|
|
254
|
+
|
|
255
|
+
if client_message.error is not None:
|
|
256
|
+
statistics_error_list.append(client_message.error)
|
|
259
257
|
|
|
260
258
|
# Since we need a list for raw bytes, we'll add the 'response_raw_bytes' to our list object.
|
|
261
259
|
# But we need to re-initiate it first.
|
|
@@ -265,107 +263,65 @@ def thread_worker_main(
|
|
|
265
263
|
client_message.response_list_of_raw_decoded = list()
|
|
266
264
|
# Make HTTP Response parsing only if there was response at all.
|
|
267
265
|
if response_raw_bytes:
|
|
268
|
-
response_raw_decoded =
|
|
269
|
-
|
|
266
|
+
response_raw_decoded, is_http_response, response_parsing_error = (
|
|
267
|
+
HTTPResponseParse(response_raw_bytes).parse())
|
|
268
|
+
|
|
269
|
+
if is_http_response:
|
|
270
|
+
client_message.response_list_of_raw_decoded.append(response_raw_decoded)
|
|
271
|
+
else:
|
|
272
|
+
client_message.response_list_of_raw_decoded.append(None)
|
|
270
273
|
|
|
271
274
|
# So if the socket was closed and there was an error we can break the loop
|
|
272
275
|
if not service_ssl_socket:
|
|
273
276
|
break
|
|
274
277
|
|
|
275
|
-
#
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
record_path=config_static.LogRec.recordings_path).record()
|
|
281
|
-
client_message.recorded_file_path = recorded_file
|
|
282
|
-
except Exception:
|
|
283
|
-
message = "Exception in Recorder"
|
|
284
|
-
print_api(
|
|
285
|
-
message, error_type=True, logger=recorder.logger, logger_method='critical',
|
|
286
|
-
traceback_string=True)
|
|
287
|
-
print_api(
|
|
288
|
-
message, error_type=True, logger=network_logger, logger_method='critical',
|
|
289
|
-
traceback_string=True)
|
|
290
|
-
|
|
291
|
-
function_recorded = True
|
|
292
|
-
|
|
293
|
-
# Save statistics file.
|
|
294
|
-
output_statistics_csv_row()
|
|
295
|
-
|
|
296
|
-
try:
|
|
297
|
-
# If there is a response, then send it.
|
|
298
|
-
if response_raw_bytes:
|
|
299
|
-
# Sending response/s to client no matter if in record mode or not.
|
|
300
|
-
network_logger.info(
|
|
301
|
-
f"Sending messages to client: {len(client_message.response_list_of_raw_bytes)}")
|
|
302
|
-
function_data_sent = None
|
|
303
|
-
|
|
304
|
-
# Iterate through the list of byte responses.
|
|
305
|
-
for response_raw_bytes in client_message.response_list_of_raw_bytes:
|
|
306
|
-
function_data_sent = sender.Sender(
|
|
307
|
-
ssl_socket=function_client_socket_object, class_message=response_raw_bytes,
|
|
308
|
-
logger=network_logger).send()
|
|
309
|
-
|
|
310
|
-
# If there was problem with sending data, we'll break current loop.
|
|
311
|
-
if not function_data_sent:
|
|
312
|
-
break
|
|
313
|
-
# If there is no response, close the socket.
|
|
314
|
-
else:
|
|
315
|
-
function_data_sent = None
|
|
316
|
-
network_logger.info(f"Response empty, nothing to send to client.")
|
|
317
|
-
except Exception:
|
|
318
|
-
message = "Not sending anything to the client, since there is no response available"
|
|
319
|
-
print_api(
|
|
320
|
-
message, error_type=True, logger=network_logger, logger_method='critical',
|
|
321
|
-
traceback_string=True)
|
|
322
|
-
# Pass the exception
|
|
323
|
-
pass
|
|
324
|
-
# Break the while loop
|
|
325
|
-
break
|
|
278
|
+
# If there is a response, then send it.
|
|
279
|
+
if response_raw_bytes:
|
|
280
|
+
# Sending response/s to client no matter if in record mode or not.
|
|
281
|
+
network_logger.info(
|
|
282
|
+
f"Sending messages to client: {len(client_message.response_list_of_raw_bytes)}")
|
|
326
283
|
|
|
327
|
-
|
|
328
|
-
|
|
284
|
+
# Iterate through the list of byte responses.
|
|
285
|
+
for response_raw_bytes in client_message.response_list_of_raw_bytes:
|
|
286
|
+
error_on_send: str = sender.Sender(
|
|
287
|
+
ssl_socket=client_socket, class_message=response_raw_bytes,
|
|
288
|
+
logger=network_logger).send()
|
|
289
|
+
|
|
290
|
+
# If there was problem with sending data, we'll break current loop.
|
|
291
|
+
if error_on_send:
|
|
292
|
+
statistics_error_list.append(error_on_send)
|
|
293
|
+
break
|
|
294
|
+
# If response from server came back empty, then the server has closed the connection,
|
|
295
|
+
# we will do the same.
|
|
296
|
+
else:
|
|
297
|
+
network_logger.info(f"Response empty, nothing to send to client.")
|
|
329
298
|
break
|
|
299
|
+
|
|
300
|
+
record_and_statistics_write()
|
|
301
|
+
recorded = True
|
|
330
302
|
else:
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
303
|
+
# If it's the first cycle we will record the message from the client if it came empty.
|
|
304
|
+
if cycle_count == 0:
|
|
305
|
+
record_and_statistics_write()
|
|
306
|
+
|
|
307
|
+
# In other cases, we'll just break the loop, since empty message means that the other side closed the
|
|
308
|
+
# connection.
|
|
309
|
+
recorded = True
|
|
310
|
+
break
|
|
311
|
+
|
|
312
|
+
finish_thread()
|
|
313
|
+
except Exception as e:
|
|
314
|
+
exception_message = tracebacks.get_as_string(one_line=True)
|
|
315
|
+
error_message = f'Socket Thread [{str(thread_id)}] Exception: {exception_message}'
|
|
316
|
+
print_api(error_message, logger_method='critical', logger=network_logger)
|
|
317
|
+
statistics_error_list.append(error_message)
|
|
335
318
|
|
|
336
319
|
# === At this point while loop of 'client_connection_boolean' was broken =======================================
|
|
337
320
|
# If recorder wasn't executed before, then execute it now
|
|
338
|
-
if not
|
|
339
|
-
|
|
340
|
-
try:
|
|
341
|
-
recorded_file = recorder(
|
|
342
|
-
class_client_message=client_message, record_path=config_static.LogRec.recordings_path).record()
|
|
343
|
-
client_message.recorded_file_path = recorded_file
|
|
344
|
-
except Exception:
|
|
345
|
-
message = "Exception in Recorder"
|
|
346
|
-
print_api(
|
|
347
|
-
message, error_type=True, logger=recorder.logger, logger_method='critical',
|
|
348
|
-
traceback_string=True)
|
|
349
|
-
print_api(
|
|
350
|
-
message, error_type=True, logger=network_logger, logger_method='critical',
|
|
351
|
-
traceback_string=True)
|
|
352
|
-
|
|
353
|
-
# Save statistics file.
|
|
354
|
-
output_statistics_csv_row()
|
|
355
|
-
|
|
356
|
-
# At this stage there could be several times that the same socket was used to the service server - we need to
|
|
357
|
-
# close this socket as well if it still opened.
|
|
358
|
-
if service_client:
|
|
359
|
-
if service_client.socket_instance:
|
|
360
|
-
service_client.close_socket()
|
|
321
|
+
if not recorded:
|
|
322
|
+
record_and_statistics_write()
|
|
361
323
|
|
|
362
|
-
|
|
363
|
-
if function_client_socket_object:
|
|
364
|
-
function_client_socket_object.close()
|
|
365
|
-
network_logger.info(f"Closed client socket [{client_message.client_ip}:{client_message.source_port}]...")
|
|
324
|
+
finish_thread()
|
|
366
325
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
message = "Undocumented exception in thread worker"
|
|
370
|
-
print_api(
|
|
371
|
-
message, error_type=True, logger=network_logger, logger_method='critical', traceback_string=True)
|
|
326
|
+
# After the socket clean up, we will still raise the exception to the main thread.
|
|
327
|
+
raise e
|
|
@@ -12,7 +12,4 @@ class ParserParent:
|
|
|
12
12
|
# This is general parser, so we don't parse anything and 'request_body_parsed' gets empty byte string.
|
|
13
13
|
self.class_client_message.request_body_parsed = b''
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
self.logger.info(f"Parsed: {self.class_client_message.request_body_parsed[0: 100]}...")
|
|
17
|
-
except Exception:
|
|
18
|
-
pass
|
|
15
|
+
self.logger.info(f"Parsed: {self.class_client_message.request_body_parsed[0: 100]}...")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from datetime import datetime
|
|
3
|
+
import json
|
|
3
4
|
|
|
4
5
|
from ...shared_functions import build_module_names, create_custom_logger, get_json
|
|
5
6
|
from ... import message, recs_files
|
|
@@ -87,10 +88,13 @@ class RecorderParent:
|
|
|
87
88
|
# Convert the requests and responses to hex.
|
|
88
89
|
self.convert_messages()
|
|
89
90
|
# Get the message in dict / JSON format
|
|
90
|
-
record_message = get_json(self.class_client_message)
|
|
91
|
+
# record_message = get_json(self.class_client_message)
|
|
92
|
+
record_message_dict: dict = dict(self.class_client_message)
|
|
93
|
+
recorded_message_json_string = json.dumps(record_message_dict)
|
|
91
94
|
|
|
92
95
|
# Since we already dumped the object to dictionary string, we'll just save the object to regular file.
|
|
93
|
-
file_io.write_file(
|
|
96
|
+
file_io.write_file(
|
|
97
|
+
recorded_message_json_string, self.record_file_path, enable_long_file_path=True, **{'logger': self.logger})
|
|
94
98
|
|
|
95
99
|
self.logger.info(f"Recorded to file: {self.record_file_path}")
|
|
96
100
|
|
atomicshop/mitm/message.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from .. import http_parse
|
|
5
|
+
from ..basics import dicts
|
|
6
|
+
|
|
7
|
+
|
|
1
8
|
class ClientMessage:
|
|
2
9
|
""" A class that will store all the message details from the client """
|
|
3
10
|
def __init__(self):
|
|
4
11
|
self.request_raw_bytes: bytearray = bytearray()
|
|
5
|
-
|
|
6
|
-
self.
|
|
12
|
+
# noinspection PyTypeChecker
|
|
13
|
+
self.request_time_received: datetime = None
|
|
14
|
+
self.request_raw_decoded: Union[http_parse.HTTPRequestParse, any] = None
|
|
7
15
|
self.request_body_parsed = None
|
|
8
16
|
self.request_raw_hex: hex = None
|
|
9
|
-
# self.response_raw_bytes: bytearray = bytearray()
|
|
10
17
|
self.response_list_of_raw_bytes: list = list()
|
|
11
18
|
self.response_list_of_raw_decoded: list = list()
|
|
12
|
-
# self.response_raw_hex: hex = None
|
|
13
19
|
self.response_list_of_raw_hex: list = list()
|
|
14
20
|
self.server_name: str = str()
|
|
15
21
|
self.server_ip: str = str()
|
|
@@ -23,19 +29,22 @@ class ClientMessage:
|
|
|
23
29
|
self.protocol: str = str()
|
|
24
30
|
self.recorded_file_path: str = str()
|
|
25
31
|
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
def __iter__(self):
|
|
33
|
+
# __dict__ returns a dictionary containing the instance's attributes
|
|
34
|
+
for key, value in self.__dict__.items():
|
|
35
|
+
if key == 'request_raw_bytes':
|
|
36
|
+
value = str(value)
|
|
37
|
+
elif key == 'request_time_received':
|
|
38
|
+
value = value.strftime('%Y-%m-%d-%H:%M:%S.%f')
|
|
39
|
+
elif key == 'request_raw_decoded':
|
|
40
|
+
if isinstance(value, http_parse.HTTPRequestParse):
|
|
41
|
+
value = dicts.convert_complex_object_to_dict(value)
|
|
42
|
+
else:
|
|
43
|
+
value = str(value)
|
|
44
|
+
elif key == 'request_body_parsed':
|
|
45
|
+
value = dicts.convert_complex_object_to_dict(value)
|
|
46
|
+
elif key == 'response_list_of_raw_bytes':
|
|
47
|
+
value = [str(bytes_response) for bytes_response in value]
|
|
48
|
+
elif key == 'response_list_of_raw_decoded':
|
|
49
|
+
value = [dicts.convert_complex_object_to_dict(complex_response) for complex_response in value]
|
|
50
|
+
yield key, value
|