It is the general flow model for mitm traffic - active mode (for passive mode, you aren’t gonna find out in this article 😅). In each scenarios, where handler is diffrent type of proxy or rogue router, the implementation will follow specific protocol to do its job.
I’m not gonna reinvent the wheel for anything, or explain how fancy they are in technical detail. Here are some articles/references for them:
The traffic sniffing can be done at A2 step. At this phase, there are 2 possible type of traffic:
We only have to deal with SSL data, which is ofc, already solved in another model (B) too:
Assume client trusts our rootCA, and doesn’t use any certificate pinning mechanism !
So basically, the connection between client and handler is dynamic, it can be converted into SSL channel when needed, and supports both type of connection. I call that
The problem is handler doesn’t know the context of the connection and it can only figure out when the first packet arrived.
If it is plain data, nothing to worry about. But if it is
ClientHello packet, it means client wants to use SSL context, and in that case, we should be in the middle of SSL handshake process now.
Here is the whole handshake process of TLS 1.2
Awesome illustrated for this process 👉 https://tls.ulfheim.net/
As the server role, what we should do now is resuming the SSL handshake process - starting from (2) sending
For me, there are at least 2 possible ways to do it:
I’ve done this experiment in Python 3. Hardly depends on the latest openssl wrapper - pyOpenSSL
Here is an two examples of SSL server with
import socket from pyOpenSSL import SSL ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.use_privatekey_file ("cert/my.key") ctx.use_certificate_file("cert/my.crt") server = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) server.bind(("localhost", 9999)) server.listen(3) client, addr = server.accept() # Handshake already client.send("Hello from SSL channel")
import socket from pyOpenSSL import SSL ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.use_privatekey_file ("cert/my.key") ctx.use_certificate_file("cert/my.crt") server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("localhost", 9999)) server.listen(3) client_raw, addr = server.accept() client = SSL.Connection(ctx, client_raw) client.set_accept_state() # Act as server client.do_handshake() # Start the handshake client.send("Hello from SSL channel")
When a SSL socket server does handshake, it would wait to receive
ClientHello packet from client, and then do its work.
If we already read the first packet, detect
ClientHello request (by bytes header
16 03), and then do
handshake as normal, OpenSSL context variable doesn’t know about it, it will wait to receive another
ClientHello from socket.
We wish to put back read packet data into socket buffer again. It’s difficult task, and from my searching there is no possible way to do it for most type of modern socket.
Luckily, we still have an alternative solution for that. Both
linux socket support
MSGPEEK flag for
recv function, which will return the data without removing that data from the buffer.
So we don’t have to mess with OpenSSL or any SSL/TLS library to do the context converting, and this is easy to implement in any programming language not just
# ... # recv first packet in peek mode data = conn.recv(BUFFER_SIZE, socket.MSG_PEEK) # check if ssl context if data.startswith(b"\x16\x03"): print(">> Detect ssl request") # ssl ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.use_privatekey_file(KEY) ctx.use_certificate_file(CERT) sock = SSL.Connection(ctx, conn) sock.set_accept_state() try: sock.do_handshake() except: print(">> Failed to switch ssl") else: print(">> Upgraded") else: pass # ...
You can check out other examples in Github Repo above.
I also believe that
mitmproxy is currently using this method to do the same task.
As described in the above session, we wish to create a SSL context which we could put back our received data in, and resume in the middle of that.
How to do ?
ClientHelloand simulate your own TLS handshake process
It would be a fun exercise to understand SSL/TLS. I suggest you check out these packages in
BIO_memory. It’s like a virtual
file descriptor(which socket is a kind of, in
linux), where we could
readat any state. There are several examples around Internet to demostrate this supreme feature:
It’s a little bit hardcore to do the same in
pyOpenSSL wrapper, so …
tlslite-ng, a pure SSL/TLS implementation in
MyTLSConnectionclass to that
socketwith that data
RecordSocketto use our data instead of re-read from
# ... # TLSConnection -> _recordLayer (RecordLayer) -> _recordSocket (RecordSocket) class MyRecordSocket(RecordSocket): def __init__(self, sock, data): super().__init__(sock) self._amazingData = data self._startOfSomethingAmazing = True # first packet def recv(self): # check if it is ClientHello if self._startOfSomethingAmazing: self._startOfSomethingAmazing = False # assume it is SSL3 header, who cares about SSL2 ? record = RecordHeader3().parse(Parser(self._amazingData)) return (record, self._amazingData[5:]), else: return super().recv() class MyRecordLayer(RecordLayer): def __init__(self, sock, data): super().__init__(sock) self._recordSocket = MyRecordSocket(sock, data) # Wrapper with our recv data class MyTLSConnection(TLSConnection): def __init__(self, sock, data): super().__init__(sock) self._recordLayer = MyRecordLayer(sock, data) # ...
Please check out example
server_tlslite_once.py in Github Repo
certdirectory of the Github Repo.
Just a little experiment seems very simple, but not really easy to find out the answer for who has zero knowledge about it like me 😬. Hope you guys enjoy!
Feel free to fix me below. Thanks! 🖖