JARM: A Solid Fingerprinting Tool for Detecting Malicious Servers
Reading time: 9 minutes
The literature on defensive security unanimously recognizes one fact: every so often, a tool comes out that provides blue teamers with an important advantage over their adversaries.
This ever-elusive quest features essential requirements and commonalities, such as the ability to proactively seek and detect malicious hosts, or the capacity to swiftly respond to targeted network threats. And with a sharp rise in the number of incidents involving some form of malware or command and control (C2) activity resulting in data theft, vendors are in a tight race to gain their customers' trust—by leveraging newer alternatives to legacy solutions amidst shrinking budgets.
In the spirit of searching and identifying malicious servers anywhere they can be found, the folks at Salesforce Engineering have recently released an open-source application called JARM, which pivots on knowledge of Transport Layer Security (TLS) mechanisms to accomplish just that. This blog post will take you on a brief tour of JARM to show you why we consider it a worthy addition to your OSINT tools.
TLS and JARM
TLS is the next evolutionary step from its early SSL (Secure Sockets Layer) ancestor. As its predecessor, the TLS assembly introduces its own version of a handshake process (Figure 1) by which cryptographic primitives, also known as cipher suites, are agreed to between client and server prior to any data exchange. It is precisely this cryptographic agility that gives TLS its multifaceted quality; covering a wide range of applications (e.g., web traffic) and providing critical services such as confidentiality and integrity.

To understand how JARM leverages certain TLS attributes, let's take a closer look at the protocol's initial connection sequence. Immediately following the TCP handshake, the client side sends a ClientHello message containing combinations of cryptographic algorithms supported (and preferred) by the caller, versioning details, extensions, a list of compression methods, and other session parameters in blocks of application data.
In response, the server sends its own ServerHello message when a satisfactory set of algorithms has been confirmed—the packet is also formulated with the server's own version of the connection parameters used by the client. Subsequently, server and client proceed to verify each other's authenticity via digital certificates, after which both parties compute the premaster and master secrets used to derive session keys, any wrapper messages, and the remaining traffic tunneling structures.
The TLS parameters offered in the ClientHello message contain several identifying properties that are directly related to the client application. These features include OS builds, packages, libraries, and even process attributions. This level of granularity is particularly helpful in building fingerprints with a high degree of accuracy that can be leveraged to identify the same application during future sessions. Similarly, TLS servers construct their ServerHello packets based on the ClientHello ones as well as their own subset of built-in identifiers such as:
- Operating system name and version
- Server-side libraries
- Other custom configurations
Once again, this symbiotic relationship between client and server Hello packets dictates the way in which servers uniquely respond to a specific application, providing an excellent opportunity for quick identification via fingerprinting.
And this is where JARM comes in.
What is JARM?
As previously mentioned, JARM is a TLS server fingerprinting application recently released as an open source project by the engineering group at Salesforce. The tool shares some similarities with a previous method of profiling TLS clients using JA3 signatures, released by the same team, which passively examines network traffic collecting fingerprints from clients and servers alike.
The active constituent in JARM comes from its ability to send 10 customized TLS ClientHello packets to a target TLS server in search for a unique set of responses; aggregating and hashing these in such a way as to create what is called a JARM fingerprint. As the documentation shows, these specially crafted messages don't necessarily follow any sort of protocol standard or convention and can include seemingly odd approaches like reordering the list of ciphers to "trick" the server into picking a weaker one, or asking the same server to negotiate TLS 1.3 with 1.2 ciphers.
Behind the scenes, the JARM fingerprint utilizes a fuzzy hashing technique capable of concatenating two consecutive 30-character and 32-character long blocks into one fixed-length crypto hash to produce the final 62-character fingerprint. The first block bears aspects like TLS versions and ciphers chosen by the server in response to each ClientHello packet previously sent by the client, whereas the second block represents a truncated SHA256 hash of the server-side extensions. As a result, individual servers can be quickly identified from a list of pre-compiled JARM fingerprints for a multitude of purposes that include:
- Checking if servers in a group share the same TLS configuration
- Linking individual servers to specific organizations (e.g., Google)
- Identifying default applications or infrastructure
Network defenders and threat hunters will find in JARM a powerful ally in detecting potentially malicious activity threatening their environments. Again, this is particularly the case with popular attack frameworks (e.g., Cobalt Strike) and similar C2 infrastructure that can be notoriously difficult to defend against. What happens here, in effect, is that JARM helps validate whether the underlying Java version is specifically related to the C2 application or to more general use cases of the popular technology.
How is JARM used?
Adding JARM into your mix of security tools and processes can transform the way in which external hosts are tagged and checked for reputation purposes, allowing you to compare any computed fingerprints against your very own address space. All of this is made possible by JARM's ability to fingerprint the software stack linked to each one of those hosts as it would with just about any other TLS-enabled endpoint.
A sample approach to the challenge of programmatically cataloging these malicious hosts is described using Alexa's top one million sites as a backend dataset.
As shown in the table below, JARM fingerprints with an overlap of zero can be an obvious indicator that an outlier has been found- one that can potentially belong to a C2 framework or other suspicious organization.

