-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathtls_tcp_proxy.py
313 lines (254 loc) · 9.26 KB
/
tls_tcp_proxy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import time
from twisted.internet import protocol, reactor
from twisted.internet import ssl as twisted_ssl
import dns.resolver
from OpenSSL.crypto import (X509Extension, X509,
dump_privatekey, dump_certificate,
load_certificate, load_privatekey,
PKey, TYPE_RSA, X509Req)
from OpenSSL.SSL import FILETYPE_PEM
import tempfile
import os
import netifaces as ni
# Adapted from http://stackoverflow.com/a/15645169/221061
class TLSTCPProxyProtocol(protocol.Protocol):
"""
TLSTCPProxyProtocol listens for TCP connections from a
client (eg. a phone) and forwards them on to a specified
destination (eg. an app's API server) over a second TCP
connection, using a ProxyToServerProtocol.
It assumes that both legs of this trip are encrypted
using TLS.
"""
def __init__(self):
self.buffer = None
self.proxy_to_server_protocol = None
def connectionMade(self):
"""
Called by twisted when a client connects to the
proxy. Makes an TLS connection from the proxy to
the server to complete the chain.
"""
print("Connection made from CLIENT => PROXY")
proxy_to_server_factory = protocol.ClientFactory()
proxy_to_server_factory.protocol = ProxyToServerProtocol
proxy_to_server_factory.server = self
reactor.connectSSL(DST_IP, DST_PORT,
proxy_to_server_factory,
twisted_ssl.CertificateOptions())
def dataReceived(self, data):
"""
Called by twisted when the proxy receives data from
the client. Sends the data on to the server.
CLIENT ===> PROXY ===> DST
"""
print("")
print("CLIENT => SERVER")
print(FORMAT_FN(data))
WRITE_TO_FILE(data)
print("")
if self.proxy_to_server_protocol:
self.proxy_to_server_protocol.write(data)
else:
self.buffer = data
def write(self, data):
self.transport.write(data)
class ProxyToServerProtocol(protocol.Protocol):
"""
ProxyToServerProtocol connects to a server over TCP.
It sends the server data given to it by an
TLSTCPProxyProtocol, and uses the TLSTCPProxyProtocol
to send data that it receives back from the server on
to a client.
"""
def connectionMade(self):
"""
Called by twisted when the proxy connects to the
server. Flushes any buffered data on the proxy
to server.
"""
print("Connection made from PROXY => SERVER")
self.factory.server.proxy_to_server_protocol = self
self.write(self.factory.server.buffer)
self.factory.server.buffer = ''
def dataReceived(self, data):
"""
Called by twisted when the proxy receives data
from the server. Sends the data on to to the
client.
DST ===> PROXY ===> CLIENT
"""
print("")
print("SERVER => CLIENT")
print(FORMAT_FN(data))
print("")
self.factory.server.write(data)
def write(self, data):
if data:
self.transport.write(data)
# A class that represents a CA. It wraps a root CA TLS
# certificate, and can generate and sign certificates using
# this root cert.
#
# Inpsiration from
# https://github.com/allfro/pymiproxy/blob/master/src/miproxy/proxy.py
class CertificateAuthority(object):
CERT_PREFIX = 'fake-cert'
def __init__(self, ca_file, cache_dir=tempfile.mkdtemp()):
print("Initializing CertificateAuthority ca_file=%s cache_dir=%s" %
(ca_file, cache_dir))
self.ca_file = ca_file
self.cache_dir = cache_dir
if not os.path.exists(ca_file):
raise Exception("No cert exists at %s" % ca_file)
else:
self._read_ca(ca_file)
def get_cert_path(self, cn):
cnp = os.path.sep.join([self.cache_dir, '%s-%s.pem' %
(self.CERT_PREFIX, cn)])
if os.path.exists(cnp):
print("Cert already exists common_name=%s" % cn)
else:
print("Creating and signing cert common_name=%s" % cn)
key = PKey()
key.generate_key(TYPE_RSA, 2048)
# Generate CSR
req = X509Req()
req.get_subject().CN = cn
req.set_pubkey(key)
req.sign(key, 'sha1')
# Sign CSR
cert = X509()
cert.set_subject(req.get_subject())
cert.set_serial_number(123)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(31536000)
cert.set_issuer(self.cert.get_subject())
cert.set_pubkey(req.get_pubkey())
cert.sign(self.key, 'sha1')
with open(cnp, 'wb+') as f:
f.write(dump_privatekey(FILETYPE_PEM, key))
f.write(dump_certificate(FILETYPE_PEM, cert))
print("Created cert common_name=%s location=%s" % (cn, cnp))
return cnp
def _read_ca(self, file):
self.cert = load_certificate(FILETYPE_PEM, open(file).read())
self.key = load_privatekey(FILETYPE_PEM, open(file).read())
@staticmethod
def generate_ca_cert(path, common_name):
if os.path.exists(path):
print("Cert already exists at %s, not regenerating" % path)
return
# Generate key
key = PKey()
key.generate_key(TYPE_RSA, 2048)
# Generate certificate
cert = X509()
cert.set_version(3)
cert.set_serial_number(1)
cert.get_subject().CN = common_name
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(315360000)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(key)
cert.sign(key, "sha256")
with open(path, 'wb+') as f:
f.write(dump_privatekey(FILETYPE_PEM, key))
f.write(dump_certificate(FILETYPE_PEM, cert))
def get_local_ip(iface):
ni.ifaddresses(iface)
return ni.ifaddresses(iface)[ni.AF_INET][0]['addr']
# Alternative functions for formating output data
def _side_by_side_hex(data):
BLOCK_SIZE = 16
output_lines = []
for i in range(0, len(data), BLOCK_SIZE):
block = data[i:i+BLOCK_SIZE]
_hex = ["%.2x" % el for el in block]
_str = [chr(el) if chr(el).isprintable() else "." for el in block]
line = " ".join(_hex).ljust((3*BLOCK_SIZE)+4) + "".join(_str).replace("\n", ".")
output_lines.append(line)
return "\n".join(output_lines)
def _stacked_hex(data):
BLOCK_SIZE = 32
hex_lines = []
plaintext_lines = []
for i in range(0, len(data), BLOCK_SIZE):
block = data[i:i+BLOCK_SIZE]
_hex = ["%.2x" % el for el in block]
_str = [chr(el) if chr(el).isprintable() else "." for el in block]
hex_line = " ".join(_hex)
hex_lines.append(hex_line)
plaintext_line = " ".join(_str).replace("\n", ".")
plaintext_lines.append(plaintext_line)
lines = hex_lines + ["\n"] + plaintext_lines
return "\n".join(lines)
def _replayable(data):
d = data[0:4000]
_hex = "".join(["%.2x" % el for el in d])
_str = "".join([chr(el) if chr(el).isprintable() else "." for el in d])
return _hex + "\n" + _str
def _noop(data):
return data
# Change this line to use an alternative formating function
FORMAT_FN = _noop
# Record data sent to the server to files
DIR_NAME = "replays/messages-%d/" % time.time()
os.mkdir(DIR_NAME)
f_n = 0
def _write_to_file(data):
# Global variables are bad but they do the job
global f_n
with open(DIR_NAME + str(f_n), 'wb') as f:
f.write(data)
f_n += 1
WRITE_TO_FILE = _write_to_file
CA_CERT_PATH = "./ca-cert.pem"
LISTEN_PORT = 443
DST_PORT = 443
DST_HOST = "www.bbc.com"
local_ip = get_local_ip('en0')
print("Querying DNS records for %s..." % DST_HOST)
a_records = dns.resolver.query(DST_HOST, 'A')
print("Found %d A records:" % len(a_records))
for r in a_records:
print("* %s" % r.address)
print("")
assert(len(a_records) > 0)
DST_IP = a_records[0].address
print("Choosing to proxy to %s" % DST_IP)
print("""
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
-#-#-#-#-#-RUNNING TLS TCP PROXY-#-#-#-#-#-
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
Root CA path:\t%s
Dst IP:\t%s
Dst port:\t%d
Dst hostname:\t%s
Listen port:\t%d
Local IP:\t%s
""" % (CA_CERT_PATH, DST_IP, DST_PORT, DST_HOST, LISTEN_PORT, local_ip))
CertificateAuthority.generate_ca_cert(CA_CERT_PATH, "Robert's Trusty Certificate Corp")
ca = CertificateAuthority(CA_CERT_PATH)
certfile = ca.get_cert_path(DST_HOST)
with open(certfile) as f:
cert = twisted_ssl.PrivateCertificate.loadPEM(f.read())
print("""
Next steps:
1. Make sure you are spoofing DNS requests from the
device you are trying to proxy request from so that they
return your local IP (%s).
2. Make sure you have set the destination and listen
ports correctly (they should generally be the same).
3. Use the device you are proxying requests from to make
requests to %s and check that they are logged in this
terminal.
4. Look at the requests, write more code to replay them,
fiddle with them, etc.
Listening for requests on %s:%d...
""" % (local_ip, DST_HOST, local_ip, LISTEN_PORT))
factory = protocol.ServerFactory()
factory.protocol = TLSTCPProxyProtocol
reactor.listenSSL(LISTEN_PORT, factory, cert.options(),
interface=local_ip)
reactor.run()