dpyproxy 2.2.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.
Files changed (59) hide show
  1. dpyproxy/__init__.py +1 -0
  2. dpyproxy/__main__.py +4 -0
  3. dpyproxy-2.2.0.dist-info/METADATA +296 -0
  4. dpyproxy-2.2.0.dist-info/RECORD +59 -0
  5. dpyproxy-2.2.0.dist-info/WHEEL +4 -0
  6. dpyproxy-2.2.0.dist-info/entry_points.txt +2 -0
  7. dpyproxy-2.2.0.dist-info/licenses/LICENSE +201 -0
  8. enumerators/DnsProxyMode.py +39 -0
  9. enumerators/DnsResolvers.py +141 -0
  10. enumerators/HttpMethod.py +17 -0
  11. enumerators/Modules.py +38 -0
  12. enumerators/Port.py +11 -0
  13. enumerators/TcpProxyMode.py +17 -0
  14. enumerators/TlsVersion.py +21 -0
  15. enumerators/__init__.py +0 -0
  16. exception/DnsException.py +7 -0
  17. exception/ParserException.py +7 -0
  18. exception/__init__.py +0 -0
  19. main.py +94 -0
  20. modules/Module.py +45 -0
  21. modules/__init__.py +0 -0
  22. modules/dns/DnsModeDeterminator.py +358 -0
  23. modules/dns/DnsModule.py +113 -0
  24. modules/dns/DnsProxy.py +277 -0
  25. modules/dns/DnsResolver.py +18 -0
  26. modules/dns/__init__.py +0 -0
  27. modules/http/HttpModule.py +69 -0
  28. modules/http/HttpStrategies.py +849 -0
  29. modules/http/HttpUtils.py +94 -0
  30. modules/http/__init__.py +0 -0
  31. modules/tls/TcpProxy.py +106 -0
  32. modules/tls/TlsModule.py +173 -0
  33. modules/tls/__init__.py +0 -0
  34. network/DomainResolver.py +472 -0
  35. network/NetworkAddress.py +10 -0
  36. network/WrappedSocket.py +97 -0
  37. network/__init__.py +0 -0
  38. network/protocols/Dns.py +62 -0
  39. network/protocols/Http.py +109 -0
  40. network/protocols/Socksv4.py +70 -0
  41. network/protocols/Socksv5.py +106 -0
  42. network/protocols/Tls.py +113 -0
  43. network/protocols/__init__.py +0 -0
  44. network/tcp/Forwarder.py +203 -0
  45. network/tcp/TcpConnectionHandler.py +264 -0
  46. network/tcp/WrappedTcpSocket.py +30 -0
  47. network/tcp/__init__.py +0 -0
  48. network/udp/__init__.py +0 -0
  49. test/Sink.py +23 -0
  50. test/__init__.py +0 -0
  51. test/test_dns.py +98 -0
  52. test/test_http.py +57 -0
  53. test/test_tls.py +63 -0
  54. util/DnsAutoModeRuntimeMeasurement.py +62 -0
  55. util/DnsReachabilityCollector.py +160 -0
  56. util/DnsResolversDomainResolver.py +36 -0
  57. util/Util.py +62 -0
  58. util/__init__.py +0 -0
  59. util/constants.py +8 -0
