mobile-re-mcp 1.2.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 (45) hide show
  1. mobile_re_mcp-1.2.0/.gitignore +48 -0
  2. mobile_re_mcp-1.2.0/LICENSE +23 -0
  3. mobile_re_mcp-1.2.0/NOTICE.md +4 -0
  4. mobile_re_mcp-1.2.0/PKG-INFO +72 -0
  5. mobile_re_mcp-1.2.0/README.md +26 -0
  6. mobile_re_mcp-1.2.0/pyproject.toml +57 -0
  7. mobile_re_mcp-1.2.0/src/mobile_re_mcp/__init__.py +11 -0
  8. mobile_re_mcp-1.2.0/src/mobile_re_mcp/__main__.py +90 -0
  9. mobile_re_mcp-1.2.0/src/mobile_re_mcp/config.py +38 -0
  10. mobile_re_mcp-1.2.0/src/mobile_re_mcp/config.toml +9 -0
  11. mobile_re_mcp-1.2.0/src/mobile_re_mcp/frida_scripts/__init__.py +1 -0
  12. mobile_re_mcp-1.2.0/src/mobile_re_mcp/frida_scripts/index.py +127 -0
  13. mobile_re_mcp-1.2.0/src/mobile_re_mcp/guard.py +132 -0
  14. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/__init__.py +1 -0
  15. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/device.py +532 -0
  16. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/dynamic.py +614 -0
  17. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/flutter.py +345 -0
  18. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/memory.py +85 -0
  19. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/native.py +491 -0
  20. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/network.py +375 -0
  21. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/patcher.py +377 -0
  22. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/rasp.py +359 -0
  23. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/signing.py +91 -0
  24. mobile_re_mcp-1.2.0/src/mobile_re_mcp/handlers/static.py +616 -0
  25. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/bypass-frida.md +37 -0
  26. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/bypass-playbooks.md +122 -0
  27. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/bypass-root.md +36 -0
  28. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/bypass-ssl.md +25 -0
  29. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/common-mistakes.md +74 -0
  30. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/crypto-patterns.md +91 -0
  31. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/detection-patterns.md +107 -0
  32. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/first-look.md +41 -0
  33. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/framework-fingerprints.md +93 -0
  34. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/master-agent-workflow.md +32 -0
  35. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/rasp-assess.md +58 -0
  36. mobile_re_mcp-1.2.0/src/mobile_re_mcp/knowledge/stuck.md +35 -0
  37. mobile_re_mcp-1.2.0/src/mobile_re_mcp/paths.py +69 -0
  38. mobile_re_mcp-1.2.0/src/mobile_re_mcp/server.py +1186 -0
  39. mobile_re_mcp-1.2.0/src/mobile_re_mcp/setup/__init__.py +1 -0
  40. mobile_re_mcp-1.2.0/src/mobile_re_mcp/setup/check_tools.py +278 -0
  41. mobile_re_mcp-1.2.0/src/mobile_re_mcp/setup/install_tool.py +329 -0
  42. mobile_re_mcp-1.2.0/tests/conftest.py +15 -0
  43. mobile_re_mcp-1.2.0/tests/test_guard.py +85 -0
  44. mobile_re_mcp-1.2.0/tests/test_handlers.py +143 -0
  45. mobile_re_mcp-1.2.0/tests/test_server.py +67 -0
