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,94 @@
|
|
|
1
|
+
class HttpUtils:
|
|
2
|
+
@staticmethod
|
|
3
|
+
def insert(s: str, insertion: str, num: int, position: str, index: int = None) -> str:
|
|
4
|
+
"""
|
|
5
|
+
Inserts a string into another string a specified number of times at a specified position.
|
|
6
|
+
:param s: string to insert into
|
|
7
|
+
:param insertion: string to insert
|
|
8
|
+
:param num: number of times to insert
|
|
9
|
+
:param position: position to insert at, either "start", "end", "middle", "random", or "index"
|
|
10
|
+
:param index: index to insert at if position is
|
|
11
|
+
:return: modified string
|
|
12
|
+
"""
|
|
13
|
+
if position == "start":
|
|
14
|
+
result = num * insertion + s
|
|
15
|
+
if position == "end":
|
|
16
|
+
result = s + num * insertion
|
|
17
|
+
if position == "middle" or position == "random":
|
|
18
|
+
index = len(s) // 2
|
|
19
|
+
result = s[:index] + num * insertion + s[index:]
|
|
20
|
+
if position == "index" and index is not None:
|
|
21
|
+
result = s[:index] + num * insertion + s[index:]
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def replace(s: str, replacement: str, num: int, start_index: int, end_index: int) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Replaces a part of a string with another string a specified number of times.
|
|
28
|
+
:param s: string to replace in
|
|
29
|
+
:param replacement: string to replace with
|
|
30
|
+
:param num: number of times to replace
|
|
31
|
+
:param start_index: start index of part to replace
|
|
32
|
+
:param end_index: end index of part to replace
|
|
33
|
+
:return: modified string
|
|
34
|
+
"""
|
|
35
|
+
return s[:start_index] + num * replacement + s[end_index:]
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def duplicate(s: str, start_index: int, end_index: int) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Duplicates a part of a string with another string a specified number of times.
|
|
41
|
+
:param s: string to duplicate in
|
|
42
|
+
:param start_index: start index of part to duplicate in
|
|
43
|
+
:param end_index: end index of part to duplicate in
|
|
44
|
+
:return: modified string
|
|
45
|
+
"""
|
|
46
|
+
return 2 * s[start_index:end_index]
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def index_host_header(data_list: list[list[str]], number: int = 1) -> int:
|
|
50
|
+
"""
|
|
51
|
+
Indexes all host headers.
|
|
52
|
+
:param data_list: list of host headers
|
|
53
|
+
:param number: number of host headers
|
|
54
|
+
:return: indexed host headers
|
|
55
|
+
"""
|
|
56
|
+
count = 0
|
|
57
|
+
for i, line in enumerate(data_list):
|
|
58
|
+
if "HOST" in line[0].upper():
|
|
59
|
+
count += 1
|
|
60
|
+
if count == number:
|
|
61
|
+
return i
|
|
62
|
+
return -1
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def index_content_length_header(data_list: list[list[str]], number: int = 1) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Indexes all content length headers.
|
|
68
|
+
:param data_list: list of content length headers
|
|
69
|
+
:param number: number of content length headers
|
|
70
|
+
:return: indexed content length headers
|
|
71
|
+
"""
|
|
72
|
+
count = 0
|
|
73
|
+
for i, line in enumerate(data_list):
|
|
74
|
+
if "CONTENT-LENGTH" in line[0].upper():
|
|
75
|
+
count += 1
|
|
76
|
+
if count == number:
|
|
77
|
+
return i
|
|
78
|
+
return -1
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def index_transfer_encoding_header(data_list: list[list[str]], number: int = 1) -> int:
|
|
82
|
+
"""
|
|
83
|
+
Indexes all transfer encoding headers.
|
|
84
|
+
:param data_list: list of transfer encoding headers
|
|
85
|
+
:param number: number of transfer encoding headers
|
|
86
|
+
:return: indexed transfer encoding headers
|
|
87
|
+
"""
|
|
88
|
+
count = 0
|
|
89
|
+
for i, line in enumerate(data_list):
|
|
90
|
+
if "TRANSFER-ENCODING" in line[0].upper():
|
|
91
|
+
count += 1
|
|
92
|
+
if count == number:
|
|
93
|
+
return i
|
|
94
|
+
return -1
|
modules/http/__init__.py
ADDED
|
File without changes
|
modules/tls/TcpProxy.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import select
|
|
3
|
+
import socket
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from enumerators.TcpProxyMode import TcpProxyMode
|
|
7
|
+
from enumerators.TlsVersion import TlsVersion
|
|
8
|
+
from network.NetworkAddress import NetworkAddress
|
|
9
|
+
from network.tcp.TcpConnectionHandler import TcpConnectionHandler
|
|
10
|
+
from network.tcp.WrappedTcpSocket import WrappedTcpSocket
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TcpProxy:
|
|
14
|
+
"""
|
|
15
|
+
Proxy server
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
address: NetworkAddress,
|
|
21
|
+
timeout: int = 120,
|
|
22
|
+
record_version: str = TlsVersion.DEFAULT.value,
|
|
23
|
+
record_frag: bool = False,
|
|
24
|
+
tcp_frag: bool = False,
|
|
25
|
+
frag_size: int = 20,
|
|
26
|
+
http_strategy: int = None,
|
|
27
|
+
http_smuggling_uncensored_url: str = "",
|
|
28
|
+
dns_server: NetworkAddress = None,
|
|
29
|
+
disabled_modes: list[TcpProxyMode] = None,
|
|
30
|
+
forward_proxy: NetworkAddress = None,
|
|
31
|
+
forward_proxy_mode: TcpProxyMode = TcpProxyMode.HTTPS,
|
|
32
|
+
forward_proxy_resolve_address: bool = False,
|
|
33
|
+
):
|
|
34
|
+
# timeout for socket reads and message reception
|
|
35
|
+
self.timeout = timeout
|
|
36
|
+
# own port
|
|
37
|
+
self.address = address
|
|
38
|
+
# record header version settings
|
|
39
|
+
self.record_version = record_version
|
|
40
|
+
# record fragmentation settings
|
|
41
|
+
self.record_frag = record_frag
|
|
42
|
+
self.tcp_frag = tcp_frag
|
|
43
|
+
self.frag_size = frag_size
|
|
44
|
+
# http manipulation strategy
|
|
45
|
+
self.http_strategy = http_strategy
|
|
46
|
+
self.http_smuggling_uncensored_url = http_smuggling_uncensored_url
|
|
47
|
+
# whether to use dot for domain resolution
|
|
48
|
+
self.dns_server = dns_server
|
|
49
|
+
self.disabled_modes = disabled_modes
|
|
50
|
+
if self.disabled_modes is None:
|
|
51
|
+
self.disabled_modes = []
|
|
52
|
+
# settings for another proxy to contact further down the line
|
|
53
|
+
self.forward_proxy = forward_proxy
|
|
54
|
+
self.forward_proxy_mode = forward_proxy_mode
|
|
55
|
+
self.forward_proxy_resolve_address = forward_proxy_resolve_address
|
|
56
|
+
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
57
|
+
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
58
|
+
self.continue_processing = True
|
|
59
|
+
|
|
60
|
+
def handle(self, client_socket: WrappedTcpSocket, address: NetworkAddress):
|
|
61
|
+
return TcpConnectionHandler(
|
|
62
|
+
client_socket,
|
|
63
|
+
address,
|
|
64
|
+
self.timeout,
|
|
65
|
+
self.record_version,
|
|
66
|
+
self.record_frag,
|
|
67
|
+
self.tcp_frag,
|
|
68
|
+
self.frag_size,
|
|
69
|
+
self.http_strategy,
|
|
70
|
+
self.http_smuggling_uncensored_url,
|
|
71
|
+
self.dns_server,
|
|
72
|
+
self.disabled_modes,
|
|
73
|
+
self.forward_proxy,
|
|
74
|
+
self.forward_proxy_mode,
|
|
75
|
+
self.forward_proxy_resolve_address,
|
|
76
|
+
).handle()
|
|
77
|
+
|
|
78
|
+
def start(self):
|
|
79
|
+
"""
|
|
80
|
+
Starts the proxy. After calling the proxy is listening for connections.
|
|
81
|
+
:return:
|
|
82
|
+
"""
|
|
83
|
+
# opening server socket
|
|
84
|
+
self.server.bind((self.address.host, self.address.port))
|
|
85
|
+
self.server.listen()
|
|
86
|
+
print(f"### Started TCP proxy on {self.address.host}:{self.address.port} ###")
|
|
87
|
+
if self.dns_server:
|
|
88
|
+
logging.debug(f"Using DNS server {self.dns_server}")
|
|
89
|
+
if self.forward_proxy:
|
|
90
|
+
logging.debug(f"Using forward proxy {self.forward_proxy}")
|
|
91
|
+
while self.continue_processing:
|
|
92
|
+
readable, _, _ = select.select([self.server], [], [], 1)
|
|
93
|
+
if not readable:
|
|
94
|
+
continue
|
|
95
|
+
# listen for incoming connections
|
|
96
|
+
try:
|
|
97
|
+
client_socket, address = self.server.accept()
|
|
98
|
+
except OSError:
|
|
99
|
+
logging.error("### Socket closed by OS. Stopping TCP Proxy ###")
|
|
100
|
+
return
|
|
101
|
+
address = NetworkAddress(address[0], address[1])
|
|
102
|
+
client_socket = WrappedTcpSocket(self.timeout, client_socket)
|
|
103
|
+
logging.info(f"request from {address.host}:{address.port}")
|
|
104
|
+
# spawn a new thread that runs the function handle()
|
|
105
|
+
threading.Thread(target=self.handle, args=(client_socket, address)).start()
|
|
106
|
+
logging.info("### Stopped proxy ###")
|
modules/tls/TlsModule.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import string
|
|
3
|
+
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
|
|
4
|
+
|
|
5
|
+
from enumerators.TcpProxyMode import TcpProxyMode
|
|
6
|
+
from enumerators.TlsVersion import TlsVersion
|
|
7
|
+
from modules.Module import Module
|
|
8
|
+
from modules.tls.TcpProxy import TcpProxy
|
|
9
|
+
from network.NetworkAddress import NetworkAddress
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TlsModule(Module):
|
|
13
|
+
"""
|
|
14
|
+
Implements circumvention methods for the TLS SNI censorship. Currently, implements options for a general TCP socket
|
|
15
|
+
as TLS is the only TCP-based protocol we support. In future version, those should be abstracted into their own
|
|
16
|
+
module.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, parser: ArgumentParser):
|
|
20
|
+
super().__init__(parser)
|
|
21
|
+
self.proxy: TcpProxy | None = None
|
|
22
|
+
self.dns_server = None
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def register_parameters(parser: ArgumentParser):
|
|
26
|
+
|
|
27
|
+
def list_of_modes(arg):
|
|
28
|
+
return list(map(lambda x: TcpProxyMode(x), arg.split(",")))
|
|
29
|
+
|
|
30
|
+
def record_header_version(arg):
|
|
31
|
+
try:
|
|
32
|
+
return TlsVersion.__getitem__(arg).value
|
|
33
|
+
except KeyError:
|
|
34
|
+
if len(arg) == 4 and all(c in string.hexdigits for c in arg):
|
|
35
|
+
return arg
|
|
36
|
+
else:
|
|
37
|
+
logging.error(
|
|
38
|
+
f"{arg} not a predefined TLS version, not 2 bytes long or contains non-hex characters."
|
|
39
|
+
)
|
|
40
|
+
exit()
|
|
41
|
+
|
|
42
|
+
tls_module = parser.add_argument_group("TLS Module")
|
|
43
|
+
|
|
44
|
+
tls_module.add_argument(
|
|
45
|
+
"--tls_disabled_modes",
|
|
46
|
+
type=list_of_modes,
|
|
47
|
+
choices=TcpProxyMode,
|
|
48
|
+
default=[],
|
|
49
|
+
help="List of proxy modes to ignore. By default, all none are disabled. Hence, all are enabled",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
tls_module.add_argument("--tls_timeout", type=int, default=10, help="Connection timeout in seconds")
|
|
53
|
+
|
|
54
|
+
tls_module.add_argument("--tls_host", type=str, default="localhost", help="Address the proxy server runs on")
|
|
55
|
+
|
|
56
|
+
tls_module.add_argument("--tls_port", type=int, default=4433, help="Port the proxy server runs on")
|
|
57
|
+
|
|
58
|
+
tls_module.add_argument(
|
|
59
|
+
"--tls_record_version",
|
|
60
|
+
type=record_header_version,
|
|
61
|
+
default=TlsVersion.DEFAULT.name,
|
|
62
|
+
help=f"Overwrites the TLS version in the TLS record with the given bytes. Pre-defined "
|
|
63
|
+
f"values {[x.name for x in TlsVersion]} or 2 byte long values such as 0303 or "
|
|
64
|
+
f"FFFF can be provided.",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
tls_module.add_argument(
|
|
68
|
+
"--tls_record_frag",
|
|
69
|
+
default=True,
|
|
70
|
+
action=BooleanOptionalAction,
|
|
71
|
+
help="Whether to use record fragmentation to forwarded TLS handshake messages",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
tls_module.add_argument(
|
|
75
|
+
"--tls_tcp_frag",
|
|
76
|
+
default=True,
|
|
77
|
+
action=BooleanOptionalAction,
|
|
78
|
+
help="Whether to use TCP fragmentation to forwarded messages.",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
tls_module.add_argument("--tls_frag_size", type=int, default=20, help="Bytes in each TCP/TLS record fragment")
|
|
82
|
+
|
|
83
|
+
tls_module.add_argument(
|
|
84
|
+
"--tls_dns_server_ip",
|
|
85
|
+
type=str,
|
|
86
|
+
default=None,
|
|
87
|
+
help="DNS server IP for all DNS queries of the TLS module. If not given, the DNS server started by the "
|
|
88
|
+
"DNS module us used. If DNS module is not used, the OS default DNS server is used.",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
tls_module.add_argument(
|
|
92
|
+
"--tls_dns_server_port",
|
|
93
|
+
type=int,
|
|
94
|
+
default=53,
|
|
95
|
+
help="DNS server port for all DNS queries. Only set if a DNS server IP is given. If not given, the "
|
|
96
|
+
"default port 53 is used.",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
tls_module.add_argument(
|
|
100
|
+
"--tls_forward_proxy_host",
|
|
101
|
+
type=str,
|
|
102
|
+
default="localhost",
|
|
103
|
+
help="Host of the forward proxy if any is present",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
tls_module.add_argument(
|
|
107
|
+
"--tls_forward_proxy_port", type=int, default=None, help="Port the forward proxy server runs on"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
tls_module.add_argument(
|
|
111
|
+
"--tls_forward_proxy_mode",
|
|
112
|
+
type=TcpProxyMode.__getitem__,
|
|
113
|
+
choices=TcpProxyMode,
|
|
114
|
+
default=TcpProxyMode.HTTPS,
|
|
115
|
+
help="The proxy type of the forward proxy",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
tls_module.add_argument(
|
|
119
|
+
"--tls_forward_proxy_resolve_address",
|
|
120
|
+
default=False,
|
|
121
|
+
action=BooleanOptionalAction,
|
|
122
|
+
help="""Whether to resolve domains before including them in the HTTP CONNECT request to the
|
|
123
|
+
second proxy""",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def extract_parameters(self, args: Namespace):
|
|
127
|
+
server_address = NetworkAddress(args.tls_host, args.tls_port)
|
|
128
|
+
forward_proxy = None
|
|
129
|
+
if args.tls_forward_proxy_port is not None:
|
|
130
|
+
forward_proxy = NetworkAddress(args.tls_forward_proxy_host, args.tls_forward_proxy_port)
|
|
131
|
+
|
|
132
|
+
if args.tls_dns_server_ip is not None and self.dns_server is None:
|
|
133
|
+
self.dns_server = NetworkAddress(args.tls_dns_server_ip, args.tls_dns_server_port)
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
args.tls_forward_proxy_mode in [TcpProxyMode.HTTP, TcpProxyMode.SNI]
|
|
137
|
+
and args.tls_forward_proxy_mode != args.proxy_mode
|
|
138
|
+
):
|
|
139
|
+
logging.debug("Forward proxy modes HTTP and SNI only usable if proxy mode is HTTP or SNI respectively.")
|
|
140
|
+
exit()
|
|
141
|
+
|
|
142
|
+
self.proxy = TcpProxy(
|
|
143
|
+
address=server_address,
|
|
144
|
+
timeout=args.tls_timeout,
|
|
145
|
+
record_version=args.tls_record_version,
|
|
146
|
+
record_frag=args.tls_record_frag,
|
|
147
|
+
tcp_frag=args.tls_tcp_frag,
|
|
148
|
+
frag_size=args.tls_frag_size,
|
|
149
|
+
dns_server=self.dns_server,
|
|
150
|
+
disabled_modes=args.tls_disabled_modes,
|
|
151
|
+
forward_proxy=forward_proxy,
|
|
152
|
+
forward_proxy_mode=args.tls_forward_proxy_mode,
|
|
153
|
+
forward_proxy_resolve_address=args.tls_forward_proxy_resolve_address,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def start(self):
|
|
157
|
+
self.proxy.start()
|
|
158
|
+
|
|
159
|
+
def stop(self):
|
|
160
|
+
self.proxy.continue_processing = False
|
|
161
|
+
logging.info("Waiting for proxy to stop")
|
|
162
|
+
|
|
163
|
+
def set_dns_server(self, dns_server: NetworkAddress):
|
|
164
|
+
"""
|
|
165
|
+
Sets the DNS server for the TLS module.
|
|
166
|
+
:param dns_server: NetworkAddress of the DNS server to use.
|
|
167
|
+
"""
|
|
168
|
+
if not self.dns_server:
|
|
169
|
+
self.dns_server = NetworkAddress(
|
|
170
|
+
"127.0.0.1" if dns_server.host == "0.0.0.0" else dns_server.host, dns_server.port
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
logging.warning("DNS server manually overwritten in TLS module. Not setting address of DNS module server.")
|
modules/tls/__init__.py
ADDED
|
File without changes
|