├── logo.png
├── ntrl.png
├── Pipfile
├── LICENSE
├── proxy_handler.py
├── setup.sh
├── config.py
├── .gitignore
├── red_team_tool_jarms.json
├── main.py
├── Pipfile.lock
├── README.md
└── setup.py
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netskopeoss/jarm_randomizer/HEAD/logo.png
--------------------------------------------------------------------------------
/ntrl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netskopeoss/jarm_randomizer/HEAD/ntrl.png
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | requests = "*"
8 | pyjarm = "*"
9 | urllib3 = "*"
10 | shodan = "*"
11 | pybinaryedge = "*"
12 |
13 | [dev-packages]
14 |
15 | [requires]
16 | python_version = "3.9"
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021, Netskope OSS
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/proxy_handler.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2021 Netskope, Inc.
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4 | following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7 | disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13 | products derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 |
23 | Written by Dagmawi Mulugeta
24 | """
25 |
26 | import urllib.request
27 | import urllib
28 |
29 | from http.server import SimpleHTTPRequestHandler
30 |
31 |
32 | class ProxyHandler(SimpleHTTPRequestHandler):
33 | def do_GET(self):
34 | """
35 | Handler for a GET request
36 | """
37 | # Update this based on the format the proxy is supposed to use
38 | url = self.path[1:]
39 | self.send_response(200)
40 | self.end_headers()
41 | with urllib.request.urlopen(url) as url_content:
42 | self.copyfile(url_content, self.wfile)
43 |
44 |
45 | def serve_forever(httpd):
46 | with httpd:
47 | httpd.serve_forever()
48 |
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #Copyright 2021 Netskope, Inc.
4 | #Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
5 | #following conditions are met:
6 | #
7 | #1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
8 | #disclaimer.
9 | #
10 | #2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
11 | #disclaimer in the documentation and/or other materials provided with the distribution.
12 | #
13 | #3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
14 | #products derived from this software without specific prior written permission.
15 | #
16 | #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
17 | #INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | #DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 | #SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | #SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21 | #WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | #OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | #
24 | #Written by Dagmawi Mulugeta
25 |
26 | set -e
27 |
28 | # Generate a certificate
29 | if [ ! -f cert.pem ]; then
30 | echo "[x] Generate a certificate and private key file"
31 | openssl req -newkey rsa:4096 -nodes -keyout key.pem -x509 -days 365 -out cert.pem
32 | fi
33 |
34 | # Get a list of ciphers
35 | echo "[x] Grabbing the list of ciphers that are supported on this system"
36 | openssl ciphers -v | cut -d" " -f1 | sort | uniq > ./ciphers
37 |
38 | # Check which server configurations would work
39 | echo "[x] Running setup.py to grab the valid JARMs"
40 | pipenv run python3 setup.py
41 | rm ciphers
42 |
43 | # Ready to use
44 | echo "[x] Proxy is ready to use"
45 | echo "[x] Run pipenv run python3 ./main.py to start the proxy server"
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2021 Netskope, Inc.
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4 | following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7 | disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13 | products derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 |
23 | Written by Dagmawi Mulugeta
24 | """
25 |
26 | import ssl
27 |
28 | # List of TLS versions supported
29 | ssl_versions = [ssl.PROTOCOL_TLS,
30 | ssl.PROTOCOL_TLSv1_2]
31 |
32 | # API keys for grabbing JARM metrics
33 | SHODAN_KEY = ''
34 | BINARY_EDGE_KEY = ''
35 |
36 | # File path settings
37 | paths = {
38 | 'possible_jarms': './possible_jarms.json',
39 | 'valid_configs': './valid_configs.json',
40 | 'invalid_configs': './invalid_configs.json',
41 | 'raw_jarm_stats': './raw_jarm_stats.json',
42 | 'red_team_tool_jarms': './red_team_tool_jarms.json',
43 | }
44 |
45 | # Configurations for the proxy
46 | ip = '127.0.0.1'
47 | port = 8443
48 |
49 | # Path to the TLS private key and cert files
50 | keyfile = 'key.pem'
51 | certfile = "cert.pem"
52 |
53 | # If there is a specific config that is required based on the stats/preference.
54 | # For now, you only have the option to set both or none...will come back and change this
55 | force_ssl_version = None
56 | force_cipher = None
57 |
58 | # Cycle JARM configs at certain interval. Avoid setting this and the force_ssl_version and
59 | # force_cipher above. That would not work too well
60 | cycle_jarms = False
61 | cycle_interval_secs = 5
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
140 | previous_run
141 |
--------------------------------------------------------------------------------
/red_team_tool_jarms.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "tool": "Mythic",
4 | "framework": "python 3 w/aiohttp 3",
5 | "jarm": "2ad2ad0002ad2ad00042d42d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb",
6 | "link": "https://github.com/its-a-feature/Mythic"
7 | },
8 | {
9 | "tool": "Metasploit ssl listener",
10 | "framework": "ruby 2.7.0p0",
11 | "jarm": "07d14d16d21d21d00042d43d000000aa99ce74e2c6d013c745aa52b5cc042d",
12 | "link": "https://github.com/rapid7/metasploit-framework"
13 | },
14 | {
15 | "tool": "Metasploit ssl listener",
16 | "framework": "ruby",
17 | "jarm": "07d14d16d21d21d07c42d43d000000f50d155305214cf247147c43c0f1a823",
18 | "link": "https://github.com/rapid7/metasploit-framework"
19 | },
20 | {
21 | "tool": "Cobalt Strike",
22 | "framework": "Java 11",
23 | "jarm": "07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1",
24 | "link": "https://www.cobaltstrike.com/"
25 | },
26 | {
27 | "tool": "Merlin",
28 | "framework": "go 1.15.2 linux/amd64",
29 | "jarm": "29d21b20d29d29d21c41d21b21b41d494e0df9532e75299f15ba73156cee38",
30 | "link": "https://github.com/Ne0nd0g/merlin"
31 | },
32 | {
33 | "tool": "Deimos",
34 | "framework": "go 1.15.2 linux/amd64 with github.com/gorilla/websocket package",
35 | "jarm": "00000000000000000041d00000041d9535d5979f591ae8e547c5e5743e5b64",
36 | "link": "https://github.com/DeimosC2/DeimosC2"
37 | },
38 | {
39 | "tool": "MacC2",
40 | "framework": "python 3.8.6 w/aiohttp 3",
41 | "jarm": "2ad2ad0002ad2ad22c42d42d000000faabb8fd156aa8b4d8a37853e1063261",
42 | "link": "https://github.com/cedowens/MacC2"
43 | },
44 | {
45 | "tool": "MacC2",
46 | "framework": "python 3.8.2 w/aiohttp 3",
47 | "jarm": "2ad2ad0002ad2ad00042d42d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb",
48 | "link": "https://github.com/cedowens/MacC2"
49 | },
50 | {
51 | "tool": "MacShellSwift",
52 | "framework": "python 3.8.6 socket",
53 | "jarm": "2ad000000000000000000000000000eeebf944d0b023a00f510f06a29b4f46",
54 | "link": "https://github.com/cedowens/MacShellSwift"
55 | },
56 | {
57 | "tool": "MacShell",
58 | "framework": "python 3.8.6 socket",
59 | "jarm": "2ad000000000000000000000000000eeebf944d0b023a00f510f06a29b4f46",
60 | "link": "https://github.com/cedowens/MacShellSwift"
61 | },
62 | {
63 | "tool": "Sliver",
64 | "framework": "go 1.15.2 linux/amd64",
65 | "jarm": "2ad2ad0002ad2ad00041d2ad2ad41da5207249a18099be84ef3c8811adc883",
66 | "link": "https://github.com/BishopFox/sliver"
67 | },
68 | {
69 | "tool": "EvilGinx2",
70 | "framework": "go 1.10.4 linux/amd64",
71 | "jarm": "20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6",
72 | "link": "https://github.com/kgretzky/evilginx2"
73 | },
74 | {
75 | "tool": "Shad0w",
76 | "framework": "python 3.8 flask",
77 | "jarm": "2ad2ad0002ad2ad00042d42d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb",
78 | "link": "https://github.com/bats3c/shad0w"
79 | },
80 | {
81 | "tool": "Get2",
82 | "framework": "N/A",
83 | "jarm": "07d19d12d21d21d07c07d19d07d21da5a8ab90bcc6bf8bbc6fbec4bcaa8219",
84 | "link": ""
85 | },
86 | {
87 | "tool": "GRAT2 C2",
88 | "framework": "python3 http.server",
89 | "jarm": "2ad2ad0002ad2ad00042d42d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb",
90 | "link": "https://github.com/r3nhat/GRAT2"
91 | },
92 | {
93 | "tool": "Covenant",
94 | "framework": "ASP.net core",
95 | "jarm": "21d14d00000000021c21d14d21d21d1ee8ae98bf3ef941e91529a93ac62b8b",
96 | "link": "https://github.com/cobbr/Covenant"
97 | },
98 | {
99 | "tool": "SILENTRINITY",
100 | "framework": "ironpython",
101 | "jarm": "2ad2ad0002ad2ad00042d42d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb",
102 | "link": "https://github.com/byt3bl33d3r/SILENTTRINITY"
103 | },
104 | {
105 | "tool": "PoshC2",
106 | "framework": "python3 http.server",
107 | "jarm": "2ad2ad0002ad2ad22c42d42d000000faabb8fd156aa8b4d8a37853e1063261",
108 | "link": "https://github.com/nettitude/PoshC2"
109 | }
110 | ]
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | Copyright 2021 Netskope, Inc.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
5 | following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
8 | disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
11 | disclaimer in the documentation and/or other materials provided with the distribution.
12 |
13 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
14 | products derived from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
24 | Written by Dagmawi Mulugeta
25 | """
26 |
27 | import ssl
28 | import random
29 | import json
30 | import time
31 | import config
32 | import proxy_handler
33 |
34 | from http.server import HTTPServer
35 | from threading import Thread
36 |
37 |
38 | def get_jarm_from_local(tls_version, cipher):
39 | """
40 | Grab the JARM fingerprint from the local 'possible_jarms' store.
41 | This should be ran after setup.sh to output the possible configurations.
42 | """
43 | try:
44 | with open(config.paths['possible_jarms']) as _file:
45 | jarms = json.load(_file)
46 | for j in jarms:
47 | if any(c['tls_version'] == tls_version and c['cipher'] == cipher for c in jarms[j]['configs']):
48 | return j
49 | except:
50 | return ''
51 |
52 |
53 | def grab_valid_configs(return_all=False):
54 | """
55 | Grab all the valid configurations for this system
56 | """
57 | tls_version = config.force_ssl_version if config.force_ssl_version else None
58 | cipher = config.force_cipher if config.force_ssl_version else None
59 | if tls_version and cipher:
60 | return tls_version, cipher, get_jarm_from_local(tls_version, cipher)
61 | else:
62 | with open(config.paths['valid_configs']) as _file:
63 | valid_configs = json.load(_file)
64 | if return_all:
65 | new_valid_configs = list({e['jarm']: e for e in valid_configs}.values())
66 | return new_valid_configs
67 | else:
68 | choice = random.choice(valid_configs)
69 | return int(choice['tls_version']), choice['cipher'], choice['jarm']
70 |
71 |
72 | def start_server(version, cipher, jarm):
73 | """
74 | Start the Proxy Server on the desired network and port.
75 | """
76 | httpd = HTTPServer((config.ip, config.port), proxy_handler.ProxyHandler)
77 | httpd.socket = ssl.wrap_socket(
78 | sock=httpd.socket,
79 | keyfile=config.keyfile,
80 | certfile=config.certfile,
81 | server_side=True,
82 | ssl_version=version,
83 | ciphers=cipher
84 | )
85 | print(f"\n[x] Selected configs: TLS -> {version}, Cipher -> {cipher}, JARM -> {jarm}")
86 | if config.cycle_jarms:
87 | print(f"[x] Cycle mode selected: server running on https://{config.ip}:{config.port} for {config.cycle_interval_secs} secs")
88 | httpd.server_activate()
89 | thread = Thread(target=proxy_handler.serve_forever, args=(httpd,))
90 | thread.setDaemon(True)
91 | thread.start()
92 | return httpd
93 | else:
94 | print(f"[x] Server running on https://{config.ip}:{config.port} forever...")
95 | httpd.serve_forever()
96 |
97 |
98 | def main():
99 | """
100 | Check the supplied configurations and start the Proxy Server.
101 | """
102 | if config.cycle_jarms:
103 | while True:
104 | all_configs = grab_valid_configs(return_all=True)
105 | random.shuffle(all_configs)
106 | for ssl_config in all_configs:
107 | httpd = start_server(ssl_config['tls_version'], ssl_config['cipher'], ssl_config['jarm'])
108 | time.sleep(config.cycle_interval_secs)
109 | httpd.shutdown()
110 | time.sleep(3)
111 | else:
112 | tls_version, cipher, jarm = grab_valid_configs()
113 | start_server(tls_version, cipher, jarm)
114 |
115 |
116 | if __name__ == '__main__':
117 | main()
118 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "a710f2da56ceb0319e1f48d04396e72b8ee4d65a1c8426fd4e50d97cd9a2bcec"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.9"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "certifi": {
20 | "hashes": [
21 | "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
22 | "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
23 | ],
24 | "markers": "python_version >= '3.6'",
25 | "version": "==2022.6.15"
26 | },
27 | "chardet": {
28 | "hashes": [
29 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
30 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
31 | ],
32 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
33 | "version": "==4.0.0"
34 | },
35 | "click": {
36 | "hashes": [
37 | "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
38 | "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
39 | ],
40 | "markers": "python_version >= '3.7'",
41 | "version": "==8.1.3"
42 | },
43 | "click-plugins": {
44 | "hashes": [
45 | "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b",
46 | "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"
47 | ],
48 | "version": "==1.1.1"
49 | },
50 | "colorama": {
51 | "hashes": [
52 | "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da",
53 | "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"
54 | ],
55 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
56 | "version": "==0.4.5"
57 | },
58 | "configparser": {
59 | "hashes": [
60 | "sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa",
61 | "sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d"
62 | ],
63 | "markers": "python_version >= '3.6'",
64 | "version": "==5.2.0"
65 | },
66 | "idna": {
67 | "hashes": [
68 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
69 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
70 | ],
71 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
72 | "version": "==2.10"
73 | },
74 | "pybinaryedge": {
75 | "hashes": [
76 | "sha256:00cf2f253aa44c7d6589a56d70c5b820a5060c3a0a2aee018f0b4ed732fe7632"
77 | ],
78 | "index": "pypi",
79 | "version": "==0.5"
80 | },
81 | "pyjarm": {
82 | "hashes": [
83 | "sha256:73110c42e65ce21944d18ebf0639d1ade3d36ef36b416d4577cf83574fce6541",
84 | "sha256:c955396901e59710d61b1cb7e442735fe9840ed6d8dd87ffba8cef159cc083c4"
85 | ],
86 | "index": "pypi",
87 | "version": "==0.0.5"
88 | },
89 | "requests": {
90 | "hashes": [
91 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
92 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
93 | ],
94 | "index": "pypi",
95 | "version": "==2.25.1"
96 | },
97 | "shodan": {
98 | "hashes": [
99 | "sha256:7e2bddbc1b60bf620042d0010f4b762a80b43111dbea9c041d72d4325e260c23"
100 | ],
101 | "index": "pypi",
102 | "version": "==1.25.0"
103 | },
104 | "urllib3": {
105 | "hashes": [
106 | "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
107 | "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
108 | ],
109 | "index": "pypi",
110 | "version": "==1.26.5"
111 | },
112 | "xlsxwriter": {
113 | "hashes": [
114 | "sha256:df0aefe5137478d206847eccf9f114715e42aaea077e6a48d0e8a2152e983010",
115 | "sha256:e89f4a1d2fa2c9ea15cde77de95cd3fd8b0345d0efb3964623f395c8c4988b7f"
116 | ],
117 | "markers": "python_version >= '3.4'",
118 | "version": "==3.0.3"
119 | }
120 | },
121 | "develop": {}
122 | }
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # JARM Randomizer
4 |
5 |
6 |
7 | ## Introduction
8 |
9 | JARM Randomizer is a Python3 tool that iterates over supported server side TLS version and Cipher suites to defeat JARM based fingerprinting.
10 | This tool was open sourced as part of [JARM Randomizer: Evading JARM Fingerprinting](https://conference.hitb.org/hitbsecconf2021ams/sessions/commsec-jarm-randomizer-evading-jarm-fingerprinting/ "JARM Randomizer: Evading JARM Fingerprinting") for HiTB Amsterdam 2021.
11 |
12 | ## Setup
13 |
14 | ### Dependencies
15 |
16 | This tool relies on the following to be installed on the system:
17 |
18 | - [pipenv](https://pypi.org/project/pipenv/)
19 | - [Python 3.9](https://docs.python.org/3/whatsnew/3.9.html)
20 | - [openssl](https://www.openssl.org/)
21 |
22 | Once these dependencies has been installed, run `pipenv install` in the root directory to setup the virtual environment and install the required dependencies.
23 |
24 | ## Usage
25 |
26 | ### Configuration file
27 |
28 | The `config.py` file present in the root directory can be modified to match the desired configurations.
29 |
30 | To set what network/port you would like the proxy to serve, change these configurations:
31 |
32 | ```python
33 | # Configurations for the proxy
34 | ip = '0.0.0.0'
35 | port = 8443
36 | ```
37 |
38 | To set what private key and SSL/TLS certificate path, change these configurations:
39 |
40 | ```python
41 | # Path to the TLS private key and cert files
42 | keyfile = 'key.pem'
43 | certfile = "cert.pem"
44 | ```
45 |
46 | During the setup process, the proxy will read in and output certain required files.
47 | To tweak these file paths (Possibly to output them all in an `output` directory), change these configurations:
48 |
49 | ```python
50 | # File path settings
51 | paths = {
52 | 'possible_jarms': './possible_jarms.json', # The output file that has the possible JARMs, calid TLS - Cipher pairs, and general stats of occurence on the internet.
53 | 'valid_configs': './valid_configs.json', # A lighter version of the possible_jarms.json that just has the TLS - Cipher pairs.
54 | 'invalid_configs': './invalid_configs.json', # A list of TLS - Cipher pairs that the proxy can not support.
55 | 'raw_jarm_stats': './raw_jarm_stats.json', # A raw dump of everything the proxy found during setup. Helpful for debugging and research.
56 | 'red_team_tool_jarms': './red_team_tool_jarms.json', # An input file contianing JARMs for red team tools that was mapped from https://github.com/cedowens/C2-JARM.
57 | }
58 | ```
59 |
60 | If these API keys are provided, the setup process will also check the possible JARMs for occurence on the internet.
61 |
62 | ```python
63 | # API keys for grabbing JARM metrics
64 | SHODAN_KEY = ''
65 | BINARY_EDGE_KEY = ''
66 | ```
67 |
68 | If there is a specific TLS - Cipher pair that is desired, change these configurations to match.
69 |
70 | ```python
71 | # If there is a specific config that is required based on the stats/preference.
72 | # For now, you only have the option to set both or none...will come back and change this
73 | force_ssl_version = None # E.g., 2
74 | force_cipher = None # E.g., 'ECDHE-RSA-CHACHA20-POLY1305'
75 | ```
76 |
77 | If you would like to cycle through the TLS - Cipher pairs that are supported on a system, change these configurations to match.
78 |
79 | ```python
80 | # Cycle JARM configs at certain interval. Avoid setting this and the force_ssl_version and
81 | # force_cipher above. That would not work too well
82 | cycle_jarms = True
83 | cycle_interval_secs = 5
84 | ```
85 |
86 | ### Grabbing valid configurations
87 |
88 | Once the `config.py` has been tweaked to match the desired configurations, run the following command to setup the proxy
89 |
90 | ```bash
91 | ubuntu@jarm:~/jarm_randomizer$ chmod u+x ./setup.sh && ./setup.sh
92 |
93 | [x] Grabbing the list of ciphers that are supported on this system
94 | [x] Running setup.py to grab the valid JARMs
95 | [X] Finding all the possible JARMs
96 | [x] Validating tls 2 and cipher AES128-GCM-SHA256
97 | 127.0.0.1 - - [11/May/2021 17:32:25] "GET /http://google.com HTTP/1.1" 200 -
98 | [x] Validating tls 2 and cipher AES128-SHA
99 | ...
100 | [X] There are 27 possible JARMS across 70 TLS - Cipher pairs
101 | [X] Grabbing the metrics for the JARMs...might take a while for long list of JARMs
102 | [X] Writing the results to disk
103 | [x] Proxy is ready to use
104 | [x] Run python3 ./main.py to start the proxy server
105 | ```
106 |
107 | If there is no private key and certificate at the specified file paths, this script will generate a self signed certificate for testing purposes.
108 |
109 | ### Running Proxy
110 |
111 | Once the `config.py` has been updated to match the desire configurations, and `setup.sh` has been run, the proxy is ready to use.
112 |
113 | Run the following command to start running the proxy
114 |
115 | ```bash
116 | ubuntu@jarm:~/jarm_randomizer$ pipenv run python3 ./main.py
117 |
118 | [x] Selected configs: TLS -> 5, Cipher -> ECDHE-RSA-CHACHA20-POLY1305, JARM -> 3fd3fd0003fd3fd0003fd3fd3fd3fd02098c5f1b1aef82f7daaf9fed36c4e8
119 | [x] Server running on https://0.0.0.0:8443 forever...
120 | ```
121 |
122 | ### Testing the proxy
123 |
124 | If we run the following command, we should recieve a valid proxied response
125 |
126 | ```bash
127 | ubuntu@jarm:~$ curl -k https://127.0.0.1:8443/http://google.com
128 | ```
129 |
130 | In the above example add `-k` if the certificate was self signed.
131 |
132 | The `proxy_handler.py` file contains logic to handle the specific proxy request.
133 | If other proxy specific changes are required, change this script to match.
134 |
135 | ## Future Improvements
136 |
137 | We have ongoing research to identify areas of improvment around JARM randomizer including:
138 |
139 | - Scale to generate larger list of signatures
140 | - Ability to mimic a targeted server’s JARM
141 | - Dig deeper into the extensions
142 | - Stick sessions to not rotate configuration based on IP address
143 |
144 | For the latest research around JARM Randomizer, check out our [Blog](https://www.netskope.com/blog/category/netskope-threat-labs "Netskope Threat Labs Blog")
145 |
146 | ## Feedback
147 |
148 | Any and all feedback around JARM Randomizer are welcome:
149 |
150 | - [Twitter](https://twitter.com/dagmulu)
151 | - [Linkedin](https://www.linkedin.com/in/dmulugeta)
152 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | """
4 | Copyright 2021 Netskope, Inc.
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6 | following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
9 | disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
15 | products derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | Written by Dagmawi Mulugeta
26 | """
27 |
28 | import ssl
29 | import time
30 | import json
31 | import random
32 | import requests
33 | import urllib3
34 | import shodan
35 |
36 | import config
37 | import proxy_handler
38 |
39 | from pybinaryedge import BinaryEdge
40 | from jarm.scanner.scanner import Scanner
41 | from http.server import HTTPServer
42 | from threading import Thread
43 |
44 | urllib3.disable_warnings()
45 |
46 |
47 | def start_test_server(version, cipher):
48 | """
49 | Start a test server on a random port to verify the TLS handshake
50 | """
51 | test_port = random.randint(8000, 9999)
52 | httpd = HTTPServer((config.ip, test_port), proxy_handler.ProxyHandler)
53 | httpd.socket = ssl.wrap_socket(
54 | sock=httpd.socket,
55 | keyfile=config.keyfile,
56 | certfile=config.certfile,
57 | server_side=True,
58 | ssl_version=version,
59 | ciphers=cipher
60 | )
61 | httpd.server_activate()
62 | thread = Thread(target=proxy_handler.serve_forever, args=(httpd,))
63 | thread.setDaemon(True)
64 | thread.start()
65 | return test_port, httpd
66 |
67 |
68 | def grab_unvalidated_ciphers():
69 | """
70 | Load the cipher list
71 | """
72 | with open('./ciphers', 'r') as _file:
73 | return sorted([line.strip() for line in _file])
74 |
75 |
76 | def validate_working_configs():
77 | """
78 | Load the cipher list after openssl has ran.
79 | Then test the configurations the proxy can support.
80 | """
81 | blocklist = {}
82 | jarms = []
83 | for version in config.ssl_versions:
84 | c_list = grab_unvalidated_ciphers()
85 | for cipher in c_list:
86 | print(f"[x] Validating tls {version} and cipher {cipher}")
87 | try:
88 | test_port, httpd = start_test_server(version, cipher)
89 | x = requests.get(f'https://{config.ip}:{test_port}/http://google.com', verify=False)
90 |
91 | jarms.append({
92 | 'tls_version': version,
93 | 'cipher': cipher,
94 | 'jarm': Scanner.scan(config.ip, test_port)[0],
95 | })
96 | time.sleep(1)
97 | httpd.shutdown()
98 | except Exception as e:
99 | blocklist[f'{version}-{cipher}'] = str(e)
100 | if httpd:
101 | httpd.shutdown()
102 | return jarms, blocklist
103 |
104 |
105 | def get_key(obj, key, default=None):
106 | """
107 | Utility method to grab nested key
108 | """
109 | try:
110 | result = obj[key.split('.')[0]]
111 | for k in key.split('.')[1:]:
112 | result = result[k]
113 | except Exception as e:
114 | result = default
115 | return result
116 |
117 |
118 | def query_shodan(jarm):
119 | """
120 | Query Shodan and parse the results
121 | """
122 | raw_result = shodan.Shodan(config.SHODAN_KEY).search(f'ssl.jarm:{jarm}')
123 | cert_orgs = set()
124 | isps = set()
125 | cipher_versions = set()
126 | cipher_names = set()
127 | hostnames = set()
128 | domains = set()
129 | orgs = set()
130 | cloud_providers = set()
131 | servers = set()
132 |
133 | for r in raw_result['matches']:
134 | cert_orgs.add(get_key(r, 'ssl.cert.subject.O', default=''))
135 | cipher_versions.add(get_key(r, 'ssl.cipher.version', default=''))
136 | cipher_names.add(get_key(r, 'ssl.cipher.name', default=''))
137 | orgs.add(get_key(r, 'org', default=''))
138 | isps.add(get_key(r, 'isp', default=''))
139 | cloud_providers.add(get_key(r, 'cloud.provider', default=''))
140 | hostnames.update(get_key(r, 'hostnames', default=[]))
141 | domains.update(get_key(r, 'domains', default=[]))
142 |
143 | if 'data' in r and len(r['data'].split('Server: ')) > 1:
144 | servers.add(r['data'].split('Server: ')[1].split('\r\n')[0])
145 |
146 | parsed_results = {
147 | 'total': raw_result['total'],
148 | 'cert_orgs': list(cert_orgs),
149 | 'cipher_versions': list(cipher_versions),
150 | 'cipher_names': list(cipher_names),
151 | 'hostnames': list(hostnames),
152 | 'domains': list(domains),
153 | 'orgs': list(orgs),
154 | 'isps': list(isps),
155 | 'cloud_providers': list(cloud_providers),
156 | 'servers': sorted(list(servers))
157 | }
158 | return raw_result, parsed_results
159 |
160 |
161 | def query_binary_edge(jarm):
162 | """
163 | Query Binary Edge and parse the results
164 | """
165 | raw_result = BinaryEdge(config.BINARY_EDGE_KEY).host_search(f'jarm.jarm_hash:"{jarm}"')
166 | ips = set()
167 | ports = set()
168 | protocols = set()
169 |
170 | for r in raw_result['events']:
171 | ips.add(get_key(r, 'target.ip', default=''))
172 | ports.add(get_key(r, 'target.port', default=''))
173 | protocols.add(get_key(r, 'target.protocol', default=''))
174 |
175 | parsed_results = {
176 | 'total': raw_result['total'],
177 | 'ips': list(ips),
178 | 'ports': list(ports),
179 | 'protocols': list(protocols),
180 | }
181 | return raw_result, parsed_results
182 |
183 |
184 | def check_red_team_usage(jarm):
185 | """
186 | Checks the JARM against a list of C2 tools here: https://github.com/cedowens/C2-JARM
187 | """
188 | with open(config.paths['red_team_tool_jarms']) as _file:
189 | tools = json.load(_file)
190 | usage = []
191 | for tool in tools:
192 | if jarm == tool['jarm']:
193 | usage.append(tool)
194 | return usage
195 |
196 |
197 | def grab_stats_for_jarms(jarms):
198 | """
199 | Grab the occurence of JARM on the internet and red team tool usage
200 | """
201 | stats = {}
202 | for jarm in jarms:
203 | red_team_usage = check_red_team_usage(jarm['jarm'])
204 | stats[jarm['jarm']] = {
205 | 'raw': {
206 | 'binary_edge': {},
207 | 'shodan': {}
208 | },
209 | 'red_team_usage': red_team_usage,
210 | 'parsed_be_results': {},
211 | 'parsed_shodan_results': {}
212 | }
213 | if config.BINARY_EDGE_KEY:
214 | raw_be_result, parsed_be_results = query_binary_edge(jarm['jarm'])
215 | stats[jarm['jarm']]['raw']['binary_edge'] = raw_be_result
216 | stats[jarm['jarm']]['parsed_be_results'] = parsed_be_results
217 |
218 | if config.SHODAN_KEY:
219 | raw_shodan_result, parsed_shodan_results = query_shodan(jarm=jarm['jarm'])
220 | stats[jarm['jarm']]['raw']['shodan'] = raw_shodan_result
221 | stats[jarm['jarm']]['parsed_shodan_results'] = parsed_shodan_results
222 | return stats
223 |
224 |
225 | def reformat_possible_configs(jarms, stats):
226 | """
227 | Reformat the data into useful stats and configs
228 | """
229 | possible_jarms = {}
230 | valid_configs = []
231 | for entry in jarms:
232 | jarm = entry['jarm']
233 | tls_version = entry['tls_version']
234 | cipher = entry['cipher']
235 | valid_configs.append({
236 | 'jarm': jarm,
237 | 'tls_version': tls_version,
238 | 'cipher': cipher,
239 | })
240 | if jarm in possible_jarms:
241 | possible_jarms[jarm]['configs'].append({
242 | 'tls_version': tls_version,
243 | 'cipher': cipher
244 | })
245 | else:
246 | possible_jarms[jarm] = {
247 | 'configs': [
248 | {
249 | 'tls_version': tls_version,
250 | 'cipher': cipher
251 | }
252 | ],
253 | 'binary_edge': stats[jarm]['parsed_be_results'],
254 | 'shodan': stats[jarm]['parsed_shodan_results'],
255 | 'red_team_usage': stats[jarm]['red_team_usage'],
256 | }
257 | return valid_configs, possible_jarms
258 |
259 |
260 | def write_results_and_configs(jarms, blocklist, stats):
261 | """
262 | Write the configurations and stats to disk
263 | """
264 | with open(config.paths['invalid_configs'], 'w') as _file:
265 | json.dump(blocklist, _file, indent=2)
266 |
267 | with open(config.paths['raw_jarm_stats'], 'w') as _file:
268 | json.dump(stats, _file, indent=2)
269 |
270 | valid_configs, possible_jarms = reformat_possible_configs(jarms, stats)
271 |
272 | with open(config.paths['possible_jarms'], 'w') as _file:
273 | json.dump(possible_jarms, _file, indent=2)
274 |
275 | with open(config.paths['valid_configs'], 'w') as _file:
276 | json.dump(valid_configs, _file, indent=2)
277 |
278 |
279 | def main():
280 | """
281 | Perform the required setup to run the proxy
282 | """
283 |
284 | print("[X] Finding all the possible JARMs")
285 | jarms, blocklist = validate_working_configs()
286 | print(f"[X] There are {len(set(j['jarm'] for j in jarms))} possible JARMS across {len(jarms)} TLS - Cipher pairs")
287 |
288 | print("[X] Grabbing the metrics for the JARMs...might take a while for long list of JARMs")
289 | stats = grab_stats_for_jarms(jarms)
290 |
291 | print("[X] Writing the results to disk")
292 | write_results_and_configs(jarms, blocklist, stats)
293 |
294 |
295 | if __name__ == '__main__':
296 | main()
297 |
--------------------------------------------------------------------------------