mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
ssl: rudimentary decryption for TLS 1.2
Several limitations still apply: - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 is the only supported cipher suite - Some tests are broken due to a failing assertion regarding bytestring - No newly written tests for decryption (the patch was tested extensively for our paper) - Several small open technical questions marked with FIXME - Architecture in the ssl module might not be optimal
This commit is contained in:
parent
db534b79fb
commit
2d950ffde9
21 changed files with 541 additions and 13 deletions
106
scripts/policy/protocols/ssl/decryption.zeek
Normal file
106
scripts/policy/protocols/ssl/decryption.zeek
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#! Decrypt SSL/TLS payloads
|
||||||
|
|
||||||
|
@load base/frameworks/input
|
||||||
|
@load base/frameworks/notice
|
||||||
|
@load base/protocols/conn
|
||||||
|
@load base/protocols/ssl
|
||||||
|
|
||||||
|
module SSL;
|
||||||
|
|
||||||
|
# Local
|
||||||
|
const input_stream_name = "input-tls-keylog-file";
|
||||||
|
|
||||||
|
type Idx: record {
|
||||||
|
client_random: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Val: record {
|
||||||
|
secret: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
global randoms: table[string] of string = {};
|
||||||
|
|
||||||
|
export {
|
||||||
|
redef record Info += {
|
||||||
|
# decryption uses client_random as identifier
|
||||||
|
client_random: string &log &optional;
|
||||||
|
};
|
||||||
|
|
||||||
|
const keylog_file = getenv("ZEEK_TLS_KEYLOG_FILE") &redef;
|
||||||
|
|
||||||
|
global secrets: table[string] of string = {} &redef;
|
||||||
|
global keys: table[string] of string = {} &redef;
|
||||||
|
|
||||||
|
event SSL::add_keys(client_random: string, val: string)
|
||||||
|
{
|
||||||
|
SSL::keys[client_random] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
event SSL::add_secret(client_random: string, val: string)
|
||||||
|
{
|
||||||
|
SSL::secrets[client_random] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec)
|
||||||
|
{
|
||||||
|
c$ssl$client_random = client_random;
|
||||||
|
|
||||||
|
if ( client_random in keys )
|
||||||
|
{
|
||||||
|
set_keys(c, keys[client_random]);
|
||||||
|
}
|
||||||
|
else if ( client_random in secrets )
|
||||||
|
{
|
||||||
|
set_secret(c, secrets[client_random]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string)
|
||||||
|
{
|
||||||
|
if ( c$ssl?$client_random )
|
||||||
|
{
|
||||||
|
if ( c$ssl$client_random in keys )
|
||||||
|
{
|
||||||
|
set_keys(c, keys[c$ssl$client_random]);
|
||||||
|
}
|
||||||
|
else if ( c$ssl$client_random in secrets )
|
||||||
|
{
|
||||||
|
set_secret(c, secrets[c$ssl$client_random]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
# FIXME: should this be moved to reporter.log or removed completely?
|
||||||
|
#print "No suitable key or secret found for random:", randoms[c$uid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event SSL::tls_input_done()
|
||||||
|
{
|
||||||
|
continue_processing();
|
||||||
|
}
|
||||||
|
|
||||||
|
event Input::end_of_data(name: string, source: string)
|
||||||
|
{
|
||||||
|
if ( name == input_stream_name )
|
||||||
|
{
|
||||||
|
event SSL::tls_input_done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event zeek_init()
|
||||||
|
{
|
||||||
|
# listen for secrets
|
||||||
|
Broker::subscribe("/zeek/tls/decryption");
|
||||||
|
|
||||||
|
# FIXME: is such a functionality helpful?
|
||||||
|
# ingest keylog file if the environment is set
|
||||||
|
if ( keylog_file != "" )
|
||||||
|
{
|
||||||
|
suspend_processing();
|
||||||
|
|
||||||
|
Input::add_table([$name=input_stream_name, $source=keylog_file, $destination=secrets, $idx=Idx, $val=Val, $want_record=F]);
|
||||||
|
Input::remove(input_stream_name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -223,7 +223,7 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count)
|
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string)
|
||||||
{
|
{
|
||||||
if ( !c?$ssl )
|
if ( !c?$ssl )
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -101,6 +101,7 @@
|
||||||
@load protocols/ssh/geo-data.zeek
|
@load protocols/ssh/geo-data.zeek
|
||||||
@load protocols/ssh/interesting-hostnames.zeek
|
@load protocols/ssh/interesting-hostnames.zeek
|
||||||
@load protocols/ssh/software.zeek
|
@load protocols/ssh/software.zeek
|
||||||
|
#@load protocols/ssl/decryption.zeek
|
||||||
@load protocols/ssl/expiring-certs.zeek
|
@load protocols/ssl/expiring-certs.zeek
|
||||||
@load protocols/ssl/extract-certs-pem.zeek
|
@load protocols/ssl/extract-certs-pem.zeek
|
||||||
@load protocols/ssl/heartbleed.zeek
|
@load protocols/ssl/heartbleed.zeek
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@load test-all-policy.zeek
|
@load test-all-policy.zeek
|
||||||
|
|
||||||
# Scripts which are commented out in test-all-policy.zeek.
|
# Scripts which are commented out in test-all-policy.zeek.
|
||||||
|
@load protocols/ssl/decryption.zeek
|
||||||
@load protocols/ssl/notary.zeek
|
@load protocols/ssl/notary.zeek
|
||||||
@load frameworks/control/controllee.zeek
|
@load frameworks/control/controllee.zeek
|
||||||
@load frameworks/control/controller.zeek
|
@load frameworks/control/controller.zeek
|
||||||
|
|
|
@ -10,8 +10,8 @@ zeek_plugin_bif(events.bif)
|
||||||
zeek_plugin_bif(functions.bif)
|
zeek_plugin_bif(functions.bif)
|
||||||
zeek_plugin_bif(consts.bif)
|
zeek_plugin_bif(consts.bif)
|
||||||
zeek_plugin_pac(tls-handshake.pac tls-handshake-protocol.pac tls-handshake-analyzer.pac ssl-defs.pac
|
zeek_plugin_pac(tls-handshake.pac tls-handshake-protocol.pac tls-handshake-analyzer.pac ssl-defs.pac
|
||||||
proc-client-hello.pac
|
proc-client-hello-tls.pac
|
||||||
proc-server-hello.pac
|
proc-server-hello-tls.pac
|
||||||
proc-certificate.pac
|
proc-certificate.pac
|
||||||
tls-handshake-signed_certificate_timestamp.pac
|
tls-handshake-signed_certificate_timestamp.pac
|
||||||
)
|
)
|
||||||
|
|
|
@ -72,4 +72,9 @@ void DTLS_Analyzer::SendHandshake(uint16_t raw_tls_version, uint8_t msg_type, ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DTLS_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version)
|
||||||
|
{
|
||||||
|
// noop for now as DTLS decryption is currently not supported
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace zeek::analyzer::dtls
|
} // namespace zeek::analyzer::dtls
|
||||||
|
|
|
@ -27,6 +27,8 @@ public:
|
||||||
static analyzer::Analyzer* Instantiate(Connection* conn)
|
static analyzer::Analyzer* Instantiate(Connection* conn)
|
||||||
{ return new DTLS_Analyzer(conn); }
|
{ return new DTLS_Analyzer(conn); }
|
||||||
|
|
||||||
|
bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
binpac::DTLS::SSL_Conn* interp;
|
binpac::DTLS::SSL_Conn* interp;
|
||||||
binpac::TLSHandshake::Handshake_Conn* handshake_interp;
|
binpac::TLSHandshake::Handshake_Conn* handshake_interp;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "zeek/analyzer/protocol/ssl/SSL.h"
|
#include "zeek/analyzer/protocol/ssl/SSL.h"
|
||||||
|
|
||||||
|
#include "zeek/analyzer/Manager.h"
|
||||||
#include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h"
|
#include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h"
|
||||||
#include "zeek/Reporter.h"
|
#include "zeek/Reporter.h"
|
||||||
#include "zeek/util.h"
|
#include "zeek/util.h"
|
||||||
|
@ -8,6 +9,73 @@
|
||||||
#include "zeek/analyzer/protocol/ssl/ssl_pac.h"
|
#include "zeek/analyzer/protocol/ssl/ssl_pac.h"
|
||||||
#include "zeek/analyzer/protocol/ssl/tls-handshake_pac.h"
|
#include "zeek/analyzer/protocol/ssl/tls-handshake_pac.h"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/kdf.h>
|
||||||
|
|
||||||
|
#define MSB(a) ((a>>8)&0xff)
|
||||||
|
#define LSB(a) (a&0xff)
|
||||||
|
|
||||||
|
static void fmt_seq(uint32_t num, u_char* buf)
|
||||||
|
{
|
||||||
|
memset(buf, 0, 8);
|
||||||
|
uint32_t netnum = htonl(num);
|
||||||
|
memcpy(buf+4, &netnum, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_hex(std::string name, u_char* data, int len)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
printf("%s (%d): ", name.c_str(), len);
|
||||||
|
if (len > 0)
|
||||||
|
printf("0x%02x", data[0]);
|
||||||
|
|
||||||
|
for (i = 1; i < len; i++)
|
||||||
|
{
|
||||||
|
printf(" 0x%02x", data[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tls1_prf(const u_char *secret, int secret_len, const char* label,
|
||||||
|
const u_char* rnd1, int rnd1_len, const u_char* rnd2, int rnd2_len, u_char* out, size_t out_len)
|
||||||
|
{
|
||||||
|
// alloc buffers
|
||||||
|
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL);
|
||||||
|
size_t seed_len = strlen(label) + rnd1_len + rnd2_len;
|
||||||
|
u_char *seed = (u_char*) malloc(seed_len);
|
||||||
|
u_char *ptr = seed;
|
||||||
|
|
||||||
|
// seed = label + rnd1 + rnd2
|
||||||
|
memcpy(ptr, label, strlen(label));
|
||||||
|
ptr += strlen(label);
|
||||||
|
memcpy(ptr, rnd1, rnd1_len);
|
||||||
|
ptr += rnd1_len;
|
||||||
|
memcpy(ptr, rnd2, rnd2_len);
|
||||||
|
ptr += rnd2_len;
|
||||||
|
|
||||||
|
if (EVP_PKEY_derive_init(pctx) <= 0)
|
||||||
|
goto abort; /* Error */
|
||||||
|
// FIXME: sha384 should not be hardcoded
|
||||||
|
if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0)
|
||||||
|
goto abort; /* Error */
|
||||||
|
if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret, secret_len) <= 0)
|
||||||
|
goto abort; /* Error */
|
||||||
|
if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seed_len) <= 0)
|
||||||
|
goto abort; /* Error */
|
||||||
|
if (EVP_PKEY_derive(pctx, out, &out_len) <= 0)
|
||||||
|
goto abort; /* Error */
|
||||||
|
|
||||||
|
EVP_PKEY_CTX_free(pctx);
|
||||||
|
free(seed);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
abort:
|
||||||
|
EVP_PKEY_CTX_free(pctx);
|
||||||
|
free(seed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
namespace zeek::analyzer::ssl {
|
namespace zeek::analyzer::ssl {
|
||||||
|
|
||||||
SSL_Analyzer::SSL_Analyzer(Connection* c)
|
SSL_Analyzer::SSL_Analyzer(Connection* c)
|
||||||
|
@ -16,12 +84,20 @@ SSL_Analyzer::SSL_Analyzer(Connection* c)
|
||||||
interp = new binpac::SSL::SSL_Conn(this);
|
interp = new binpac::SSL::SSL_Conn(this);
|
||||||
handshake_interp = new binpac::TLSHandshake::Handshake_Conn(this);
|
handshake_interp = new binpac::TLSHandshake::Handshake_Conn(this);
|
||||||
had_gap = false;
|
had_gap = false;
|
||||||
|
c_seq = 0;
|
||||||
|
s_seq = 0;
|
||||||
|
// FIXME: should this be initialized to nullptr?
|
||||||
|
secret = new zeek::StringVal(0, "");
|
||||||
|
keys = new zeek::StringVal(0, "");
|
||||||
|
pia = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
SSL_Analyzer::~SSL_Analyzer()
|
SSL_Analyzer::~SSL_Analyzer()
|
||||||
{
|
{
|
||||||
delete interp;
|
delete interp;
|
||||||
delete handshake_interp;
|
delete handshake_interp;
|
||||||
|
delete secret;
|
||||||
|
delete keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SSL_Analyzer::Done()
|
void SSL_Analyzer::Done()
|
||||||
|
@ -98,4 +174,166 @@ void SSL_Analyzer::Undelivered(uint64_t seq, int len, bool orig)
|
||||||
interp->NewGap(orig, len);
|
interp->NewGap(orig, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SSL_Analyzer::SetSecret(const u_char* data, int len)
|
||||||
|
{
|
||||||
|
// FIXME: Is this the proper way to initialize a zeek::StringVal?
|
||||||
|
// the alternative requires a cast: new zeek::StringVal(len, data)
|
||||||
|
secret = new zeek::StringVal(new zeek::String(data, len, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSL_Analyzer::SetKeys(const u_char* data, int len)
|
||||||
|
{
|
||||||
|
keys = new zeek::StringVal(new zeek::String(data, len, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version)
|
||||||
|
{
|
||||||
|
// Unsupported cipher suite. Currently supported:
|
||||||
|
// - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 == 0xC030
|
||||||
|
auto cipher = handshake_interp->chosen_cipher();
|
||||||
|
if ( cipher != 0xC030 )
|
||||||
|
{
|
||||||
|
//printf("Unsupported cipher suite: %d\n", cipher);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither secret or key present: abort
|
||||||
|
if ( secret->Len() == 0 && keys->Len() == 0 )
|
||||||
|
{
|
||||||
|
// FIXME: this is just for debugging
|
||||||
|
printf("Could not decrypt packet (missing key):\n");
|
||||||
|
print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secret present, but no keys derived yet: derive keys
|
||||||
|
if ( secret->Len() != 0 && keys->Len() == 0 )
|
||||||
|
{
|
||||||
|
uint32_t ts = htonl((uint32_t) handshake_interp->gmt_unix_time());
|
||||||
|
|
||||||
|
u_char crand[32] = {0x00};
|
||||||
|
u_char keybuf[72];
|
||||||
|
|
||||||
|
auto c_rnd = handshake_interp->client_random();
|
||||||
|
auto s_rnd = handshake_interp->server_random();
|
||||||
|
memcpy(crand, &(ts), 4);
|
||||||
|
memcpy(crand + 4, c_rnd.data(), c_rnd.length());
|
||||||
|
|
||||||
|
tls1_prf(secret->Bytes(), secret->Len(), "key expansion", s_rnd.data(), s_rnd.length(),
|
||||||
|
crand, sizeof(crand), keybuf, sizeof(keybuf));
|
||||||
|
|
||||||
|
// save derived keys
|
||||||
|
SetKeys(keybuf, sizeof(keybuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys present: decrypt TLS application data
|
||||||
|
if ( keys->Len() != 0 )
|
||||||
|
{
|
||||||
|
// session keys & AEAD data
|
||||||
|
u_char c_wk[32];
|
||||||
|
u_char s_wk[32];
|
||||||
|
u_char c_iv[4];
|
||||||
|
u_char s_iv[4];
|
||||||
|
u_char s_aead_nonce[12];
|
||||||
|
u_char s_aead_tag[13];
|
||||||
|
|
||||||
|
memcpy(c_wk, keys->Bytes(), 32);
|
||||||
|
memcpy(s_wk, &(keys->Bytes()[32]), 32);
|
||||||
|
memcpy(c_iv, &(keys->Bytes()[64]), 4);
|
||||||
|
memcpy(s_iv, &(keys->Bytes()[68]), 4);
|
||||||
|
|
||||||
|
// FIXME: should we change types here?
|
||||||
|
u_char* encrypted = (u_char*)data;
|
||||||
|
size_t encrypted_len = len;
|
||||||
|
|
||||||
|
// FIXME: should this be moved to SSL_Conn?
|
||||||
|
if ( is_orig )
|
||||||
|
c_seq++;
|
||||||
|
else
|
||||||
|
s_seq++;
|
||||||
|
|
||||||
|
if ( is_orig )
|
||||||
|
memcpy(s_aead_nonce, c_iv, 4);
|
||||||
|
else
|
||||||
|
memcpy(s_aead_nonce, s_iv, 4);
|
||||||
|
memcpy(&(s_aead_nonce[4]), encrypted, 8);
|
||||||
|
|
||||||
|
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||||
|
EVP_CIPHER_CTX_init(ctx);
|
||||||
|
EVP_CipherInit(ctx, EVP_aes_256_gcm(), NULL, NULL, 0);
|
||||||
|
|
||||||
|
encrypted += 8;
|
||||||
|
// FIXME: is this because of nonce and aead tag?
|
||||||
|
encrypted_len -= 8;
|
||||||
|
encrypted_len -= 16;
|
||||||
|
|
||||||
|
if (is_orig)
|
||||||
|
EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce);
|
||||||
|
else
|
||||||
|
EVP_DecryptInit(ctx, EVP_aes_256_gcm(), s_wk, s_aead_nonce);
|
||||||
|
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encrypted + encrypted_len);
|
||||||
|
|
||||||
|
if (is_orig)
|
||||||
|
fmt_seq(c_seq, s_aead_tag);
|
||||||
|
else
|
||||||
|
fmt_seq(s_seq, s_aead_tag);
|
||||||
|
|
||||||
|
s_aead_tag[8] = content_type;
|
||||||
|
s_aead_tag[9] = MSB(raw_tls_version);
|
||||||
|
s_aead_tag[10] = LSB(raw_tls_version);
|
||||||
|
s_aead_tag[11] = MSB(encrypted_len);
|
||||||
|
s_aead_tag[12] = LSB(encrypted_len);
|
||||||
|
|
||||||
|
u_char *decrypted = (u_char*)malloc(encrypted_len);
|
||||||
|
int decrypted_len = 0;
|
||||||
|
|
||||||
|
EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag, 13);
|
||||||
|
EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*) encrypted, encrypted_len);
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) )
|
||||||
|
{
|
||||||
|
printf("Decryption failed with return code %d. Invalid key?\n", res);
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
free(decrypted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
ForwardDecryptedData(decrypted_len, reinterpret_cast<const u_char*>(decrypted), is_orig);
|
||||||
|
|
||||||
|
free(decrypted);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is only reached if key derivation somehow failed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSL_Analyzer::ForwardDecryptedData(int len, const u_char* data, bool is_orig)
|
||||||
|
{
|
||||||
|
if ( ! pia )
|
||||||
|
{
|
||||||
|
pia = new analyzer::pia::PIA_TCP(Conn());
|
||||||
|
if ( AddChildAnalyzer(pia) )
|
||||||
|
{
|
||||||
|
pia->FirstPacket(true, nullptr);
|
||||||
|
pia->FirstPacket(false, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
reporter->FatalError("Could not initialize PIA");
|
||||||
|
|
||||||
|
// FIXME: Also statically add HTTP/H2 at the moment.
|
||||||
|
// We should move this bit to scriptland
|
||||||
|
auto http = analyzer_mgr->InstantiateAnalyzer("HTTP", Conn());
|
||||||
|
if ( http )
|
||||||
|
AddChildAnalyzer(http);
|
||||||
|
auto http2 = analyzer_mgr->InstantiateAnalyzer("HTTP2", Conn());
|
||||||
|
if ( http2 )
|
||||||
|
AddChildAnalyzer(http2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForwardStream(len, data, is_orig);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace zeek::analyzer::ssl
|
} // namespace zeek::analyzer::ssl
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "zeek/analyzer/protocol/pia/PIA.h"
|
||||||
#include "zeek/analyzer/protocol/tcp/TCP.h"
|
#include "zeek/analyzer/protocol/tcp/TCP.h"
|
||||||
|
|
||||||
#include "zeek/analyzer/protocol/ssl/events.bif.h"
|
#include "zeek/analyzer/protocol/ssl/events.bif.h"
|
||||||
|
@ -33,11 +34,24 @@ public:
|
||||||
static analyzer::Analyzer* Instantiate(Connection* conn)
|
static analyzer::Analyzer* Instantiate(Connection* conn)
|
||||||
{ return new SSL_Analyzer(conn); }
|
{ return new SSL_Analyzer(conn); }
|
||||||
|
|
||||||
|
// Key material for decryption
|
||||||
|
void SetSecret(const u_char* data, int len);
|
||||||
|
void SetKeys(const u_char* data, int len);
|
||||||
|
|
||||||
|
bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version);
|
||||||
|
void ForwardDecryptedData(int len, const u_char* data, bool is_orig);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
binpac::SSL::SSL_Conn* interp;
|
binpac::SSL::SSL_Conn* interp;
|
||||||
binpac::TLSHandshake::Handshake_Conn* handshake_interp;
|
binpac::TLSHandshake::Handshake_Conn* handshake_interp;
|
||||||
bool had_gap;
|
bool had_gap;
|
||||||
|
|
||||||
|
// FIXME: should this be moved into the connection?
|
||||||
|
int c_seq;
|
||||||
|
int s_seq;
|
||||||
|
zeek::StringVal *secret;
|
||||||
|
zeek::StringVal *keys;
|
||||||
|
zeek::analyzer::pia::PIA_TCP *pia;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace zeek::analyzer::ssl
|
} // namespace zeek::analyzer::ssl
|
||||||
|
|
|
@ -558,9 +558,11 @@ event ssl_plaintext_data%(c: connection, is_orig: bool, record_version: count, c
|
||||||
##
|
##
|
||||||
## length: length of the entire message.
|
## length: length of the entire message.
|
||||||
##
|
##
|
||||||
|
## payload: encrypted payload of the SSL/TLS message
|
||||||
|
##
|
||||||
## .. zeek:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello
|
## .. zeek:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello
|
||||||
## ssl_alert ssl_heartbeat ssl_probable_encrypted_handshake_message
|
## ssl_alert ssl_heartbeat ssl_probable_encrypted_handshake_message
|
||||||
event ssl_encrypted_data%(c: connection, is_orig: bool, record_version: count, content_type: count, length: count%);
|
event ssl_encrypted_data%(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string%);
|
||||||
|
|
||||||
## This event is generated for application data records of TLS 1.3 connections of which
|
## This event is generated for application data records of TLS 1.3 connections of which
|
||||||
## we suspect that they contain handshake messages.
|
## we suspect that they contain handshake messages.
|
||||||
|
|
|
@ -16,3 +16,25 @@ function set_ssl_established%(c: connection%): any
|
||||||
static_cast<zeek::analyzer::ssl::SSL_Analyzer*>(sa)->StartEncryption();
|
static_cast<zeek::analyzer::ssl::SSL_Analyzer*>(sa)->StartEncryption();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
function set_secret%(c: connection, secret: string%): bool
|
||||||
|
%{
|
||||||
|
analyzer::Analyzer* sa = c->FindAnalyzer("SSL");
|
||||||
|
if ( sa )
|
||||||
|
{
|
||||||
|
static_cast<zeek::analyzer::ssl::SSL_Analyzer*>(sa)->SetSecret(secret->Bytes(), secret->Len());
|
||||||
|
return zeek::val_mgr->True();
|
||||||
|
}
|
||||||
|
return zeek::val_mgr->False();
|
||||||
|
%}
|
||||||
|
|
||||||
|
function set_keys%(c: connection, keys: string%): bool
|
||||||
|
%{
|
||||||
|
analyzer::Analyzer* sa = c->FindAnalyzer("SSL");
|
||||||
|
if ( sa )
|
||||||
|
{
|
||||||
|
static_cast<zeek::analyzer::ssl::SSL_Analyzer*>(sa)->SetKeys(keys->Bytes(), keys->Len());
|
||||||
|
return zeek::val_mgr->True();
|
||||||
|
}
|
||||||
|
return zeek::val_mgr->False();
|
||||||
|
%}
|
||||||
|
|
56
src/analyzer/protocol/ssl/proc-client-hello-tls.pac
Normal file
56
src/analyzer/protocol/ssl/proc-client-hello-tls.pac
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
function proc_client_hello(
|
||||||
|
version : uint16, ts : double,
|
||||||
|
client_random : bytestring,
|
||||||
|
session_id : uint8[],
|
||||||
|
cipher_suites16 : uint16[],
|
||||||
|
cipher_suites24 : uint24[],
|
||||||
|
compression_methods: uint8[]) : bool
|
||||||
|
%{
|
||||||
|
if ( ! version_ok(version) )
|
||||||
|
{
|
||||||
|
zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version));
|
||||||
|
zeek_analyzer()->SetSkip(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
zeek_analyzer()->ProtocolConfirmation();
|
||||||
|
|
||||||
|
if ( ssl_client_hello )
|
||||||
|
{
|
||||||
|
vector<int> cipher_suites;
|
||||||
|
|
||||||
|
if ( cipher_suites16 )
|
||||||
|
std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(cipher_suites));
|
||||||
|
else
|
||||||
|
std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(cipher_suites), to_int());
|
||||||
|
|
||||||
|
auto cipher_vec = zeek::make_intrusive<zeek::VectorVal>(zeek::id::index_vec);
|
||||||
|
|
||||||
|
for ( unsigned int i = 0; i < cipher_suites.size(); ++i )
|
||||||
|
{
|
||||||
|
auto ciph = zeek::val_mgr->Count(cipher_suites[i]);
|
||||||
|
cipher_vec->Assign(i, ciph);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto comp_vec = zeek::make_intrusive<zeek::VectorVal>(zeek::id::index_vec);
|
||||||
|
|
||||||
|
if ( compression_methods )
|
||||||
|
{
|
||||||
|
for ( unsigned int i = 0; i < compression_methods->size(); ++i )
|
||||||
|
{
|
||||||
|
auto comp = zeek::val_mgr->Count((*compression_methods)[i]);
|
||||||
|
comp_vec->Assign(i, comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_client_random(client_random);
|
||||||
|
set_gmt_unix_time(ts);
|
||||||
|
zeek::BifEvent::enqueue_ssl_client_hello(zeek_analyzer(), zeek_analyzer()->Conn(),
|
||||||
|
version, record_version(), ts,
|
||||||
|
zeek::make_intrusive<zeek::StringVal>(client_random.length(),
|
||||||
|
(const char*) client_random.data()),
|
||||||
|
{zeek::AdoptRef{}, to_string_val(session_id)},
|
||||||
|
std::move(cipher_vec), std::move(comp_vec));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
%}
|
41
src/analyzer/protocol/ssl/proc-server-hello-tls.pac
Normal file
41
src/analyzer/protocol/ssl/proc-server-hello-tls.pac
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
function proc_server_hello(
|
||||||
|
version : uint16, v2 : bool,
|
||||||
|
server_random : bytestring,
|
||||||
|
session_id : uint8[],
|
||||||
|
cipher_suites16 : uint16[],
|
||||||
|
cipher_suites24 : uint24[],
|
||||||
|
comp_method : uint8) : bool
|
||||||
|
%{
|
||||||
|
if ( ! version_ok(version) )
|
||||||
|
{
|
||||||
|
zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version));
|
||||||
|
zeek_analyzer()->SetSkip(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ssl_server_hello )
|
||||||
|
{
|
||||||
|
vector<int>* ciphers = new vector<int>();
|
||||||
|
|
||||||
|
if ( cipher_suites16 )
|
||||||
|
std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(*ciphers));
|
||||||
|
else
|
||||||
|
std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(*ciphers), to_int());
|
||||||
|
|
||||||
|
uint32 ts = 0;
|
||||||
|
if ( v2 == 0 && server_random.length() >= 4 )
|
||||||
|
ts = ntohl(*((uint32*)server_random.data()));
|
||||||
|
|
||||||
|
set_server_random(server_random);
|
||||||
|
zeek::BifEvent::enqueue_ssl_server_hello(zeek_analyzer(),
|
||||||
|
zeek_analyzer()->Conn(),
|
||||||
|
version, record_version(), ts,
|
||||||
|
zeek::make_intrusive<zeek::StringVal>(server_random.length(),
|
||||||
|
(const char*) server_random.data()),
|
||||||
|
{zeek::AdoptRef{}, to_string_val(session_id)},
|
||||||
|
ciphers->size()==0 ? 0 : ciphers->at(0), comp_method);
|
||||||
|
|
||||||
|
delete ciphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
%}
|
|
@ -43,7 +43,7 @@ refine connection SSL_Conn += {
|
||||||
return true;
|
return true;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
function proc_ciphertext_record(rec : SSLRecord) : bool
|
function proc_ciphertext_record(rec : SSLRecord, cont: bytestring) : bool
|
||||||
%{
|
%{
|
||||||
if ( established_ == false && determine_tls13() == 1 )
|
if ( established_ == false && determine_tls13() == 1 )
|
||||||
{
|
{
|
||||||
|
@ -62,8 +62,15 @@ refine connection SSL_Conn += {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ssl_encrypted_data )
|
if ( ssl_encrypted_data )
|
||||||
|
{
|
||||||
zeek::BifEvent::enqueue_ssl_encrypted_data(zeek_analyzer(),
|
zeek::BifEvent::enqueue_ssl_encrypted_data(zeek_analyzer(),
|
||||||
zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length});
|
zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length},
|
||||||
|
zeek::make_intrusive<zeek::StringVal>(cont.length(), (const char*) cont.data()));
|
||||||
|
if (rec->content_type() == APPLICATION_DATA)
|
||||||
|
{
|
||||||
|
zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.data(), rec->is_orig(), rec->content_type(), rec->raw_tls_version());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
%}
|
%}
|
||||||
|
@ -123,7 +130,7 @@ refine typeattr UnknownRecord += &let {
|
||||||
};
|
};
|
||||||
|
|
||||||
refine typeattr CiphertextRecord += &let {
|
refine typeattr CiphertextRecord += &let {
|
||||||
proc : bool = $context.connection.proc_ciphertext_record(rec);
|
proc : bool = $context.connection.proc_ciphertext_record(rec, cont);
|
||||||
}
|
}
|
||||||
|
|
||||||
refine typeattr PlaintextRecord += &let {
|
refine typeattr PlaintextRecord += &let {
|
||||||
|
|
|
@ -96,7 +96,7 @@ type UnknownRecord(rec: SSLRecord) = record {
|
||||||
};
|
};
|
||||||
|
|
||||||
type CiphertextRecord(rec: SSLRecord) = record {
|
type CiphertextRecord(rec: SSLRecord) = record {
|
||||||
cont : bytestring &restofdata &transient;
|
cont : bytestring &restofdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
|
|
||||||
refine connection Handshake_Conn += {
|
refine connection Handshake_Conn += {
|
||||||
|
|
||||||
%include proc-client-hello.pac
|
%include proc-client-hello-tls.pac
|
||||||
%include proc-server-hello.pac
|
%include proc-server-hello-tls.pac
|
||||||
%include proc-certificate.pac
|
%include proc-certificate.pac
|
||||||
|
|
||||||
function proc_session_ticket_handshake(rec: SessionTicketHandshake, is_orig: bool): bool
|
function proc_session_ticket_handshake(rec: SessionTicketHandshake, is_orig: bool): bool
|
||||||
|
|
|
@ -943,6 +943,9 @@ refine connection Handshake_Conn += {
|
||||||
uint32 chosen_cipher_;
|
uint32 chosen_cipher_;
|
||||||
uint16 chosen_version_;
|
uint16 chosen_version_;
|
||||||
uint16 record_version_;
|
uint16 record_version_;
|
||||||
|
bytestring client_random_;
|
||||||
|
bytestring server_random_;
|
||||||
|
uint32 gmt_unix_time_;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
%init{
|
%init{
|
||||||
|
@ -950,6 +953,10 @@ refine connection Handshake_Conn += {
|
||||||
chosen_version_ = UNKNOWN_VERSION;
|
chosen_version_ = UNKNOWN_VERSION;
|
||||||
|
|
||||||
record_version_ = 0;
|
record_version_ = 0;
|
||||||
|
// FIXME: How should bytestrings be initialized?
|
||||||
|
// client_random_ = ??
|
||||||
|
// server_random_ = ??
|
||||||
|
gmt_unix_time_ = 0;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
function chosen_cipher() : int %{ return chosen_cipher_; %}
|
function chosen_cipher() : int %{ return chosen_cipher_; %}
|
||||||
|
@ -983,5 +990,29 @@ refine connection Handshake_Conn += {
|
||||||
record_version_ = version;
|
record_version_ = version;
|
||||||
return true;
|
return true;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
function client_random() : bytestring %{ return client_random_; %}
|
||||||
|
|
||||||
|
function set_client_random(client_random: bytestring) : bool
|
||||||
|
%{
|
||||||
|
client_random_.init(client_random.data(), client_random.length());
|
||||||
|
return true;
|
||||||
|
%}
|
||||||
|
|
||||||
|
function server_random() : bytestring %{ return server_random_; %}
|
||||||
|
|
||||||
|
function set_server_random(server_random: bytestring) : bool
|
||||||
|
%{
|
||||||
|
server_random_.init(server_random.data(), server_random.length());
|
||||||
|
return true;
|
||||||
|
%}
|
||||||
|
|
||||||
|
function gmt_unix_time() : uint32 %{ return gmt_unix_time_; %}
|
||||||
|
|
||||||
|
function set_gmt_unix_time(ts: uint32) : bool
|
||||||
|
%{
|
||||||
|
gmt_unix_time_ = ts;
|
||||||
|
return true;
|
||||||
|
%}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9079,6 +9079,7 @@ XXXXXXXXXX.XXXXXX ssl_encrypted_data
|
||||||
[2] record_version: count = 771
|
[2] record_version: count = 771
|
||||||
[3] content_type: count = 22
|
[3] content_type: count = 22
|
||||||
[4] length: count = 32
|
[4] length: count = 32
|
||||||
|
[5] payload: string = \x1c\x1c\x84S/9\x14e\xb6'\xe5,\x03\x0fY\xdf\x1b\xcfu\xc84\xae\x1a"\xea]9j'\xbeZ\xa7
|
||||||
|
|
||||||
XXXXXXXXXX.XXXXXX raw_packet
|
XXXXXXXXXX.XXXXXX raw_packet
|
||||||
[0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=91, cap_len=91, src=58:b0:35:86:54:8d, dst=cc:b2:55:f4:62:92, vlan=<uninitialized>, inner_vlan=<uninitialized>, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=77, id=51331, ttl=64, p=6, src=192.168.133.100, dst=17.167.150.73], ip6=<uninitialized>, tcp=[sport=49655/tcp, dport=443/tcp, seq=3289393854, ack=2319612745, hl=20, dl=37, reserved=0, flags=24, win=8192], udp=<uninitialized>, icmp=<uninitialized>]
|
[0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=91, cap_len=91, src=58:b0:35:86:54:8d, dst=cc:b2:55:f4:62:92, vlan=<uninitialized>, inner_vlan=<uninitialized>, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=77, id=51331, ttl=64, p=6, src=192.168.133.100, dst=17.167.150.73], ip6=<uninitialized>, tcp=[sport=49655/tcp, dport=443/tcp, seq=3289393854, ack=2319612745, hl=20, dl=37, reserved=0, flags=24, win=8192], udp=<uninitialized>, icmp=<uninitialized>]
|
||||||
|
@ -9176,6 +9177,7 @@ XXXXXXXXXX.XXXXXX ssl_encrypted_data
|
||||||
[2] record_version: count = 771
|
[2] record_version: count = 771
|
||||||
[3] content_type: count = 22
|
[3] content_type: count = 22
|
||||||
[4] length: count = 32
|
[4] length: count = 32
|
||||||
|
[5] payload: string = Z\x99\x17~d\x06\xbd;\xb4\xdf\xe2\xb3~9,|\xac\xdb\xb4\xeb\xcc\x95.\x17\xd2Q\x8a\x96\xdb\x13\x09!
|
||||||
|
|
||||||
XXXXXXXXXX.XXXXXX raw_packet
|
XXXXXXXXXX.XXXXXX raw_packet
|
||||||
[0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=97, cap_len=97, src=cc:b2:55:f4:62:92, dst=58:b0:35:86:54:8d, vlan=<uninitialized>, inner_vlan=<uninitialized>, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=83, id=50807, ttl=243, p=6, src=17.167.150.73, dst=192.168.133.100], ip6=<uninitialized>, tcp=[sport=443/tcp, dport=49655/tcp, seq=2319612745, ack=3289393891, hl=20, dl=43, reserved=0, flags=24, win=3626], udp=<uninitialized>, icmp=<uninitialized>]
|
[0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=97, cap_len=97, src=cc:b2:55:f4:62:92, dst=58:b0:35:86:54:8d, vlan=<uninitialized>, inner_vlan=<uninitialized>, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=83, id=50807, ttl=243, p=6, src=17.167.150.73, dst=192.168.133.100], ip6=<uninitialized>, tcp=[sport=443/tcp, dport=49655/tcp, seq=2319612745, ack=3289393891, hl=20, dl=43, reserved=0, flags=24, win=3626], udp=<uninitialized>, icmp=<uninitialized>]
|
||||||
|
|
|
@ -27,7 +27,7 @@ event ssl_plaintext_data(c: connection, is_orig: bool, record_version: count, co
|
||||||
print "Plaintext data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length;
|
print "Plaintext data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length;
|
||||||
}
|
}
|
||||||
|
|
||||||
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count)
|
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string)
|
||||||
{
|
{
|
||||||
print "Encrypted data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length;
|
print "Encrypted data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ event ssl_established(c: connection)
|
||||||
print "established", c$id;
|
print "established", c$id;
|
||||||
}
|
}
|
||||||
|
|
||||||
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count)
|
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string)
|
||||||
{
|
{
|
||||||
print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type;
|
print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
redef SSL::disable_analyzer_after_detection=F;
|
redef SSL::disable_analyzer_after_detection=F;
|
||||||
|
|
||||||
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count)
|
event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string)
|
||||||
{
|
{
|
||||||
print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type;
|
print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue