This commit is contained in:
Patrick Kelley 2025-05-07 14:11:26 -04:00
commit c3f8aeaa1f
19 changed files with 1510 additions and 0 deletions

2
CODEOWNERS Normal file
View File

@ -0,0 +1,2 @@
# Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing.
#ECCN:Open Source

5
CONTRIBUTING-ARCHIVED.md Normal file
View File

@ -0,0 +1,5 @@
# ARCHIVED
This project is `Archived` and is no longer actively maintained;
We are not accepting contributions or Pull Requests.

12
LICENSE.txt Normal file
View File

@ -0,0 +1,12 @@
Copyright (c) 2017, Salesforce.com, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

140
README.md Normal file
View File

@ -0,0 +1,140 @@
# JA3 - A method for profiling SSL/TLS Clients
JA3 was invented at Salesforce in 2017. However, the project is no longer being actively maintained by Salesforce. Its original creator, John Althouse, maintains the latest in TLS client fingerprinting technology at [FoxIO-LLC](https://github.com/FoxIO-LLC/ja4).
JA3 is a method for creating SSL/TLS client fingerprints that should be easy to produce on any platform and can be easily shared for threat intelligence.
Before using, please read this blog post: [TLS Fingerprinting with JA3 and JA3S](https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967)
This repo includes JA3 and JA3S scripts for [Zeek](https://www.zeekurity.org/) and [Python](https://www.python.org/). You can find a nice Rust implementation of the JA3 algorithm [here](https://github.com/jabedude/ja3-rs)
JA3 support has also been added to:
[Moloch](http://molo.ch/)
[Trisul NSM](https://github.com/trisulnsm/trisul-scripts/tree/master/lua/frontend_scripts/reassembly/ja3)
[NGiNX](https://github.com/fooinha/nginx-ssl-ja3)
[BFE](https://github.com/bfenetworks/bfe)
[MISP](https://github.com/MISP)
[Darktrace](https://www.darktrace.com/)
[Suricata](https://suricata-ids.org/tag/ja3/)
[Elastic.co Packetbeat](https://www.elastic.co/guide/en/beats/packetbeat/master/exported-fields-tls.html)
[Splunk](https://www.splunk.com/blog/2017/12/18/configuring-ja3-with-bro-for-splunk.html)
[MantisNet](https://www.mantisnet.com/)
[ICEBRG](http://icebrg.io/)
[Redsocks](https://www.redsocks.eu/)
[NetWitness](https://github.com/timetology/nw/tree/master/parsers/ssl_ja3)
[ExtraHop](https://www.extrahop.com/)
[Vectra Cognito Platform](https://vectra.ai/)
[Corvil](https://www.corvil.com/blog/2018/environmentally-conscious-understanding-your-network)
[Java](https://github.com/lafaspot/ja3_4java)
[Go](https://github.com/open-ch/ja3)
[Security Onion](https://securityonion.net/)
[AIEngine](https://bitbucket.org/camp0/aiengine)
[RockNSM](https://rocknsm.io/)
[Corelight](https://www.corelight.com/products/software)
[VirusTotal](https://blog.virustotal.com/2019/10/in-house-dynamic-analysis-virustotal-jujubox.html#ja3)
[SELKS](https://www.stamus-networks.com/selks-6)
[Stamus Networks](https://www.stamus-networks.com/)
[IBM QRadar Network Insights (QNI)](https://community.ibm.com/community/user/security/blogs/tom-obremski1/2020/10/23/qni-ja3-ja3s-for-network-encryption)
[InQuest](https://inquest.net)
[Cloudflare](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/)
[AWS Network Firewall](https://docs.aws.amazon.com/network-firewall/latest/developerguide/aws-managed-rule-groups-threat-signature.html)
[Azure Firewall](https://learn.microsoft.com/en-us/azure/firewall/idps-signature-categories)
[AWS WAF](https://aws.amazon.com/about-aws/whats-new/2023/09/aws-waf-ja3-fingerprint-match/)
[Google Cloud](https://cloud.google.com/load-balancing/docs/https/custom-headers-global)
and more...
## Examples
JA3 fingerprint for the standard Tor client:
```
e7d705a3286e19ea42f587b344ee6865
```
JA3 fingerprint for the Trickbot malware:
```
6734f37431670b3ab4292b8f60f29984
```
JA3 fingerprint for the Emotet malware:
```
4d7a28d6f2263ed61de88ca66eb011e3
```
While destination IPs, Ports, and X509 certificates change, the JA3 fingerprint remains constant for the client application in these examples across our sample set. Please be aware that these are just examples, not indicative of all versions ever.
## Lists
Example lists of known JA3's and their associated applications can be found [here](https://github.com/salesforce/ja3/tree/master/lists).
A more up-to-date crowd sourced method of gathering and reporting on JA3s can be found at [ja3er.com](https://ja3er.com).
## How it works
TLS and its predecessor, SSL, I will refer to both as “SSL” for simplicity, are used to encrypt communication for both common applications, to keep your data secure, and malware, so it can hide in the noise. To initiate a SSL session, a client will send a SSL Client Hello packet following the TCP 3-way handshake. This packet and the way in which it is generated is dependant on packages and methods used when building the client application. The server, if accepting SSL connections, will respond with a SSL Server Hello packet that is formulated based on server-side libraries and configurations as well as details in the Client Hello. Because SSL negotiations are transmitted in the clear, its possible to fingerprint and identify client applications using the details in the SSL Client Hello packet.
JA3 is a method of TLS fingerprinting that was inspired by the [research](https://blog.squarelemon.com/tls-fingerprinting/) and works of [Lee Brotherston](https://twitter.com/synackpse) and his TLS Fingerprinting tool: [FingerprinTLS](https://github.com/LeeBrotherston/tls-fingerprinting/tree/master/fingerprintls).
JA3 gathers the decimal values of the bytes for the following fields in the Client Hello packet; SSL Version, Accepted Ciphers, List of Extensions, Elliptic Curves, and Elliptic Curve Formats. It then concatenates those values together in order, using a "," to delimit each field and a "-" to delimit each value in each field.
The field order is as follows:
```
SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat
```
Example:
```
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0
```
If there are no SSL Extensions in the Client Hello, the fields are left empty.
Example:
```
769,4-5-10-9-100-98-3-6-19-18-99,,,
```
These strings are then MD5 hashed to produce an easily consumable and shareable 32 character fingerprint. This is the JA3 SSL Client Fingerprint.
```
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0 --> ada70206e40642a3e4461f35503241d5
769,4-5-10-9-100-98-3-6-19-18-99,,, --> de350869b8c85de67a350c8d186f11e6
```
We also needed to introduce some code to account for Googles GREASE (Generate Random Extensions And Sustain Extensibility) as described [here](https://tools.ietf.org/html/draft-davidben-tls-grease-01). Google uses this as a mechanism to prevent extensibility failures in the TLS ecosystem. JA3 ignores these values completely to ensure that programs utilizing GREASE can still be identified with a single JA3 hash.
## JA3S
JA3S is JA3 for the Server side of the SSL/TLS communication and fingerprints how servers respond to particular clients.
JA3S uses the following field order:
```
SSLVersion,Cipher,SSLExtension
```
With JA3S it is possible to fingerprint the entire cryptographic negotiation between client and it's server by combining JA3 + JA3S. That is because servers will respond to different clients differently but will always respond to the same client the same.
For the Trickbot example:
```
JA3 = 6734f37431670b3ab4292b8f60f29984 ( Fingerprint of Trickbot )
JA3S = 623de93db17d313345d7ea481e7443cf ( Fingerprint of Command and Control Server Response )
```
For the Emotet example:
```
JA3 = 4d7a28d6f2263ed61de88ca66eb011e3 ( Fingerprint of Emotet )
JA3S = 80b3a14bccc8598a1f3bbe83e71f735f ( Fingerprint of Command and Control Server Response )
```
In these malware examples, the command and control server always responds to the malware client in exactly the same way, it does not deviate. So even though the traffic is encrypted and one may not know the command and control server's IPs or domains as they are constantly changing, we can still identify, with reasonable confidence, the malicious communication by fingerprinting the TLS negotiation between client and server. Again, please be aware that these are examples, not indicative of all versions ever, and are intended to illustrate what is possible.
## Intriguing Possibilities
JA3 is a much more effective way to detect malicious activity over SSL than IP or domain based IOCs. Since JA3 detects the client application, it doesnt matter if malware uses DGA (Domain Generation Algorithms), or different IPs for each C2 host, or even if the malware uses Twitter for C2, JA3 can detect the malware itself based on how it communicates rather than what it communicates to.
JA3 is also an excellent detection mechanism in locked-down environments where only a few specific applications are allowed to be installed. In these types of environments one could build a whitelist of expected applications and then alert on any other JA3 hits.
For more details on what you can see and do with JA3 and JA3S, please see this DerbyCon 2018 talk: https://www.youtube.com/watch?v=NI0Lmp0K1zc or this [blog post.](https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967)
Please contact me on twitter @4A4133 or over email, let me know what you find and if you have any feature requests.
___
### JA3 Created by
[John Althouse](https://www.linkedin.com/in/johnalthouse/)
[Jeff Atkinson](https://www.linkedin.com/in/annh/)
[Josh Atkins](https://www.linkedin.com/in/joshratkins/)
Please send questions and comments to **[John Althouse](https://twitter.com/4A4133)**.

13
lists/README.md Normal file
View File

@ -0,0 +1,13 @@
## Lists
[osx-nix-ja3.csv](https://raw.githubusercontent.com/salesforce/ja3/master/lists/osx-nix-ja3.csv) is a list of JA3 hashes to application name(s) for OSX and Linux.
This list was generated using an automated process with some manual checking. As applications can vary per environment, please use this list as a best-guess and as an example of JA3's capabilities. Any assistance in fine tuning is appreciated. For example, some of the "Used by many programs" JA3s can likely be linked to specific APIs.
We're working on a Windows list and getting the automation working there. If you would like to contribute, please make a pull request or contact [John Althouse](mailto:jalthouse@salesforce.com).
## 3rd Party Lists
[JA3er Crowd Sourced DB](https://ja3er.com) JA3er uses a crowd sourced method of building and maintaining an up-to-date database of JA3s and their associated applications.
[Lee Brotherson's DB](https://github.com/trisulnsm/trisul-scripts/tree/master/lua/frontend_scripts/reassembly/ja3/prints)
Trisul created a script to convert Lee Brotherston's database of 400+ fingerprints to JA3.

161
lists/osx-nix-ja3.csv Normal file
View File

@ -0,0 +1,161 @@
"Copyright (c) 2017 salesforce.com inc.
All rights reserved.
Licensed under the BSD 3-Clause license.
For full license text see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause",
61d50e7771aee7f2f4b89a7200b4d45e,"AcroCEF"
49a6cf42956937669a01438f26e7c609,"AIM"
561145462cfc7de1d6a97e93d3264786,"Airmail 3"
f6fd83a21f9f3c5f9ff7b5c63bbc179d,"Alation Compose"
6003b52942a2e1e1ea72d802d153ec08,"Amazon Music"
eb149984fc9c44d85ed7f12c90d818be,"Amazon Music,Dreamweaver,Spotify"
8e3f1bf87bc652a20de63bfd4952b16a,"AnypointStudio"
5507277945374659a5b4572e1b6d9b9f,"apple.geod"
f753495f2eab5155c61b760c838018f8,"apple.geod"
ba40fea2b2638908a3b3b482ac78d729,"apple.geod,parsecd,apple.photomoments"
474e73aea21d1e0910f25c3e6c178535,"apple.WebKit.Networking"
eeeb5e7485f5e10cbc39db4cfb69b264,"apple.WebKit.Networking"
d4693422c5ce1565377aca25940ad80c,"apple.WebKit.Networking,CalendarAgent,Go for Gmail"
63de2b6188d5694e79b678f585b13264,"apple.WebKit.Networking,Chatter,FieldServiceApp,socialstudio"
3e4e87dda5a3162306609b7e330441d2,"apple.WebKit.Networking,itunesstored"
7b343af1092863fdd822d6f10645abfb,"apple.WebKit.Networking,itunesstored"
a312f9162a08eeedf7feb7a13cd7e9bb,"apple.WebKit.Networking,Spotify,WhatsApp,Skype,iTunes"
c5c11e6105c56fd29cc72c3ac7a2b78b,"AT&T Connect"
fa030dbcb2e3c7141d3c2803780ee8db,"Battle.net,Dropbox"
0ef9ca1c10d3f186f5786e1ef3461a46,"bitgo,ShapeShift"
cdec81515ccc75a5aa41eb3db22226e6,"BlueJeans,CEPHtmlEngine"
83e04bc58d402f9633983cbf22724b02,"Charles,Google Play Music Desktop Player,Postman,Slack,and other desktop programs"
424008725394c634a4616b8b1f2828a5,"Charles,java,eclipse"
be9f1360cf52dc1f61ae025252f192a3,"Chromium"
def8761e4bcaaf91d99801a22ac6f6d4,"Chromium"
fc5cb0985a5f5e295163cc8ffff8a6e1,"Chromium"
e7d46c98b078477c4324031e0d3b22f5,"Cisco AnyConnect Secure Mobility Client"
ed36017db541879619c399c95e22067d,"Cisco AnyConnect Secure Mobility Client"
5ee1a653fb824db7182714897fd3b5df,"Citrix Viewer"
a9d17f74e55dd53fcf7c234f8a240919,"Covenant Eyes"
c882d9444412c00e71b643f3f54145ff,"Creative Cloud"
bc0608d33dc64506b42f7f5f87958f37,"cscan"
ccaa60f6ccc701bde536ef409be3cf63,"curl no-SNI"
fe048fe8faf797796e278f2b4f1e9c24,"curl SNI"
4fcd1770545298cc119865aeba81daba,"Deezer"
4c40bf8baa7c301c5dba8a20bc4119e2,"Dynalist,Postman,Google Chrome,Franz,GOG Galaxy"
0411bbb5ff27ad46e1874a7a8beedacb,"eclipse"
4990c9da08f44a01ecd7ddc3837caf25,"eclipse"
fa106fe5beec443af7e211ef8902e7e0,"eclipse"
d74778f454e2b047e030b291b94dd698,"eclipse,java"
187dfde7edc8ceddccd3deeccc21daeb,"eclipse,java,studio,STS"
8c5a50f1e833ed581e9cfc690814719a,"eclipse,JavaApplicationStub,idea"
1fbe5382f9d8430fe921df747c46d95f,"FieldServiceApp,socialstudio"
0a81538cf247c104edb677bdb8902ed5,"firefox"
0b6592fd91d4843c823b75e49b43838d,"firefox"
0ffee3ba8e615ad22535e7f771690a28,"firefox"
1c15aca4a38bad90f9c40678f6aface9,"firefox"
5163bc7c08f57077bc652ec370459c2f,"firefox"
a88f1426c4603f2a8cd8bb41e875cb75,"firefox"
b03910cc6de801d2fcfa0c3b9f397df4,"firefox"
bfcc1a3891601edb4f137ab7ab25b840,"firefox"
ce694315cbb81ce95e6ae4ae8cbafde6,"firefox"
f15797a734d0b4f171a86fd35c9a5e43,"firefox"
07b4162d4db57554961824a21c4a0fde,"firefox,thunderbird"
61d0d709fe7ac199ef4b2c52bc8cef75,"firefox,thunderbird"
8498fe4268764dbf926a38283e9d3d8f,"Franz,Google Chrome,Kiwi,Spotify,nwjs,Slack"
900c1fa84b4ea86537e1d148ee16eae8,"Fuze"
107144b88827da5da9ed42d8776ccdc5,"geod"
c46941d4de99445aef6b497679474cf4,"geod"
002205d0f96c37c5e660b9f041363c11,"Google Chrome"
073eede15b2a5a0302d823ecbd5ad15b,"Google Chrome"
0b61c673ee71fe9ee725bd687c455809,"Google Chrome"
6cd1b944f5885e2cfbe98a840b75eeb8,"Google Chrome"
94c485bca29d5392be53f2b8cf7f4304,"Google Chrome"
b4f4e6164f938870486578536fc1ffce,"Google Chrome"
b8f81673c0e1d29908346f3bab892b9b,"Google Chrome"
baaac9b6bf25ad098115c71c59d29e51,"Google Chrome"
bc6c386f480ee97b9d9e52d472b772d8,"Google Chrome"
da949afd9bd6df820730f8f171584a71,"Google Chrome"
f58966d34ff9488a83797b55c804724d,"Google Chrome"
fd6314b03413399e4f23d1524d206692,"Google Chrome"
0e46737668fe75092919ee047a0b5945,"Google Chrome Helper"
39fa85654105398ee7ef6a3a1c81d685,"Google Chrome Helper"
4ba7b7022f5f5e1e500bb19199d8b1a4,"Google Chrome Helper"
5498cef2cca704eb01cf2041cc1089c1,"Google Chrome,Slack"
d27fb8deca6e3b9739db3fda2b229fe3,"Google Drive File Stream"
ae340571b4fd0755c4a0821b18d8fa93,"Google Earth"
f059212ce3de94b1e8253a7522cb1b44,"Google Photos Backup"
fd10cc8cce9493a966c57249e074755f,"gramblr"
3e860202fc555b939e83e7a7ab518c38,"hola_svc"
56ac3a0bef0824c49e4b569941937088,"hola_svc"
5c1c89f930122bccc7a97d52f73bea2c,"hola_svc"
77310efe11f1943306ee317cf02150b7,"hola_svc"
8bd59c4b7f3193db80fd64318429bcec,"hola_svc"
d1f9f9b224387d2597f02095fcec96d7,"hola_svc"
ff1040ba1e3d235855ef0d7cd9237fdc,"hola_svc"
5af143afdbf58ec11ab3b3d53dd4e5e3,"IDSyncDaemon"
d06acbe8ac31e753f40600a9d6717cba,"Inbox OSX"
093081b45872912be9a1f2a8163fe041,"java"
2080bf56cb87e64303e27fcd781e7efd,"java"
225a24b45f0f1adbc2e245d4624c6e08,"java"
3afe1fb5976d0999abe833b14b7d6485,"java"
3b844830bfbb12eb5d2f8dc281d349a9,"java"
51a7ad14509fd614c7bb3a50c4982b8c,"java"
550628650380ff418de25d3d890e836e,"java"
5b270b309ad8c6478586a15dece20a88,"java"
5d7abe53ae15b4272a34f10431e06bf3,"java"
7c7a68b96d2aab15d678497a12119f4f,"java"
88afa0dea1608e28f50acbad32d7f195,"java"
8ce6933b8c12ce931ca238e9420cc5dd,"java"
a61299f9b501adcf680b9275d79d4ac6,"java"
a9fead344bf3ac09f62df3cd9b22c268,"java"
4056657a50a8a4e5cfac40ba48becfa2,"java,eclipse"
f22bdd57e3a52de86cda40da2d84e83b,"java,eclipse,Cyberduck"
028563cffc7a3a2e32090aee0294d636,"java,eclipse,STS"
5f9b53f0d39dc9d940a3b5568fe5f0bb,"java,JavaApplicationStub"
2db6873021f2a95daa7de0d93a1d1bf2,"java,studio,eclipse"
c376061f96329e1020865a1dc726927d,"JavaApplicationStub"
e516ad69a423f8e0407307aa7bfd6344,"Kindle,stack,nextcloud"
3959d0a1344896e9fb5c0564ca0a2956,"LeagueClientUx"
0fe51fa93812c2ebb50a655222a57bf2,"LINE Messaging"
2e094913d88f0ad8dc69447cb7d2ce65,"LINE Messaging"
193349d34561d1d5d1a270172eb2d97e,"LogMeIn Client"
d732ca39155f38942f90e9fc2b0f97f7,"Maxthon"
c9dbeed362a32f9a50a26f4d9b32bbd8,"Messenger,Jumpshare"
6acb250ada693067812c3335705dae79,"mono-sgen,Syncplicity,Axure RP 8,Amazon Drive"
3ee4aaac7147ff2b80ada31686db660c,"node-webkit,Kindle"
641df9d6dbe7fdb74f70c8ad93def8cc,"node.js"
9811c1bb9f0f6835d5c13a831cca4173,"node.js"
106ecbd3d14b4dc6e413494263720afe,"node.js,Postman,WhatsApp"
49de9b1c7e60bd3b8e1d4f7a49ba362e,"nwjs,Chromium"
38cbe70b308f42da7c9980c0e1c89656,"p4v,owncloud"
62448833d8230241227c03b7d441e31b,"parsecd,apple.geod,apple.photomoments,photoanalysisd,FreedomProxy"
e846898acc767ebeb2b4388e58a968d4,"postbox-bin"
a7823092705a5e91ce2b7f561b6e5b98,"Qsync Client"
c048d9f26a79e11ca7276499ef24daf3,"RescueTime,Plantronics Hub"
d219efd07cbb8fbe547e6a5335843f0f,"ruby"
c36fb08942cf19508c08d96af22d4ffc,"Safari"
844166382cc98d98595e6778c470f5d5,"Salesforce Files"
49a341a21f4fd4ac63b027ff2b1a331f,"Skype"
a5aa6e939e4770e3b8ac38ce414fd0d5,"Slack"
116ffc8889873efad60457cd55eaf543,"Spark"
8db4b0f8e9dd8f2fff38ee7c5a1e4496,"SpotlightNetHelper,Safari"
39cf5b7a13a764494de562add874f016,"Steam OSX"
2d3854d1cbcdceece83eabd85bdcc056,"Tableau"
a585c632a2b49be1256881fb0c16c864,"Tableau"
cd7c06b9459c9cfd4af2dba5696ea930,"Tableau"
df65746370dcabc9b4f370c6e14a8156,"True Key"
84071ea96fc8a60c55fc8a405e214c0f,"Used by many desktop apps,Quip,Spotify,GitHub Desktop"
40fd0a5e81ebdcf0ec82a4710a12dec1,"Used by many programs on OSX,apple.WebKit.Networking"
618ee2509ef52bf0b8216e1564eea909,"Used by many programs on OSX,apple.WebKit.Networking"
799135475da362592a4be9199d258726,"Used by many programs on OSX,apple.WebKit.Networking"
7b530a25af9016a9d12de5abc54d9e74,"Used by many programs on OSX,apple.WebKit.Networking"
7e72698146290dd68239f788a452e7d8,"Used by many programs on OSX,apple.WebKit.Networking"
a9aecaa66ad9c6cfe1c361da31768506,"Used by many programs on OSX,apple.WebKit.Networking"
c05de18b01a054f2f6900ffe96b3da7a,"Used by many programs on OSX,apple.WebKit.Networking"
c07cb55f88702033a8f52c046d23e0b2,"Used by many programs on OSX,apple.WebKit.Networking"
e4d448cdfe06dc1243c1eb026c74ac9a,"Used by many programs on OSX,apple.WebKit.Networking"
f1c5cf087b959cec31bd6285407f689a,"Used by many programs on OSX,apple.WebKit.Networking"
488b6b601cb141b062d4da7f524b4b22,"Used by many programs,Python,PHP,Git,dotnet,Adobe"
f28d34ce9e732f644de2350027d74c3f,"Used by many programs,Quip,Aura,Spotify,Chatty"
190dfb280fe3b541acc6a2e5f00690e6,"Used by many programs,Quip,Spotify,Dropbox,GitHub Desktop,etc"
20dd18bdd3209ea718989030a6f93364,"Used by many programs,Slack,Postman,Spotify,Google Chrome"
e0224fc1c33658f2d3d963bfb0a76a85,"Viber"
01319090aea981dde6fc8d6ae71ead54,"vpnkit"
84607748f3887541dd60fe974a042c71,"wineserver"
c2b4710c6888a5d47befe865c8e6fb19,"ZwiftApp"
1 Copyright (c) 2017 salesforce.com inc. All rights reserved. Licensed under the BSD 3-Clause license. For full license text see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
2 61d50e7771aee7f2f4b89a7200b4d45e AcroCEF
3 49a6cf42956937669a01438f26e7c609 AIM
4 561145462cfc7de1d6a97e93d3264786 Airmail 3
5 f6fd83a21f9f3c5f9ff7b5c63bbc179d Alation Compose
6 6003b52942a2e1e1ea72d802d153ec08 Amazon Music
7 eb149984fc9c44d85ed7f12c90d818be Amazon Music,Dreamweaver,Spotify
8 8e3f1bf87bc652a20de63bfd4952b16a AnypointStudio
9 5507277945374659a5b4572e1b6d9b9f apple.geod
10 f753495f2eab5155c61b760c838018f8 apple.geod
11 ba40fea2b2638908a3b3b482ac78d729 apple.geod,parsecd,apple.photomoments
12 474e73aea21d1e0910f25c3e6c178535 apple.WebKit.Networking
13 eeeb5e7485f5e10cbc39db4cfb69b264 apple.WebKit.Networking
14 d4693422c5ce1565377aca25940ad80c apple.WebKit.Networking,CalendarAgent,Go for Gmail
15 63de2b6188d5694e79b678f585b13264 apple.WebKit.Networking,Chatter,FieldServiceApp,socialstudio
16 3e4e87dda5a3162306609b7e330441d2 apple.WebKit.Networking,itunesstored
17 7b343af1092863fdd822d6f10645abfb apple.WebKit.Networking,itunesstored
18 a312f9162a08eeedf7feb7a13cd7e9bb apple.WebKit.Networking,Spotify,WhatsApp,Skype,iTunes
19 c5c11e6105c56fd29cc72c3ac7a2b78b AT&T Connect
20 fa030dbcb2e3c7141d3c2803780ee8db Battle.net,Dropbox
21 0ef9ca1c10d3f186f5786e1ef3461a46 bitgo,ShapeShift
22 cdec81515ccc75a5aa41eb3db22226e6 BlueJeans,CEPHtmlEngine
23 83e04bc58d402f9633983cbf22724b02 Charles,Google Play Music Desktop Player,Postman,Slack,and other desktop programs
24 424008725394c634a4616b8b1f2828a5 Charles,java,eclipse
25 be9f1360cf52dc1f61ae025252f192a3 Chromium
26 def8761e4bcaaf91d99801a22ac6f6d4 Chromium
27 fc5cb0985a5f5e295163cc8ffff8a6e1 Chromium
28 e7d46c98b078477c4324031e0d3b22f5 Cisco AnyConnect Secure Mobility Client
29 ed36017db541879619c399c95e22067d Cisco AnyConnect Secure Mobility Client
30 5ee1a653fb824db7182714897fd3b5df Citrix Viewer
31 a9d17f74e55dd53fcf7c234f8a240919 Covenant Eyes
32 c882d9444412c00e71b643f3f54145ff Creative Cloud
33 bc0608d33dc64506b42f7f5f87958f37 cscan
34 ccaa60f6ccc701bde536ef409be3cf63 curl no-SNI
35 fe048fe8faf797796e278f2b4f1e9c24 curl SNI
36 4fcd1770545298cc119865aeba81daba Deezer
37 4c40bf8baa7c301c5dba8a20bc4119e2 Dynalist,Postman,Google Chrome,Franz,GOG Galaxy
38 0411bbb5ff27ad46e1874a7a8beedacb eclipse
39 4990c9da08f44a01ecd7ddc3837caf25 eclipse
40 fa106fe5beec443af7e211ef8902e7e0 eclipse
41 d74778f454e2b047e030b291b94dd698 eclipse,java
42 187dfde7edc8ceddccd3deeccc21daeb eclipse,java,studio,STS
43 8c5a50f1e833ed581e9cfc690814719a eclipse,JavaApplicationStub,idea
44 1fbe5382f9d8430fe921df747c46d95f FieldServiceApp,socialstudio
45 0a81538cf247c104edb677bdb8902ed5 firefox
46 0b6592fd91d4843c823b75e49b43838d firefox
47 0ffee3ba8e615ad22535e7f771690a28 firefox
48 1c15aca4a38bad90f9c40678f6aface9 firefox
49 5163bc7c08f57077bc652ec370459c2f firefox
50 a88f1426c4603f2a8cd8bb41e875cb75 firefox
51 b03910cc6de801d2fcfa0c3b9f397df4 firefox
52 bfcc1a3891601edb4f137ab7ab25b840 firefox
53 ce694315cbb81ce95e6ae4ae8cbafde6 firefox
54 f15797a734d0b4f171a86fd35c9a5e43 firefox
55 07b4162d4db57554961824a21c4a0fde firefox,thunderbird
56 61d0d709fe7ac199ef4b2c52bc8cef75 firefox,thunderbird
57 8498fe4268764dbf926a38283e9d3d8f Franz,Google Chrome,Kiwi,Spotify,nwjs,Slack
58 900c1fa84b4ea86537e1d148ee16eae8 Fuze
59 107144b88827da5da9ed42d8776ccdc5 geod
60 c46941d4de99445aef6b497679474cf4 geod
61 002205d0f96c37c5e660b9f041363c11 Google Chrome
62 073eede15b2a5a0302d823ecbd5ad15b Google Chrome
63 0b61c673ee71fe9ee725bd687c455809 Google Chrome
64 6cd1b944f5885e2cfbe98a840b75eeb8 Google Chrome
65 94c485bca29d5392be53f2b8cf7f4304 Google Chrome
66 b4f4e6164f938870486578536fc1ffce Google Chrome
67 b8f81673c0e1d29908346f3bab892b9b Google Chrome
68 baaac9b6bf25ad098115c71c59d29e51 Google Chrome
69 bc6c386f480ee97b9d9e52d472b772d8 Google Chrome
70 da949afd9bd6df820730f8f171584a71 Google Chrome
71 f58966d34ff9488a83797b55c804724d Google Chrome
72 fd6314b03413399e4f23d1524d206692 Google Chrome
73 0e46737668fe75092919ee047a0b5945 Google Chrome Helper
74 39fa85654105398ee7ef6a3a1c81d685 Google Chrome Helper
75 4ba7b7022f5f5e1e500bb19199d8b1a4 Google Chrome Helper
76 5498cef2cca704eb01cf2041cc1089c1 Google Chrome,Slack
77 d27fb8deca6e3b9739db3fda2b229fe3 Google Drive File Stream
78 ae340571b4fd0755c4a0821b18d8fa93 Google Earth
79 f059212ce3de94b1e8253a7522cb1b44 Google Photos Backup
80 fd10cc8cce9493a966c57249e074755f gramblr
81 3e860202fc555b939e83e7a7ab518c38 hola_svc
82 56ac3a0bef0824c49e4b569941937088 hola_svc
83 5c1c89f930122bccc7a97d52f73bea2c hola_svc
84 77310efe11f1943306ee317cf02150b7 hola_svc
85 8bd59c4b7f3193db80fd64318429bcec hola_svc
86 d1f9f9b224387d2597f02095fcec96d7 hola_svc
87 ff1040ba1e3d235855ef0d7cd9237fdc hola_svc
88 5af143afdbf58ec11ab3b3d53dd4e5e3 IDSyncDaemon
89 d06acbe8ac31e753f40600a9d6717cba Inbox OSX
90 093081b45872912be9a1f2a8163fe041 java
91 2080bf56cb87e64303e27fcd781e7efd java
92 225a24b45f0f1adbc2e245d4624c6e08 java
93 3afe1fb5976d0999abe833b14b7d6485 java
94 3b844830bfbb12eb5d2f8dc281d349a9 java
95 51a7ad14509fd614c7bb3a50c4982b8c java
96 550628650380ff418de25d3d890e836e java
97 5b270b309ad8c6478586a15dece20a88 java
98 5d7abe53ae15b4272a34f10431e06bf3 java
99 7c7a68b96d2aab15d678497a12119f4f java
100 88afa0dea1608e28f50acbad32d7f195 java
101 8ce6933b8c12ce931ca238e9420cc5dd java
102 a61299f9b501adcf680b9275d79d4ac6 java
103 a9fead344bf3ac09f62df3cd9b22c268 java
104 4056657a50a8a4e5cfac40ba48becfa2 java,eclipse
105 f22bdd57e3a52de86cda40da2d84e83b java,eclipse,Cyberduck
106 028563cffc7a3a2e32090aee0294d636 java,eclipse,STS
107 5f9b53f0d39dc9d940a3b5568fe5f0bb java,JavaApplicationStub
108 2db6873021f2a95daa7de0d93a1d1bf2 java,studio,eclipse
109 c376061f96329e1020865a1dc726927d JavaApplicationStub
110 e516ad69a423f8e0407307aa7bfd6344 Kindle,stack,nextcloud
111 3959d0a1344896e9fb5c0564ca0a2956 LeagueClientUx
112 0fe51fa93812c2ebb50a655222a57bf2 LINE Messaging
113 2e094913d88f0ad8dc69447cb7d2ce65 LINE Messaging
114 193349d34561d1d5d1a270172eb2d97e LogMeIn Client
115 d732ca39155f38942f90e9fc2b0f97f7 Maxthon
116 c9dbeed362a32f9a50a26f4d9b32bbd8 Messenger,Jumpshare
117 6acb250ada693067812c3335705dae79 mono-sgen,Syncplicity,Axure RP 8,Amazon Drive
118 3ee4aaac7147ff2b80ada31686db660c node-webkit,Kindle
119 641df9d6dbe7fdb74f70c8ad93def8cc node.js
120 9811c1bb9f0f6835d5c13a831cca4173 node.js
121 106ecbd3d14b4dc6e413494263720afe node.js,Postman,WhatsApp
122 49de9b1c7e60bd3b8e1d4f7a49ba362e nwjs,Chromium
123 38cbe70b308f42da7c9980c0e1c89656 p4v,owncloud
124 62448833d8230241227c03b7d441e31b parsecd,apple.geod,apple.photomoments,photoanalysisd,FreedomProxy
125 e846898acc767ebeb2b4388e58a968d4 postbox-bin
126 a7823092705a5e91ce2b7f561b6e5b98 Qsync Client
127 c048d9f26a79e11ca7276499ef24daf3 RescueTime,Plantronics Hub
128 d219efd07cbb8fbe547e6a5335843f0f ruby
129 c36fb08942cf19508c08d96af22d4ffc Safari
130 844166382cc98d98595e6778c470f5d5 Salesforce Files
131 49a341a21f4fd4ac63b027ff2b1a331f Skype
132 a5aa6e939e4770e3b8ac38ce414fd0d5 Slack
133 116ffc8889873efad60457cd55eaf543 Spark
134 8db4b0f8e9dd8f2fff38ee7c5a1e4496 SpotlightNetHelper,Safari
135 39cf5b7a13a764494de562add874f016 Steam OSX
136 2d3854d1cbcdceece83eabd85bdcc056 Tableau
137 a585c632a2b49be1256881fb0c16c864 Tableau
138 cd7c06b9459c9cfd4af2dba5696ea930 Tableau
139 df65746370dcabc9b4f370c6e14a8156 True Key
140 84071ea96fc8a60c55fc8a405e214c0f Used by many desktop apps,Quip,Spotify,GitHub Desktop
141 40fd0a5e81ebdcf0ec82a4710a12dec1 Used by many programs on OSX,apple.WebKit.Networking
142 618ee2509ef52bf0b8216e1564eea909 Used by many programs on OSX,apple.WebKit.Networking
143 799135475da362592a4be9199d258726 Used by many programs on OSX,apple.WebKit.Networking
144 7b530a25af9016a9d12de5abc54d9e74 Used by many programs on OSX,apple.WebKit.Networking
145 7e72698146290dd68239f788a452e7d8 Used by many programs on OSX,apple.WebKit.Networking
146 a9aecaa66ad9c6cfe1c361da31768506 Used by many programs on OSX,apple.WebKit.Networking
147 c05de18b01a054f2f6900ffe96b3da7a Used by many programs on OSX,apple.WebKit.Networking
148 c07cb55f88702033a8f52c046d23e0b2 Used by many programs on OSX,apple.WebKit.Networking
149 e4d448cdfe06dc1243c1eb026c74ac9a Used by many programs on OSX,apple.WebKit.Networking
150 f1c5cf087b959cec31bd6285407f689a Used by many programs on OSX,apple.WebKit.Networking
151 488b6b601cb141b062d4da7f524b4b22 Used by many programs,Python,PHP,Git,dotnet,Adobe
152 f28d34ce9e732f644de2350027d74c3f Used by many programs,Quip,Aura,Spotify,Chatty
153 190dfb280fe3b541acc6a2e5f00690e6 Used by many programs,Quip,Spotify,Dropbox,GitHub Desktop,etc
154 20dd18bdd3209ea718989030a6f93364 Used by many programs,Slack,Postman,Spotify,Google Chrome
155 e0224fc1c33658f2d3d963bfb0a76a85 Viber
156 01319090aea981dde6fc8d6ae71ead54 vpnkit
157 84607748f3887541dd60fe974a042c71 wineserver
158 c2b4710c6888a5d47befe865c8e6fb19 ZwiftApp

55
python/README.rst Normal file
View File

@ -0,0 +1,55 @@
pyJA3
=====
.. image:: https://readthedocs.org/projects/pyja3/badge/?version=latest
:target: http://pyja3.readthedocs.io/en/latest/?badge=latest
.. image:: https://badge.fury.io/py/pyja3.svg
:target: https://badge.fury.io/py/pyja3
JA3 provides fingerprinting services on SSL packets. This is a python wrapper around JA3 logic in order to produce valid JA3 fingerprints from an input PCAP file.
Getting Started
---------------
1. Install the pyja3 module:
``pip install pyja3`` or ``python setup.py install``
2. Test with a PCAP file or download a sample:
$(venv) ja3 --json /your/file.pcap
Example
-------
Output from sample PCAP::
[
{
"destination_ip": "192.168.1.3",
"destination_port": 443,
"ja3": "769,255-49162-49172-136-135-57-56-49167-49157-132-53-49159-49161-49169-49171-69-68-51-50-49164-49166-49154-49156-150-65-4-5-47-49160-49170-22-19-49165-49155-65279-10,0-10-11-35,23-24-25,0",
"ja3_digest": "2aef69b4ba1938c3a400de4188743185",
"source_ip": "192.168.1.4",
"source_port": 2061,
"timestamp": 1350802591.754299
},
{
"destination_ip": "192.168.1.3",
"destination_port": 443,
"ja3": "769,255-49162-49172-136-135-57-56-49167-49157-132-53-49159-49161-49169-49171-69-68-51-50-49164-49166-49154-49156-150-65-4-5-47-49160-49170-22-19-49165-49155-65279-10,0-10-11-35,23-24-25,0",
"ja3_digest": "2aef69b4ba1938c3a400de4188743185",
"source_ip": "192.168.1.4",
"source_port": 2068,
"timestamp": 1350802597.517011
}
]
Changelog
---------
2018-02-05
~~~~~~~~~~
* Change: Ported single script to valid Python Package
* Change: Re-factored code to be cleaner and PEP8 compliant
* Change: Supported Python2 and Python3

284
python/ja3.py Executable file
View File

@ -0,0 +1,284 @@
#!/usr/bin/env python
"""Generate JA3 fingerprints from PCAPs using Python."""
import argparse
import dpkt
import json
import socket
import binascii
import struct
import os
from hashlib import md5
__author__ = "Tommy Stallings"
__copyright__ = "Copyright (c) 2017, salesforce.com, inc."
__credits__ = ["John B. Althouse", "Jeff Atkinson", "Josh Atkins"]
__license__ = "BSD 3-Clause License"
__version__ = "1.0.0"
__maintainer__ = "Tommy Stallings, Brandon Dixon"
__email__ = "tommy.stallings2@gmail.com"
GREASE_TABLE = {0x0a0a: True, 0x1a1a: True, 0x2a2a: True, 0x3a3a: True,
0x4a4a: True, 0x5a5a: True, 0x6a6a: True, 0x7a7a: True,
0x8a8a: True, 0x9a9a: True, 0xaaaa: True, 0xbaba: True,
0xcaca: True, 0xdada: True, 0xeaea: True, 0xfafa: True}
# GREASE_TABLE Ref: https://tools.ietf.org/html/draft-davidben-tls-grease-00
SSL_PORT = 443
TLS_HANDSHAKE = 22
def convert_ip(value):
"""Convert an IP address from binary to text.
:param value: Raw binary data to convert
:type value: str
:returns: str
"""
try:
return socket.inet_ntop(socket.AF_INET, value)
except ValueError:
return socket.inet_ntop(socket.AF_INET6, value)
def parse_variable_array(buf, byte_len):
"""Unpack data from buffer of specific length.
:param buf: Buffer to operate on
:type buf: bytes
:param byte_len: Length to process
:type byte_len: int
:returns: bytes, int
"""
_SIZE_FORMATS = ['!B', '!H', '!I', '!I']
assert byte_len <= 4
size_format = _SIZE_FORMATS[byte_len - 1]
padding = b'\x00' if byte_len == 3 else b''
size = struct.unpack(size_format, padding + buf[:byte_len])[0]
data = buf[byte_len:byte_len + size]
return data, size + byte_len
def ntoh(buf):
"""Convert to network order.
:param buf: Bytes to convert
:type buf: bytearray
:returns: int
"""
if len(buf) == 1:
return buf[0]
elif len(buf) == 2:
return struct.unpack('!H', buf)[0]
elif len(buf) == 4:
return struct.unpack('!I', buf)[0]
else:
raise ValueError('Invalid input buffer size for NTOH')
def convert_to_ja3_segment(data, element_width):
"""Convert a packed array of elements to a JA3 segment.
:param data: Current PCAP buffer item
:type: str
:param element_width: Byte count to process at a time
:type element_width: int
:returns: str
"""
int_vals = list()
data = bytearray(data)
if len(data) % element_width:
message = '{count} is not a multiple of {width}'
message = message.format(count=len(data), width=element_width)
raise ValueError(message)
for i in range(0, len(data), element_width):
element = ntoh(data[i: i + element_width])
if element not in GREASE_TABLE:
int_vals.append(element)
return "-".join(str(x) for x in int_vals)
def process_extensions(client_handshake):
"""Process any extra extensions and convert to a JA3 segment.
:param client_handshake: Handshake data from the packet
:type client_handshake: dpkt.ssl.TLSClientHello
:returns: list
"""
if not hasattr(client_handshake, "extensions"):
# Needed to preserve commas on the join
return ["", "", ""]
exts = list()
elliptic_curve = ""
elliptic_curve_point_format = ""
for ext_val, ext_data in client_handshake.extensions:
if not GREASE_TABLE.get(ext_val):
exts.append(ext_val)
if ext_val == 0x0a:
a, b = parse_variable_array(ext_data, 2)
# Elliptic curve points (16 bit values)
elliptic_curve = convert_to_ja3_segment(a, 2)
elif ext_val == 0x0b:
a, b = parse_variable_array(ext_data, 1)
# Elliptic curve point formats (8 bit values)
elliptic_curve_point_format = convert_to_ja3_segment(a, 1)
else:
continue
results = list()
results.append("-".join([str(x) for x in exts]))
results.append(elliptic_curve)
results.append(elliptic_curve_point_format)
return results
def process_pcap(pcap, any_port=False):
"""Process packets within the PCAP.
:param pcap: Opened PCAP file to be processed
:type pcap: dpkt.pcap.Reader
:param any_port: Whether or not to search for non-SSL ports
:type any_port: bool
"""
decoder = dpkt.ethernet.Ethernet
linktype = pcap.datalink()
if linktype == dpkt.pcap.DLT_LINUX_SLL:
decoder = dpkt.sll.SLL
elif linktype == dpkt.pcap.DLT_NULL or linktype == dpkt.pcap.DLT_LOOP:
decoder = dpkt.loopback.Loopback
results = list()
for timestamp, buf in pcap:
try:
eth = decoder(buf)
except Exception:
continue
if not isinstance(eth.data, (dpkt.ip.IP, dpkt.ip6.IP6)):
# We want an IP packet
continue
if not isinstance(eth.data.data, dpkt.tcp.TCP):
# TCP only
continue
ip = eth.data
tcp = ip.data
if not (tcp.dport == SSL_PORT or tcp.sport == SSL_PORT or any_port):
# Doesn't match SSL port or we are picky
continue
if len(tcp.data) <= 0:
continue
tls_handshake = bytearray(tcp.data)
if tls_handshake[0] != TLS_HANDSHAKE:
continue
records = list()
try:
records, bytes_used = dpkt.ssl.tls_multi_factory(tcp.data)
except dpkt.ssl.SSL3Exception:
continue
except dpkt.dpkt.NeedData:
continue
if len(records) <= 0:
continue
for record in records:
if record.type != TLS_HANDSHAKE:
continue
if len(record.data) == 0:
continue
client_hello = bytearray(record.data)
if client_hello[0] != 1:
# We only want client HELLO
continue
try:
handshake = dpkt.ssl.TLSHandshake(record.data)
except dpkt.dpkt.NeedData:
# Looking for a handshake here
continue
if not isinstance(handshake.data, dpkt.ssl.TLSClientHello):
# Still not the HELLO
continue
client_handshake = handshake.data
buf, ptr = parse_variable_array(client_handshake.data, 1)
buf, ptr = parse_variable_array(client_handshake.data[ptr:], 2)
ja3 = [str(client_handshake.version)]
# Cipher Suites (16 bit values)
ja3.append(convert_to_ja3_segment(buf, 2))
ja3 += process_extensions(client_handshake)
ja3 = ",".join(ja3)
record = {"source_ip": convert_ip(ip.src),
"destination_ip": convert_ip(ip.dst),
"source_port": tcp.sport,
"destination_port": tcp.dport,
"ja3": ja3,
"ja3_digest": md5(ja3.encode()).hexdigest(),
"timestamp": timestamp,
"client_hello_pkt": binascii.hexlify(tcp.data).decode('utf-8')}
results.append(record)
return results
def main():
"""Intake arguments from the user and print out JA3 output."""
desc = "A python script for extracting JA3 fingerprints from PCAP files"
parser = argparse.ArgumentParser(description=(desc))
parser.add_argument("pcap", help="The pcap file to process")
help_text = "Look for client hellos on any port instead of just 443"
parser.add_argument("-a", "--any_port", required=False,
action="store_true", default=False,
help=help_text)
help_text = "Print out as JSON records for downstream parsing"
parser.add_argument("-j", "--json", required=False, action="store_true",
default=False, help=help_text)
help_text = "Print packet related data for research (json only)"
parser.add_argument("-r", "--research", required=False, action="store_true",
default=False, help=help_text)
args = parser.parse_args()
# Use an iterator to process each line of the file
output = None
with open(args.pcap, 'rb') as fp:
try:
capture = dpkt.pcap.Reader(fp)
except ValueError as e_pcap:
try:
fp.seek(0, os.SEEK_SET)
capture = dpkt.pcapng.Reader(fp)
except ValueError as e_pcapng:
raise Exception(
"File doesn't appear to be a PCAP or PCAPng: %s, %s" %
(e_pcap, e_pcapng))
output = process_pcap(capture, any_port=args.any_port)
if args.json:
if not args.research:
def remove_items(x):
del x['client_hello_pkt']
list(map(remove_items,output))
output = json.dumps(output, indent=4, sort_keys=True)
print(output)
else:
for record in output:
tmp = '[{dest}:{port}] JA3: {segment} --> {digest}'
tmp = tmp.format(dest=record['destination_ip'],
port=record['destination_port'],
segment=record['ja3'],
digest=record['ja3_digest'])
print(tmp)
if __name__ == "__main__":
main()

0
python/ja3/__init__.py Normal file
View File

275
python/ja3/ja3.py Normal file
View File

@ -0,0 +1,275 @@
#!/usr/bin/env python
"""Generate JA3 fingerprints from PCAPs using Python."""
import argparse
import dpkt
import json
import socket
import struct
import os
from hashlib import md5
__author__ = "Tommy Stallings"
__copyright__ = "Copyright (c) 2017, salesforce.com, inc."
__credits__ = ["John B. Althouse", "Jeff Atkinson", "Josh Atkins"]
__license__ = "BSD 3-Clause License"
__version__ = "1.0.0"
__maintainer__ = "Tommy Stallings, Brandon Dixon"
__email__ = "tommy.stallings@salesforce.com"
GREASE_TABLE = {0x0a0a: True, 0x1a1a: True, 0x2a2a: True, 0x3a3a: True,
0x4a4a: True, 0x5a5a: True, 0x6a6a: True, 0x7a7a: True,
0x8a8a: True, 0x9a9a: True, 0xaaaa: True, 0xbaba: True,
0xcaca: True, 0xdada: True, 0xeaea: True, 0xfafa: True}
# GREASE_TABLE Ref: https://tools.ietf.org/html/draft-davidben-tls-grease-00
SSL_PORT = 443
TLS_HANDSHAKE = 22
def convert_ip(value):
"""Convert an IP address from binary to text.
:param value: Raw binary data to convert
:type value: str
:returns: str
"""
try:
return socket.inet_ntop(socket.AF_INET, value)
except ValueError:
return socket.inet_ntop(socket.AF_INET6, value)
def parse_variable_array(buf, byte_len):
"""Unpack data from buffer of specific length.
:param buf: Buffer to operate on
:type buf: bytes
:param byte_len: Length to process
:type byte_len: int
:returns: bytes, int
"""
_SIZE_FORMATS = ['!B', '!H', '!I', '!I']
assert byte_len <= 4
size_format = _SIZE_FORMATS[byte_len - 1]
padding = b'\x00' if byte_len == 3 else b''
size = struct.unpack(size_format, padding + buf[:byte_len])[0]
data = buf[byte_len:byte_len + size]
return data, size + byte_len
def ntoh(buf):
"""Convert to network order.
:param buf: Bytes to convert
:type buf: bytearray
:returns: int
"""
if len(buf) == 1:
return buf[0]
elif len(buf) == 2:
return struct.unpack('!H', buf)[0]
elif len(buf) == 4:
return struct.unpack('!I', buf)[0]
else:
raise ValueError('Invalid input buffer size for NTOH')
def convert_to_ja3_segment(data, element_width):
"""Convert a packed array of elements to a JA3 segment.
:param data: Current PCAP buffer item
:type: str
:param element_width: Byte count to process at a time
:type element_width: int
:returns: str
"""
int_vals = list()
data = bytearray(data)
if len(data) % element_width:
message = '{count} is not a multiple of {width}'
message = message.format(count=len(data), width=element_width)
raise ValueError(message)
for i in range(0, len(data), element_width):
element = ntoh(data[i: i + element_width])
if element not in GREASE_TABLE:
int_vals.append(element)
return "-".join(str(x) for x in int_vals)
def process_extensions(client_handshake):
"""Process any extra extensions and convert to a JA3 segment.
:param client_handshake: Handshake data from the packet
:type client_handshake: dpkt.ssl.TLSClientHello
:returns: list
"""
if not hasattr(client_handshake, "extensions"):
# Needed to preserve commas on the join
return ["", "", ""]
exts = list()
elliptic_curve = ""
elliptic_curve_point_format = ""
for ext_val, ext_data in client_handshake.extensions:
if not GREASE_TABLE.get(ext_val):
exts.append(ext_val)
if ext_val == 0x0a:
a, b = parse_variable_array(ext_data, 2)
# Elliptic curve points (16 bit values)
elliptic_curve = convert_to_ja3_segment(a, 2)
elif ext_val == 0x0b:
a, b = parse_variable_array(ext_data, 1)
# Elliptic curve point formats (8 bit values)
elliptic_curve_point_format = convert_to_ja3_segment(a, 1)
else:
continue
results = list()
results.append("-".join([str(x) for x in exts]))
results.append(elliptic_curve)
results.append(elliptic_curve_point_format)
return results
def process_pcap(pcap, any_port=False):
"""Process packets within the PCAP.
:param pcap: Opened PCAP file to be processed
:type pcap: dpkt.pcap.Reader
:param any_port: Whether or not to search for non-SSL ports
:type any_port: bool
"""
decoder = dpkt.ethernet.Ethernet
linktype = pcap.datalink()
if linktype == dpkt.pcap.DLT_LINUX_SLL:
decoder = dpkt.sll.SLL
elif linktype == dpkt.pcap.DLT_NULL or linktype == dpkt.pcap.DLT_LOOP:
decoder = dpkt.loopback.Loopback
results = list()
for timestamp, buf in pcap:
try:
eth = decoder(buf)
except Exception:
continue
if not isinstance(eth.data, (dpkt.ip.IP, dpkt.ip6.IP6)):
# We want an IP packet
continue
if not isinstance(eth.data.data, dpkt.tcp.TCP):
# TCP only
continue
ip = eth.data
tcp = ip.data
if not (tcp.dport == SSL_PORT or tcp.sport == SSL_PORT or any_port):
# Doesn't match SSL port or we are picky
continue
if len(tcp.data) <= 0:
continue
tls_handshake = bytearray(tcp.data)
if tls_handshake[0] != TLS_HANDSHAKE:
continue
records = list()
try:
records, bytes_used = dpkt.ssl.tls_multi_factory(tcp.data)
except dpkt.ssl.SSL3Exception:
continue
except dpkt.dpkt.NeedData:
continue
if len(records) <= 0:
continue
for record in records:
if record.type != TLS_HANDSHAKE:
continue
if len(record.data) == 0:
continue
client_hello = bytearray(record.data)
if client_hello[0] != 1:
# We only want client HELLO
continue
try:
handshake = dpkt.ssl.TLSHandshake(record.data)
except dpkt.dpkt.NeedData:
# Looking for a handshake here
continue
if not isinstance(handshake.data, dpkt.ssl.TLSClientHello):
# Still not the HELLO
continue
client_handshake = handshake.data
buf, ptr = parse_variable_array(client_handshake.data, 1)
buf, ptr = parse_variable_array(client_handshake.data[ptr:], 2)
ja3 = [str(client_handshake.version)]
# Cipher Suites (16 bit values)
ja3.append(convert_to_ja3_segment(buf, 2))
ja3 += process_extensions(client_handshake)
ja3 = ",".join(ja3)
record = {"source_ip": convert_ip(ip.src),
"destination_ip": convert_ip(ip.dst),
"source_port": tcp.sport,
"destination_port": tcp.dport,
"ja3": ja3,
"ja3_digest": md5(ja3.encode()).hexdigest(),
"timestamp": timestamp}
results.append(record)
return results
def main():
"""Intake arguments from the user and print out JA3 output."""
desc = "A python script for extracting JA3 fingerprints from PCAP files"
parser = argparse.ArgumentParser(description=(desc))
parser.add_argument("pcap", help="The pcap file to process")
help_text = "Look for client hellos on any port instead of just 443"
parser.add_argument("-a", "--any_port", required=False,
action="store_true", default=False,
help=help_text)
help_text = "Print out as JSON records for downstream parsing"
parser.add_argument("-j", "--json", required=False, action="store_true",
default=True, help=help_text)
args = parser.parse_args()
# Use an iterator to process each line of the file
output = None
with open(args.pcap, 'rb') as fp:
try:
capture = dpkt.pcap.Reader(fp)
except ValueError as e_pcap:
try:
fp.seek(0, os.SEEK_SET)
capture = dpkt.pcapng.Reader(fp)
except ValueError as e_pcapng:
raise Exception(
"File doesn't appear to be a PCAP or PCAPng: %s, %s" %
(e_pcap, e_pcapng))
output = process_pcap(capture, any_port=args.any_port)
if args.json:
output = json.dumps(output, indent=4, sort_keys=True)
print(output)
else:
for record in output:
tmp = '[{dest}:{port}] JA3: {segment} --> {digest}'
tmp = tmp.format(dest=record['destination_ip'],
port=record['destination_port'],
segment=record['ja3'],
digest=record['ja3_digest'])
print(tmp)
if __name__ == "__main__":
main()

197
python/ja3s.py Normal file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env python
"""Generate JA3 fingerprints from PCAPs using Python."""
import argparse
import dpkt
import json
import socket
import struct
import os
from hashlib import md5
from distutils.version import LooseVersion
__author__ = "Tommy Stallings"
__copyright__ = "Copyright (c) 2017, salesforce.com, inc."
__credits__ = ["John B. Althouse", "Jeff Atkinson", "Josh Atkins"]
__license__ = "BSD 3-Clause License"
__version__ = "1.0.1"
__maintainer__ = "Tommy Stallings, Brandon Dixon"
__email__ = "tommy.stallings2@gmail.com"
SSL_PORT = 443
TLS_HANDSHAKE = 22
def convert_ip(value):
"""Convert an IP address from binary to text.
:param value: Raw binary data to convert
:type value: str
:returns: str
"""
try:
return socket.inet_ntop(socket.AF_INET, value)
except ValueError:
return socket.inet_ntop(socket.AF_INET6, value)
def process_extensions(server_handshake):
"""Process any extra extensions and convert to a JA3 segment.
:param client_handshake: Handshake data from the packet
:type client_handshake: dpkt.ssl.TLSClientHello
:returns: list
"""
if not hasattr(server_handshake, "extensions"):
# Needed to preserve commas on the join
return [""]
exts = list()
for ext_val, ext_data in server_handshake.extensions:
exts.append(ext_val)
results = list()
results.append("-".join([str(x) for x in exts]))
return results
def process_pcap(pcap, any_port=False):
"""Process packets within the PCAP.
:param pcap: Opened PCAP file to be processed
:type pcap: dpkt.pcap.Reader
:param any_port: Whether or not to search for non-SSL ports
:type any_port: bool
"""
decoder = dpkt.ethernet.Ethernet
linktype = pcap.datalink()
if linktype == dpkt.pcap.DLT_LINUX_SLL:
decoder = dpkt.sll.SLL
elif linktype == dpkt.pcap.DLT_NULL or linktype == dpkt.pcap.DLT_LOOP:
decoder = dpkt.loopback.Loopback
results = list()
for timestamp, buf in pcap:
try:
eth = decoder(buf)
except Exception:
continue
if not isinstance(eth.data, (dpkt.ip.IP, dpkt.ip6.IP6)):
# We want an IP packet
continue
if not isinstance(eth.data.data, dpkt.tcp.TCP):
# TCP only
continue
ip = eth.data
tcp = ip.data
if not (tcp.dport == SSL_PORT or tcp.sport == SSL_PORT or any_port):
# Doesn't match SSL port or we are picky
continue
if len(tcp.data) <= 0:
continue
tls_handshake = bytearray(tcp.data)
if tls_handshake[0] != TLS_HANDSHAKE:
continue
records = list()
try:
records, bytes_used = dpkt.ssl.tls_multi_factory(tcp.data)
except dpkt.ssl.SSL3Exception:
continue
except dpkt.dpkt.NeedData:
continue
if len(records) <= 0:
continue
for record in records:
if record.type != TLS_HANDSHAKE:
continue
if len(record.data) == 0:
continue
server_hello = bytearray(record.data)
if server_hello[0] != 2:
# We only want server HELLO
continue
try:
handshake = dpkt.ssl.TLSHandshake(record.data)
except dpkt.dpkt.NeedData:
# Looking for a handshake here
continue
if not isinstance(handshake.data, dpkt.ssl.TLSServerHello):
# Still not the HELLO
continue
server_handshake = handshake.data
ja3 = [str(server_handshake.version)]
# Cipher Suites (16 bit values)
if LooseVersion(dpkt.__version__) <= LooseVersion('1.9.1'):
ja3.append(str(server_handshake.cipher_suite))
else:
ja3.append(str(server_handshake.ciphersuite.code))
ja3 += process_extensions(server_handshake)
ja3 = ",".join(ja3)
record = {"source_ip": convert_ip(ip.src),
"destination_ip": convert_ip(ip.dst),
"source_port": tcp.sport,
"destination_port": tcp.dport,
"ja3": ja3,
"ja3_digest": md5(ja3.encode()).hexdigest(),
"timestamp": timestamp}
results.append(record)
return results
def main():
"""Intake arguments from the user and print out JA3 output."""
desc = "A python script for extracting JA3 fingerprints from PCAP files"
parser = argparse.ArgumentParser(description=(desc))
parser.add_argument("pcap", help="The pcap file to process")
help_text = "Look for client hellos on any port instead of just 443"
parser.add_argument("-a", "--any_port", required=False,
action="store_true", default=False,
help=help_text)
help_text = "Print out as JSON records for downstream parsing"
parser.add_argument("-j", "--json", required=False, action="store_true",
default=False, help=help_text)
args = parser.parse_args()
# Use an iterator to process each line of the file
output = None
with open(args.pcap, 'rb') as fp:
try:
capture = dpkt.pcap.Reader(fp)
except ValueError as e_pcap:
try:
fp.seek(0, os.SEEK_SET)
capture = dpkt.pcapng.Reader(fp)
except ValueError as e_pcapng:
raise Exception(
"File doesn't appear to be a PCAP or PCAPng: %s, %s" %
(e_pcap, e_pcapng))
output = process_pcap(capture, any_port=args.any_port)
if args.json:
output = json.dumps(output, indent=4, sort_keys=True)
print(output)
else:
for record in output:
tmp = '[{dest}:{port}] JA3S: {segment} --> {digest}'
tmp = tmp.format(dest=record['destination_ip'],
port=record['destination_port'],
segment=record['ja3'],
digest=record['ja3_digest'])
print(tmp)
if __name__ == "__main__":
main()

1
python/requirements.txt Normal file
View File

@ -0,0 +1 @@
dpkt==1.9.1

39
python/setup.py Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env python
import os
from setuptools import setup, find_packages
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setup(
name='pyja3',
version='1.1.0',
description='Generate JA3 fingerprints from PCAPs using Python.',
url="https://github.com/salesforce/ja3",
author="Tommy Stallings",
author_email="tommy.stallings2@gmail.com",
maintainer = "John B. Althouse",
maintainer_email = "jalthouse@salesforce.com",
license="BSD",
packages=find_packages(),
install_requires=['dpkt'],
long_description=read('README.rst'),
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries'
],
package_data={
'pyja3': [],
},
entry_points={
'console_scripts': [
'ja3 = ja3.ja3:main'
]
},
keywords=['ja3', 'fingerprints', 'defender', 'ssl', 'packets']
)

52
zeek/README.md Normal file
View File

@ -0,0 +1,52 @@
## Features
- **ja3.zeek** will add the field "ja3" to ssl.log.
- It can also append fields used by JA3 to ssl.log
- **intel_ja3.zeek** will add INTEL::JA3 to the Zeek Intel Framwork
- This will allow you to import JA3 fingerprints directly into your intel feed.
- **ja3s.zeek** will add the field "ja3s" to ssl.log, JA3 for the server hello.
- It can also append fields used by JA3S to ssl.log.
- Tested on Zeek 3.0.0
## Installation
- If you're running Zeek >= 3.0.0 or a Zeek product like Corelight, you can install by using the Zeek Package Manager and this one simple command:
```bash
zkg install ja3
```
- For everyone else, download the files to zeek/share/zeek/site/ja3 and add this line to your local.zeek script:
```bash
@load ./ja3
```
## Configuration
By default ja3.zeek will only append ja3 to the ssl.log. However, if you would like to log all aspects of the SSL Client Hello Packet, uncomment the following lines in ja3.zeek
```bash
# ja3_version: string &optional &log;
# ja3_ciphers: string &optional &log;
# ja3_extensions: string &optional &log;
# ja3_ec: string &optional &log;
# ja3_ec_fmt: string &optional &log;
```
...
```bash
#c$ssl$ja3_version = cat(c$tlsfp$client_version);
#c$ssl$ja3_ciphers = c$tlsfp$client_ciphers;
#c$ssl$ja3_extensions = c$tlsfp$extensions;
#c$ssl$ja3_ec = c$tlsfp$e_curves;
#c$ssl$ja3_ec_fmt = c$tlsfp$ec_point_fmt;
```
The same changes can be made in ja3s.zeek as well.
___
### JA3 Created by
[John B. Althouse](mailto:jalthouse@salesforce.com)
[Jeff Atkinson](mailto:jatkinson@salesforce.com)
[Josh Atkins](mailto:j.atkins@salesforce.com)
Please send questions and comments to **[John B. Althouse](mailto:jalthouse@salesforce.com)**.

3
zeek/__load__.zeek Normal file
View File

@ -0,0 +1,3 @@
@load ./ja3.zeek
@load ./intel_ja3.zeek
@load ./ja3s.zeek

28
zeek/intel_ja3.zeek Normal file
View File

@ -0,0 +1,28 @@
# This Zeek script adds JA3 to the Zeek Intel Framework as Intel::JA3
#
# Author: John B. Althouse (jalthouse@salesforce.com)
#
# Copyright (c) 2017, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
module Intel;
export {
redef enum Intel::Type += { Intel::JA3 };
}
export {
redef enum Intel::Where += { SSL::IN_JA3 };
}
@if ( Version::at_least("2.6") || ( Version::number == 20500 && Version::info$commit >= 944 ) )
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)
@else
event ssl_client_hello(c: connection, version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec)
@endif
{
if ( c$ssl?$ja3 )
Intel::seen([$indicator=c$ssl$ja3, $indicator_type=Intel::JA3, $conn=c, $where=SSL::IN_JA3]);
}

155
zeek/ja3.zeek Normal file
View File

@ -0,0 +1,155 @@
# This Zeek script appends JA3 to ssl.log
# Version 1.4 (January 2020)
#
# Authors: John B. Althouse (jalthouse@salesforce.com) & Jeff Atkinson (jatkinson@salesforce.com)
#
# Copyright (c) 2017, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
module JA3;
export {
redef enum Log::ID += { LOG };
}
type TLSFPStorage: record {
client_version: count &default=0 &log;
client_ciphers: string &default="" &log;
extensions: string &default="" &log;
e_curves: string &default="" &log;
ec_point_fmt: string &default="" &log;
};
redef record connection += {
tlsfp: TLSFPStorage &optional;
};
redef record SSL::Info += {
ja3: string &optional &log;
# LOG FIELD VALUES ##
# ja3_version: string &optional &log;
# ja3_ciphers: string &optional &log;
# ja3_extensions: string &optional &log;
# ja3_ec: string &optional &log;
# ja3_ec_fmt: string &optional &log;
};
# Google. https://tools.ietf.org/html/draft-davidben-tls-grease-01
const grease: set[int] = {
2570,
6682,
10794,
14906,
19018,
23130,
27242,
31354,
35466,
39578,
43690,
47802,
51914,
56026,
60138,
64250
};
const sep = "-";
event zeek_init() {
Log::create_stream(JA3::LOG,[$columns=TLSFPStorage, $path="tlsfp"]);
}
event ssl_extension(c: connection, is_orig: bool, code: count, val: string)
{
if ( is_orig == T ) {
if ( code in grease ) {
return;
}
if ( ! c?$tlsfp ){
c$tlsfp=TLSFPStorage();
}
if ( c$tlsfp$extensions == "" ) {
c$tlsfp$extensions = cat(code);
}
else {
c$tlsfp$extensions = string_cat(c$tlsfp$extensions, sep,cat(code));
}
}
}
event ssl_extension_ec_point_formats(c: connection, is_orig: bool, point_formats: index_vec)
{
if ( is_orig == T ) {
if ( !c?$tlsfp )
c$tlsfp=TLSFPStorage();
for ( i in point_formats ) {
if ( point_formats[i] in grease ) {
next;
}
if ( c$tlsfp$ec_point_fmt == "" ) {
c$tlsfp$ec_point_fmt += cat(point_formats[i]);
}
else {
c$tlsfp$ec_point_fmt += string_cat(sep,cat(point_formats[i]));
}
}
}
}
event ssl_extension_elliptic_curves(c: connection, is_orig: bool, curves: index_vec)
{
if ( !c?$tlsfp )
c$tlsfp=TLSFPStorage();
if ( is_orig == T ) {
for ( i in curves ) {
if ( curves[i] in grease ) {
next;
}
if ( c$tlsfp$e_curves == "" ) {
c$tlsfp$e_curves += cat(curves[i]);
}
else {
c$tlsfp$e_curves += string_cat(sep,cat(curves[i]));
}
}
}
}
@if ( ( Version::number >= 20600 ) || ( Version::number == 20500 && Version::info$commit >= 944 ) )
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) &priority=1
@else
event ssl_client_hello(c: connection, version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec) &priority=1
@endif
{
if ( !c?$tlsfp )
c$tlsfp=TLSFPStorage();
c$tlsfp$client_version = version;
for ( i in ciphers ) {
if ( ciphers[i] in grease ) {
next;
}
if ( c$tlsfp$client_ciphers == "" ) {
c$tlsfp$client_ciphers += cat(ciphers[i]);
}
else {
c$tlsfp$client_ciphers += string_cat(sep,cat(ciphers[i]));
}
}
local sep2 = ",";
local ja3_string = string_cat(cat(c$tlsfp$client_version),sep2,c$tlsfp$client_ciphers,sep2,c$tlsfp$extensions,sep2,c$tlsfp$e_curves,sep2,c$tlsfp$ec_point_fmt);
local tlsfp_1 = md5_hash(ja3_string);
c$ssl$ja3 = tlsfp_1;
# LOG FIELD VALUES ##
#c$ssl$ja3_version = cat(c$tlsfp$client_version);
#c$ssl$ja3_ciphers = c$tlsfp$client_ciphers;
#c$ssl$ja3_extensions = c$tlsfp$extensions;
#c$ssl$ja3_ec = c$tlsfp$e_curves;
#c$ssl$ja3_ec_fmt = c$tlsfp$ec_point_fmt;
#
# FOR DEBUGGING ##
#print "JA3: "+tlsfp_1+" Fingerprint String: "+ja3_string;
}

82
zeek/ja3s.zeek Normal file
View File

@ -0,0 +1,82 @@
# This Zeek script appends JA3S (JA3 Server) to ssl.log
# Version 1.1 (January 2020)
# This builds a fingerprint for the SSL Server Hello packet based on SSL/TLS version, cipher picked, and extensions used.
# Designed to be used in conjunction with JA3 to fingerprint SSL communication between clients and servers.
#
# Authors: John B. Althouse (jalthouse@salesforce.com) Jeff Atkinson (jatkinson@salesforce.com)
# Copyright (c) 2018, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
#
module JA3_Server;
export {
redef enum Log::ID += { LOG };
}
type JA3Sstorage: record {
server_version: count &default=0 &log;
server_cipher: count &default=0 &log;
server_extensions: string &default="" &log;
};
redef record connection += {
ja3sfp: JA3Sstorage &optional;
};
redef record SSL::Info += {
ja3s: string &optional &log;
# LOG FIELD VALUES #
# ja3s_version: string &optional &log;
# ja3s_cipher: string &optional &log;
# ja3s_extensions: string &optional &log;
};
const sep = "-";
event zeek_init() {
Log::create_stream(JA3_Server::LOG,[$columns=JA3Sstorage, $path="ja3sfp"]);
}
event ssl_extension(c: connection, is_orig: bool, code: count, val: string)
{
if ( ! c?$ja3sfp )
c$ja3sfp=JA3Sstorage();
if ( is_orig == F ) {
if ( c$ja3sfp$server_extensions == "" ) {
c$ja3sfp$server_extensions = cat(code);
}
else {
c$ja3sfp$server_extensions = string_cat(c$ja3sfp$server_extensions, sep,cat(code));
}
}
}
@if ( ( Version::number >= 20600 ) || ( Version::number == 20500 && Version::info$commit >= 944 ) )
event ssl_server_hello(c: connection, version: count, record_version: count, possible_ts: time, server_random: string, session_id: string, cipher: count, comp_method: count) &priority=1
@else
event ssl_server_hello(c: connection, version: count, possible_ts: time, server_random: string, session_id: string, cipher: count, comp_method: count) &priority=1
@endif
{
if ( !c?$ja3sfp )
c$ja3sfp=JA3Sstorage();
c$ja3sfp$server_version = version;
c$ja3sfp$server_cipher = cipher;
local sep2 = ",";
local ja3s_string = string_cat(cat(c$ja3sfp$server_version),sep2,cat(c$ja3sfp$server_cipher),sep2,c$ja3sfp$server_extensions);
local ja3sfp_1 = md5_hash(ja3s_string);
c$ssl$ja3s = ja3sfp_1;
# LOG FIELD VALUES #
#c$ssl$ja3s_version = cat(c$ja3sfp$server_version);
#c$ssl$ja3s_cipher = cat(c$ja3sfp$server_cipher);
#c$ssl$ja3s_extensions = c$ja3sfp$server_extensions;
#
# FOR DEBUGGING #
#print "JA3S: "+ja3sfp_1+" Fingerprint String: "+ja3s_string;
}

6
zkg.meta Normal file
View File

@ -0,0 +1,6 @@
[package]
script_dir = zeek
description = JA3 creates 32 character SSL client fingerprints and logs them as a field in ssl.log. These fingerprints can easily be shared as threat intelligence or used as correlation items for enhanced alerting and analysis. This package also adds JA3 to the Zeek Intel Framework.
https://github.com/salesforce/ja3
tags = intel, ssl, logging
version = 1.2.0