Taking a closer look at JARM fingerprints such as the one for Cobalt Strike (07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1)
, our own SecurityTrails team embarked on massive Internet sweep over port 443 (HTTPS) looking for hosts matching said parameter. Although the findings (reflected below) included a significant number of false positives, there were obvious "candidates" that had links to hostnames such as "redteam.server" or "cobaltstrike", which stood out as high-quality targets. Similarly, though belonging to the not-so-obvious category, there were sites such as "o365outlook.com" and "adobefax.ml" whose combined set of additional attributes (e.g., hosting provider) eventually landed them in the true positive bucket.

Another scheme that takes advantage of the power of JARM is Shodan's Facet Analysis—a service that includes access to a database of discovered TLS instances and their generated JARM fingerprints that can be leveraged using a web browser or the API, if automation is desired. The tool is an excellent way of conducting a spot check analysis, which is often the case when embarking on a quick investigation.
But enough of theory! Let's find out how to install JARM.
Installing JARM
Our good friends at Salesforce have provided us with a Github repository from which to download JARM in two different formats, jarm.py and jarm.sh. Getting JARM onto your machine is as easy as cloning the repository using:
git clone https://github.com/salesforce/jarm.git
or by downloading the entire directory using the "Download ZIP" option.
To use jarm.py, you must provide the IP or domain name of the target TLS server whose JARM fingerprint you wish to calculate. There are a handful of optional arguments that can provide additional functionality. These include:
[-h] :: shows the help menu
[-i INPUT] :: accepts a list of IP addresses or domains to scan, or specific ports with comma separation (e.g., 8.8.4.4,853)
[-o] :: outputs or appends results to a CSV file
[-p PORT] :: accepts a custom port to scan (defaults to 443)
[-v] :: displays the JARM results before being hashed (verbose mode)
[-V] :: prints out the tool's version number
Additionally, jarm.sh is a Bash wrapper script that can automate JARM scans at speed using an external list of IPs, sending the results to a file of your choosing. Finally, as the documentation explains, it is important to note that JARM is not to be considered to possess all (if any) of the cryptographic qualities associated with a true hashing algorithm. Its sole purpose is simply to provide high-performance fingerprinting with enough flexibility for contextualized information and easy consumption.
Testing and results
JARM's argument list is short and straightforward—passing the -h argument to jarm.py will provide us with all available options:

We proceeded to put JARM to the test by scanning google.com using the Python version of the tool. The verbose result is shown below:

The above screenshot gives us an overview of what took place. After attempting to resolve the given domain or server to its corresponding IP address, jarm.py prints it, along with the host's JARM fingerprint and the handshakes prior to hashing.
Next, let's use the -p option to specify a custom port and watch the results:

This time around, the domain/port combination did not successfully resolve to an IP address as the output shows. In addition, the JARM fingerprinting containing all zeros constitutes an empty hash.
Finally, let's examine the use of jarm.sh by running it against Alexa's top 500 sites. As per the documentation, the two required arguments are 1) a file (alexa500.txt) containing the list of IPs or domains to be checked and 2) the name of the output file that will hold any resulting JARM fingerprints.
bash jarm.sh alexa500.txt results.csv
As shown below, the resulting csv file contains the domain name, resolved IP address (or fail message) and the corresponding JARM fingerprint of each site included in the input file—individual handshakes have been omitted for visibility purposes.

Summary
JARM offers a unique perspective into the detection, categorization, and potential correlation of TLS activity tracing back to malicious servers or even entire campaigns associated with the use of some of the most popular attack frameworks. These closing thoughts, however, come with a word of advice: never use JARM as a sole indicator or proof of nefarious origin. Always complement your analysis with the proper amount of metadata and other techniques to adequately classify your traffic and build your case.
Ultimately, we invite you to give JARM a try beyond the realm of detection and response for example, in areas that involve asset classification or simple application cataloging. We guarantee you won’t be disappointed.