@@ -0,0 +1,109 @@
1
+ from enumerators.HttpMethod import HttpMethod
2
+ from exception.ParserException import ParserException
3
+ from network.NetworkAddress import NetworkAddress
4
+ from network.tcp.WrappedTcpSocket import WrappedTcpSocket
5
+
6
+
7
+ class Http:
8
+ """
9
+ Implements methods to parse HTTP messages.
10
+ """
11
+
12
+ @staticmethod
13
+ def is_valid_http(wrapped_socket: WrappedTcpSocket) -> bool:
14
+ try:
15
+ Http._parse_http_method(wrapped_socket, HttpMethod.all(), True)
16
+ return True
17
+ except ParserException:
18
+ return False
19
+
20
+ @staticmethod
21
+ def _parse_http_method(wrapped_socket: WrappedTcpSocket, methods: list[str], peek: bool = False) -> (str, int, str):
22
+ """
23
+ Reads the first line of a http method to parse the domain from it.
24
+ :return: host and port in the method, defaults to port 80 if no port found
25
+ """
26
+ try:
27
+ # read complete message
28
+ message = wrapped_socket.read_until([b"\n\n", b"\r\n\r\n"], 500, peek).decode("ASCII")
29
+ # extract first line, assume that \r\n are at least used coherently in the message
30
+ if "\r\n" in message:
31
+ first_line = message.split("\r\n")[0]
32
+ else:
33
+ first_line = message.split("\n")[0]
34
+ except UnicodeDecodeError as e:
35
+ raise ParserException(f"Could not decode ASCII in first line of HTTP request with exception {e}")
36
+ # check for any method
37
+ parsed_method = False
38
+ for method in methods:
39
+ if first_line.upper().startswith(f"{method.upper()} "):
40
+ parsed_method = True
41
+ if not parsed_method:
42
+ raise ParserException(f"Not a {methods} request")
43
+ if first_line.count(" ") not in [1, 2]:
44
+ raise ParserException("Not a valid HTTP request, could not determine target URI")
45
+ if first_line.count(" ") == 1:
46
+ # HTTP/0.9
47
+ _, uri = first_line.split(" ")
48
+ version = "HTTP/0.9"
49
+ else:
50
+ _, uri, version = first_line.split(" ")
51
+ if version.upper() != "HTTP/1.1" and version.upper() != "HTTP/1.0" and version.upper() != "HTTP/0.9":
52
+ raise ParserException("Not a valid HTTP request, only HTTP/0.9, HTTP/1.0, and HTTP/1.1 supported")
53
+ host, _, port = Http.parse_uri(uri)
54
+ return host, port, version
55
+
56
+ @staticmethod
57
+ def read_http_get(wrapped_socket: WrappedTcpSocket) -> (str, int, str):
58
+ """
59
+ Reads the first line of a http get request.
60
+ :return: host, port, and version from the http get request.
61
+ """
62
+ return Http._parse_http_method(wrapped_socket, ["GET"], True)
63
+
64
+ @staticmethod
65
+ def read_http_connect(wrapped_socket: WrappedTcpSocket) -> (str, int, str):
66
+ """
67
+ Reads the first line of a http connect request.
68
+ :return: host, port, and http version from the http connect request.
69
+ """
70
+ return Http._parse_http_method(wrapped_socket, ["CONNECT"], False)
71
+
72
+ @staticmethod
73
+ def parse_uri(uri: str) -> (str, str, int):
74
+ """
75
+ Parses a URI into its host, path and port components.
76
+ Parameters are currently not parsed.
77
+ :param uri: uri to parse
78
+ :return: host, path, port
79
+ """
80
+ if "://" in uri:
81
+ # remove protocol prefix if present
82
+ uri = uri.split("://")[1]
83
+
84
+ if "/" in uri:
85
+ # split host and path
86
+ uri, path = uri.split("/", 1)
87
+ path = "/" + path
88
+ else:
89
+ path = "/"
90
+
91
+ if ":" in uri:
92
+ # split host and port
93
+ uri, port = uri.split(":")
94
+ port = int(port)
95
+ else:
96
+ port = 80
97
+
98
+ return uri, path, port
99
+
100
+ @staticmethod
101
+ def connect_message(server_address: NetworkAddress, version: str) -> bytes:
102
+ return (
103
+ f"CONNECT {server_address.host}:{server_address.port} {version}\n"
104
+ f"Host: {server_address.host}:{server_address.port}\n\n".encode("ASCII")
105
+ )
106
+
107
+ @staticmethod
108
+ def http_200_ok(version: str) -> bytes:
109
+ return f"{version} 200 OK\n\n".encode("ASCII")
@@ -0,0 +1,70 @@
1
+ from exception.ParserException import ParserException
2
+ from network.NetworkAddress import NetworkAddress
3
+ from network.tcp.WrappedTcpSocket import WrappedTcpSocket
4
+ from util.constants import SOCKSv4_HEADER
5
+ from util.Util import is_valid_ipv4_address
6
+
7
+
8
+ class Socksv4:
9
+ """
10
+ Implements SOCKSv4a protocol
11
+ """
12
+
13
+ REQUEST_MODE = b"\01"
14
+ BIND_MODE = b"\02"
15
+
16
+ @staticmethod
17
+ def read_socks4(connection_socket: WrappedTcpSocket) -> tuple[str, int]:
18
+
19
+ version = connection_socket.recv(1)
20
+ if version != SOCKSv4_HEADER:
21
+ raise ParserException("Not a SOCKSv4 request")
22
+
23
+ mode = connection_socket.recv(1)
24
+ if mode == Socksv4.BIND_MODE:
25
+ raise ParserException("BIND mode not supported")
26
+ if mode != Socksv4.REQUEST_MODE:
27
+ raise ParserException(f"Socks mode {mode} not supported")
28
+
29
+ port = int.from_bytes(connection_socket.recv(2), byteorder="big")
30
+ if not 0 <= port <= 65535:
31
+ raise ParserException(f"Invalid port {port}")
32
+
33
+ # str ip from bytes
34
+ ip = ".".join(f"{c}" for c in connection_socket.recv(4))
35
+
36
+ # ignore user_id but parse bytes until null byte
37
+ connection_socket.read_until([b"\x00"])
38
+
39
+ # socksv4a allows for domain/ip? behind user id
40
+ if ip.startswith("0.0.0."):
41
+ ip = connection_socket.read_until([b"\x00"])[:-1].decode("utf-8")
42
+
43
+ return ip, port
44
+
45
+ @staticmethod
46
+ def socks4_request(server_address: NetworkAddress):
47
+ if not is_valid_ipv4_address(server_address.host):
48
+ # Socksv4a domain encoding
49
+ return (
50
+ SOCKSv4_HEADER
51
+ + b"\x01"
52
+ + server_address.port.to_bytes(2, byteorder="big")
53
+ + "0.0.0.1".encode("'utf-8")
54
+ + b"\x00"
55
+ + server_address.host.encode("utf-8")
56
+ + b"\x00"
57
+ )
58
+ else:
59
+ # Socksv4 ip encoding
60
+ return (
61
+ SOCKSv4_HEADER
62
+ + b"\x01"
63
+ + server_address.port.to_bytes(2, byteorder="big")
64
+ + bytes([int(i) for i in server_address.host.split(".")])
65
+ + b"\x00"
66
+ )
67
+
68
+ @staticmethod
69
+ def socks4_ok() -> bytes:
70
+ return b"\00\x5a\xff\xff\xff\xff\xff\xff"
@@ -0,0 +1,106 @@
1
+ import socket
2
+
3
+ from exception.ParserException import ParserException
4
+ from network.NetworkAddress import NetworkAddress
5
+ from network.tcp.WrappedTcpSocket import WrappedTcpSocket
6
+ from util.constants import SOCKSv5_HEADER
7
+ from util.Util import is_valid_ipv4_address
8
+
9
+
10
+ class Socksv5:
11
+ """
12
+ Implements SOCKSv5 protocol, only supports no auth
13
+ """
14
+
15
+ REQUEST_MODE = b"\x01"
16
+ BIND_MODE = b"\x02"
17
+ UDP_PORT = b"\x03"
18
+ RESERVED_BYTE = b"\x00"
19
+ NO_AUTH = b"\x00"
20
+
21
+ @staticmethod
22
+ def read_socks5(connection_socket: WrappedTcpSocket) -> tuple[str, int]:
23
+
24
+ # read connection methods
25
+ version = connection_socket.recv(1)
26
+ if version != SOCKSv5_HEADER:
27
+ raise ParserException("Not a SOCKSv5 request")
28
+
29
+ number_authentication_methods = int.from_bytes(connection_socket.recv(1), byteorder="big")
30
+ if number_authentication_methods == 0:
31
+ connection_socket.send(SOCKSv5_HEADER + b"\xff")
32
+ raise ParserException("No auth method provided")
33
+
34
+ authentication_methods = connection_socket.read(number_authentication_methods)
35
+ if Socksv5.NO_AUTH not in authentication_methods:
36
+ connection_socket.send(SOCKSv5_HEADER + b"\xff")
37
+ raise ParserException("No auth method not supported by client")
38
+
39
+ # always choose no auth
40
+ connection_socket.send(SOCKSv5_HEADER + Socksv5.NO_AUTH)
41
+
42
+ # receive destination address
43
+ version = connection_socket.recv(1)
44
+ if version != SOCKSv5_HEADER:
45
+ raise ParserException("Not a SOCKSv5 request")
46
+
47
+ mode = connection_socket.recv(1)
48
+ if mode == Socksv5.BIND_MODE:
49
+ raise ParserException("BIND mode not supported")
50
+ if mode == Socksv5.UDP_PORT:
51
+ raise ParserException("UDP mode not supported")
52
+ if mode != Socksv5.REQUEST_MODE:
53
+ raise ParserException(f"Socks mode {mode} not supported")
54
+
55
+ if connection_socket.recv(1) != Socksv5.RESERVED_BYTE:
56
+ raise ParserException("Invalid reserved byte")
57
+
58
+ host = Socksv5._read_address(connection_socket)
59
+
60
+ port = int.from_bytes(connection_socket.read(2), byteorder="big")
61
+ if not 0 <= port <= 65535:
62
+ raise ParserException(f"Invalid port {port}")
63
+
64
+ return host, port
65
+
66
+ @staticmethod
67
+ def _read_address(connection_socket: WrappedTcpSocket) -> str:
68
+ address_type = connection_socket.recv(1)
69
+ if address_type == b"\x01":
70
+ # ipv4
71
+ host = ".".join(f"{c}" for c in connection_socket.read(4))
72
+ elif address_type == b"\x04":
73
+ # ipv6
74
+ host = ":".join(f"{c}" for c in connection_socket.read(16))
75
+ elif address_type == b"\x03":
76
+ # domain
77
+ length = int.from_bytes(connection_socket.read(1), byteorder="big")
78
+ host = connection_socket.read(length).decode("utf-8")
79
+ else:
80
+ raise ParserException(f"Address type {address_type} not supported")
81
+ return host
82
+
83
+ @staticmethod
84
+ def socks5_auth_methods() -> bytes:
85
+ return SOCKSv5_HEADER + b"\x01" + Socksv5.NO_AUTH
86
+
87
+ @staticmethod
88
+ def socks5_request(server_address: NetworkAddress):
89
+ if not is_valid_ipv4_address(server_address.host):
90
+ domain = server_address.host.encode("utf-8")
91
+ address = b"\x03" + len(domain).to_bytes() + domain
92
+ else:
93
+ address = b"\x01" + socket.inet_aton(server_address.host)
94
+ return (
95
+ SOCKSv5_HEADER
96
+ + b"\x01"
97
+ + Socksv5.RESERVED_BYTE
98
+ + address
99
+ + server_address.port.to_bytes(2, byteorder="big")
100
+ )
101
+
102
+ @staticmethod
103
+ def socks5_ok(connection_socket: WrappedTcpSocket) -> bytes:
104
+ host_ip, host_port = connection_socket.socket.getsockname()
105
+ host_address = b"\x01" + socket.inet_aton(host_ip)
106
+ return SOCKSv5_HEADER + b"\x00" + Socksv5.RESERVED_BYTE + host_address + host_port.to_bytes(2, byteorder="big")
@@ -0,0 +1,113 @@
1
+ from time import time
2
+
3
+ from exception.ParserException import ParserException
4
+ from network.tcp.WrappedTcpSocket import WrappedTcpSocket
5
+ from util.constants import TLS_1_0_HEADER, TLS_1_1_HEADER, TLS_1_2_HEADER
6
+
7
+
8
+ class Tls:
9
+ @staticmethod
10
+ def read_sni(wrapped_socket: WrappedTcpSocket, timeout: int) -> str:
11
+ """
12
+ Attempts to read the host from the SNI extension. If the client does not send a SNI extension, None is returned.
13
+ :return: host of the sni extension
14
+ """
15
+
16
+ try:
17
+ tls_message = Tls._read_tls_message(wrapped_socket, peek=True, timeout=timeout)
18
+ except ParserException as e:
19
+ raise e
20
+ except Exception as e:
21
+ raise ParserException(e)
22
+
23
+ # check if record is a client hello
24
+ if tls_message[0] != 0x01:
25
+ raise ParserException("Not a client hello")
26
+
27
+ # skip everything until SNI extension
28
+ p = 38
29
+ # session_id
30
+ p += 1 + int.from_bytes(tls_message[p : p + 1], byteorder="big")
31
+ # cipher suites
32
+ p += 2 + int.from_bytes(tls_message[p : p + 2], byteorder="big")
33
+ # compression methods
34
+ p += 1 + int.from_bytes(tls_message[p : p + 1], byteorder="big")
35
+
36
+ if p >= len(tls_message):
37
+ raise ParserException("No extensions present")
38
+
39
+ # extensions
40
+ p += 2
41
+
42
+ while p < len(tls_message):
43
+ ext_type = int.from_bytes(tls_message[p : p + 2], byteorder="big")
44
+ p += 2
45
+ ext_length = int.from_bytes(tls_message[p : p + 2], byteorder="big")
46
+ p += 2
47
+
48
+ if ext_type != 0:
49
+ # skip over not sni
50
+ p += ext_length
51
+ else:
52
+ # sni
53
+ list_len = int.from_bytes(tls_message[p : p + 2], byteorder="big")
54
+ p += 2
55
+ _list_len = p + list_len
56
+ while p < _list_len:
57
+ name_type = int.from_bytes(tls_message[p : p + 1], byteorder="big")
58
+ p += 1
59
+ name_len = int.from_bytes(tls_message[p : p + 2], byteorder="big")
60
+ p += 2
61
+ if name_type != 0:
62
+ # unknown name type, skip
63
+ p += name_len
64
+ else:
65
+ # hostname
66
+ hostname = tls_message[p : p + name_len]
67
+ return hostname.decode("ASCII")
68
+ raise ParserException("No SNI present")
69
+
70
+ @staticmethod
71
+ def _read_tls_message(wrapped_socket: WrappedTcpSocket, timeout: int, peek=False) -> bytes:
72
+ """
73
+ Reads the content of the next tls message from the socket.
74
+ :param: whether to peek the tls message.
75
+ :return: The content of the TLS message
76
+ """
77
+ message = b""
78
+ buffer = b""
79
+ # headers
80
+ len_to_read = 4
81
+ # prevent infinite sockets
82
+ timestamp = time()
83
+ # parse records until message complete
84
+ while len(message) < len_to_read and int(time() - timestamp) < timeout:
85
+ record = Tls._read_tls_record(wrapped_socket)
86
+ buffer += record
87
+ message += record[5:]
88
+ if len(message) >= 4:
89
+ # can parse message length
90
+ len_to_read = int.from_bytes(message[1:4], byteorder="big")
91
+ if peek:
92
+ # re-inject all read records
93
+ wrapped_socket.inject(buffer)
94
+ return message
95
+
96
+ @staticmethod
97
+ def _read_tls_record(
98
+ wrapped_socket: WrappedTcpSocket,
99
+ ) -> bytes:
100
+ """
101
+ Reads the content of the next tls record from the wire with headers. Throws exception if no record is received.
102
+ :return: The contents of the TLS record
103
+ """
104
+ # read record header
105
+ data = wrapped_socket.read(5)
106
+
107
+ # check if first 3 bytes are a tls header
108
+ if data[:3] != TLS_1_0_HEADER and data[:3] != TLS_1_1_HEADER and data[:3] != TLS_1_2_HEADER:
109
+ raise ParserException("Not a TLS connection")
110
+
111
+ # read record length
112
+ record_length = int.from_bytes(data[3:5], byteorder="big")
113
+ return data + wrapped_socket.read(record_length)
File without changes
@@ -0,0 +1,203 @@
1
+ import logging
2
+ import threading
3
+
4
+ from enumerators.TlsVersion import TlsVersion
5
+ from modules.http.HttpStrategies import HttpStrategies
6
+ from network.protocols.Http import Http
7
+ from network.tcp.WrappedTcpSocket import WrappedTcpSocket
8
+ from util.constants import STANDARD_SOCKET_RECEIVE_SIZE, TLS_1_0_HEADER, TLS_1_1_HEADER, TLS_1_2_HEADER
9
+
10
+
11
+ class Forwarder:
12
+ def __init__(
13
+ self,
14
+ socket1: WrappedTcpSocket,
15
+ socket1_name: str,
16
+ socket2: WrappedTcpSocket,
17
+ socket2_name: str,
18
+ record_frag: bool = False,
19
+ record_version: str = TlsVersion.DEFAULT.value,
20
+ frag_size: int = 0,
21
+ http_strategy=None,
22
+ http_smuggling_uncensored_url: str = "",
23
+ ):
24
+ self.socket1 = socket1
25
+ self.socket2 = socket2
26
+ self.socket1_name = socket1_name
27
+ self.socket2_name = socket2_name
28
+ self.record_frag = record_frag
29
+ self.record_version = record_version
30
+ self.frag_size = frag_size
31
+ self.http_strategy = http_strategy
32
+ self.http_smuggling_uncensored_url = http_smuggling_uncensored_url
33
+
34
+ def start(self):
35
+ # start sending thread
36
+ threading.Thread(
37
+ target=self._forward,
38
+ args=(
39
+ self.socket1,
40
+ self.socket2,
41
+ f"{self.socket1_name}->{self.socket2_name}",
42
+ self.record_frag,
43
+ self.record_version,
44
+ self.http_strategy,
45
+ ),
46
+ ).start()
47
+ # start receiving thread
48
+ threading.Thread(
49
+ target=self._forward,
50
+ args=(
51
+ self.socket2,
52
+ self.socket1,
53
+ f"{self.socket2_name}->{self.socket1_name}",
54
+ ),
55
+ ).start()
56
+
57
+ def _forward(
58
+ self,
59
+ from_socket: WrappedTcpSocket,
60
+ to_socket: WrappedTcpSocket,
61
+ direction: str,
62
+ record_frag=False,
63
+ record_version: str = TlsVersion.DEFAULT.value,
64
+ http_strategy=None,
65
+ ):
66
+ """
67
+ Forwards data between two sockets with optional record manipulation. Falls back to forwarding if no TLS records
68
+ can be parsed from the connection anymore.
69
+ :param to_socket: Socket to receive data from.
70
+ :param from_socket: Socket to forward data to.
71
+ :param record_frag: Whether to fragment handshake records
72
+ :return: None
73
+ """
74
+ alter_tls = record_frag or (record_version != TlsVersion.DEFAULT.value)
75
+ try:
76
+ while True:
77
+ # if record frag is configured, it takes precedence over http manipulations
78
+ if alter_tls:
79
+ # fragment TLS
80
+ if not self.attempt_tls_fragmentation(
81
+ from_socket, to_socket, direction, record_frag, record_version
82
+ ):
83
+ # turn of record frag and force normal forwarding
84
+ alter_tls = False
85
+ continue
86
+
87
+ elif http_strategy is not None:
88
+ if not self.attempt_http_manipulations(from_socket, to_socket, direction, http_strategy):
89
+ # turn of htt manipulations and force normal forwarding
90
+ http_strategy = None
91
+ continue
92
+ else:
93
+ # normal forwarding
94
+ data = from_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
95
+ if not data:
96
+ self.debug("Connection closed, closing both sockets", direction)
97
+ to_socket.try_close()
98
+ break
99
+ else:
100
+ to_socket.send(data)
101
+ logging.info("### Cancelled forwarding ###")
102
+ except BrokenPipeError as e:
103
+ self.debug(f"Forwarding broken with {e}", direction)
104
+ to_socket.try_close()
105
+ except OSError as e:
106
+ if e.errno == 9:
107
+ # Bad file descriptor, socket closed by other forwarding queue
108
+ to_socket.try_close()
109
+ else:
110
+ self.debug(f"OSError while forwarding, closing sockets: {e}", direction)
111
+ to_socket.try_close()
112
+ except Exception as e:
113
+ self.debug(f"Exception while forwarding: {e}", direction)
114
+ to_socket.try_close()
115
+ self.info(f"{direction}: Closed connection", direction)
116
+
117
+ def attempt_http_manipulations(
118
+ self, from_socket: WrappedTcpSocket, to_socket: WrappedTcpSocket, direction: str, http_strategy
119
+ ) -> bool:
120
+ """
121
+ Attempts to parse HTTP requests and apply manipulations if an HTTP strategy is set. Sends the data.
122
+
123
+ Returns: True on successful parsing, false otherwise
124
+ """
125
+
126
+ if not Http.is_valid_http(from_socket):
127
+ self.debug(
128
+ "Received non-HTTP data, turning off HTTP manipulations for this and following requests", direction
129
+ )
130
+ return False
131
+ # we allow 4k bytes max
132
+ data = from_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
133
+ self.debug("Received HTTP data and manipulating request", direction)
134
+ data = HttpStrategies.manipulations(
135
+ data, strategy=http_strategy, http_smuggling_uncensored_url=self.http_smuggling_uncensored_url
136
+ )
137
+ self.debug(f"After applying Manipulation: {data}", direction)
138
+ to_socket.send(data)
139
+ return True
140
+
141
+ def attempt_tls_fragmentation(
142
+ self, from_socket: WrappedTcpSocket, to_socket: WrappedTcpSocket, direction: str, record_frag, record_version
143
+ ) -> bool:
144
+ """
145
+ Attempts to parse and fragment TLS handshake records. Sends data if fragmentable, does not if otherwise.
146
+
147
+ Returns: True on successful parsing, false otherwise
148
+ """
149
+ try:
150
+ record_header = from_socket.peek(5)
151
+ except Exception:
152
+ self.debug("Could not read record_header bytes. Disabling record fragmentation", direction)
153
+ return False
154
+ base_header = record_header[:3]
155
+ record_len = int.from_bytes(record_header[3:], byteorder="big")
156
+ is_tls = base_header == TLS_1_0_HEADER or base_header == TLS_1_1_HEADER or base_header == TLS_1_2_HEADER
157
+ if not is_tls:
158
+ self.debug(
159
+ f"Received first non-handshake TLS record header: {record_header}. Turning off "
160
+ f"TLS record fragmentation for this and following records",
161
+ direction,
162
+ )
163
+ # did not receive tls record
164
+ return False
165
+ else:
166
+ self.debug("Received TLS handshake record - fragmenting", direction)
167
+ try:
168
+ record = from_socket.read(5 + record_len)
169
+ except Exception:
170
+ self.debug(f"Could not read {record_len} record bytes. Disabling record fragmentation", direction)
171
+ return False
172
+ if record_version != TlsVersion.DEFAULT.value:
173
+ record_version_bytes = bytes.fromhex(record_version)
174
+ record = self.replace_version_in_record_header(record, record_version_bytes)
175
+ base_header = base_header[:1] + record_version_bytes
176
+ if record_frag:
177
+ record = self.fragment_record(base_header, record_len, record[5:])
178
+ to_socket.send(record)
179
+ return True
180
+
181
+ def fragment_record(self, record_base_header: bytes, record_length: int, record_body: bytes) -> bytes:
182
+ fragments = [record_body[i : i + self.frag_size] for i in range(0, record_length, self.frag_size)]
183
+ fragmented_message = b""
184
+ for fragment in fragments:
185
+ # construct header
186
+ fragmented_message += record_base_header + int.to_bytes(len(fragment), byteorder="big", length=2)
187
+ fragmented_message += fragment
188
+ return fragmented_message
189
+
190
+ def replace_version_in_record_header(self, record: bytes, version: bytes) -> bytes:
191
+ header_to_keep = record[:1]
192
+ record_length_and_body = record[3:]
193
+ return header_to_keep + version + record_length_and_body
194
+
195
+ # LOGGER utility functions
196
+ def _logger_string(self, message: str, prefix: str) -> str:
197
+ return f"{prefix}: {message}"
198
+
199
+ def debug(self, message: str, prefix: str):
200
+ logging.debug(self._logger_string(message, prefix))
201
+
202
+ def info(self, message: str, prefix: str):
203
+ logging.info(self._logger_string(message, prefix))