In this post, we will play with Bro IDS as a client fingerprinting techniques exploration tool.
As is known, during the initial TLS handshake (used, among others, by HTTPS on web browsers), a message called ClientHello is exchanged. In this message, the client specifies the supported cryptographic primitives (the so-called cipher suites).
For example, Firefox 50.1.0 under Linux sends a ClientHello like this, as shown with the Wireshark dissector:
There are webpages that inform us about the TLS characteristics of our browser, such as Qualys SSL Labs SSL Client Test.
The interesting thing is that other type of TLS client will normally use a different set of ciphersuites. For example, for wget:
We have a handshake as follows:
As we can see, wget sends 68 different ciphersuites (insted of Firefox’ 15 suites), and their order is also different.
Therefore, in addition to other parameters, such as the TLS version used (which is also present in the messages), the number of ciphersuites and their order in the ClientHello message provide us a fingerprint of the client.
Firefox will generate a different fingerprint than Chrome, and in the same way, the fingerprint of the Tor client (if we don’t use it with pluggable transports) will be different of those of a Python script, Microsoft web request APIs or a RAT coded in Delphi…
This is a fact that we can take advantage of to idenfity unathorized applications or to detect malware that does not bother to hide in plain sight as a HTTPS browser used inside the organization for its command and control or exfiltration activities ;-)
It’s not rocket science to parse the ClientHello messages; there are p0f extensions and ad hoc tools such as FingerprinTLS. As expected, Bro IDS is also able to dissect the TLS protocol; we can find the constants used in the file /usr/local/bro/share/bro/base/protocols/ssl/consts.bro.
The magic numbers of the TLS versions and the mapping with the corresponding names are:
In the same way, a little later in the file, we have the different ciphersuites and the cipher_desc data structure:
As we saw in the previous post, when an interesting fact occurs, Bro “shoots” an event, that in the case of a ClientHello message is (/usr/local/bro/share/bro/base/bif/plugins/Bro_SSL.events.bif.bro):
So we already have all the ingredients to execute fingerprinting. The script would look something like:
The ciphers vector present in the event prototype stores the indexes with which it is possible to access the different ciphersuites contained in the message, which we map to their corresponding text strings using the cipher_desc table. To show them separated by a comma, we use the join_string_vec() function, similar to the join() function/method of Perl, Python, and other languages. If we save the script in the log_cipher_suites.bro file, we can perform a quick test by passing it on a previously captured tls.pcap file:
And there we have the 15 ciphersuites sent by Firefox, that uses TLSv1.2.
The logging framework
The print works, and all will be stored in stdout.log when Bro runs as a daemon, but… what if we want to send the output to another file, or to Elasticsearch? For that Bro provides the logging framework. Its usage is very simple (once you’ve got the catch, of course ;-)
To transform the previous “test” script into the final version, first we create a module called TlsFingerprint:
Next, we have to define two variables that the module will export to the rest of Bro. By convention, they are called LOG and Info:
As we can see, Info is the entry that will be recorded in the file. Each log entry will contain the timestamp (in the usual UN*X epoch format), a pair of identifiers, the TLS version and the different ciphersuites. The record is defined using Bro data types, such as vector of string, and the framework will be in charge of automatically storing it in a text file, Elasticsearch, or whatever backend we have configured.
To specify the name of the log file, we hook the initialization of Bro (event bro_init()) and we call the create_stream() function of the Log module (where resides the logging framework code):
The file will therefore be called tls_finger.log, and it will reside within the Bro log directories. It will be rotated automatically.
Finally, the new code for the ssl_client_hello() event is as follows:
As we see, we defined the rec variale as the type of record that we declared before (TlsFingerprint::Info), we fill it with the corresponding values values, and finally we call the function Log::write(). Bro already “knows” how to represent data types such as connection or vector of string in the log file or the corresponding backend, separating the fields (source IP address, source port, destination IP address, destination port, etc.) as appropriate.
The final tls_finger.bro would be as follows:
If we put it inside the /usr/local/share/bro/site dir, we can load it from local.bro with the following line:
After the corresponding broctl deploy, the log file will be populated with entries such as:
As we keep getting more info, it’s time to analyze it in search of anomalies ;-) For example, to sort the sets of ciphersuites from least to most used:
Of course, well-done, quality malware will try to hide in plain sight, impersonating the TLS fingerprint of the web browser normally used in the pwned workstation, but perhaps we get to hunt some cool stuff ;-)
Best regards! @pmarinram