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.
- dpyproxy/__init__.py +1 -0
- dpyproxy/__main__.py +4 -0
- dpyproxy-2.2.0.dist-info/METADATA +296 -0
- dpyproxy-2.2.0.dist-info/RECORD +59 -0
- dpyproxy-2.2.0.dist-info/WHEEL +4 -0
- dpyproxy-2.2.0.dist-info/entry_points.txt +2 -0
- dpyproxy-2.2.0.dist-info/licenses/LICENSE +201 -0
- enumerators/DnsProxyMode.py +39 -0
- enumerators/DnsResolvers.py +141 -0
- enumerators/HttpMethod.py +17 -0
- enumerators/Modules.py +38 -0
- enumerators/Port.py +11 -0
- enumerators/TcpProxyMode.py +17 -0
- enumerators/TlsVersion.py +21 -0
- enumerators/__init__.py +0 -0
- exception/DnsException.py +7 -0
- exception/ParserException.py +7 -0
- exception/__init__.py +0 -0
- main.py +94 -0
- modules/Module.py +45 -0
- modules/__init__.py +0 -0
- modules/dns/DnsModeDeterminator.py +358 -0
- modules/dns/DnsModule.py +113 -0
- modules/dns/DnsProxy.py +277 -0
- modules/dns/DnsResolver.py +18 -0
- modules/dns/__init__.py +0 -0
- modules/http/HttpModule.py +69 -0
- modules/http/HttpStrategies.py +849 -0
- modules/http/HttpUtils.py +94 -0
- modules/http/__init__.py +0 -0
- modules/tls/TcpProxy.py +106 -0
- modules/tls/TlsModule.py +173 -0
- modules/tls/__init__.py +0 -0
- network/DomainResolver.py +472 -0
- network/NetworkAddress.py +10 -0
- network/WrappedSocket.py +97 -0
- network/__init__.py +0 -0
- network/protocols/Dns.py +62 -0
- network/protocols/Http.py +109 -0
- network/protocols/Socksv4.py +70 -0
- network/protocols/Socksv5.py +106 -0
- network/protocols/Tls.py +113 -0
- network/protocols/__init__.py +0 -0
- network/tcp/Forwarder.py +203 -0
- network/tcp/TcpConnectionHandler.py +264 -0
- network/tcp/WrappedTcpSocket.py +30 -0
- network/tcp/__init__.py +0 -0
- network/udp/__init__.py +0 -0
- test/Sink.py +23 -0
- test/__init__.py +0 -0
- test/test_dns.py +98 -0
- test/test_http.py +57 -0
- test/test_tls.py +63 -0
- util/DnsAutoModeRuntimeMeasurement.py +62 -0
- util/DnsReachabilityCollector.py +160 -0
- util/DnsResolversDomainResolver.py +36 -0
- util/Util.py +62 -0
- util/__init__.py +0 -0
- util/constants.py +8 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
from enumerators.TcpProxyMode import TcpProxyMode
|
|
5
|
+
from exception.ParserException import ParserException
|
|
6
|
+
from network.DomainResolver import DomainResolver
|
|
7
|
+
from network.NetworkAddress import NetworkAddress
|
|
8
|
+
from network.protocols.Dns import Dns
|
|
9
|
+
from network.protocols.Http import Http
|
|
10
|
+
from network.protocols.Socksv4 import Socksv4
|
|
11
|
+
from network.protocols.Socksv5 import Socksv5
|
|
12
|
+
from network.protocols.Tls import Tls
|
|
13
|
+
from network.tcp.Forwarder import Forwarder
|
|
14
|
+
from network.tcp.WrappedTcpSocket import WrappedTcpSocket
|
|
15
|
+
from util.constants import (
|
|
16
|
+
STANDARD_SOCKET_RECEIVE_SIZE,
|
|
17
|
+
TLS_1_0_HEADER,
|
|
18
|
+
TLS_1_1_HEADER,
|
|
19
|
+
TLS_1_2_HEADER,
|
|
20
|
+
SOCKSv4_HEADER,
|
|
21
|
+
SOCKSv5_HEADER,
|
|
22
|
+
)
|
|
23
|
+
from util.Util import is_valid_ipv4_address
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TcpConnectionHandler:
|
|
27
|
+
"""
|
|
28
|
+
Handles a single client connection of the proxy server.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
connection_socket: WrappedTcpSocket,
|
|
34
|
+
address: NetworkAddress,
|
|
35
|
+
timeout: int,
|
|
36
|
+
record_version: str,
|
|
37
|
+
record_frag: bool,
|
|
38
|
+
tcp_frag: bool,
|
|
39
|
+
frag_size: int,
|
|
40
|
+
http_strategy: int,
|
|
41
|
+
http_smuggling_uncensored_url: str,
|
|
42
|
+
dns_server: NetworkAddress,
|
|
43
|
+
disabled_modes: list[TcpProxyMode],
|
|
44
|
+
forward_proxy: NetworkAddress,
|
|
45
|
+
forward_proxy_mode: TcpProxyMode,
|
|
46
|
+
forward_proxy_resolve_address: bool,
|
|
47
|
+
):
|
|
48
|
+
self.connection_socket = connection_socket
|
|
49
|
+
self.address = address
|
|
50
|
+
self.proxy_mode = None
|
|
51
|
+
self.timeout = timeout
|
|
52
|
+
self.record_frag = record_frag
|
|
53
|
+
self.record_version = record_version
|
|
54
|
+
self.tcp_frag = tcp_frag
|
|
55
|
+
self.frag_size = frag_size
|
|
56
|
+
self.http_strategy = http_strategy
|
|
57
|
+
self.http_smuggling_uncensored_url = http_smuggling_uncensored_url
|
|
58
|
+
self.dns_server = dns_server
|
|
59
|
+
self.disabled_modes = disabled_modes
|
|
60
|
+
self.forward_proxy = forward_proxy
|
|
61
|
+
self.forward_proxy_mode = forward_proxy_mode
|
|
62
|
+
self.forward_proxy_resolve_address = forward_proxy_resolve_address
|
|
63
|
+
|
|
64
|
+
def handle(self):
|
|
65
|
+
"""
|
|
66
|
+
Handles the connection to a single client.
|
|
67
|
+
:return: None
|
|
68
|
+
"""
|
|
69
|
+
# determine proxy mode / message type
|
|
70
|
+
self.proxy_mode = self.get_proxy_mode()
|
|
71
|
+
if self.proxy_mode in self.disabled_modes:
|
|
72
|
+
self.info(f"Proxy mode {self.proxy_mode} is disabled. Stopping!")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# determine destination address
|
|
76
|
+
try:
|
|
77
|
+
final_server_address, http_version = self.get_destination_address()
|
|
78
|
+
except ParserException as e:
|
|
79
|
+
logging.warning(f"Could not parse initial proxy message with {e}. Stopping!")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# resolve domain if no forward proxy or the forward proxy needs a resolved address
|
|
83
|
+
if (not is_valid_ipv4_address(final_server_address.host)) and (
|
|
84
|
+
self.forward_proxy_resolve_address or self.forward_proxy is None
|
|
85
|
+
):
|
|
86
|
+
if self.dns_server:
|
|
87
|
+
host = DomainResolver.resolve_udp_static_to_ip(
|
|
88
|
+
final_server_address.host, resolver=self.dns_server, timeout=self.timeout
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
host = DomainResolver.resolve_local(final_server_address.host)
|
|
92
|
+
self.debug(f"Resolved {host} from {final_server_address.host}")
|
|
93
|
+
final_server_address.host = host
|
|
94
|
+
|
|
95
|
+
# set correct target
|
|
96
|
+
if self.forward_proxy is None:
|
|
97
|
+
target_address = (final_server_address.host, final_server_address.port)
|
|
98
|
+
else:
|
|
99
|
+
target_address = (self.forward_proxy.host, self.forward_proxy.port)
|
|
100
|
+
self.debug(f"Using forward proxy {target_address}")
|
|
101
|
+
|
|
102
|
+
# open socket to server
|
|
103
|
+
try:
|
|
104
|
+
server_socket = socket.create_connection(target_address)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
self.info(f"Could not connect to server due to {e}.")
|
|
107
|
+
self.connection_socket.try_close()
|
|
108
|
+
return
|
|
109
|
+
if self.tcp_frag and self.record_frag:
|
|
110
|
+
# align record and tcp fragment size
|
|
111
|
+
server_socket = WrappedTcpSocket(self.timeout, server_socket, self.frag_size + 5)
|
|
112
|
+
elif self.tcp_frag:
|
|
113
|
+
server_socket = WrappedTcpSocket(self.timeout, server_socket, self.frag_size)
|
|
114
|
+
else:
|
|
115
|
+
server_socket = WrappedTcpSocket(self.timeout, server_socket)
|
|
116
|
+
logging.info(
|
|
117
|
+
f"Connected {final_server_address.host}:{final_server_address.port} "
|
|
118
|
+
f"to {target_address[0]}:{target_address[1]}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self.send_proxy_answer(http_version, server_socket)
|
|
122
|
+
|
|
123
|
+
if self.forward_proxy is not None:
|
|
124
|
+
self.connect_forward_proxy(server_socket, final_server_address, http_version)
|
|
125
|
+
|
|
126
|
+
# start proxying
|
|
127
|
+
Forwarder(
|
|
128
|
+
self.connection_socket,
|
|
129
|
+
self.address.__str__(),
|
|
130
|
+
server_socket,
|
|
131
|
+
f"{target_address[0]}:{target_address[1]}",
|
|
132
|
+
self.record_frag,
|
|
133
|
+
self.record_version,
|
|
134
|
+
self.frag_size,
|
|
135
|
+
self.http_strategy,
|
|
136
|
+
self.http_smuggling_uncensored_url,
|
|
137
|
+
).start()
|
|
138
|
+
|
|
139
|
+
def get_proxy_mode(self) -> TcpProxyMode:
|
|
140
|
+
"""
|
|
141
|
+
Determines the mode of the proxy based on the first client message
|
|
142
|
+
"""
|
|
143
|
+
header = self.connection_socket.peek(3)
|
|
144
|
+
|
|
145
|
+
if header.startswith(TLS_1_0_HEADER) or header.startswith(TLS_1_1_HEADER) or header.startswith(TLS_1_2_HEADER):
|
|
146
|
+
self.debug("Determined SNI Proxy Request")
|
|
147
|
+
return TcpProxyMode.SNI
|
|
148
|
+
|
|
149
|
+
if header.startswith(SOCKSv4_HEADER):
|
|
150
|
+
self.debug("Determined SOCKSv4 Proxy Request")
|
|
151
|
+
return TcpProxyMode.SOCKSv4
|
|
152
|
+
|
|
153
|
+
if header.startswith(SOCKSv5_HEADER):
|
|
154
|
+
self.debug("Determined SOCKSv5 Proxy Request")
|
|
155
|
+
return TcpProxyMode.SOCKSv5
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
ascii_decoded_header = header.decode("ASCII")
|
|
159
|
+
except UnicodeDecodeError:
|
|
160
|
+
raise ParserException(f"Could not determine message type of message {header}")
|
|
161
|
+
else:
|
|
162
|
+
if ascii_decoded_header.upper().startswith("CON"):
|
|
163
|
+
self.debug("Determined HTTPS Proxy Request")
|
|
164
|
+
return TcpProxyMode.HTTPS
|
|
165
|
+
else:
|
|
166
|
+
# assume we have http
|
|
167
|
+
self.debug("Determined HTTP Proxy Request")
|
|
168
|
+
return TcpProxyMode.HTTP
|
|
169
|
+
|
|
170
|
+
def get_destination_address(self) -> (NetworkAddress, str):
|
|
171
|
+
"""
|
|
172
|
+
Reads a proxy destination address and returns the host and port of the destination.
|
|
173
|
+
:return: Host, port, and optional http version of the destination server
|
|
174
|
+
"""
|
|
175
|
+
http_version = None
|
|
176
|
+
if self.proxy_mode == TcpProxyMode.HTTP:
|
|
177
|
+
host, port, http_version = Http.read_http_get(self.connection_socket)
|
|
178
|
+
self.debug(f"Read host {host} and port {port} from HTTP GET")
|
|
179
|
+
elif self.proxy_mode == TcpProxyMode.HTTPS:
|
|
180
|
+
host, port, http_version = Http.read_http_connect(self.connection_socket)
|
|
181
|
+
self.debug(f"Read host {host} and port {port} from HTTP CONNECT")
|
|
182
|
+
elif self.proxy_mode == TcpProxyMode.SNI:
|
|
183
|
+
host, port = Tls.read_sni(self.connection_socket, self.timeout), 443
|
|
184
|
+
self.debug(f"Read host {host} and port {port} from SNI")
|
|
185
|
+
elif self.proxy_mode == TcpProxyMode.SOCKSv4:
|
|
186
|
+
host, port = Socksv4.read_socks4(self.connection_socket)
|
|
187
|
+
self.debug(f"Read host {host} and port {port} from SOCKSv4")
|
|
188
|
+
elif self.proxy_mode == TcpProxyMode.SOCKSv5:
|
|
189
|
+
host, port = Socksv5.read_socks5(self.connection_socket)
|
|
190
|
+
self.debug(f"Read host {host} and port {port} from SOCKSv5")
|
|
191
|
+
elif self.proxy_mode == TcpProxyMode.DNS:
|
|
192
|
+
host, port = Dns.read_dns(self.connection_socket)
|
|
193
|
+
self.debug(f"Read host {host} and port {port} from DNS")
|
|
194
|
+
else:
|
|
195
|
+
raise ParserException("Unknown proxy type")
|
|
196
|
+
return NetworkAddress(host, port), http_version
|
|
197
|
+
|
|
198
|
+
def send_proxy_answer(self, http_version: str, server_socket: WrappedTcpSocket):
|
|
199
|
+
if self.proxy_mode == TcpProxyMode.HTTPS:
|
|
200
|
+
# answer with 200 OK
|
|
201
|
+
self.connection_socket.send(Http.http_200_ok(http_version))
|
|
202
|
+
elif self.proxy_mode == TcpProxyMode.SOCKSv4:
|
|
203
|
+
# answer with Socksv4 okay
|
|
204
|
+
self.connection_socket.send(Socksv4.socks4_ok())
|
|
205
|
+
elif self.proxy_mode == TcpProxyMode.SOCKSv5:
|
|
206
|
+
# answer with Socksv5 okay
|
|
207
|
+
self.connection_socket.send(Socksv5.socks5_ok(server_socket))
|
|
208
|
+
|
|
209
|
+
def connect_forward_proxy(
|
|
210
|
+
self, server_socket: WrappedTcpSocket, final_server_address: NetworkAddress, http_version: str
|
|
211
|
+
):
|
|
212
|
+
try:
|
|
213
|
+
# send proxy messages if necessary
|
|
214
|
+
if self.forward_proxy_mode == TcpProxyMode.HTTPS:
|
|
215
|
+
server_socket.send(Http.connect_message(final_server_address, http_version))
|
|
216
|
+
self.debug("Sent HTTP CONNECT to forward proxy")
|
|
217
|
+
# receive HTTP 200 OK
|
|
218
|
+
answer = server_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
|
|
219
|
+
if not answer.upper().startswith(Http.http_200_ok(http_version)):
|
|
220
|
+
raise ParserException(f"Forward proxy rejected the connection with {answer}")
|
|
221
|
+
|
|
222
|
+
elif self.forward_proxy == TcpProxyMode.SOCKSv4:
|
|
223
|
+
server_socket.send(Socksv4.socks4_request(final_server_address))
|
|
224
|
+
self.debug("Sent SOCKSv4 to forward proxy")
|
|
225
|
+
# receive SOCKSv4 OK
|
|
226
|
+
answer = server_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
|
|
227
|
+
if not answer.upper().startswith(Socksv4.socks4_ok()) and len(answer) != 8:
|
|
228
|
+
raise ParserException(f"Forward proxy rejected the connection with {answer}")
|
|
229
|
+
|
|
230
|
+
elif self.forward_proxy == TcpProxyMode.SOCKSv5:
|
|
231
|
+
server_socket.send(Socksv5.socks5_auth_methods())
|
|
232
|
+
self.debug("Sent SOCKSv5 auth methods")
|
|
233
|
+
answer = server_socket.recv(2)
|
|
234
|
+
if answer == b"\x05\xff":
|
|
235
|
+
raise ParserException("Forward proxy does not support no auth")
|
|
236
|
+
if answer != b"\x05\x00":
|
|
237
|
+
raise ParserException(f"Forward proxy rejected the connection with {answer}")
|
|
238
|
+
server_socket.send(Socksv5.socks5_request(final_server_address))
|
|
239
|
+
self.debug("Sent SOCKSv5 to forward proxy")
|
|
240
|
+
# receive SOCKSv5 OK
|
|
241
|
+
answer = server_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
|
|
242
|
+
if not answer.upper().startswith(Socksv5.socks5_ok(server_socket)):
|
|
243
|
+
self.debug(f"Forward proxy rejected the connection with {answer}")
|
|
244
|
+
except Exception:
|
|
245
|
+
self.debug("Could not send proxy message")
|
|
246
|
+
self.connection_socket.try_close()
|
|
247
|
+
logging.info("Closed connections")
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
# LOGGER utility functions
|
|
251
|
+
def _logger_string(self, message: str) -> str:
|
|
252
|
+
return f"{self.address.host}:{self.address.port}: {message}"
|
|
253
|
+
|
|
254
|
+
def debug(self, message: str):
|
|
255
|
+
logging.debug(self._logger_string(message))
|
|
256
|
+
|
|
257
|
+
def warn(self, message: str):
|
|
258
|
+
logging.warning(self._logger_string(message))
|
|
259
|
+
|
|
260
|
+
def error(self, message: str):
|
|
261
|
+
logging.error(self._logger_string(message))
|
|
262
|
+
|
|
263
|
+
def info(self, message: str):
|
|
264
|
+
logging.info(self._logger_string(message))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
from network.WrappedSocket import WrappedSocket
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WrappedTcpSocket(WrappedSocket):
|
|
7
|
+
"""
|
|
8
|
+
Wraps a socket with useful utility functions.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, timeout: int, _socket: socket.socket, tcp_frag_size=0):
|
|
12
|
+
self.tcp_frag_size = tcp_frag_size
|
|
13
|
+
super().__init__(timeout, _socket)
|
|
14
|
+
|
|
15
|
+
def send(self, data: bytes, *args, **kwargs) -> int:
|
|
16
|
+
"""
|
|
17
|
+
Wraps send() of the wrapped socket. Split into tcp fragments if given as value.
|
|
18
|
+
:return: Return value of the wrapped socket's send method
|
|
19
|
+
"""
|
|
20
|
+
if self.tcp_frag_size <= 0:
|
|
21
|
+
return self.socket.send(data, *args, **kwargs)
|
|
22
|
+
else:
|
|
23
|
+
# split into fragments and send each separately
|
|
24
|
+
fragments = (data[i : i + self.tcp_frag_size] for i in range(0, len(data), self.tcp_frag_size))
|
|
25
|
+
total_sent = 0
|
|
26
|
+
for fragment in fragments:
|
|
27
|
+
total_sent += self.socket.send(fragment, *args, **kwargs)
|
|
28
|
+
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 0)
|
|
29
|
+
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
30
|
+
return total_sent
|
network/tcp/__init__.py
ADDED
|
File without changes
|
network/udp/__init__.py
ADDED
|
File without changes
|
test/Sink.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Sink:
|
|
5
|
+
def __init__(self, ip: str, port: int):
|
|
6
|
+
self.socket = None
|
|
7
|
+
self.ip = ip
|
|
8
|
+
self.port = port
|
|
9
|
+
|
|
10
|
+
def start(self):
|
|
11
|
+
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
12
|
+
server.bind((self.ip, self.port))
|
|
13
|
+
server.settimeout(5)
|
|
14
|
+
server.listen()
|
|
15
|
+
self.socket = server
|
|
16
|
+
|
|
17
|
+
def close(self):
|
|
18
|
+
self.socket.close()
|
|
19
|
+
|
|
20
|
+
def receive_message(self):
|
|
21
|
+
conn, addr = self.socket.accept()
|
|
22
|
+
with conn:
|
|
23
|
+
return conn.recv(65535)
|
test/__init__.py
ADDED
|
File without changes
|
test/test_dns.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from modules.dns.DnsModeDeterminator import DnsModeDeterminator
|
|
2
|
+
|
|
3
|
+
IP_RANGES = [
|
|
4
|
+
"8.8.4.0/24",
|
|
5
|
+
"8.8.8.0/24",
|
|
6
|
+
"8.34.208.0/20",
|
|
7
|
+
"8.35.192.0/20",
|
|
8
|
+
"23.236.48.0/20",
|
|
9
|
+
"23.251.128.0/19",
|
|
10
|
+
"34.0.0.0/15",
|
|
11
|
+
"34.2.0.0/16",
|
|
12
|
+
"34.3.0.0/23",
|
|
13
|
+
"34.3.3.0/24",
|
|
14
|
+
"34.3.4.0/24",
|
|
15
|
+
"34.3.8.0/21",
|
|
16
|
+
"34.3.16.0/20",
|
|
17
|
+
"34.3.32.0/19",
|
|
18
|
+
"34.3.64.0/18",
|
|
19
|
+
"34.4.0.0/14",
|
|
20
|
+
"34.8.0.0/13",
|
|
21
|
+
"34.16.0.0/12",
|
|
22
|
+
"34.32.0.0/11",
|
|
23
|
+
"34.64.0.0/10",
|
|
24
|
+
"34.128.0.0/10",
|
|
25
|
+
"35.184.0.0/13",
|
|
26
|
+
"35.192.0.0/14",
|
|
27
|
+
"35.196.0.0/15",
|
|
28
|
+
"35.198.0.0/16",
|
|
29
|
+
"35.199.0.0/17",
|
|
30
|
+
"35.199.128.0/18",
|
|
31
|
+
"35.200.0.0/13",
|
|
32
|
+
"35.208.0.0/12",
|
|
33
|
+
"35.224.0.0/12",
|
|
34
|
+
"35.240.0.0/13",
|
|
35
|
+
"57.140.192.0/18",
|
|
36
|
+
"64.15.112.0/20",
|
|
37
|
+
"64.233.160.0/19",
|
|
38
|
+
"66.22.228.0/23",
|
|
39
|
+
"66.102.0.0/20",
|
|
40
|
+
"66.249.64.0/19",
|
|
41
|
+
"70.32.128.0/19",
|
|
42
|
+
"72.14.192.0/18",
|
|
43
|
+
"74.114.24.0/21",
|
|
44
|
+
"74.125.0.0/16",
|
|
45
|
+
"104.154.0.0/15",
|
|
46
|
+
"104.196.0.0/14",
|
|
47
|
+
"104.237.160.0/19",
|
|
48
|
+
"107.167.160.0/19",
|
|
49
|
+
"107.178.192.0/18",
|
|
50
|
+
"108.59.80.0/20",
|
|
51
|
+
"108.170.192.0/18",
|
|
52
|
+
"108.177.0.0/17",
|
|
53
|
+
"130.211.0.0/16",
|
|
54
|
+
"136.22.160.0/20",
|
|
55
|
+
"136.22.176.0/21",
|
|
56
|
+
"136.22.184.0/23",
|
|
57
|
+
"136.22.186.0/24",
|
|
58
|
+
"136.124.0.0/15",
|
|
59
|
+
"142.250.0.0/15",
|
|
60
|
+
"146.148.0.0/17",
|
|
61
|
+
"152.65.208.0/22",
|
|
62
|
+
"152.65.214.0/23",
|
|
63
|
+
"152.65.218.0/23",
|
|
64
|
+
"152.65.222.0/23",
|
|
65
|
+
"152.65.224.0/19",
|
|
66
|
+
"162.120.128.0/17",
|
|
67
|
+
"162.216.148.0/22",
|
|
68
|
+
"162.222.176.0/21",
|
|
69
|
+
"172.110.32.0/21",
|
|
70
|
+
"172.217.0.0/16",
|
|
71
|
+
"172.253.0.0/16",
|
|
72
|
+
"173.194.0.0/16",
|
|
73
|
+
"173.255.112.0/20",
|
|
74
|
+
"192.104.160.0/23",
|
|
75
|
+
"192.158.28.0/22",
|
|
76
|
+
"192.178.0.0/15",
|
|
77
|
+
"193.186.4.0/24",
|
|
78
|
+
"199.36.154.0/23",
|
|
79
|
+
"199.36.156.0/24",
|
|
80
|
+
"199.192.112.0/22",
|
|
81
|
+
"199.223.232.0/21",
|
|
82
|
+
"207.223.160.0/20",
|
|
83
|
+
"208.65.152.0/22",
|
|
84
|
+
"208.68.108.0/22",
|
|
85
|
+
"208.81.188.0/22",
|
|
86
|
+
"208.117.224.0/19",
|
|
87
|
+
"209.85.128.0/17",
|
|
88
|
+
"216.58.192.0/19",
|
|
89
|
+
"216.73.80.0/20",
|
|
90
|
+
"216.239.32.0/19",
|
|
91
|
+
"216.252.220.0/22",
|
|
92
|
+
]
|
|
93
|
+
DOMAIN = "www.google.dj"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_auto_mode_works():
|
|
97
|
+
resolver = DnsModeDeterminator(5, DOMAIN, IP_RANGES, False).generate_working_resolver().__next__()
|
|
98
|
+
assert resolver is not None
|
test/test_http.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from modules.tls.TcpProxy import TcpProxy
|
|
8
|
+
from network.NetworkAddress import NetworkAddress
|
|
9
|
+
from test.Sink import Sink
|
|
10
|
+
|
|
11
|
+
NETWORK_ADDRESS = NetworkAddress("127.0.0.1", 8090)
|
|
12
|
+
CONNECT_DATA = "CONNECT 127.0.0.1:8091 HTTP/1.1\r\n\r\n"
|
|
13
|
+
HTTP_REQUEST = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture(scope="module")
|
|
17
|
+
def setup_sink():
|
|
18
|
+
sink = Sink("127.0.0.1", 8091)
|
|
19
|
+
sink.start()
|
|
20
|
+
yield sink
|
|
21
|
+
sink.close()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_basic_manipulation(setup_sink):
|
|
25
|
+
data = run_server(TcpProxy(address=NETWORK_ADDRESS, http_strategy=2), setup_sink)
|
|
26
|
+
assert data.decode() == "GET / OPTIONS\r\nHost: example.com\r\n\r\n"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_smuggling_manipulation(setup_sink):
|
|
30
|
+
data = run_server(
|
|
31
|
+
TcpProxy(address=NETWORK_ADDRESS, http_strategy=101, http_smuggling_uncensored_url="https://www.gov.cn/"),
|
|
32
|
+
setup_sink,
|
|
33
|
+
)
|
|
34
|
+
assert (
|
|
35
|
+
data.decode()
|
|
36
|
+
== "GET / HTTP/1.1\r\nHost: www.gov.cn\r\nContent-Length:: 42\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
|
|
37
|
+
"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_server(proxy: TcpProxy, sink: Sink) -> bytes:
|
|
42
|
+
"""
|
|
43
|
+
Runs the test and return the bytes sent from the proxy to the sink server.
|
|
44
|
+
"""
|
|
45
|
+
thread = threading.Thread(target=proxy.start)
|
|
46
|
+
thread.start()
|
|
47
|
+
time.sleep(1)
|
|
48
|
+
|
|
49
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
50
|
+
s.connect(("127.0.0.1", 8090))
|
|
51
|
+
s.sendall(CONNECT_DATA.encode())
|
|
52
|
+
time.sleep(1)
|
|
53
|
+
s.sendall(HTTP_REQUEST.encode())
|
|
54
|
+
ret = sink.receive_message()
|
|
55
|
+
proxy.server.close()
|
|
56
|
+
thread.join()
|
|
57
|
+
return ret
|
test/test_tls.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from enumerators.TlsVersion import TlsVersion
|
|
8
|
+
from modules.tls.TcpProxy import TcpProxy
|
|
9
|
+
from network.NetworkAddress import NetworkAddress
|
|
10
|
+
from test.Sink import Sink
|
|
11
|
+
|
|
12
|
+
NETWORK_ADDRESS = NetworkAddress("127.0.0.1", 8080)
|
|
13
|
+
CONNECT_DATA = "CONNECT 127.0.0.1:8081 HTTP/1.1\r\n\r\n"
|
|
14
|
+
TLS_DATA = "16030106210100061d0303b3d78ad49b3066c9b41f466f2b4226bc872ce9b9a3cfc2bb3f58532f9dc2034320bd27d7e698e64187429fb27c25d956020efb22139d2a580f20d84bb19c9648a500461302130313011304c02cc030cca9cca8c0adc02bc02fc0acc023c027c00ac014c009c013009dc09d009cc09c003d003c0035002f009fccaac09f009ec09e006b0067003900330100058eff010001000000000f000d00000a676f6f676c652e636f6d000b000403000102000a001c001a11ec11eb11ed001d0017001e00190018010001010102010301040010000e000c02683208687474702f312e31001600000017000000310000000d00280026090409050906040305030603080708080809080a080b08040805080604010501060103030301002b00050403040303002d00020101003304ea04e811ec04c09f52732ca78d99c9a447fc7ae8e47113f6b886d23b0fe61a0b77680ff30dda340ce80733b159a30a731726ca18a793677c30acf865b60dc669e260366de44be3c14fed8a176980ba5f0b00dda0634f8b8ea387a2b8b514a5b613c8e81289993c750a97a07996b858aef8540178156ba8822a7aa273e81366cce35cfd6313543b4565bba053566494379749e91fa981339a96a043f72402e0403f497aa3d66a382c318677acda56874d72020982227d9822f0e70fe6450798f407aca14ea168331a6199ed49aca49602bfaa785c2787270248beb176dbe046f6f86ca0c9ad42b31e32fc8f091c9217808f160991ae158f7a3a9c02b7938e902ab4c1ab355cb9b803873815a9e86b43c944a3d6b593b83346aff7504622bd617a648da5500c3a85a6076d0bd4272d3767ee5cb3749c08d75a6542f45834ec80bee40277671225e02900fdb6571459d8d7498d8b0e852738a07a3508146066793f87f707115112f74a1c33610f070842f2bbbe3fd3b3c7f089d4249f48883d20c29faa6c852ab3801082614309720df314827c534e66b091302d63319c6f3b91d999627d3c8e56247148103f4f2b19dab1422b192bc594217f634cc5497edf80ab05674cba251f4d0b73565320ad111765727f2223891925a94731afb8bb6c6ad4931df3732a93abb227328557c01d6996858995df8194a6f67a34515446c54439d945c4fb11133601b6d7ae84d70ae83684d7568a0ddb5e0b884b26e9c9fda8c15e9284182a5291c61c1fc0a2afe2accf8429fdc2b077c771ab6b330e915103646b5b152bc97a37350c1367e7827618a4f8569fad8712666babc50543296550b0a54ea6aac713a5c2b310690ab9c212a777d0317bd7c9b05049bd335266d9b2c3d66b2523671d6805aef31c851987b9131bb8bd5ac563a16c0ba6a359d79e19378e5da1bb8d9c7d17001417d69dc8f2667a193af92569ae645b66639076d526b3345c237b2dad74221309617482658de51b3af47ed71c9f92fb4ab5ab001d243c940b186455cefad54b57e8369f0661842a857c97433690351b041605130dd459486e5a2e48aa45e5dc032ad78e839645247342d869a834125ff20329dc079fbfe3bb5b617ddda30faad7a90106098c35c613355ad54cc1ad7a9214091752412380b3c0d9104b0efb72ee48cddd62bb20ba42db808fdab20e525141e73b3ff8a412858349f4da415dc847de0caf08e2800afb328e16adb3e99d911702500c4547f128a61c63173167362bce3ec8b22b82514d3632ad4a40feb95f54882a72907676298f5f482de54a90ff00968d2a41d86c9e353307ec73ae647902ca1837e82644687a1ebab9199e6c3a4282276f4106da4a01daac4726c7225de93414dc8d18e5661dac1dac8856922b678056ceebe7bf9fccb6c295136a43973d00b560a52b0570527249ca4bf6b70ec0306823b3695b213e4b0b3dc9a196f1c8e3e86f53c30d16858ce80180a33c5dfff25b9130636221bd5a682cdfd195eecc36cf236b51f0892ba62edb4b720b51ca3218abe0733cf72317eaa3839d02034b218ffda39b06e4be4a54aafb698060a78e0222933458397d3221d183245b7c3c908351a4a0b4420b73d1bb282d3004fe9aeb55669dc7a2efbd6ff0402598065ac66b24cede6d3dd2111f8e2a976b7a4bd38d9be2e826a1cb8cab7d95fe5fc6d77fbefa1ccf8d000be4d8d13c001d00207d2d6fb8382a8a063ee5aadd099095e13d15edcad631402dfcff0d41da401315001b0003020001" # noqa: E501
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(scope="module")
|
|
18
|
+
def setup_sink():
|
|
19
|
+
sink = Sink("127.0.0.1", 8081)
|
|
20
|
+
sink.start()
|
|
21
|
+
yield sink
|
|
22
|
+
sink.close()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_record_frag(setup_sink):
|
|
26
|
+
data = run_server(TcpProxy(address=NETWORK_ADDRESS, record_frag=True, frag_size=1), setup_sink)
|
|
27
|
+
assert data.hex().startswith("160301000101160301000100")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_record_version(setup_sink):
|
|
31
|
+
data = run_server(TcpProxy(address=NETWORK_ADDRESS, record_version=TlsVersion.TLS13.value), setup_sink)
|
|
32
|
+
assert data.hex().startswith("1603040621")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_server(proxy: TcpProxy, sink: Sink) -> bytes:
|
|
36
|
+
"""
|
|
37
|
+
Runs the test and return the bytes sent from the proxy to the sink server.
|
|
38
|
+
"""
|
|
39
|
+
thread = threading.Thread(target=proxy.start)
|
|
40
|
+
thread.start()
|
|
41
|
+
time.sleep(1)
|
|
42
|
+
|
|
43
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
44
|
+
s.connect(("127.0.0.1", 8080))
|
|
45
|
+
s.sendall(CONNECT_DATA.encode())
|
|
46
|
+
time.sleep(1)
|
|
47
|
+
s.sendall(bytes.fromhex(TLS_DATA))
|
|
48
|
+
ret = sink.receive_message()
|
|
49
|
+
proxy.server.close()
|
|
50
|
+
thread.join()
|
|
51
|
+
return ret
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## TODO: cant really test this from python without running tcpdump or sth.
|
|
55
|
+
# def test_tcp_frag():
|
|
56
|
+
# data = run_server(
|
|
57
|
+
# TcpProxy(
|
|
58
|
+
# address=NETWORK_ADDRESS,
|
|
59
|
+
# tcp_frag=True,
|
|
60
|
+
# frag_size=20
|
|
61
|
+
# ),
|
|
62
|
+
# SINK
|
|
63
|
+
# )
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import statistics
|
|
4
|
+
|
|
5
|
+
# hack to add parent to pythonpath
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
|
|
10
|
+
from enumerators.DnsProxyMode import DnsProxyMode
|
|
11
|
+
from modules.dns.DnsProxy import DnsProxy
|
|
12
|
+
from network.NetworkAddress import NetworkAddress
|
|
13
|
+
|
|
14
|
+
WIKIMEDIA_RANGES = [
|
|
15
|
+
"185.15.56.0/22",
|
|
16
|
+
"91.198.174.0/24",
|
|
17
|
+
"195.200.68.0/24",
|
|
18
|
+
"193.46.90.0/24",
|
|
19
|
+
"198.35.26.0/23",
|
|
20
|
+
"208.80.152.0/22",
|
|
21
|
+
"103.102.166.0/24",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
IRAN_BLOCK_PAGES = ["10.10.34.34", "10.10.34.35", "10.10.34.36"]
|
|
25
|
+
|
|
26
|
+
test_amount = 100
|
|
27
|
+
startup_times = []
|
|
28
|
+
for i in range(test_amount):
|
|
29
|
+
server_address = NetworkAddress("localhost", 4433)
|
|
30
|
+
resolver_address = NetworkAddress(None, 4433)
|
|
31
|
+
|
|
32
|
+
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
33
|
+
|
|
34
|
+
proxy = DnsProxy(
|
|
35
|
+
proxy_mode=DnsProxyMode.AUTO,
|
|
36
|
+
address=server_address,
|
|
37
|
+
timeout=3,
|
|
38
|
+
dns_resolver_address=resolver_address,
|
|
39
|
+
censored_domain="twitter.com",
|
|
40
|
+
compare_ip_ranges=IRAN_BLOCK_PAGES,
|
|
41
|
+
block_page_ips=True,
|
|
42
|
+
add_sni=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
startup_time = proxy.start(time_measurement_only=True)
|
|
46
|
+
startup_times.append(startup_time)
|
|
47
|
+
|
|
48
|
+
# Basic statistics
|
|
49
|
+
average = statistics.mean(startup_times)
|
|
50
|
+
median = statistics.median(startup_times)
|
|
51
|
+
minimum = min(startup_times)
|
|
52
|
+
maximum = max(startup_times)
|
|
53
|
+
stdev = statistics.stdev(startup_times) # Sample standard deviation
|
|
54
|
+
|
|
55
|
+
# Print summary
|
|
56
|
+
print("========= Timing Statistics =========")
|
|
57
|
+
print(f"Count: {len(startup_times)}")
|
|
58
|
+
print(f"Average: {average:.2f} seconds")
|
|
59
|
+
print(f"Median: {median:.2f} seconds")
|
|
60
|
+
print(f"Min: {minimum:.2f} seconds")
|
|
61
|
+
print(f"Max: {maximum:.2f} seconds")
|
|
62
|
+
print(f"Stdev: {stdev:.2f} seconds")
|