tlsLibHunter 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. tlslibhunter-0.1.0/LICENSE +21 -0
  2. tlslibhunter-0.1.0/MANIFEST.in +3 -0
  3. tlslibhunter-0.1.0/PKG-INFO +119 -0
  4. tlslibhunter-0.1.0/README.md +81 -0
  5. tlslibhunter-0.1.0/pyproject.toml +13 -0
  6. tlslibhunter-0.1.0/setup.cfg +4 -0
  7. tlslibhunter-0.1.0/setup.py +67 -0
  8. tlslibhunter-0.1.0/tests/test_classifier.py +65 -0
  9. tlslibhunter-0.1.0/tests/test_cli.py +45 -0
  10. tlslibhunter-0.1.0/tests/test_config.py +61 -0
  11. tlslibhunter-0.1.0/tests/test_encoding.py +45 -0
  12. tlslibhunter-0.1.0/tests/test_fingerprints.py +137 -0
  13. tlslibhunter-0.1.0/tests/test_output_formatters.py +110 -0
  14. tlslibhunter-0.1.0/tests/test_platform_overrides.py +125 -0
  15. tlslibhunter-0.1.0/tests/test_tls_indicators.py +89 -0
  16. tlslibhunter-0.1.0/tlsLibHunter.egg-info/PKG-INFO +119 -0
  17. tlslibhunter-0.1.0/tlsLibHunter.egg-info/SOURCES.txt +59 -0
  18. tlslibhunter-0.1.0/tlsLibHunter.egg-info/dependency_links.txt +1 -0
  19. tlslibhunter-0.1.0/tlsLibHunter.egg-info/entry_points.txt +2 -0
  20. tlslibhunter-0.1.0/tlsLibHunter.egg-info/requires.txt +3 -0
  21. tlslibhunter-0.1.0/tlsLibHunter.egg-info/top_level.txt +1 -0
  22. tlslibhunter-0.1.0/tlslibhunter/__init__.py +24 -0
  23. tlslibhunter-0.1.0/tlslibhunter/__main__.py +6 -0
  24. tlslibhunter-0.1.0/tlslibhunter/about.py +6 -0
  25. tlslibhunter-0.1.0/tlslibhunter/backends/__init__.py +27 -0
  26. tlslibhunter-0.1.0/tlslibhunter/backends/base.py +140 -0
  27. tlslibhunter-0.1.0/tlslibhunter/backends/frida_backend.py +176 -0
  28. tlslibhunter-0.1.0/tlslibhunter/cli.py +135 -0
  29. tlslibhunter-0.1.0/tlslibhunter/config.py +71 -0
  30. tlslibhunter-0.1.0/tlslibhunter/extractor/__init__.py +1 -0
  31. tlslibhunter-0.1.0/tlslibhunter/extractor/android_extractor.py +159 -0
  32. tlslibhunter-0.1.0/tlslibhunter/extractor/base.py +49 -0
  33. tlslibhunter-0.1.0/tlslibhunter/extractor/disk_extractor.py +54 -0
  34. tlslibhunter-0.1.0/tlslibhunter/extractor/ios_extractor.py +118 -0
  35. tlslibhunter-0.1.0/tlslibhunter/extractor/memory_extractor.py +131 -0
  36. tlslibhunter-0.1.0/tlslibhunter/extractor/strategy.py +106 -0
  37. tlslibhunter-0.1.0/tlslibhunter/hunter.py +212 -0
  38. tlslibhunter-0.1.0/tlslibhunter/output/__init__.py +19 -0
  39. tlslibhunter-0.1.0/tlslibhunter/output/json_formatter.py +19 -0
  40. tlslibhunter-0.1.0/tlslibhunter/output/plain_formatter.py +48 -0
  41. tlslibhunter-0.1.0/tlslibhunter/output/table_formatter.py +113 -0
  42. tlslibhunter-0.1.0/tlslibhunter/platforms/__init__.py +1 -0
  43. tlslibhunter-0.1.0/tlslibhunter/platforms/android.py +57 -0
  44. tlslibhunter-0.1.0/tlslibhunter/platforms/base.py +34 -0
  45. tlslibhunter-0.1.0/tlslibhunter/platforms/detection.py +40 -0
  46. tlslibhunter-0.1.0/tlslibhunter/platforms/ios.py +22 -0
  47. tlslibhunter-0.1.0/tlslibhunter/platforms/linux.py +25 -0
  48. tlslibhunter-0.1.0/tlslibhunter/platforms/macos.py +21 -0
  49. tlslibhunter-0.1.0/tlslibhunter/platforms/windows.py +93 -0
  50. tlslibhunter-0.1.0/tlslibhunter/scanner/__init__.py +1 -0
  51. tlslibhunter-0.1.0/tlslibhunter/scanner/classifier.py +150 -0
  52. tlslibhunter-0.1.0/tlslibhunter/scanner/fingerprints.py +209 -0
  53. tlslibhunter-0.1.0/tlslibhunter/scanner/module_scanner.py +219 -0
  54. tlslibhunter-0.1.0/tlslibhunter/scanner/results.py +88 -0
  55. tlslibhunter-0.1.0/tlslibhunter/scanner/tls_indicators.py +163 -0
  56. tlslibhunter-0.1.0/tlslibhunter/scripts/extractor_agent.js +137 -0
  57. tlslibhunter-0.1.0/tlslibhunter/scripts/scanner_agent.js +169 -0
  58. tlslibhunter-0.1.0/tlslibhunter/utils/__init__.py +1 -0
  59. tlslibhunter-0.1.0/tlslibhunter/utils/adb.py +87 -0
  60. tlslibhunter-0.1.0/tlslibhunter/utils/encoding.py +50 -0
  61. tlslibhunter-0.1.0/tlslibhunter/utils/process_resolver.py +62 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Baier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include tlslibhunter/scripts *.js
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: tlsLibHunter
3
+ Version: 0.1.0
4
+ Summary: Identifies TLS/SSL libraries in running processes using Frida-based dynamic instrumentation.
5
+ Home-page: https://github.com/fkie-cad/tlsLibHunter
6
+ Author: Daniel Baier
7
+ Author-email: daniel.baier@fkie.fraunhofer.de
8
+ License: GPL-3.0-only
9
+ Project-URL: Source, https://github.com/fkie-cad/tlsLibHunter
10
+ Project-URL: Issues, https://github.com/fkie-cad/tlsLibHunter/issues
11
+ Keywords: frida,ssl,tls,instrumentation,security
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: JavaScript
17
+ Classifier: Topic :: Security
18
+ Classifier: Topic :: Software Development :: Debuggers
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: frida>=16.0.0
23
+ Requires-Dist: frida-tools>=12.0.0
24
+ Requires-Dist: rich
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: keywords
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: project-url
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ # TLSLibHunter
40
+
41
+ Identify and extract TLS/SSL libraries from running processes using dynamic instrumentation.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install tlsLibHunter
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ### CLI Usage
52
+
53
+ ```bash
54
+ # List TLS libraries in a local process
55
+ tlsLibHunter firefox -l
56
+
57
+ # Scan and extract TLS libraries
58
+ tlsLibHunter firefox
59
+
60
+ # Android device
61
+ tlsLibHunter com.example.app -m -l
62
+
63
+ # JSON output
64
+ tlsLibHunter firefox -l -f json
65
+ ```
66
+
67
+ ### Example output:
68
+
69
+ ```bash
70
+ tlslibhunter -m -l Chrome
71
+ INFO: Platform: android
72
+ INFO: Found 324 loaded modules
73
+ INFO: Pattern match in libssl.so: 1 hits
74
+ INFO: Detected: libssl.so (boringssl, system)
75
+ INFO: Pattern match in libmonochrome_64.so: 1 hits
76
+ INFO: Fingerprint: libmonochrome_64.so identified as boringssl
77
+ INFO: Detected: libmonochrome_64.so (boringssl, app)
78
+ INFO: Scan complete: 2 TLS libraries found in 298 modules (8.06s)
79
+ TLS Libraries in 'Chrome' (android)
80
+ ┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
81
+ ┃ # ┃ Library ┃ Type ┃ Class ┃ Size ┃ Path ┃
82
+ ┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
83
+ │ 1 │ libssl.so │ boringssl │ system │ 376.0 KiB │ /apex/com.… │
84
+ │ 2 │ libmonochrome_64.so │ boringssl │ app │ 119.1 MiB │ /data/app/~~NlI… │
85
+ └──────┴─────────────────────┴───────────┴────────┴───────────┴────────────────────────────┘
86
+
87
+ Scanned 298 modules in 8.06s
88
+ ```
89
+
90
+
91
+ ### Python API
92
+
93
+ ```python
94
+ from tlslibhunter import TLSLibHunter
95
+
96
+ # Scan a local process
97
+ hunter = TLSLibHunter("firefox")
98
+ result = hunter.scan()
99
+ for lib in result.libraries:
100
+ print(f"{lib.name} ({lib.library_type}) - {lib.path}")
101
+
102
+ # Scan and extract
103
+ result = hunter.scan()
104
+ extractions = hunter.extract(result, output_dir="./extracted_libs")
105
+ ```
106
+
107
+ ## Features
108
+
109
+ - Memory scanning for TLS string patterns
110
+ - Supports OpenSSL, BoringSSL, GnuTLS, wolfSSL, mbedTLS, NSS, SChannel, SecureTransport
111
+ - Multi-platform: Android, iOS, Windows, Linux, macOS
112
+ - Multiple extraction methods: disk copy, ADB pull, APK extraction, memory dump
113
+ - Clean Python API for programmatic use
114
+ - Backend abstraction (currently only frida but might be extended to other frameworks in the future)
115
+
116
+ ## License
117
+
118
+ MIT
119
+
@@ -0,0 +1,81 @@
1
+ # TLSLibHunter
2
+
3
+ Identify and extract TLS/SSL libraries from running processes using dynamic instrumentation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install tlsLibHunter
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### CLI Usage
14
+
15
+ ```bash
16
+ # List TLS libraries in a local process
17
+ tlsLibHunter firefox -l
18
+
19
+ # Scan and extract TLS libraries
20
+ tlsLibHunter firefox
21
+
22
+ # Android device
23
+ tlsLibHunter com.example.app -m -l
24
+
25
+ # JSON output
26
+ tlsLibHunter firefox -l -f json
27
+ ```
28
+
29
+ ### Example output:
30
+
31
+ ```bash
32
+ tlslibhunter -m -l Chrome
33
+ INFO: Platform: android
34
+ INFO: Found 324 loaded modules
35
+ INFO: Pattern match in libssl.so: 1 hits
36
+ INFO: Detected: libssl.so (boringssl, system)
37
+ INFO: Pattern match in libmonochrome_64.so: 1 hits
38
+ INFO: Fingerprint: libmonochrome_64.so identified as boringssl
39
+ INFO: Detected: libmonochrome_64.so (boringssl, app)
40
+ INFO: Scan complete: 2 TLS libraries found in 298 modules (8.06s)
41
+ TLS Libraries in 'Chrome' (android)
42
+ ┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
43
+ ┃ # ┃ Library ┃ Type ┃ Class ┃ Size ┃ Path ┃
44
+ ┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
45
+ │ 1 │ libssl.so │ boringssl │ system │ 376.0 KiB │ /apex/com.… │
46
+ │ 2 │ libmonochrome_64.so │ boringssl │ app │ 119.1 MiB │ /data/app/~~NlI… │
47
+ └──────┴─────────────────────┴───────────┴────────┴───────────┴────────────────────────────┘
48
+
49
+ Scanned 298 modules in 8.06s
50
+ ```
51
+
52
+
53
+ ### Python API
54
+
55
+ ```python
56
+ from tlslibhunter import TLSLibHunter
57
+
58
+ # Scan a local process
59
+ hunter = TLSLibHunter("firefox")
60
+ result = hunter.scan()
61
+ for lib in result.libraries:
62
+ print(f"{lib.name} ({lib.library_type}) - {lib.path}")
63
+
64
+ # Scan and extract
65
+ result = hunter.scan()
66
+ extractions = hunter.extract(result, output_dir="./extracted_libs")
67
+ ```
68
+
69
+ ## Features
70
+
71
+ - Memory scanning for TLS string patterns
72
+ - Supports OpenSSL, BoringSSL, GnuTLS, wolfSSL, mbedTLS, NSS, SChannel, SecureTransport
73
+ - Multi-platform: Android, iOS, Windows, Linux, macOS
74
+ - Multiple extraction methods: disk copy, ADB pull, APK extraction, memory dump
75
+ - Clean Python API for programmatic use
76
+ - Backend abstraction (currently only frida but might be extended to other frameworks in the future)
77
+
78
+ ## License
79
+
80
+ MIT
81
+
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.ruff]
6
+ target-version = "py38"
7
+ line-length = 120
8
+
9
+ [tool.ruff.lint]
10
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
11
+
12
+ [tool.ruff.lint.isort]
13
+ known-first-party = ["tlslibhunter"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python3
2
+ import importlib.util
3
+ from pathlib import Path
4
+
5
+ from setuptools import find_packages, setup
6
+
7
+ # Paths
8
+ ROOT = Path(__file__).resolve().parent
9
+ PKG = "tlslibhunter"
10
+ ABOUT = ROOT / PKG / "about.py"
11
+ README = ROOT / "README.md"
12
+
13
+ # Load metadata from about.py safely
14
+ spec = importlib.util.spec_from_file_location(f"{PKG}.about", ABOUT)
15
+ about = importlib.util.module_from_spec(spec)
16
+ spec.loader.exec_module(about) # type: ignore[attr-defined]
17
+
18
+ # Long description
19
+ long_description = README.read_text(encoding="utf-8") if README.exists() else ""
20
+
21
+ # Runtime requirements
22
+ install_requires = [
23
+ "frida>=16.0.0",
24
+ "frida-tools>=12.0.0",
25
+ "rich",
26
+ ]
27
+
28
+ setup(
29
+ name="tlsLibHunter",
30
+ version=about.__version__,
31
+ description=("Identifies TLS/SSL libraries in running processes using Frida-based dynamic instrumentation."),
32
+ long_description=long_description,
33
+ long_description_content_type="text/markdown",
34
+ url="https://github.com/fkie-cad/tlsLibHunter",
35
+ author=about.__author__,
36
+ author_email="daniel.baier@fkie.fraunhofer.de",
37
+ license="GPL-3.0-only",
38
+ packages=find_packages(exclude=("tests",)),
39
+ python_requires=">=3.8",
40
+ install_requires=install_requires,
41
+ # Include non-Python assets inside the package
42
+ package_data={
43
+ "tlslibhunter": [
44
+ "scripts/*.js",
45
+ ],
46
+ },
47
+ include_package_data=True,
48
+ classifiers=[
49
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
50
+ "Operating System :: OS Independent",
51
+ "Natural Language :: English",
52
+ "Programming Language :: Python :: 3 :: Only",
53
+ "Programming Language :: JavaScript",
54
+ "Topic :: Security",
55
+ "Topic :: Software Development :: Debuggers",
56
+ ],
57
+ keywords=["frida", "ssl", "tls", "instrumentation", "security"],
58
+ entry_points={
59
+ "console_scripts": [
60
+ "tlsLibHunter=tlslibhunter.cli:main",
61
+ ],
62
+ },
63
+ project_urls={
64
+ "Source": "https://github.com/fkie-cad/tlsLibHunter",
65
+ "Issues": "https://github.com/fkie-cad/tlsLibHunter/issues",
66
+ },
67
+ )
@@ -0,0 +1,65 @@
1
+ """Tests for module classifier."""
2
+
3
+ from tlslibhunter.scanner.classifier import ModuleClassifier
4
+
5
+
6
+ class TestAndroidClassifier:
7
+ def setup_method(self):
8
+ self.clf = ModuleClassifier("android", package_name="com.example.app")
9
+
10
+ def test_system_library(self):
11
+ info = self.clf.classify_module("libssl.so", "/system/lib64/libssl.so")
12
+ assert info["classification"] == "system"
13
+
14
+ def test_app_library_data_app(self):
15
+ info = self.clf.classify_module(
16
+ "libcustom.so",
17
+ "/data/app/~~abc==/com.example.app-xyz==/lib/arm64/libcustom.so",
18
+ )
19
+ assert info["classification"] == "app"
20
+
21
+ def test_apk_inner_is_app(self):
22
+ info = self.clf.classify_module(
23
+ "libcronet.so",
24
+ "/data/app/~~abc==/com.example.app-xyz==/base.apk!/lib/arm64-v8a/libcronet.so",
25
+ )
26
+ assert info["classification"] == "app"
27
+
28
+ def test_scan_worthy_skips_libc(self):
29
+ assert not self.clf.is_scan_worthy("libc.so", "/system/lib64/libc.so")
30
+
31
+ def test_scan_worthy_skips_odex(self):
32
+ assert not self.clf.is_scan_worthy("base.odex", "/data/app/base.odex")
33
+
34
+ def test_scan_worthy_allows_libssl(self):
35
+ assert self.clf.is_scan_worthy("libssl.so", "/system/lib64/libssl.so")
36
+
37
+
38
+ class TestWindowsClassifier:
39
+ def setup_method(self):
40
+ self.clf = ModuleClassifier("windows")
41
+
42
+ def test_system_dll(self):
43
+ info = self.clf.classify_module("kernel32.dll", "C:\\Windows\\System32\\kernel32.dll")
44
+ assert info["classification"] == "system"
45
+
46
+ def test_app_dll(self):
47
+ info = self.clf.classify_module("myapp.dll", "C:\\Program Files\\MyApp\\myapp.dll")
48
+ assert info["classification"] == "app"
49
+
50
+ def test_scan_worthy_skips_ntdll(self):
51
+ assert not self.clf.is_scan_worthy("ntdll.dll", "C:\\Windows\\System32\\ntdll.dll")
52
+
53
+
54
+ class TestLinuxClassifier:
55
+ def setup_method(self):
56
+ self.clf = ModuleClassifier("linux")
57
+
58
+ def test_system_library(self):
59
+ info = self.clf.classify_module("libssl.so.3", "/usr/lib/x86_64-linux-gnu/libssl.so.3")
60
+ assert info["classification"] == "system"
61
+ assert info["library_type"] == "openssl"
62
+
63
+ def test_app_library(self):
64
+ info = self.clf.classify_module("libcustom.so", "/opt/myapp/lib/libcustom.so")
65
+ assert info["classification"] == "app"
@@ -0,0 +1,45 @@
1
+ """Tests for CLI argument parsing."""
2
+
3
+ from tlslibhunter.cli import build_parser
4
+
5
+
6
+ class TestCLIParsing:
7
+ def setup_method(self):
8
+ self.parser = build_parser()
9
+
10
+ def test_mobile_does_not_consume_target(self):
11
+ """Regression test: -m should not steal the TARGET positional arg."""
12
+ args = self.parser.parse_args(["-m", "Chrome", "-l"])
13
+ assert args.target == "Chrome"
14
+ assert args.mobile is True
15
+ assert args.list_only is True
16
+
17
+ def test_mobile_flag_only(self):
18
+ args = self.parser.parse_args(["app", "-m"])
19
+ assert args.target == "app"
20
+ assert args.mobile is True
21
+ assert args.serial is None
22
+
23
+ def test_serial_without_m(self):
24
+ args = self.parser.parse_args(["firefox", "--serial", "ABC"])
25
+ assert args.target == "firefox"
26
+ assert args.serial == "ABC"
27
+
28
+ def test_serial_with_m(self):
29
+ args = self.parser.parse_args(["firefox", "-m", "--serial", "ABC"])
30
+ assert args.target == "firefox"
31
+ assert args.mobile is True
32
+ assert args.serial == "ABC"
33
+
34
+ def test_list_only_with_output(self):
35
+ args = self.parser.parse_args(["firefox", "-l", "-o", "out"])
36
+ assert args.target == "firefox"
37
+ assert args.list_only is True
38
+ assert args.output == "out"
39
+
40
+ def test_basic_target(self):
41
+ args = self.parser.parse_args(["firefox"])
42
+ assert args.target == "firefox"
43
+ assert args.mobile is False
44
+ assert args.serial is None
45
+ assert args.list_only is False
@@ -0,0 +1,61 @@
1
+ """Tests for HunterConfig dataclass."""
2
+
3
+ import warnings
4
+
5
+ from tlslibhunter.config import HunterConfig
6
+
7
+
8
+ class TestHunterConfig:
9
+ def test_defaults(self):
10
+ config = HunterConfig(target="firefox")
11
+ assert config.target == "firefox"
12
+ assert config.backend == "frida"
13
+ assert config.timeout == 10
14
+ assert not config.spawn
15
+ assert not config.list_only
16
+ assert config.mobile is False
17
+ assert config.serial is None
18
+
19
+ def test_is_mobile_true(self):
20
+ config = HunterConfig(target="app", mobile=True)
21
+ assert config.is_mobile
22
+
23
+ def test_is_mobile_serial(self):
24
+ config = HunterConfig(target="app", serial="ABC123")
25
+ assert config.is_mobile
26
+ assert config.device_serial == "ABC123"
27
+
28
+ def test_serial_implies_mobile(self):
29
+ config = HunterConfig(target="app", serial="XYZ")
30
+ assert config.is_mobile
31
+ assert config.device_serial == "XYZ"
32
+
33
+ def test_is_mobile_false(self):
34
+ config = HunterConfig(target="app")
35
+ assert not config.is_mobile
36
+ assert config.device_serial is None
37
+
38
+ def test_mobile_string_deprecation(self):
39
+ with warnings.catch_warnings(record=True) as w:
40
+ warnings.simplefilter("always")
41
+ config = HunterConfig(target="app", mobile="ABC123")
42
+ assert len(w) == 1
43
+ assert issubclass(w[0].category, DeprecationWarning)
44
+ assert "serial" in str(w[0].message).lower()
45
+ # Should have migrated to serial
46
+ assert config.mobile is True
47
+ assert config.serial == "ABC123"
48
+ assert config.is_mobile
49
+ assert config.device_serial == "ABC123"
50
+
51
+ def test_effective_output_dir_default(self):
52
+ config = HunterConfig(target="firefox")
53
+ assert config.effective_output_dir == "./tls_libs_firefox"
54
+
55
+ def test_effective_output_dir_custom(self):
56
+ config = HunterConfig(target="firefox", output_dir="/tmp/out")
57
+ assert config.effective_output_dir == "/tmp/out"
58
+
59
+ def test_effective_output_dir_sanitizes_slashes(self):
60
+ config = HunterConfig(target="com.example/app")
61
+ assert "/" not in config.effective_output_dir.split("tls_libs_")[-1]
@@ -0,0 +1,45 @@
1
+ """Tests for hex pattern encoding utilities."""
2
+
3
+ from tlslibhunter.utils.encoding import ascii_to_hex, build_scan_patterns, utf16le_to_hex
4
+
5
+
6
+ class TestAsciiToHex:
7
+ def test_simple_string(self):
8
+ assert ascii_to_hex("ABC") == "41 42 43"
9
+
10
+ def test_underscore(self):
11
+ assert ascii_to_hex("A_B") == "41 5f 42"
12
+
13
+ def test_client_random(self):
14
+ result = ascii_to_hex("CLIENT_RANDOM")
15
+ assert result.startswith("43 4c 49 45 4e 54")
16
+
17
+ def test_empty_string(self):
18
+ assert ascii_to_hex("") == ""
19
+
20
+
21
+ class TestUtf16leToHex:
22
+ def test_simple_string(self):
23
+ assert utf16le_to_hex("AB") == "41 00 42 00"
24
+
25
+ def test_single_char(self):
26
+ assert utf16le_to_hex("A") == "41 00"
27
+
28
+
29
+ class TestBuildScanPatterns:
30
+ def test_returns_list(self):
31
+ patterns = build_scan_patterns("TEST")
32
+ assert isinstance(patterns, list)
33
+ assert len(patterns) > 0
34
+
35
+ def test_contains_ascii(self):
36
+ patterns = build_scan_patterns("TEST")
37
+ assert ascii_to_hex("TEST") in patterns
38
+
39
+ def test_contains_utf16le(self):
40
+ patterns = build_scan_patterns("TEST")
41
+ assert utf16le_to_hex("TEST") in patterns
42
+
43
+ def test_no_duplicates(self):
44
+ patterns = build_scan_patterns("TEST")
45
+ assert len(patterns) == len(set(patterns))
@@ -0,0 +1,137 @@
1
+ """Tests for TLS library fingerprint identification."""
2
+
3
+ from tlslibhunter.scanner.fingerprints import (
4
+ LIBRARY_FINGERPRINTS,
5
+ fingerprint_library,
6
+ get_all_fingerprint_strings,
7
+ )
8
+
9
+
10
+ class TestFingerprintLibrary:
11
+ def test_boringssl_identified(self):
12
+ lib_type, _ = fingerprint_library(["BoringSSL"])
13
+ assert lib_type == "boringssl"
14
+
15
+ def test_boringssl_over_openssl(self):
16
+ """BoringSSL should win even when OpenSSL strings are present."""
17
+ lib_type, _ = fingerprint_library(["BoringSSL", "OpenSSL 3."])
18
+ assert lib_type == "boringssl"
19
+
20
+ def test_libressl_over_openssl(self):
21
+ """LibreSSL should win even when OpenSSL strings are present."""
22
+ lib_type, _ = fingerprint_library(["LibreSSL", "OpenSSL 1.1."])
23
+ assert lib_type == "libressl"
24
+
25
+ def test_openssl_alone(self):
26
+ lib_type, _ = fingerprint_library(["OpenSSL 3.0.12"])
27
+ assert lib_type == "openssl"
28
+
29
+ def test_gnutls(self):
30
+ lib_type, _ = fingerprint_library(["GnuTLS"])
31
+ assert lib_type == "gnutls"
32
+
33
+ def test_wolfssl(self):
34
+ lib_type, _ = fingerprint_library(["wolfSSL"])
35
+ assert lib_type == "wolfssl"
36
+
37
+ def test_mbedtls(self):
38
+ lib_type, _ = fingerprint_library(["Mbed TLS"])
39
+ assert lib_type == "mbedtls"
40
+
41
+ def test_nss(self):
42
+ lib_type, _ = fingerprint_library(["NSS_GetVersion"])
43
+ assert lib_type == "nss"
44
+
45
+ def test_s2n(self):
46
+ lib_type, _ = fingerprint_library(["s2n_negotiate"])
47
+ assert lib_type == "s2n"
48
+
49
+ def test_matrixssl(self):
50
+ lib_type, _ = fingerprint_library(["matrixssl"])
51
+ assert lib_type == "matrixssl"
52
+
53
+ def test_botan(self):
54
+ lib_type, _ = fingerprint_library(["Botan"])
55
+ assert lib_type == "botan"
56
+
57
+ def test_gotls(self):
58
+ lib_type, _ = fingerprint_library(["crypto/tls"])
59
+ assert lib_type == "gotls"
60
+
61
+ def test_rustls(self):
62
+ lib_type, _ = fingerprint_library(["rustls"])
63
+ assert lib_type == "rustls"
64
+
65
+ def test_unknown_on_no_match(self):
66
+ lib_type, _ = fingerprint_library(["random"])
67
+ assert lib_type == "unknown"
68
+
69
+ def test_empty_input(self):
70
+ lib_type, version = fingerprint_library([])
71
+ assert lib_type == "unknown"
72
+ assert version == ""
73
+
74
+
75
+ class TestVersionExtraction:
76
+ def test_openssl_version(self):
77
+ _, version = fingerprint_library(["OpenSSL 3.1.4"])
78
+ assert version == "3.1.4"
79
+
80
+ def test_openssl_version_with_letter(self):
81
+ _, version = fingerprint_library(["OpenSSL 1.0.2k"])
82
+ assert version == "1.0.2k"
83
+
84
+ def test_libressl_version(self):
85
+ _, version = fingerprint_library(["LibreSSL 3.8.1"])
86
+ assert version == "3.8.1"
87
+
88
+ def test_gnutls_version(self):
89
+ _, version = fingerprint_library(["GnuTLS 3.7.9"])
90
+ assert version == "3.7.9"
91
+
92
+ def test_wolfssl_version(self):
93
+ _, version = fingerprint_library(["wolfSSL 5.6.3"])
94
+ assert version == "5.6.3"
95
+
96
+ def test_mbedtls_version(self):
97
+ _, version = fingerprint_library(["Mbed TLS 3.6.0"])
98
+ assert version == "3.6.0"
99
+
100
+ def test_boringssl_no_version(self):
101
+ """BoringSSL has no version strings by design."""
102
+ _, version = fingerprint_library(["BoringSSL"])
103
+ assert version == ""
104
+
105
+ def test_no_version_when_unknown(self):
106
+ _, version = fingerprint_library(["random_string"])
107
+ assert version == ""
108
+
109
+
110
+ class TestFingerprintDataIntegrity:
111
+ def test_all_fingerprint_strings_unique(self):
112
+ all_strings = get_all_fingerprint_strings()
113
+ assert len(all_strings) == len(set(all_strings))
114
+
115
+ def test_each_fingerprint_has_strings(self):
116
+ for fp in LIBRARY_FINGERPRINTS:
117
+ assert len(fp.fingerprint_strings) > 0, (
118
+ f"{fp.library_type} has no fingerprint strings"
119
+ )
120
+
121
+ def test_each_fingerprint_has_library_type(self):
122
+ for fp in LIBRARY_FINGERPRINTS:
123
+ assert fp.library_type, f"Missing library_type for {fp.display_name}"
124
+
125
+ def test_each_fingerprint_has_display_name(self):
126
+ for fp in LIBRARY_FINGERPRINTS:
127
+ assert fp.display_name, f"Missing display_name for {fp.library_type}"
128
+
129
+ def test_boringssl_before_openssl(self):
130
+ """BoringSSL must be checked before OpenSSL in priority order."""
131
+ types = [fp.library_type for fp in LIBRARY_FINGERPRINTS]
132
+ assert types.index("boringssl") < types.index("openssl")
133
+
134
+ def test_libressl_before_openssl(self):
135
+ """LibreSSL must be checked before OpenSSL in priority order."""
136
+ types = [fp.library_type for fp in LIBRARY_FINGERPRINTS]
137
+ assert types.index("libressl") < types.index("openssl")