# Directory Structure ``` ├── APktool.py ├── gemini.md ├── README.md └── settings.json ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Apktool MCP Server 2 | 3 | [](https://opensource.org/licenses/MIT) 4 | [](https://www.python.org/downloads/) 5 | [](https://modelcontextprotocol.io/) 6 | [](https://github.com/google-gemini/gemini-cli) 7 | 8 | A powerful **Model Context Protocol (MCP) server** that exposes [Apktool](https://ibotpeaches.github.io/Apktool/) functionality for Android APK analysis and reverse engineering. Integrates seamlessly with **Gemini CLI** to provide AI-powered APK security analysis, privacy auditing, and reverse engineering guidance through natural language commands. 9 | 10 | ## 🚀 Features 11 | 12 | ### 🔍 **Comprehensive APK Analysis** 13 | - **Decompile APKs** to extract resources, manifest, and smali code 14 | - **Analyze permissions** and app components for security assessment 15 | - **Extract string resources** and detect hardcoded secrets 16 | - **Search smali code** for specific patterns and security vulnerabilities 17 | - **Recompile modified APKs** after making changes 18 | 19 | ### 🤖 **AI-Powered Workflows** 20 | - **Natural language commands** for complex APK analysis tasks 21 | - **Automated security audits** with AI-generated insights 22 | - **Privacy compliance checking** and GDPR/CCPA analysis 23 | - **Step-by-step reverse engineering** guidance 24 | - **Intelligent vulnerability detection** and risk assessment 25 | 26 | ### 🛠 **8 Core Tools** 27 | | Tool | Description | 28 | |------|-------------| 29 | | `decode_apk` | Decompile APK files to extract all components | 30 | | `build_apk` | Recompile APK from modified source directory | 31 | | `install_framework` | Install system frameworks for system app analysis | 32 | | `analyze_manifest` | Parse AndroidManifest.xml for permissions and components | 33 | | `extract_strings` | Extract string resources with locale support | 34 | | `list_permissions` | Enumerate all requested permissions | 35 | | `find_smali_references` | Search for patterns in decompiled smali code | 36 | | `get_apk_info` | Get basic APK metadata and information | 37 | 38 | ### 📋 **Specialized Analysis Prompts** 39 | - **Security Analysis**: Comprehensive vulnerability assessment 40 | - **Privacy Audit**: Data collection and compliance analysis 41 | - **Reverse Engineering Guide**: Step-by-step analysis workflows 42 | 43 | ## 📦 Installation 44 | 45 | ### Prerequisites 46 | 47 | **1. Java JDK 8+** (Required by Apktool) 48 | ```bash 49 | # Ubuntu/Debian 50 | sudo apt update && sudo apt install default-jdk 51 | 52 | # macOS (Homebrew) 53 | brew install openjdk 54 | 55 | # Verify installation 56 | java -version 57 | ``` 58 | 59 | **2. Apktool** (Core dependency) 60 | ```bash 61 | # Option 1: Package manager (recommended) 62 | # Ubuntu/Debian 63 | sudo apt install apktool 64 | 65 | # macOS 66 | brew install apktool 67 | 68 | # Option 2: Manual installation 69 | # Download from https://ibotpeaches.github.io/Apktool/install/ 70 | 71 | # Verify installation 72 | apktool --version 73 | ``` 74 | 75 | **3. Python 3.10+** 76 | ```bash 77 | python3 --version # Should be 3.10 or higher 78 | ``` 79 | 80 | ### Setup Instructions 81 | 82 | **1. Clone the repository** 83 | ```bash 84 | git clone https://github.com/SecFathy/APktool-MCP.git 85 | cd APktool-MCP 86 | ``` 87 | 88 | **2. Create virtual environment** 89 | ```bash 90 | python3 -m venv venv 91 | source venv/bin/activate # Linux/macOS 92 | # or 93 | venv\Scripts\activate # Windows 94 | ``` 95 | 96 | **3. Install dependencies** 97 | ```bash 98 | pip install -r requirements.txt 99 | ``` 100 | 101 | **4. Test the installation** 102 | ```bash 103 | python3 apktool_server.py 104 | # Should start the MCP server successfully 105 | ``` 106 | 107 | ## ⚙️ Configuration 108 | 109 | ### Gemini CLI Integration 110 | 111 | **1. Install Gemini CLI** 112 | ```bash 113 | # Follow instructions at https://github.com/google-gemini/gemini-cli 114 | ``` 115 | 116 | **2. Configure MCP Server** 117 | 118 | Edit your Gemini CLI configuration file: 119 | - **Linux/macOS**: `~/.config/gemini-cli/config.json` 120 | - **Windows**: `%APPDATA%\gemini-cli\config.json` 121 | 122 | ```json 123 | { 124 | "mcpServers": { 125 | "apktool": { 126 | "command": "python3", 127 | "args": ["/absolute/path/to/apktool_server.py"], 128 | "env": { 129 | "APKTOOL_WORK_DIR": "/path/to/workspace" 130 | } 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | ### Claude Desktop Integration (Alternative) 137 | 138 | Edit Claude Desktop configuration: 139 | - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` 140 | - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 141 | - **Linux**: `~/.config/Claude/claude_desktop_config.json` 142 | 143 | ```json 144 | { 145 | "mcpServers": { 146 | "apktool": { 147 | "command": "python3", 148 | "args": ["/absolute/path/to/apktool_server.py"], 149 | "env": { 150 | "APKTOOL_WORK_DIR": "/path/to/workspace" 151 | } 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | ## 🎯 Usage Examples 158 | 159 | ### Natural Language Commands 160 | 161 | ```bash 162 | # Start Gemini CLI 163 | gemini 164 | 165 | # Security Analysis 166 | > "Analyze the APK at ./suspicious_app.apk for security vulnerabilities" 167 | 168 | # Permission Analysis 169 | > "What permissions does ./myapp.apk request and are any of them dangerous?" 170 | 171 | # Code Analysis 172 | > "Find any hardcoded API keys or secrets in ./social_app.apk" 173 | 174 | # Privacy Audit 175 | > "Generate a privacy compliance report for ./messenger_app.apk" 176 | 177 | # Reverse Engineering 178 | > "Help me understand how the authentication works in ./banking_app.apk" 179 | ``` 180 | 181 | ### Direct Tool Usage 182 | 183 | ```bash 184 | # Decompile an APK 185 | > Use decode_apk to decompile ./sample.apk 186 | 187 | # Analyze permissions 188 | > Use list_permissions on the decompiled directory ./sample 189 | 190 | # Search for patterns 191 | > Use find_smali_references to search for "crypto" in ./sample 192 | 193 | # Extract strings 194 | > Use extract_strings from ./sample for locale "en" 195 | 196 | # Rebuild APK 197 | > Use build_apk to recompile ./sample into ./sample_modified.apk 198 | ``` 199 | 200 | ### Guided Workflows 201 | 202 | ```bash 203 | # Run automated security analysis 204 | > Run the security analysis prompt on ./target_app.apk 205 | 206 | # Perform privacy audit 207 | > Execute privacy audit workflow for ./social_media_app.apk 208 | 209 | # Get reverse engineering guidance 210 | > Use the reverse engineering guide for analyzing login functionality in ./app.apk 211 | ``` 212 | 213 | ## 📁 Project Structure 214 | 215 | ``` 216 | apktool-mcp-server/ 217 | ├── apktool_server.py # Main MCP server implementation 218 | ├── requirements.txt # Python dependencies 219 | ├── config.json # Example Gemini CLI configuration 220 | ├── README.md # This file 221 | ├── GEMINI.md # AI assistant context file 222 | ├── LICENSE # MIT license 223 | ├── examples/ # Usage examples and samples 224 | │ ├── sample_analysis.py # Example analysis scripts 225 | │ └── workflows/ # Common workflow examples 226 | ├── tests/ # Unit tests 227 | │ ├── test_server.py # Server functionality tests 228 | │ └── test_tools.py # Individual tool tests 229 | └── docs/ # Additional documentation 230 | ├── SECURITY.md # Security guidelines 231 | ├── CONTRIBUTING.md # Contribution guidelines 232 | └── TROUBLESHOOTING.md # Common issues and solutions 233 | ``` 234 | 235 | ## 🔒 Security Considerations 236 | 237 | ### ⚠️ **Important Security Notes** 238 | 239 | - **Legal Compliance**: Only analyze APKs you own or have explicit permission to analyze 240 | - **Malware Risk**: Unknown APKs may contain malicious code - use in isolated environments 241 | - **Data Privacy**: Decompiled APKs may contain sensitive user information 242 | - **Workspace Isolation**: Configure dedicated workspace with restricted permissions 243 | - **Process Limits**: Server includes timeouts to prevent resource exhaustion 244 | 245 | ### **Best Practices** 246 | 247 | ```bash 248 | # Use dedicated workspace 249 | export APKTOOL_WORK_DIR="/secure/isolated/workspace" 250 | 251 | # Set appropriate permissions 252 | chmod 750 /secure/isolated/workspace 253 | 254 | # Monitor resource usage 255 | htop # Watch memory and CPU during analysis 256 | 257 | # Clean up after analysis 258 | rm -rf /secure/isolated/workspace/* 259 | ``` 260 | 261 | ## 🧪 Testing 262 | 263 | ### Run Unit Tests 264 | ```bash 265 | # Install test dependencies 266 | pip install pytest pytest-asyncio 267 | 268 | # Run all tests 269 | pytest tests/ 270 | 271 | # Run with coverage 272 | pytest --cov=apktool_server tests/ 273 | ``` 274 | 275 | ### Manual Testing 276 | ```bash 277 | # Test server startup 278 | python3 apktool_server.py 279 | 280 | # Test with sample APK 281 | # Download a sample APK and test basic functionality 282 | ``` 283 | 284 | ### Integration Testing 285 | ```bash 286 | # Test Gemini CLI integration 287 | gemini 288 | > /tools # Should list apktool tools 289 | > Use decode_apk to analyze sample.apk 290 | ``` 291 | 292 | ## 🤝 Contributing 293 | 294 | We welcome contributions! Please see [CONTRIBUTING.md](docs/CONTRIBUTING.md) for details. 295 | 296 | ### Development Setup 297 | ```bash 298 | # Clone and setup development environment 299 | git clone https://github.com/SecFathy/APktool-MCP.git 300 | cd APktool-MCP 301 | python3 -m venv venv 302 | source venv/bin/activate 303 | pip install -r requirements.txt 304 | pip install -r requirements-dev.txt 305 | 306 | # Run tests 307 | pytest 308 | 309 | # Format code 310 | black apktool_server.py 311 | ``` 312 | ``` -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "theme": "Dracula", 3 | "selectedAuthType": "oauth-personal", 4 | "mcpServers": { 5 | "myPythonServer": { 6 | "command": "python", 7 | "args": ["mcp_server.py", "--port", "8080"], 8 | "cwd": "./mcp_tools/python", 9 | "timeout": 5000 10 | }, 11 | "myNodeServer": { 12 | "command": "node", 13 | "args": ["mcp_server.js"], 14 | "cwd": "./mcp_tools/node" 15 | }, 16 | "myDockerServer": { 17 | "command": "docker", 18 | "args": ["run", "i", "--rm", "-e", "API_KEY", "ghcr.io/foo/bar"], 19 | "env": { 20 | "API_KEY": "$MY_API_TOKEN" 21 | } 22 | }, 23 | "apktool": { 24 | "command": "python3", 25 | "args": ["/Users/secfathy/Desktop/Workflow/APktool-MCP/APktool.py"], 26 | "cwd": "/Users/secfathy/Desktop/Workflow/APktool-MCP", 27 | "env": { 28 | "APKTOOL_WORK_DIR": "/Users/secfathy/Desktop/Workflow/APktool-MCP", 29 | "PYTHONPATH": "/Users/secfathy/Desktop/Workflow/APktool-MCP" 30 | }, 31 | "timeout": 30000, 32 | "trust": false 33 | } 34 | } 35 | } 36 | ``` -------------------------------------------------------------------------------- /gemini.md: -------------------------------------------------------------------------------- ```markdown 1 | # Apktool MCP Server: AI-Powered Android APK Analysis 2 | 3 | The **Apktool MCP Server** is a Model Context Protocol (MCP) server designed to expose `Apktool` functionality. It enables AI models, particularly those integrated with the Gemini CLI, to perform advanced Android APK analysis, reverse engineering, and modification workflows. 4 | 5 | ## 🚀 Key Features 6 | 7 | This server bridges the gap between powerful AI capabilities and `Apktool`'s robust set of tools, allowing for automated and intelligent interactions with Android application packages. 8 | 9 | * **Decompilation (`decode_apk`):** Decompile APKs into human-readable resources, AndroidManifest.xml, and Smali code for in-depth analysis. 10 | * **Recompilation (`build_apk`):** Recompile modified Smali code and resources back into an installable APK. 11 | * **Framework Management (`install_framework`):** Install necessary framework APKs for decompiling system applications or specific Android versions. 12 | * **Manifest Analysis (`analyze_manifest`, `list_permissions`):** Extract and analyze crucial information from `AndroidManifest.xml`, including declared permissions, activities, services, and receivers. 13 | * **Resource Extraction (`extract_strings`):** Pull string resources for various locales from decompiled APKs. 14 | * **Smali Code Analysis (`find_smali_references`):** Search for specific patterns, API calls, or sensitive information within the Smali assembly code. 15 | * **APK Information (`get_apk_info`):** Obtain basic metadata about an APK file. 16 | 17 | ## ✨ Integration with Gemini CLI 18 | 19 | This server is designed to be consumed by the Gemini CLI. When connected, the Gemini AI can leverage the exposed Apktool functionalities as "tools" and "resources" to facilitate complex APK analysis tasks. 20 | 21 | ### Exposed Tools (via `list_tools`) 22 | 23 | The following `apktool-mcp` tools are available for the AI to call: 24 | 25 | * `decode_apk`: Decompile an APK. 26 | * `build_apk`: Recompile an APK. 27 | * `install_framework`: Install a framework APK. 28 | * `analyze_manifest`: Analyze `AndroidManifest.xml`. 29 | * `extract_strings`: Extract string resources. 30 | * `list_permissions`: List all requested permissions. 31 | * `find_smali_references`: Search Smali code. 32 | * `get_apk_info`: Get basic APK information. 33 | 34 | ### Exposed Resources (via `list_resources` and `read_resource`) 35 | 36 | The AI can read specific files from decompiled APKs: 37 | 38 | * `apktool://apk/<apk_name>/manifest`: Access the `AndroidManifest.xml` of a decompiled APK. 39 | * `apktool://apk/<apk_name>/apktool_yml`: Access the `apktool.yml` configuration file. 40 | 41 | ### Available Prompts (via `list_prompts` and `get_prompt`) 42 | 43 | Pre-defined prompts guide the AI in common APK analysis scenarios: 44 | 45 | * `analyze_security`: Guides the AI to perform a comprehensive security assessment. 46 | * `privacy_audit`: Directs the AI to audit an APK for privacy-related concerns. 47 | * `reverse_engineer_guide`: Provides step-by-step instructions for reverse engineering a target feature within an APK. 48 | 49 | ## 🛠️ Setup and Running 50 | 51 | ### Prerequisites 52 | 53 | * **Python 3.x:** Ensure you have Python 3 installed. 54 | * **Apktool:** The `apktool` executable must be installed and available in your system's PATH. If not, the server will issue a warning. 55 | * Installation instructions for Apktool can be found here: [https://ibotpeaches.github.io/Apktool/install/](https://ibotpeaches.github.io/Apktool/install/) 56 | * **MCP Library:** Install the Model Context Protocol library: 57 | ```bash 58 | pip install google-mcp 59 | ``` 60 | 61 | ### Running the Server 62 | 63 | 1. **Save the server code:** Save your Python server code (the one you provided) as `apktool_server.py`. 64 | 2. **Run the server:** 65 | ```bash 66 | python apktool_server.py 67 | ``` 68 | The server will run and listen for connections, typically via standard I/O (stdio), which is the default for Gemini CLI integration. 69 | 70 | ## 🚀 Example Workflow with Gemini CLI (Conceptual) 71 | 72 | Once the server is running, you can interact with it using the Gemini CLI. 73 | 74 | 1. **Connect the server to Gemini CLI:** 75 | ```bash 76 | gemini context add --server-command "python apktool_server.py" apktool-server 77 | ``` 78 | 2. **List available tools/prompts:** 79 | ```bash 80 | gemini context list-tools apktool-server 81 | gemini context list-prompts apktool-server 82 | ``` 83 | 3. **Use a prompt (e.g., to analyze an APK):** 84 | First, ensure you have an APK file, for example, `my_app.apk`, in a location accessible by the server (e.g., in the same directory or specified with an absolute path). 85 | 86 | ```bash 87 | gemini prompt apktool-server analyze_security --apk_path my_app.apk 88 | ``` 89 | The Gemini AI will then use the `apktool-mcp` server to call `decode_apk`, `analyze_manifest`, `list_permissions`, and `find_smali_references` as guided by the prompt, returning its analysis. 90 | 91 | 4. **Directly call a tool:** 92 | ```bash 93 | gemini tool apktool-server decode_apk --apk_path my_app.apk --output_dir my_app_decompiled 94 | ``` 95 | 96 | --- ``` -------------------------------------------------------------------------------- /APktool.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Apktool MCP Server 4 | A Model Context Protocol server that exposes Apktool functionality for Android APK analysis and modification. 5 | Integrates with Gemini CLI for AI-powered APK reverse engineering workflows. 6 | """ 7 | 8 | import asyncio 9 | import json 10 | import os 11 | import subprocess 12 | import tempfile 13 | import shutil 14 | from pathlib import Path 15 | from typing import Any, Dict, List, Optional 16 | import logging 17 | 18 | from mcp.server import Server 19 | from mcp.types import ( 20 | Tool, 21 | TextContent, 22 | ImageContent, 23 | EmbeddedResource, 24 | Resource, 25 | Prompt, 26 | GetPromptResult, 27 | CallToolResult, 28 | ListResourcesResult, 29 | ListPromptsResult, 30 | ListToolsResult, 31 | ReadResourceResult, 32 | ) 33 | 34 | # Configure logging 35 | logging.basicConfig(level=logging.INFO) 36 | logger = logging.getLogger("apktool-mcp") 37 | 38 | class ApktoolMCPServer: 39 | def __init__(self, apktool_path: str = "apktool", work_dir: str = None): 40 | """ 41 | Initialize the Apktool MCP Server 42 | 43 | Args: 44 | apktool_path: Path to apktool executable (default: "apktool") 45 | work_dir: Working directory for APK operations (default: temp dir) 46 | """ 47 | self.apktool_path = apktool_path 48 | self.work_dir = Path(work_dir) if work_dir else Path(tempfile.mkdtemp()) 49 | self.work_dir.mkdir(exist_ok=True) 50 | 51 | # Initialize MCP server 52 | self.server = Server("apktool-mcp") 53 | self._setup_tools() 54 | self._setup_resources() 55 | self._setup_prompts() 56 | 57 | def _setup_tools(self): 58 | """Register all available tools with the MCP server""" 59 | 60 | @self.server.list_tools() 61 | async def list_tools() -> ListToolsResult: 62 | return ListToolsResult( 63 | tools=[ 64 | Tool( 65 | name="decode_apk", 66 | description="Decompile an APK file to extract resources, manifest, and smali code", 67 | inputSchema={ 68 | "type": "object", 69 | "properties": { 70 | "apk_path": {"type": "string", "description": "Path to the APK file"}, 71 | "output_dir": {"type": "string", "description": "Output directory name (optional)"}, 72 | "force": {"type": "boolean", "description": "Force overwrite existing directory", "default": False}, 73 | "no_res": {"type": "boolean", "description": "Do not decode resources", "default": False}, 74 | "no_src": {"type": "boolean", "description": "Do not decode sources", "default": False} 75 | }, 76 | "required": ["apk_path"] 77 | } 78 | ), 79 | Tool( 80 | name="build_apk", 81 | description="Recompile/build an APK from decompiled source directory", 82 | inputSchema={ 83 | "type": "object", 84 | "properties": { 85 | "source_dir": {"type": "string", "description": "Path to decompiled APK directory"}, 86 | "output_apk": {"type": "string", "description": "Output APK filename (optional)"}, 87 | "force": {"type": "boolean", "description": "Force build all files", "default": False} 88 | }, 89 | "required": ["source_dir"] 90 | } 91 | ), 92 | Tool( 93 | name="install_framework", 94 | description="Install framework APK for system app decompilation", 95 | inputSchema={ 96 | "type": "object", 97 | "properties": { 98 | "framework_path": {"type": "string", "description": "Path to framework APK file"}, 99 | "tag": {"type": "string", "description": "Tag for framework identification (optional)"} 100 | }, 101 | "required": ["framework_path"] 102 | } 103 | ), 104 | Tool( 105 | name="analyze_manifest", 106 | description="Analyze AndroidManifest.xml from a decompiled APK", 107 | inputSchema={ 108 | "type": "object", 109 | "properties": { 110 | "apk_dir": {"type": "string", "description": "Path to decompiled APK directory"} 111 | }, 112 | "required": ["apk_dir"] 113 | } 114 | ), 115 | Tool( 116 | name="extract_strings", 117 | description="Extract all string resources from a decompiled APK", 118 | inputSchema={ 119 | "type": "object", 120 | "properties": { 121 | "apk_dir": {"type": "string", "description": "Path to decompiled APK directory"}, 122 | "locale": {"type": "string", "description": "Specific locale (e.g., 'en', 'es')", "default": ""} 123 | }, 124 | "required": ["apk_dir"] 125 | } 126 | ), 127 | Tool( 128 | name="list_permissions", 129 | description="List all permissions requested by an APK", 130 | inputSchema={ 131 | "type": "object", 132 | "properties": { 133 | "apk_dir": {"type": "string", "description": "Path to decompiled APK directory"} 134 | }, 135 | "required": ["apk_dir"] 136 | } 137 | ), 138 | Tool( 139 | name="find_smali_references", 140 | description="Search for specific patterns in smali code", 141 | inputSchema={ 142 | "type": "object", 143 | "properties": { 144 | "apk_dir": {"type": "string", "description": "Path to decompiled APK directory"}, 145 | "pattern": {"type": "string", "description": "Search pattern or string"}, 146 | "case_sensitive": {"type": "boolean", "description": "Case sensitive search", "default": True} 147 | }, 148 | "required": ["apk_dir", "pattern"] 149 | } 150 | ), 151 | Tool( 152 | name="get_apk_info", 153 | description="Get basic information about an APK file using aapt", 154 | inputSchema={ 155 | "type": "object", 156 | "properties": { 157 | "apk_path": {"type": "string", "description": "Path to the APK file"} 158 | }, 159 | "required": ["apk_path"] 160 | } 161 | ) 162 | ] 163 | ) 164 | 165 | @self.server.call_tool() 166 | async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult: 167 | try: 168 | if name == "decode_apk": 169 | result = await self._decode_apk(**arguments) 170 | elif name == "build_apk": 171 | result = await self._build_apk(**arguments) 172 | elif name == "install_framework": 173 | result = await self._install_framework(**arguments) 174 | elif name == "analyze_manifest": 175 | result = await self._analyze_manifest(**arguments) 176 | elif name == "extract_strings": 177 | result = await self._extract_strings(**arguments) 178 | elif name == "list_permissions": 179 | result = await self._list_permissions(**arguments) 180 | elif name == "find_smali_references": 181 | result = await self._find_smali_references(**arguments) 182 | elif name == "get_apk_info": 183 | result = await self._get_apk_info(**arguments) 184 | else: 185 | raise ValueError(f"Unknown tool: {name}") 186 | 187 | return CallToolResult(content=[TextContent(type="text", text=result)]) 188 | 189 | except Exception as e: 190 | error_msg = f"Error executing {name}: {str(e)}" 191 | logger.error(error_msg) 192 | return CallToolResult(content=[TextContent(type="text", text=error_msg)], isError=True) 193 | 194 | def _setup_resources(self): 195 | """Setup resources for accessing decompiled APK data""" 196 | 197 | @self.server.list_resources() 198 | async def list_resources() -> ListResourcesResult: 199 | resources = [] 200 | 201 | # List all decompiled APK directories 202 | for item in self.work_dir.iterdir(): 203 | if item.is_dir(): 204 | resources.append( 205 | Resource( 206 | uri=f"apktool://apk/{item.name}/manifest", 207 | name=f"{item.name} - AndroidManifest.xml", 208 | mimeType="application/xml" 209 | ) 210 | ) 211 | resources.append( 212 | Resource( 213 | uri=f"apktool://apk/{item.name}/apktool_yml", 214 | name=f"{item.name} - apktool.yml", 215 | mimeType="application/yaml" 216 | ) 217 | ) 218 | 219 | return ListResourcesResult(resources=resources) 220 | 221 | @self.server.read_resource() 222 | async def read_resource(uri: str) -> ReadResourceResult: 223 | try: 224 | if uri.startswith("apktool://apk/"): 225 | parts = uri.split("/") 226 | apk_name = parts[3] 227 | resource_type = parts[4] 228 | 229 | apk_dir = self.work_dir / apk_name 230 | 231 | if resource_type == "manifest": 232 | manifest_path = apk_dir / "AndroidManifest.xml" 233 | if manifest_path.exists(): 234 | content = manifest_path.read_text(encoding='utf-8') 235 | return ReadResourceResult(contents=[TextContent(type="text", text=content)]) 236 | 237 | elif resource_type == "apktool_yml": 238 | yml_path = apk_dir / "apktool.yml" 239 | if yml_path.exists(): 240 | content = yml_path.read_text(encoding='utf-8') 241 | return ReadResourceResult(contents=[TextContent(type="text", text=content)]) 242 | 243 | raise FileNotFoundError(f"Resource not found: {uri}") 244 | 245 | except Exception as e: 246 | error_msg = f"Error reading resource {uri}: {str(e)}" 247 | return ReadResourceResult(contents=[TextContent(type="text", text=error_msg)]) 248 | 249 | def _setup_prompts(self): 250 | """Setup prompts for common APK analysis tasks""" 251 | 252 | @self.server.list_prompts() 253 | async def list_prompts() -> ListPromptsResult: 254 | return ListPromptsResult( 255 | prompts=[ 256 | Prompt( 257 | name="analyze_security", 258 | description="Analyze APK for potential security issues", 259 | arguments=[ 260 | {"name": "apk_path", "description": "Path to APK file", "required": True} 261 | ] 262 | ), 263 | Prompt( 264 | name="privacy_audit", 265 | description="Audit APK for privacy-related permissions and data collection", 266 | arguments=[ 267 | {"name": "apk_path", "description": "Path to APK file", "required": True} 268 | ] 269 | ), 270 | Prompt( 271 | name="reverse_engineer_guide", 272 | description="Step-by-step guide for reverse engineering an APK", 273 | arguments=[ 274 | {"name": "apk_path", "description": "Path to APK file", "required": True}, 275 | {"name": "target_feature", "description": "Specific feature to analyze", "required": False} 276 | ] 277 | ) 278 | ] 279 | ) 280 | 281 | @self.server.get_prompt() 282 | async def get_prompt(name: str, arguments: Dict[str, str]) -> GetPromptResult: 283 | if name == "analyze_security": 284 | apk_path = arguments.get("apk_path", "") 285 | prompt_text = f""" 286 | Please perform a comprehensive security analysis of the APK file: {apk_path} 287 | 288 | Steps to follow: 289 | 1. Use decode_apk to decompile the APK 290 | 2. Use analyze_manifest to examine permissions and components 291 | 3. Use list_permissions to identify potentially dangerous permissions 292 | 4. Use find_smali_references to search for: 293 | - Crypto/encryption usage 294 | - Network communications 295 | - File I/O operations 296 | - Sensitive API calls 297 | 5. Look for hardcoded secrets, API keys, or credentials 298 | 6. Analyze app components for potential vulnerabilities 299 | 300 | Provide a detailed security assessment with: 301 | - Risk level (Low/Medium/High) 302 | - Identified vulnerabilities 303 | - Recommendations for mitigation 304 | """ 305 | 306 | elif name == "privacy_audit": 307 | apk_path = arguments.get("apk_path", "") 308 | prompt_text = f""" 309 | Conduct a privacy audit for the APK file: {apk_path} 310 | 311 | Analysis should include: 312 | 1. Decompile the APK using decode_apk 313 | 2. Extract and analyze all permissions with list_permissions 314 | 3. Identify data collection patterns in smali code 315 | 4. Check for third-party SDK integrations 316 | 5. Examine network communications and endpoints 317 | 6. Review privacy policy compliance indicators 318 | 319 | Generate a privacy report covering: 320 | - Personal data types collected 321 | - Data sharing with third parties 322 | - User consent mechanisms 323 | - Compliance with privacy regulations (GDPR, CCPA) 324 | """ 325 | 326 | elif name == "reverse_engineer_guide": 327 | apk_path = arguments.get("apk_path", "") 328 | target_feature = arguments.get("target_feature", "general functionality") 329 | prompt_text = f""" 330 | Create a reverse engineering guide for APK: {apk_path} 331 | Target analysis: {target_feature} 332 | 333 | Provide step-by-step instructions for: 334 | 1. Initial APK decompilation and structure analysis 335 | 2. Understanding the app architecture from AndroidManifest.xml 336 | 3. Identifying key components and entry points 337 | 4. Analyzing smali code for the target feature 338 | 5. Resource analysis (strings, layouts, assets) 339 | 6. Modification strategies if needed 340 | 7. Recompilation and testing approaches 341 | 342 | Include specific apktool commands and file locations to examine. 343 | """ 344 | 345 | else: 346 | prompt_text = f"Unknown prompt: {name}" 347 | 348 | return GetPromptResult( 349 | description=f"APK analysis prompt for {name}", 350 | messages=[ 351 | { 352 | "role": "user", 353 | "content": { 354 | "type": "text", 355 | "text": prompt_text 356 | } 357 | } 358 | ] 359 | ) 360 | 361 | async def _run_command(self, cmd: List[str], cwd: Optional[Path] = None) -> str: 362 | """Execute a command and return its output""" 363 | try: 364 | process = await asyncio.create_subprocess_exec( 365 | *cmd, 366 | stdout=asyncio.subprocess.PIPE, 367 | stderr=asyncio.subprocess.PIPE, 368 | cwd=cwd or self.work_dir 369 | ) 370 | stdout, stderr = await process.communicate() 371 | 372 | output = stdout.decode('utf-8', errors='ignore') 373 | error = stderr.decode('utf-8', errors='ignore') 374 | 375 | if process.returncode != 0: 376 | raise RuntimeError(f"Command failed: {' '.join(cmd)}\nError: {error}") 377 | 378 | return output if output else error 379 | 380 | except Exception as e: 381 | raise RuntimeError(f"Failed to execute command: {' '.join(cmd)}\nError: {str(e)}") 382 | 383 | async def _decode_apk(self, apk_path: str, output_dir: str = None, 384 | force: bool = False, no_res: bool = False, 385 | no_src: bool = False) -> str: 386 | """Decompile an APK file using apktool""" 387 | apk_file = Path(apk_path) 388 | if not apk_file.exists(): 389 | raise FileNotFoundError(f"APK file not found: {apk_path}") 390 | 391 | if not output_dir: 392 | output_dir = apk_file.stem 393 | 394 | output_path = self.work_dir / output_dir 395 | 396 | cmd = [self.apktool_path, "d", str(apk_file)] 397 | 398 | if force: 399 | cmd.append("-f") 400 | if no_res: 401 | cmd.append("-r") 402 | if no_src: 403 | cmd.append("-s") 404 | 405 | cmd.extend(["-o", str(output_path)]) 406 | 407 | result = await self._run_command(cmd) 408 | 409 | return f"Successfully decompiled APK to: {output_path}\n\nOutput:\n{result}" 410 | 411 | async def _build_apk(self, source_dir: str, output_apk: str = None, 412 | force: bool = False) -> str: 413 | """Build/recompile an APK from source directory""" 414 | source_path = Path(source_dir) 415 | if not source_path.exists(): 416 | raise FileNotFoundError(f"Source directory not found: {source_dir}") 417 | 418 | cmd = [self.apktool_path, "b", str(source_path)] 419 | 420 | if force: 421 | cmd.append("-f") 422 | 423 | if output_apk: 424 | cmd.extend(["-o", output_apk]) 425 | 426 | result = await self._run_command(cmd) 427 | 428 | return f"Successfully built APK from: {source_path}\n\nOutput:\n{result}" 429 | 430 | async def _install_framework(self, framework_path: str, tag: str = None) -> str: 431 | """Install framework APK for system app decompilation""" 432 | framework_file = Path(framework_path) 433 | if not framework_file.exists(): 434 | raise FileNotFoundError(f"Framework file not found: {framework_path}") 435 | 436 | cmd = [self.apktool_path, "if", str(framework_file)] 437 | 438 | if tag: 439 | cmd.extend(["-t", tag]) 440 | 441 | result = await self._run_command(cmd) 442 | 443 | return f"Successfully installed framework: {framework_path}\n\nOutput:\n{result}" 444 | 445 | async def _analyze_manifest(self, apk_dir: str) -> str: 446 | """Analyze AndroidManifest.xml from decompiled APK""" 447 | manifest_path = Path(apk_dir) / "AndroidManifest.xml" 448 | if not manifest_path.exists(): 449 | raise FileNotFoundError(f"AndroidManifest.xml not found in: {apk_dir}") 450 | 451 | content = manifest_path.read_text(encoding='utf-8') 452 | 453 | # Extract key information 454 | analysis = [] 455 | lines = content.split('\n') 456 | 457 | for line in lines: 458 | line = line.strip() 459 | if 'package=' in line: 460 | analysis.append(f"Package: {line}") 461 | elif 'android:name=' in line and 'activity' in line.lower(): 462 | analysis.append(f"Activity: {line}") 463 | elif 'android:name=' in line and 'service' in line.lower(): 464 | analysis.append(f"Service: {line}") 465 | elif 'android:name=' in line and 'receiver' in line.lower(): 466 | analysis.append(f"Receiver: {line}") 467 | elif 'uses-permission' in line: 468 | analysis.append(f"Permission: {line}") 469 | 470 | analysis_text = '\n'.join(analysis) if analysis else "No key elements found" 471 | 472 | return f"AndroidManifest.xml Analysis:\n\n{analysis_text}\n\nFull content:\n{content}" 473 | 474 | async def _extract_strings(self, apk_dir: str, locale: str = "") -> str: 475 | """Extract string resources from decompiled APK""" 476 | res_dir = Path(apk_dir) / "res" 477 | if not res_dir.exists(): 478 | raise FileNotFoundError(f"Resources directory not found: {res_dir}") 479 | 480 | strings_files = [] 481 | if locale: 482 | strings_files = list(res_dir.glob(f"values-{locale}/strings.xml")) 483 | else: 484 | strings_files = list(res_dir.glob("values*/strings.xml")) 485 | 486 | if not strings_files: 487 | return f"No string files found for locale: {locale or 'default'}" 488 | 489 | all_strings = [] 490 | for strings_file in strings_files: 491 | content = strings_file.read_text(encoding='utf-8') 492 | all_strings.append(f"\n--- {strings_file.name} ---\n{content}") 493 | 494 | return f"Extracted strings from {len(strings_files)} files:\n{''.join(all_strings)}" 495 | 496 | async def _list_permissions(self, apk_dir: str) -> str: 497 | """List all permissions from AndroidManifest.xml""" 498 | manifest_path = Path(apk_dir) / "AndroidManifest.xml" 499 | if not manifest_path.exists(): 500 | raise FileNotFoundError(f"AndroidManifest.xml not found in: {apk_dir}") 501 | 502 | content = manifest_path.read_text(encoding='utf-8') 503 | permissions = [] 504 | 505 | for line in content.split('\n'): 506 | if 'uses-permission' in line: 507 | permissions.append(line.strip()) 508 | 509 | if not permissions: 510 | return "No permissions found in AndroidManifest.xml" 511 | 512 | return f"Found {len(permissions)} permissions:\n\n" + '\n'.join(permissions) 513 | 514 | async def _find_smali_references(self, apk_dir: str, pattern: str, 515 | case_sensitive: bool = True) -> str: 516 | """Search for patterns in smali code""" 517 | smali_dirs = [] 518 | apk_path = Path(apk_dir) 519 | 520 | # Find all smali directories 521 | for item in apk_path.iterdir(): 522 | if item.is_dir() and item.name.startswith('smali'): 523 | smali_dirs.append(item) 524 | 525 | if not smali_dirs: 526 | return "No smali directories found" 527 | 528 | matches = [] 529 | search_pattern = pattern if case_sensitive else pattern.lower() 530 | 531 | for smali_dir in smali_dirs: 532 | for smali_file in smali_dir.rglob("*.smali"): 533 | try: 534 | content = smali_file.read_text(encoding='utf-8') 535 | search_content = content if case_sensitive else content.lower() 536 | 537 | if search_pattern in search_content: 538 | # Find line numbers 539 | lines = content.split('\n') 540 | for i, line in enumerate(lines, 1): 541 | line_search = line if case_sensitive else line.lower() 542 | if search_pattern in line_search: 543 | matches.append(f"{smali_file.relative_to(apk_path)}:{i}: {line.strip()}") 544 | except Exception as e: 545 | continue 546 | 547 | if not matches: 548 | return f"Pattern '{pattern}' not found in smali code" 549 | 550 | return f"Found {len(matches)} matches for '{pattern}':\n\n" + '\n'.join(matches[:50]) 551 | 552 | async def _get_apk_info(self, apk_path: str) -> str: 553 | """Get basic APK information using aapt or alternative""" 554 | apk_file = Path(apk_path) 555 | if not apk_file.exists(): 556 | raise FileNotFoundError(f"APK file not found: {apk_path}") 557 | 558 | try: 559 | # Try using aapt if available 560 | cmd = ["aapt", "dump", "badging", str(apk_file)] 561 | result = await self._run_command(cmd) 562 | return f"APK Information:\n\n{result}" 563 | except: 564 | # Fallback to basic file information 565 | stat = apk_file.stat() 566 | return f"APK File Information:\n" \ 567 | f"Path: {apk_file}\n" \ 568 | f"Size: {stat.st_size} bytes\n" \ 569 | f"Modified: {stat.st_mtime}\n" \ 570 | f"Note: aapt not available for detailed analysis" 571 | 572 | async def run(self, transport_type: str = "stdio"): 573 | """Run the MCP server""" 574 | if transport_type == "stdio": 575 | from mcp.server.stdio import stdio_server 576 | async with stdio_server() as (read_stream, write_stream): 577 | await self.server.run(read_stream, write_stream, 578 | self.server.create_initialization_options()) 579 | else: 580 | raise ValueError(f"Unsupported transport type: {transport_type}") 581 | 582 | # Server instance 583 | server_instance = None 584 | 585 | async def main(): 586 | """Main entry point for the MCP server""" 587 | import sys 588 | 589 | # Check if apktool is available 590 | try: 591 | process = await asyncio.create_subprocess_exec( 592 | "apktool", "--version", 593 | stdout=asyncio.subprocess.PIPE, 594 | stderr=asyncio.subprocess.PIPE 595 | ) 596 | await process.communicate() 597 | if process.returncode != 0: 598 | raise FileNotFoundError() 599 | except: 600 | print("Warning: apktool not found in PATH. Please install apktool first.", file=sys.stderr) 601 | print("Visit: https://ibotpeaches.github.io/Apktool/install/", file=sys.stderr) 602 | 603 | # Create and run server 604 | global server_instance 605 | server_instance = ApktoolMCPServer() 606 | await server_instance.run() 607 | 608 | if __name__ == "__main__": 609 | asyncio.run(main()) ```