@@ -0,0 +1,48 @@
1
+ # Build output
2
+ dist/
3
+
4
+ # Dependencies
5
+ node_modules/
6
+
7
+ # APK / IPA — never commit binary test files
8
+ *.apk
9
+ *.ipa
10
+ *.aab
11
+
12
+ # apktool / decompiled output folders
13
+ *_decompiled/
14
+ smali*/
15
+ baksmali*/
16
+
17
+ # Environment & secrets
18
+ .env
19
+ .env.*
20
+ *.keystore
21
+ *.jks
22
+
23
+ # OS
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Editor
28
+ .vscode/
29
+ .idea/
30
+ *.swp
31
+ *.swo
32
+
33
+ # Logs
34
+ *.log
35
+ npm-debug.log*
36
+
37
+ # Claude memory (local only)
38
+ .claude/
39
+
40
+ # Python
41
+ __pycache__/
42
+ *.py[cod]
43
+ *.egg-info/
44
+ .venv/
45
+ .pytest_cache/
46
+ .ruff_cache/
47
+ python/dist/
48
+ python/build/
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Serhatcck
4
+ Copyright (c) 2026 FlyPhantomFly
5
+ Copyright (c) 2026 mobile-security-mcp contributors (Python server)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ # Notice
2
+ The Python server in this package is derived from `mobile-security-mcp` 0.1.4 on PyPI (MIT, by "mobile-security-mcp contributors"). Its public repository could not be found, so the source used here is the published 0.1.4 package. The original declares the MIT license but ships no license file or named copyright holder. The MIT notice is kept in `LICENSE` and the original contributors are credited there.
3
+ Changes from 0.1.4: renamed to `mobile-re-mcp`, argument checks on every tool call, radare2 and ripgrep injection fixes, loopback-only SSE with a token for anything else, loopback-only mitmproxy, private scratch folders instead of `/tmp`, SHA-256 pinned downloads, notes stored in the user data folder, `install_tool` off by default, and the SDK pinned below 2.0.
4
+ This project is not affiliated with or endorsed by the original authors.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.5
2
+ Name: mobile-re-mcp
3
+ Version: 1.2.0
4
+ Summary: Android dynamic analysis MCP server: adb, Frida, mitmproxy, apktool, jadx
5
+ Project-URL: Homepage, https://github.com/FlyPhantomFly/mobile-re-mcp
6
+ Project-URL: Repository, https://github.com/FlyPhantomFly/mobile-re-mcp
7
+ Project-URL: Issues, https://github.com/FlyPhantomFly/mobile-re-mcp/issues
8
+ Project-URL: Changelog, https://github.com/FlyPhantomFly/mobile-re-mcp/blob/master/CHANGELOG.md
9
+ Author: FlyPhantomFly, mobile-security-mcp contributors
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ License-File: NOTICE.md
13
+ Keywords: adb,android,apktool,frida,mcp,mitmproxy,mobile-security,model-context-protocol,pentest,reverse-engineering
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Security
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: mcp<2,>=1.30
23
+ Provides-Extra: all
24
+ Requires-Dist: androguard>=3.4.0a1; extra == 'all'
25
+ Requires-Dist: apkid>=2.1.0; extra == 'all'
26
+ Requires-Dist: apkleaks>=2.6.0; extra == 'all'
27
+ Requires-Dist: frida-tools>=12.0.0; extra == 'all'
28
+ Requires-Dist: frida>=16.0.0; extra == 'all'
29
+ Requires-Dist: mitmproxy>=10.0.0; extra == 'all'
30
+ Requires-Dist: quark-engine>=23.0.0; extra == 'all'
31
+ Provides-Extra: analysis
32
+ Requires-Dist: quark-engine>=23.0.0; extra == 'analysis'
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8; extra == 'dev'
35
+ Requires-Dist: ruff>=0.6; extra == 'dev'
36
+ Provides-Extra: frida
37
+ Requires-Dist: frida-tools>=12.0.0; extra == 'frida'
38
+ Requires-Dist: frida>=16.0.0; extra == 'frida'
39
+ Provides-Extra: network
40
+ Requires-Dist: mitmproxy>=10.0.0; extra == 'network'
41
+ Provides-Extra: static
42
+ Requires-Dist: androguard>=3.4.0a1; extra == 'static'
43
+ Requires-Dist: apkid>=2.1.0; extra == 'static'
44
+ Requires-Dist: apkleaks>=2.6.0; extra == 'static'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # mobile-re-mcp (Python)
48
+ The dynamic half of [mobile-re-mcp](https://github.com/FlyPhantomFly/mobile-re-mcp): an MCP server that lets an AI agent drive adb, Frida, mitmproxy, apktool and jadx against a test device. The npm package of the same name does read-only static analysis of APK and IPA files. Use that one first and this one when you need the app running.
49
+ ## Install
50
+ ```bash
51
+ pip install "mobile-re-mcp[all]"
52
+ ```
53
+ The base install is only the MCP server. Extras add the heavy tools: `frida`, `static` (androguard, apkid, apkleaks), `network` (mitmproxy), `analysis` (quark-engine). Missing tools are reported by `check_tools` instead of crashing.
54
+ The command is `mobile-re-mcp-py` so it does not clash with the npm command `mobile-re-mcp`.
55
+ ```bash
56
+ claude mcp add mobile-re-py -- mobile-re-mcp-py
57
+ ```
58
+ Other clients: command `mobile-re-mcp-py`, no arguments (stdio).
59
+ ## Tools
60
+ About 55 tools in six groups: static (apktool, jadx, apkid, apkleaks, androguard), Frida (spawn, attach, inject, memory dump), device (adb, scrcpy), network (mitmproxy, HAR export), patching (smali edits, frida-gadget injection, signing) and native/Flutter/Unity (strings, exports, disassembly, Blutter, Il2CppDumper). `check_tools` shows what is installed.
61
+ ## Safe use
62
+ This server can run commands on your phone and your computer, so treat its tools as dangerous.
63
+ - Use stdio. `--transport sse` listens on 127.0.0.1 only. Any other `--host` needs `MOBILE_RE_MCP_TOKEN` and the client must send it as a bearer token.
64
+ - Set `MOBILE_RE_MCP_ALLOWED_DIRS` (`:`-separated) to keep file arguments inside your workspace. Paths into `~/.ssh`, `~/.config/gh`, shell rc files and similar are always refused.
65
+ - Turn tools off with `MOBILE_RE_MCP_DISABLE_TOOLS=adb_shell,adb_push,adb_pull`. Better still, set those tools to ask first in your client.
66
+ - `install_tool` is off unless `MOBILE_RE_MCP_ALLOW_INSTALL=1`. `mobile-re-mcp-py --setup` still downloads the pinned tools into `~/tools` for you.
67
+ - mitmproxy listens on 127.0.0.1 only. Run `adb reverse tcp:8080 tcp:8080` and point the device proxy at 127.0.0.1:8080.
68
+ - Analyze untrusted apps in a VM. Their strings and symbol names end up in the agent's context.
69
+ ## Files
70
+ Target notes, captures and scratch files live in `~/.local/share/mobile-re-mcp` (`MOBILE_RE_MCP_HOME` to change). Your own settings go in `~/.config/mobile-re-mcp/config.toml`. Drop your own Frida scripts in `~/.local/share/mobile-re-mcp/frida_scripts`; no scripts ship with the package.
71
+ ## Credits
72
+ Derived from `mobile-security-mcp` 0.1.4 (MIT). See NOTICE.md. Licensed MIT.
@@ -0,0 +1,26 @@
1
+ # mobile-re-mcp (Python)
2
+ The dynamic half of [mobile-re-mcp](https://github.com/FlyPhantomFly/mobile-re-mcp): an MCP server that lets an AI agent drive adb, Frida, mitmproxy, apktool and jadx against a test device. The npm package of the same name does read-only static analysis of APK and IPA files. Use that one first and this one when you need the app running.
3
+ ## Install
4
+ ```bash
5
+ pip install "mobile-re-mcp[all]"
6
+ ```
7
+ The base install is only the MCP server. Extras add the heavy tools: `frida`, `static` (androguard, apkid, apkleaks), `network` (mitmproxy), `analysis` (quark-engine). Missing tools are reported by `check_tools` instead of crashing.
8
+ The command is `mobile-re-mcp-py` so it does not clash with the npm command `mobile-re-mcp`.
9
+ ```bash
10
+ claude mcp add mobile-re-py -- mobile-re-mcp-py
11
+ ```
12
+ Other clients: command `mobile-re-mcp-py`, no arguments (stdio).
13
+ ## Tools
14
+ About 55 tools in six groups: static (apktool, jadx, apkid, apkleaks, androguard), Frida (spawn, attach, inject, memory dump), device (adb, scrcpy), network (mitmproxy, HAR export), patching (smali edits, frida-gadget injection, signing) and native/Flutter/Unity (strings, exports, disassembly, Blutter, Il2CppDumper). `check_tools` shows what is installed.
15
+ ## Safe use
16
+ This server can run commands on your phone and your computer, so treat its tools as dangerous.
17
+ - Use stdio. `--transport sse` listens on 127.0.0.1 only. Any other `--host` needs `MOBILE_RE_MCP_TOKEN` and the client must send it as a bearer token.
18
+ - Set `MOBILE_RE_MCP_ALLOWED_DIRS` (`:`-separated) to keep file arguments inside your workspace. Paths into `~/.ssh`, `~/.config/gh`, shell rc files and similar are always refused.
19
+ - Turn tools off with `MOBILE_RE_MCP_DISABLE_TOOLS=adb_shell,adb_push,adb_pull`. Better still, set those tools to ask first in your client.
20
+ - `install_tool` is off unless `MOBILE_RE_MCP_ALLOW_INSTALL=1`. `mobile-re-mcp-py --setup` still downloads the pinned tools into `~/tools` for you.
21
+ - mitmproxy listens on 127.0.0.1 only. Run `adb reverse tcp:8080 tcp:8080` and point the device proxy at 127.0.0.1:8080.
22
+ - Analyze untrusted apps in a VM. Their strings and symbol names end up in the agent's context.
23
+ ## Files
24
+ Target notes, captures and scratch files live in `~/.local/share/mobile-re-mcp` (`MOBILE_RE_MCP_HOME` to change). Your own settings go in `~/.config/mobile-re-mcp/config.toml`. Drop your own Frida scripts in `~/.local/share/mobile-re-mcp/frida_scripts`; no scripts ship with the package.
25
+ ## Credits
26
+ Derived from `mobile-security-mcp` 0.1.4 (MIT). See NOTICE.md. Licensed MIT.
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mobile-re-mcp"
7
+ version = "1.2.0"
8
+ description = "Android dynamic analysis MCP server: adb, Frida, mitmproxy, apktool, jadx"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE", "NOTICE.md"]
13
+ authors = [{ name = "FlyPhantomFly" }, { name = "mobile-security-mcp contributors" }]
14
+ keywords = ["mcp", "model-context-protocol", "android", "frida", "mobile-security", "reverse-engineering", "pentest", "adb", "apktool", "mitmproxy"]
15
+ classifiers = [
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Information Technology",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Security",
23
+ ]
24
+ dependencies = ["mcp>=1.30,<2"]
25
+
26
+ [project.optional-dependencies]
27
+ frida = ["frida>=16.0.0", "frida-tools>=12.0.0"]
28
+ static = ["androguard>=3.4.0a1", "apkid>=2.1.0", "apkleaks>=2.6.0"]
29
+ network = ["mitmproxy>=10.0.0"]
30
+ analysis = ["quark-engine>=23.0.0"]
31
+ all = ["mobile-re-mcp[frida,static,network,analysis]"]
32
+ dev = ["pytest>=8", "ruff>=0.6"]
33
+
34
+ [project.scripts]
35
+ mobile-re-mcp-py = "mobile_re_mcp.__main__:main"
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/FlyPhantomFly/mobile-re-mcp"
39
+ Repository = "https://github.com/FlyPhantomFly/mobile-re-mcp"
40
+ Issues = "https://github.com/FlyPhantomFly/mobile-re-mcp/issues"
41
+ Changelog = "https://github.com/FlyPhantomFly/mobile-re-mcp/blob/master/CHANGELOG.md"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/mobile_re_mcp"]
45
+
46
+ [tool.hatch.build.targets.sdist]
47
+ include = ["src", "tests", "README.md", "LICENSE", "NOTICE.md", "pyproject.toml"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+
52
+ [tool.ruff]
53
+ line-length = 120
54
+ target-version = "py311"
55
+
56
+ [tool.ruff.lint]
57
+ select = ["F", "E9", "B", "S102", "S307", "S602", "S605"]
@@ -0,0 +1,11 @@
1
+ """mobile-re-mcp (Python): dynamic Android analysis MCP server.
2
+
3
+ Derived from mobile-security-mcp 0.1.4 (MIT). See NOTICE.md in the repository.
4
+ """
5
+
6
+ from importlib.metadata import PackageNotFoundError, version
7
+
8
+ try:
9
+ __version__ = version("mobile-re-mcp")
10
+ except PackageNotFoundError: # running from a source checkout
11
+ __version__ = "0+local"
@@ -0,0 +1,90 @@
1
+ """
2
+ Entry point for the mobile-re-mcp MCP server.
3
+
4
+ Usage:
5
+ python -m mobile_re_mcp # stdio transport (default)
6
+ python -m mobile_re_mcp --transport sse --port 8765 # SSE, loopback only
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import asyncio
13
+ import logging
14
+ import sys
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _build_parser() -> argparse.ArgumentParser:
20
+ p = argparse.ArgumentParser(
21
+ prog="mobile-re-mcp-py",
22
+ description="Android dynamic analysis MCP server (Python half of mobile-re-mcp)",
23
+ )
24
+ p.add_argument(
25
+ "--transport",
26
+ choices=["stdio", "sse"],
27
+ default="stdio",
28
+ help="Transport to use (default: stdio)",
29
+ )
30
+ p.add_argument(
31
+ "--host",
32
+ default="127.0.0.1",
33
+ help="Address for SSE transport (default: 127.0.0.1). Anything else needs MOBILE_RE_MCP_TOKEN.",
34
+ )
35
+ p.add_argument(
36
+ "--port",
37
+ type=int,
38
+ default=8765,
39
+ help="Port for SSE transport (default: 8765)",
40
+ )
41
+ p.add_argument(
42
+ "--log-level",
43
+ default="WARNING",
44
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"],
45
+ )
46
+ p.add_argument(
47
+ "--setup",
48
+ action="store_true",
49
+ help="Automatically download and install all missing RE tools into ~/tools",
50
+ )
51
+ return p
52
+
53
+
54
+ def main() -> None:
55
+ """Main entry point — starts the MCP server."""
56
+ parser = _build_parser()
57
+ args = parser.parse_args()
58
+
59
+ logging.basicConfig(
60
+ level=getattr(logging, args.log_level),
61
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
62
+ stream=sys.stderr,
63
+ )
64
+
65
+ if args.setup:
66
+ print("[*] Starting zero-touch automated setup...")
67
+ from mobile_re_mcp.setup.install_tool import install_all_missing_tools
68
+ install_all_missing_tools()
69
+ print("[*] Setup complete! You can now start the MCP server.")
70
+ sys.exit(0)
71
+
72
+ from mobile_re_mcp.paths import ensure_tools_on_path
73
+
74
+ ensure_tools_on_path()
75
+
76
+ # Import here so startup errors surface cleanly
77
+ from mobile_re_mcp.server import create_server
78
+
79
+ server = create_server()
80
+
81
+ if args.transport == "stdio":
82
+ logger.info("Starting mobile-re-mcp over stdio")
83
+ asyncio.run(server.run_stdio())
84
+ else:
85
+ logger.info("Starting mobile-re-mcp SSE on %s:%d", args.host, args.port)
86
+ asyncio.run(server.run_sse(host=args.host, port=args.port))
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
@@ -0,0 +1,38 @@
1
+ """Settings from the packaged defaults, overridden by the user's config file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from mobile_re_mcp.paths import tools_dir, user_config_path
9
+
10
+ _DEFAULTS = Path(__file__).parent / "config.toml"
11
+
12
+
13
+ def _read(path: Path) -> dict:
14
+ try:
15
+ import tomllib
16
+ except ImportError: # Python < 3.11 is not supported, kept for safety
17
+ return {}
18
+ try:
19
+ with open(path, "rb") as fh:
20
+ return tomllib.load(fh)
21
+ except (FileNotFoundError, PermissionError, ValueError):
22
+ return {}
23
+
24
+
25
+ def load_config() -> dict[str, dict[str, Any]]:
26
+ merged = _read(_DEFAULTS)
27
+ for section, values in _read(user_config_path()).items():
28
+ if isinstance(values, dict):
29
+ merged.setdefault(section, {}).update(values)
30
+ return merged
31
+
32
+
33
+ def signer_jar() -> Path:
34
+ """Absolute path to uber-apk-signer. Never resolved against the working directory."""
35
+ configured = str(load_config().get("signing", {}).get("jar_path", "") or "")
36
+ if configured and Path(configured).is_absolute():
37
+ return Path(configured)
38
+ return tools_dir() / "signing" / "uber-apk-signer.jar"
@@ -0,0 +1,9 @@
1
+ # Defaults only. Put your own settings in ~/.config/mobile-re-mcp/config.toml (or $MOBILE_RE_MCP_CONFIG).
2
+ [signing]
3
+ # Absolute path to uber-apk-signer.jar. Empty uses ~/tools/signing/uber-apk-signer.jar
4
+ jar_path = ""
5
+ # Empty keystore uses uber-apk-signer's built-in debug keystore. Passwords given here show up in the process list.
6
+ keystore_path = ""
7
+ keystore_password = ""
8
+ key_alias = ""
9
+ key_password = ""
@@ -0,0 +1 @@
1
+ """Frida script library package — categorized JS scripts for Android RE."""
@@ -0,0 +1,127 @@
1
+ """
2
+ frida_scripts/index.py — Script library index and loader.
3
+
4
+ Provides the script catalogue used by frida_list_scripts() and
5
+ frida_get_script() MCP tools. All scripts live as .js files in this
6
+ same package directory.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ _SCRIPTS_DIR = Path(__file__).parent
14
+
15
+
16
+ def _script_dirs() -> list[Path]:
17
+ """Folders searched for the catalogue's .js files: the user's own first, then the package."""
18
+ from mobile_re_mcp.paths import data_dir
19
+
20
+ return [data_dir() / "frida_scripts", _SCRIPTS_DIR]
21
+
22
+
23
+ def _find(file_name: str) -> Path | None:
24
+ for d in _script_dirs():
25
+ p = d / file_name
26
+ if p.is_file():
27
+ return p
28
+ return None
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Script catalogue
32
+ # ---------------------------------------------------------------------------
33
+
34
+ SCRIPT_CATALOGUE: list[dict] = [
35
+ {
36
+ "name": "ssl_pinning_universal",
37
+ "file": "ssl_pinning_universal.js",
38
+ "category": "ssl_pinning",
39
+ "description": "Universal SSL pinning bypass — OkHttp3/4, TrustKit, Conscrypt, native BoringSSL",
40
+ "tags": ["ssl", "pinning", "okhttp", "trustkit", "conscrypt", "boring"],
41
+ "usage": "frida -U -f <package> -l ssl_pinning_universal.js --no-pause",
42
+ },
43
+ {
44
+ "name": "root_detection_bypass",
45
+ "file": "root_detection_bypass.js",
46
+ "category": "root_detection",
47
+ "description": "Comprehensive root detection bypass — File.exists, Runtime.exec, PackageManager, Build props, RootBeer, native libc stat/access",
48
+ "tags": ["root", "magisk", "supersu", "rootbeer", "libc"],
49
+ "usage": "frida -U -f <package> -l root_detection_bypass.js --no-pause",
50
+ },
51
+ {
52
+ "name": "frida_detection_bypass",
53
+ "file": "frida_detection_bypass.js",
54
+ "category": "frida_detection",
55
+ "description": "Multi-layer Frida self-detection bypass — /proc/maps, port 27042, dlopen, strstr, debug flag",
56
+ "tags": ["frida", "anti-detection", "port", "proc", "dlopen"],
57
+ "usage": "Inject early via frida_spawn; combine with hluda server for RASP-hardened apps",
58
+ },
59
+ {
60
+ "name": "crypto_monitor",
61
+ "file": "crypto_monitor.js",
62
+ "category": "crypto",
63
+ "description": "Monitor all javax.crypto operations — log key material, IV, plaintext, ciphertext, HMAC, hash",
64
+ "tags": ["crypto", "aes", "hmac", "sha", "key", "iv", "cipher"],
65
+ "usage": "frida -U -f <package> -l crypto_monitor.js --no-pause",
66
+ },
67
+ {
68
+ "name": "memory_scan",
69
+ "file": "memory_scan.js",
70
+ "category": "memory",
71
+ "description": "Scan all rw- process memory regions for a UTF-8 string or hex byte pattern",
72
+ "tags": ["memory", "scan", "bytes", "search", "heap"],
73
+ "usage": "Inject then send config: {type:'config', search_string:'my_secret'} or search_hex:'deadbeef'",
74
+ },
75
+ {
76
+ "name": "method_tracer",
77
+ "file": "method_tracer.js",
78
+ "category": "hooks",
79
+ "description": "Trace all methods (or a specific one) on a Java class — log args and return values",
80
+ "tags": ["trace", "hook", "java", "class", "method", "args"],
81
+ "usage": "Inject then send config: {type:'config', class_name:'com.example.Foo', method_name:''}",
82
+ },
83
+ {
84
+ "name": "storage_monitor",
85
+ "file": "storage_monitor.js",
86
+ "category": "storage",
87
+ "description": "Monitor SharedPreferences writes, SQLite queries, FileInputStream/OutputStream, KeyStore",
88
+ "tags": ["storage", "sharedprefs", "sqlite", "file", "keystore"],
89
+ "usage": "frida -U -f <package> -l storage_monitor.js --no-pause",
90
+ },
91
+ {
92
+ "name": "network_monitor",
93
+ "file": "network_monitor.js",
94
+ "category": "network",
95
+ "description": "Monitor OkHttp requests/responses, HttpURLConnection, WebView.loadUrl, DNS lookups",
96
+ "tags": ["network", "http", "okhttp", "webview", "dns", "request"],
97
+ "usage": "frida -U -f <package> -l network_monitor.js --no-pause",
98
+ },
99
+ ]
100
+
101
+ _CATALOGUE_BY_NAME: dict[str, dict] = {s["name"]: s for s in SCRIPT_CATALOGUE}
102
+ _CATALOGUE_BY_CATEGORY: dict[str, list[dict]] = {}
103
+ for _s in SCRIPT_CATALOGUE:
104
+ _CATALOGUE_BY_CATEGORY.setdefault(_s["category"], []).append(_s)
105
+
106
+
107
+ def list_scripts(category: str | None = None) -> list[dict]:
108
+ """Script metadata for scripts that exist on disk, optionally filtered by category."""
109
+ pool = _CATALOGUE_BY_CATEGORY.get(category.lower(), []) if category else SCRIPT_CATALOGUE
110
+ return [m for m in pool if _find(m["file"]) is not None]
111
+
112
+
113
+ def get_categories() -> list[str]:
114
+ """Return all categories that have at least one available script."""
115
+ return sorted({m["category"] for m in list_scripts()})
116
+
117
+
118
+ def get_script_content(name: str) -> str | None:
119
+ """Load and return the JS source for a named script, or None if not found."""
120
+ path = get_script_path(name)
121
+ return path.read_text(encoding="utf-8") if path else None
122
+
123
+
124
+ def get_script_path(name: str) -> Path | None:
125
+ """Return the absolute path to a named script file."""
126
+ meta = _CATALOGUE_BY_NAME.get(name)
127
+ return _find(meta["file"]) if meta else None
@@ -0,0 +1,132 @@
1
+ """Argument checks applied to every tool call before a handler runs.
2
+
3
+ Environment:
4
+ MOBILE_RE_MCP_ALLOWED_DIRS os.pathsep-separated folders that file arguments must stay inside
5
+ MOBILE_RE_MCP_DISABLE_TOOLS comma-separated tool names to turn off
6
+ MOBILE_RE_MCP_ALLOW_INSTALL set to 1 to let the install_tool MCP tool run
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import re
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ # Host-side file arguments, by tool argument name.
17
+ PATH_KEYS = {
18
+ "apk_path", "bundle_path", "capture_path", "gadget_path", "local", "output_apk",
19
+ "output_dir", "output_path", "path", "record_file", "smali_dir", "so_path",
20
+ }
21
+ # Where each of those is written rather than read.
22
+ WRITE_KEYS = {"local", "output_apk", "output_dir", "output_path", "record_file"}
23
+ # Output files must keep one of these suffixes (tool -> key -> suffixes).
24
+ OUTPUT_SUFFIXES = {
25
+ "mitm_export_har": {"output_path": {".har", ".json"}},
26
+ "scrcpy_screenshot": {"output_path": {".png"}},
27
+ "scrcpy_start": {"record_file": {".mp4", ".mkv"}},
28
+ }
29
+
30
+ _SYMBOL = re.compile(r"(0x[0-9a-fA-F]{1,16}|[A-Za-z0-9_.@]{1,200})")
31
+ _PACKAGE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*(:[A-Za-z0-9_.]{1,64})?")
32
+ _SERIAL = re.compile(r"[A-Za-z0-9._:\-]{1,100}")
33
+ _REMOTE_PATH = re.compile(r"/[A-Za-z0-9._/\-]{1,200}")
34
+ _ABIS = {"arm64", "arm", "x86_64", "x86", "arm64-v8a", "armeabi-v7a"}
35
+
36
+ _SENSITIVE_HOME = (
37
+ ".ssh", ".gnupg", ".aws", ".azure", ".kube", ".docker", ".netrc", ".npmrc", ".pypirc",
38
+ ".git-credentials", ".config/gh", ".config/opencode", ".config/git", ".claude", ".claude.json",
39
+ ".bashrc", ".bash_profile", ".bash_login", ".bash_logout", ".profile", ".zshrc", ".zshenv",
40
+ ".zprofile", ".zlogin", ".config/fish", ".local/share/keyrings",
41
+ )
42
+
43
+
44
+ class GuardError(ValueError):
45
+ """Raised when a tool call is refused."""
46
+
47
+
48
+ def _csv(name: str) -> set[str]:
49
+ return {x.strip() for x in os.environ.get(name, "").split(",") if x.strip()}
50
+
51
+
52
+ def disabled_tools() -> set[str]:
53
+ off = _csv("MOBILE_RE_MCP_DISABLE_TOOLS")
54
+ if os.environ.get("MOBILE_RE_MCP_ALLOW_INSTALL") != "1":
55
+ off.add("install_tool")
56
+ return off
57
+
58
+
59
+ def allowed_dirs() -> list[Path]:
60
+ raw = os.environ.get("MOBILE_RE_MCP_ALLOWED_DIRS", "")
61
+ return [Path(p).expanduser().resolve() for p in raw.split(os.pathsep) if p.strip()]
62
+
63
+
64
+ def _inside(path: Path, base: Path) -> bool:
65
+ return path == base or base in path.parents
66
+
67
+
68
+ def check_path(value: Any, key: str, tool: str) -> None:
69
+ if not isinstance(value, str) or not value:
70
+ raise GuardError(f"'{key}' must be a non-empty string")
71
+ if "\x00" in value:
72
+ raise GuardError(f"'{key}' contains a NUL byte")
73
+ if value.startswith("-"):
74
+ raise GuardError(f"'{key}' must not start with '-'")
75
+ resolved = Path(value).expanduser().resolve()
76
+ home = Path.home().resolve()
77
+ for rel in _SENSITIVE_HOME:
78
+ if _inside(resolved, home / rel):
79
+ raise GuardError(f"'{key}' points into a protected location ({rel})")
80
+ if _inside(resolved, Path("/etc")) and key in WRITE_KEYS:
81
+ raise GuardError(f"'{key}' may not write into /etc")
82
+ dirs = allowed_dirs()
83
+ if dirs and not any(_inside(resolved, d) for d in dirs):
84
+ raise GuardError(f"'{key}' is outside MOBILE_RE_MCP_ALLOWED_DIRS")
85
+ suffixes = OUTPUT_SUFFIXES.get(tool, {}).get(key)
86
+ if suffixes and resolved.suffix.lower() not in suffixes:
87
+ raise GuardError(f"'{key}' must end with one of: {', '.join(sorted(suffixes))}")
88
+
89
+
90
+ def validate(tool: str, args: Any) -> dict:
91
+ """Return the checked arguments or raise GuardError."""
92
+ if tool in disabled_tools():
93
+ raise GuardError(
94
+ "install_tool is off by default; set MOBILE_RE_MCP_ALLOW_INSTALL=1 to enable it"
95
+ if tool == "install_tool"
96
+ else f"tool '{tool}' is disabled by MOBILE_RE_MCP_DISABLE_TOOLS"
97
+ )
98
+ if args is None:
99
+ return {}
100
+ if not isinstance(args, dict):
101
+ raise GuardError("arguments must be an object")
102
+ for key in PATH_KEYS & args.keys():
103
+ check_path(args[key], key, tool)
104
+ _match(args, "serial", _SERIAL, "a device serial")
105
+ _match(args, "package", _PACKAGE, "an Android package name")
106
+ if tool == "native_disasm":
107
+ _match(args, "target", _SYMBOL, "a symbol name or 0x offset", required=True)
108
+ if tool == "frida_server_push":
109
+ _match(args, "remote_path", _REMOTE_PATH, "an absolute device path")
110
+ if "abi" in args and args["abi"] not in _ABIS:
111
+ raise GuardError("'abi' must be one of " + ", ".join(sorted(_ABIS)))
112
+ for key in ("port",):
113
+ if key in args:
114
+ try:
115
+ port = int(args[key])
116
+ except (TypeError, ValueError) as exc:
117
+ raise GuardError("'port' must be an integer") from exc
118
+ if not 1024 <= port <= 65535:
119
+ raise GuardError("'port' must be between 1024 and 65535")
120
+ return args
121
+
122
+
123
+ def _match(args: dict, key: str, pattern: re.Pattern, what: str, required: bool = False) -> None:
124
+ if key not in args:
125
+ if required:
126
+ raise GuardError(f"'{key}' is required")
127
+ return
128
+ value = args[key]
129
+ if value in (None, "") and not required:
130
+ return
131
+ if not isinstance(value, str) or not pattern.fullmatch(value):
132
+ raise GuardError(f"'{key}' must be {what}")
@@ -0,0 +1 @@
1
+ """Handlers package — one module per capability domain."""