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.
- tlslibhunter-0.1.0/LICENSE +21 -0
- tlslibhunter-0.1.0/MANIFEST.in +3 -0
- tlslibhunter-0.1.0/PKG-INFO +119 -0
- tlslibhunter-0.1.0/README.md +81 -0
- tlslibhunter-0.1.0/pyproject.toml +13 -0
- tlslibhunter-0.1.0/setup.cfg +4 -0
- tlslibhunter-0.1.0/setup.py +67 -0
- tlslibhunter-0.1.0/tests/test_classifier.py +65 -0
- tlslibhunter-0.1.0/tests/test_cli.py +45 -0
- tlslibhunter-0.1.0/tests/test_config.py +61 -0
- tlslibhunter-0.1.0/tests/test_encoding.py +45 -0
- tlslibhunter-0.1.0/tests/test_fingerprints.py +137 -0
- tlslibhunter-0.1.0/tests/test_output_formatters.py +110 -0
- tlslibhunter-0.1.0/tests/test_platform_overrides.py +125 -0
- tlslibhunter-0.1.0/tests/test_tls_indicators.py +89 -0
- tlslibhunter-0.1.0/tlsLibHunter.egg-info/PKG-INFO +119 -0
- tlslibhunter-0.1.0/tlsLibHunter.egg-info/SOURCES.txt +59 -0
- tlslibhunter-0.1.0/tlsLibHunter.egg-info/dependency_links.txt +1 -0
- tlslibhunter-0.1.0/tlsLibHunter.egg-info/entry_points.txt +2 -0
- tlslibhunter-0.1.0/tlsLibHunter.egg-info/requires.txt +3 -0
- tlslibhunter-0.1.0/tlsLibHunter.egg-info/top_level.txt +1 -0
- tlslibhunter-0.1.0/tlslibhunter/__init__.py +24 -0
- tlslibhunter-0.1.0/tlslibhunter/__main__.py +6 -0
- tlslibhunter-0.1.0/tlslibhunter/about.py +6 -0
- tlslibhunter-0.1.0/tlslibhunter/backends/__init__.py +27 -0
- tlslibhunter-0.1.0/tlslibhunter/backends/base.py +140 -0
- tlslibhunter-0.1.0/tlslibhunter/backends/frida_backend.py +176 -0
- tlslibhunter-0.1.0/tlslibhunter/cli.py +135 -0
- tlslibhunter-0.1.0/tlslibhunter/config.py +71 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/__init__.py +1 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/android_extractor.py +159 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/base.py +49 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/disk_extractor.py +54 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/ios_extractor.py +118 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/memory_extractor.py +131 -0
- tlslibhunter-0.1.0/tlslibhunter/extractor/strategy.py +106 -0
- tlslibhunter-0.1.0/tlslibhunter/hunter.py +212 -0
- tlslibhunter-0.1.0/tlslibhunter/output/__init__.py +19 -0
- tlslibhunter-0.1.0/tlslibhunter/output/json_formatter.py +19 -0
- tlslibhunter-0.1.0/tlslibhunter/output/plain_formatter.py +48 -0
- tlslibhunter-0.1.0/tlslibhunter/output/table_formatter.py +113 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/__init__.py +1 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/android.py +57 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/base.py +34 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/detection.py +40 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/ios.py +22 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/linux.py +25 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/macos.py +21 -0
- tlslibhunter-0.1.0/tlslibhunter/platforms/windows.py +93 -0
- tlslibhunter-0.1.0/tlslibhunter/scanner/__init__.py +1 -0
- tlslibhunter-0.1.0/tlslibhunter/scanner/classifier.py +150 -0
- tlslibhunter-0.1.0/tlslibhunter/scanner/fingerprints.py +209 -0
- tlslibhunter-0.1.0/tlslibhunter/scanner/module_scanner.py +219 -0
- tlslibhunter-0.1.0/tlslibhunter/scanner/results.py +88 -0
- tlslibhunter-0.1.0/tlslibhunter/scanner/tls_indicators.py +163 -0
- tlslibhunter-0.1.0/tlslibhunter/scripts/extractor_agent.js +137 -0
- tlslibhunter-0.1.0/tlslibhunter/scripts/scanner_agent.js +169 -0
- tlslibhunter-0.1.0/tlslibhunter/utils/__init__.py +1 -0
- tlslibhunter-0.1.0/tlslibhunter/utils/adb.py +87 -0
- tlslibhunter-0.1.0/tlslibhunter/utils/encoding.py +50 -0
- 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,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,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")
|