842 lines
29 KiB
Python
Executable File
842 lines
29 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Auto Config Script for Zygisk-MyInjector
|
||
通过交互式命令行快速配置应用注入
|
||
|
||
完整工作流程:
|
||
1. 运行自动配置脚本(会自动检查并设置 SELinux)
|
||
cd scripts
|
||
./auto_config.py
|
||
|
||
2. 按提示选择设备、应用和配置(全部使用默认值即可)
|
||
脚本会自动完成:
|
||
- 生成配置文件
|
||
- 推送到设备
|
||
- 应用配置
|
||
- 重启应用
|
||
- 端口转发
|
||
- 快速测试
|
||
|
||
3. 如果测试成功,直接使用 Frida 连接
|
||
frida -H 127.0.0.1:27042 Gadget -l your_script.js
|
||
|
||
快速测试:
|
||
# 测试连接
|
||
frida -H 127.0.0.1:27042 Gadget -e "console.log('Connected! PID:', Process.id)"
|
||
|
||
# 列举模块
|
||
frida -H 127.0.0.1:27042 Gadget -e "Process.enumerateModules().forEach(m => console.log(m.name))"
|
||
"""
|
||
|
||
import subprocess
|
||
import json
|
||
import sys
|
||
import os
|
||
import tempfile
|
||
import shutil
|
||
import argparse
|
||
from typing import List, Dict, Optional
|
||
from pathlib import Path
|
||
|
||
try:
|
||
from prompt_toolkit import prompt
|
||
from prompt_toolkit.completion import FuzzyWordCompleter
|
||
from prompt_toolkit.styles import Style
|
||
except ImportError:
|
||
print("Error: prompt_toolkit is required. Install it with:")
|
||
print(" pip install prompt_toolkit")
|
||
sys.exit(1)
|
||
|
||
|
||
# Define style for prompts
|
||
style = Style.from_dict({
|
||
'prompt': 'ansicyan bold',
|
||
})
|
||
|
||
# Frida Gadget version
|
||
FRIDA_VERSION = "16.0.7"
|
||
MODULE_PATH = "/data/adb/modules/zygisk-myinjector"
|
||
SO_STORAGE_DIR = f"{MODULE_PATH}/so_files"
|
||
|
||
# Default ports
|
||
DEFAULT_PORTS = [27042, 65320]
|
||
|
||
# Local cache directory for downloaded gadgets
|
||
SCRIPT_DIR = Path(__file__).parent
|
||
CACHE_DIR = SCRIPT_DIR / '.cache' / 'frida-gadgets'
|
||
|
||
|
||
class ADBHelper:
|
||
"""ADB helper class for device and package operations"""
|
||
|
||
def __init__(self, device_id: Optional[str] = None):
|
||
self.device_id = device_id
|
||
self.base_cmd = ['adb']
|
||
if device_id:
|
||
self.base_cmd.extend(['-s', device_id])
|
||
|
||
def run(self, args: List[str], check=True) -> subprocess.CompletedProcess:
|
||
"""Run adb command"""
|
||
cmd = self.base_cmd + args
|
||
try:
|
||
result = subprocess.run(cmd, capture_output=True, text=True, check=check)
|
||
return result
|
||
except subprocess.CalledProcessError as e:
|
||
print(f"Error running command: {' '.join(cmd)}")
|
||
print(f"Error: {e.stderr}")
|
||
if check:
|
||
raise
|
||
return e
|
||
|
||
@staticmethod
|
||
def get_devices() -> List[Dict[str, str]]:
|
||
"""Get list of connected devices"""
|
||
result = subprocess.run(['adb', 'devices', '-l'], capture_output=True, text=True)
|
||
lines = result.stdout.strip().split('\n')[1:] # Skip header
|
||
devices = []
|
||
|
||
for line in lines:
|
||
if not line.strip():
|
||
continue
|
||
parts = line.split()
|
||
if len(parts) >= 2:
|
||
device_id = parts[0]
|
||
status = parts[1]
|
||
|
||
# Parse device info
|
||
model = ''
|
||
product = ''
|
||
for part in parts[2:]:
|
||
if part.startswith('model:'):
|
||
model = part.split(':', 1)[1]
|
||
elif part.startswith('product:'):
|
||
product = part.split(':', 1)[1]
|
||
|
||
devices.append({
|
||
'id': device_id,
|
||
'status': status,
|
||
'model': model,
|
||
'product': product
|
||
})
|
||
|
||
return devices
|
||
|
||
def get_packages(self, include_system: bool = False) -> List[str]:
|
||
"""Get list of installed packages"""
|
||
args = ['shell', 'pm', 'list', 'packages']
|
||
if not include_system:
|
||
args.append('-3') # Third-party apps only
|
||
|
||
result = self.run(args)
|
||
packages = []
|
||
for line in result.stdout.strip().split('\n'):
|
||
if line.startswith('package:'):
|
||
pkg = line.split(':', 1)[1].strip()
|
||
packages.append(pkg)
|
||
|
||
return sorted(packages)
|
||
|
||
def push_file(self, local_path: str, remote_path: str) -> bool:
|
||
"""Push file to device"""
|
||
result = self.run(['push', local_path, remote_path], check=False)
|
||
return result.returncode == 0
|
||
|
||
def send_broadcast(self, action: str, component: str, extras: Dict[str, str]) -> bool:
|
||
"""Send broadcast with extras"""
|
||
args = ['shell', 'am', 'broadcast', '-n', component, '-a', action]
|
||
|
||
for key, value in extras.items():
|
||
args.extend(['--es', key, value])
|
||
|
||
result = self.run(args, check=False)
|
||
if result.returncode == 0:
|
||
print(f"Broadcast sent successfully")
|
||
print(result.stdout)
|
||
return True
|
||
else:
|
||
print(f"Failed to send broadcast")
|
||
print(result.stderr)
|
||
return False
|
||
|
||
def get_arch(self) -> str:
|
||
"""Get device CPU architecture"""
|
||
result = self.run(['shell', 'getprop', 'ro.product.cpu.abi'])
|
||
arch = result.stdout.strip()
|
||
return arch
|
||
|
||
def file_exists(self, path: str) -> bool:
|
||
"""Check if file exists on device"""
|
||
result = self.run(['shell', f'su -c "test -f {path} && echo exists"'], check=False)
|
||
return 'exists' in result.stdout
|
||
|
||
|
||
def select_device() -> Optional[str]:
|
||
"""Interactive device selection"""
|
||
devices = ADBHelper.get_devices()
|
||
|
||
if not devices:
|
||
print("Error: No devices found. Please connect a device and enable USB debugging.")
|
||
return None
|
||
|
||
if len(devices) == 1:
|
||
device = devices[0]
|
||
print(f"Using device: {device['id']} ({device['model'] or device['product']})")
|
||
return device['id']
|
||
|
||
print("\n=== Connected Devices ===")
|
||
for idx, device in enumerate(devices, 1):
|
||
model_info = device['model'] or device['product'] or 'Unknown'
|
||
print(f"{idx}. {device['id']} - {model_info} [{device['status']}]")
|
||
|
||
while True:
|
||
try:
|
||
choice = input(f"\nSelect device (1-{len(devices)}): ").strip()
|
||
idx = int(choice) - 1
|
||
if 0 <= idx < len(devices):
|
||
selected = devices[idx]
|
||
print(f"Selected: {selected['id']} ({selected['model'] or selected['product']})")
|
||
return selected['id']
|
||
else:
|
||
print(f"Invalid choice. Please enter 1-{len(devices)}")
|
||
except (ValueError, KeyboardInterrupt):
|
||
print("\nDevice selection cancelled")
|
||
return None
|
||
|
||
|
||
def select_package(adb: ADBHelper) -> Optional[str]:
|
||
"""Interactive package selection with fuzzy completion"""
|
||
print("\n=== Loading app packages ===")
|
||
|
||
# Ask if include system apps
|
||
while True:
|
||
choice = input("Include system apps? (y/N): ").strip().lower()
|
||
if choice in ['', 'n', 'no']:
|
||
include_system = False
|
||
break
|
||
elif choice in ['y', 'yes']:
|
||
include_system = True
|
||
break
|
||
else:
|
||
print("Please enter 'y' or 'n'")
|
||
|
||
packages = adb.get_packages(include_system=include_system)
|
||
|
||
if not packages:
|
||
print("Error: No packages found")
|
||
return None
|
||
|
||
print(f"Found {len(packages)} packages")
|
||
|
||
# Create fuzzy completer
|
||
completer = FuzzyWordCompleter(packages)
|
||
|
||
print("\n=== Select Target App ===")
|
||
print("Tip: Use Tab for auto-completion, type to filter")
|
||
|
||
try:
|
||
package = prompt(
|
||
'Package name: ',
|
||
completer=completer,
|
||
style=style
|
||
).strip()
|
||
|
||
if package and package in packages:
|
||
print(f"Selected: {package}")
|
||
return package
|
||
elif package:
|
||
print(f"Warning: '{package}' not found in package list, using anyway")
|
||
return package
|
||
else:
|
||
print("No package selected")
|
||
return None
|
||
except KeyboardInterrupt:
|
||
print("\nSelection cancelled")
|
||
return None
|
||
|
||
|
||
def configure_gadget(preset_port: Optional[int] = None) -> Optional[Dict]:
|
||
"""Interactive gadget configuration"""
|
||
print("\n=== Gadget Configuration ===")
|
||
|
||
gadget_config = {}
|
||
|
||
# Gadget name
|
||
gadget_name = input("Gadget SO name (default: libgadget.so): ").strip()
|
||
gadget_config['gadgetName'] = gadget_name or 'libgadget.so'
|
||
|
||
# Mode selection
|
||
print("\nSelect mode:")
|
||
print("1. Server mode (listen for connections)")
|
||
print("2. Script mode (execute script)")
|
||
|
||
while True:
|
||
choice = input("Mode (1/2, default: 1): ").strip()
|
||
if choice in ['', '1']:
|
||
gadget_config['mode'] = 'server'
|
||
break
|
||
elif choice == '2':
|
||
gadget_config['mode'] = 'script'
|
||
break
|
||
else:
|
||
print("Invalid choice. Please enter 1 or 2")
|
||
|
||
if gadget_config['mode'] == 'server':
|
||
# Server mode configuration
|
||
address = input("Listen address (default: 0.0.0.0): ").strip()
|
||
gadget_config['address'] = address or '0.0.0.0'
|
||
|
||
# Use preset port if provided
|
||
if preset_port:
|
||
gadget_config['port'] = preset_port
|
||
print(f"\nUsing preset port: {preset_port}")
|
||
else:
|
||
print(f"\nAvailable ports: {', '.join(map(str, DEFAULT_PORTS))}")
|
||
port = input(f"Listen port (default: {DEFAULT_PORTS[0]}): ").strip()
|
||
try:
|
||
gadget_config['port'] = int(port) if port else DEFAULT_PORTS[0]
|
||
except ValueError:
|
||
print(f"Invalid port, using default {DEFAULT_PORTS[0]}")
|
||
gadget_config['port'] = DEFAULT_PORTS[0]
|
||
|
||
print("\nPort conflict behavior:")
|
||
print("1. fail - Exit if port is in use")
|
||
print("2. ignore - Continue anyway")
|
||
print("3. close - Close existing connection")
|
||
conflict = input("On port conflict (1/2/3, default: 1): ").strip()
|
||
conflict_map = {'1': 'fail', '2': 'ignore', '3': 'close', '': 'fail'}
|
||
gadget_config['onPortConflict'] = conflict_map.get(conflict, 'fail')
|
||
|
||
print("\nOn load behavior:")
|
||
print("1. resume - Continue immediately (recommended)")
|
||
print("2. wait - Wait for connection (for debugging)")
|
||
load = input("On load (1/2, default: 1): ").strip()
|
||
load_map = {'1': 'resume', '2': 'wait', '': 'resume'}
|
||
gadget_config['onLoad'] = load_map.get(load, 'resume')
|
||
|
||
else:
|
||
# Script mode configuration
|
||
script_path = input("Script path (default: /data/local/tmp/script.js): ").strip()
|
||
gadget_config['scriptPath'] = script_path or '/data/local/tmp/script.js'
|
||
|
||
return gadget_config
|
||
|
||
|
||
def download_frida_gadget(arch: str) -> Optional[str]:
|
||
"""Download Frida Gadget for specified architecture (with local caching)"""
|
||
# Map Android arch to Frida naming
|
||
arch_map = {
|
||
'arm64-v8a': 'arm64',
|
||
'armeabi-v7a': 'arm',
|
||
'x86': 'x86',
|
||
'x86_64': 'x86_64'
|
||
}
|
||
|
||
frida_arch = arch_map.get(arch)
|
||
if not frida_arch:
|
||
print(f"Unsupported architecture: {arch}")
|
||
return None
|
||
|
||
# Check local cache first
|
||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||
cached_file = CACHE_DIR / f"frida-gadget-{FRIDA_VERSION}-android-{frida_arch}.so"
|
||
|
||
if cached_file.exists():
|
||
print(f"\n✓ Using cached Frida Gadget {FRIDA_VERSION} for {arch}")
|
||
print(f" Cache: {cached_file}")
|
||
# Copy to temp location for consistent behavior
|
||
temp_dir = tempfile.mkdtemp(prefix='frida_gadget_')
|
||
temp_file = os.path.join(temp_dir, 'libgadget.so')
|
||
shutil.copy2(str(cached_file), temp_file)
|
||
return temp_file
|
||
|
||
# Download if not in cache
|
||
url = f"https://github.com/frida/frida/releases/download/{FRIDA_VERSION}/frida-gadget-{FRIDA_VERSION}-android-{frida_arch}.so.xz"
|
||
|
||
print(f"\nDownloading Frida Gadget {FRIDA_VERSION} for {arch}...")
|
||
print(f"URL: {url}")
|
||
|
||
# Create temp directory
|
||
temp_dir = tempfile.mkdtemp(prefix='frida_gadget_')
|
||
xz_file = os.path.join(temp_dir, f'frida-gadget.so.xz')
|
||
so_file = os.path.join(temp_dir, 'libgadget.so')
|
||
|
||
try:
|
||
# Download
|
||
result = subprocess.run(['curl', '-L', '-o', xz_file, url],
|
||
capture_output=True, text=True, check=False)
|
||
if result.returncode != 0:
|
||
print(f"Failed to download: {result.stderr}")
|
||
shutil.rmtree(temp_dir)
|
||
return None
|
||
|
||
print("✓ Downloaded")
|
||
|
||
# Decompress
|
||
print("Decompressing...")
|
||
result = subprocess.run(['xz', '-d', xz_file],
|
||
capture_output=True, text=True, check=False)
|
||
if result.returncode != 0:
|
||
print(f"Failed to decompress: {result.stderr}")
|
||
shutil.rmtree(temp_dir)
|
||
return None
|
||
|
||
# Rename
|
||
decompressed = xz_file.replace('.xz', '')
|
||
os.rename(decompressed, so_file)
|
||
|
||
print("✓ Decompressed")
|
||
|
||
# Save to cache for future use
|
||
try:
|
||
shutil.copy2(so_file, str(cached_file))
|
||
print(f"✓ Cached for future use: {cached_file}")
|
||
except Exception as e:
|
||
print(f"Warning: Failed to cache gadget: {e}")
|
||
|
||
return so_file
|
||
|
||
except Exception as e:
|
||
print(f"Error downloading Frida Gadget: {e}")
|
||
if os.path.exists(temp_dir):
|
||
shutil.rmtree(temp_dir)
|
||
return None
|
||
|
||
|
||
def ensure_gadget_installed(adb: ADBHelper, gadget_name: str = 'libgadget.so') -> bool:
|
||
"""Ensure Frida Gadget is installed on device"""
|
||
gadget_path = f"{SO_STORAGE_DIR}/{gadget_name}"
|
||
|
||
print(f"\n=== Checking Frida Gadget ===")
|
||
|
||
# Check if gadget already exists
|
||
if adb.file_exists(gadget_path):
|
||
print(f"✓ Gadget found: {gadget_path}")
|
||
return True
|
||
|
||
print(f"Gadget not found in: {gadget_path}")
|
||
print("Need to download and install Frida Gadget")
|
||
|
||
# Get device architecture
|
||
arch = adb.get_arch()
|
||
print(f"Device architecture: {arch}")
|
||
|
||
# Download gadget
|
||
local_gadget = download_frida_gadget(arch)
|
||
if not local_gadget:
|
||
print("Failed to download Frida Gadget")
|
||
return False
|
||
|
||
try:
|
||
# Push to device temp location
|
||
print("\nPushing to device...")
|
||
if not adb.push_file(local_gadget, '/data/local/tmp/libgadget.so'):
|
||
print("Failed to push gadget to device")
|
||
return False
|
||
|
||
print("✓ Pushed to device")
|
||
|
||
# Copy to SO storage with root
|
||
print(f"Installing to {gadget_path}...")
|
||
result = adb.run(['shell', f'su -c "mkdir -p {SO_STORAGE_DIR}"'], check=False)
|
||
result = adb.run(['shell',
|
||
f'su -c "cp /data/local/tmp/libgadget.so {gadget_path}"'],
|
||
check=False)
|
||
|
||
if result.returncode != 0:
|
||
print(f"Failed to install gadget: {result.stderr}")
|
||
return False
|
||
|
||
# Set permissions
|
||
adb.run(['shell', f'su -c "chmod 755 {gadget_path}"'], check=False)
|
||
|
||
# Verify
|
||
if adb.file_exists(gadget_path):
|
||
print(f"✓ Gadget installed successfully: {gadget_path}")
|
||
# Clean up temp file on device
|
||
adb.run(['shell', 'rm -f /data/local/tmp/libgadget.so'], check=False)
|
||
return True
|
||
else:
|
||
print("Failed to verify gadget installation")
|
||
return False
|
||
|
||
finally:
|
||
# Clean up local temp file
|
||
temp_dir = os.path.dirname(local_gadget)
|
||
if os.path.exists(temp_dir):
|
||
shutil.rmtree(temp_dir)
|
||
|
||
|
||
def generate_config_files(package_name: str, gadget_config: Dict) -> tuple:
|
||
"""Generate config.json and gadget config content"""
|
||
|
||
# Prepare SO file reference for gadget
|
||
gadget_name = gadget_config['gadgetName']
|
||
gadget_so_ref = {
|
||
"name": gadget_name,
|
||
"storedPath": f"{SO_STORAGE_DIR}/{gadget_name}",
|
||
"originalPath": f"{SO_STORAGE_DIR}/{gadget_name}"
|
||
}
|
||
|
||
# Generate main module config.json
|
||
module_config = {
|
||
"enabled": True,
|
||
"hideInjection": False,
|
||
"injectionDelay": 2,
|
||
"globalSoFiles": [],
|
||
"perAppConfig": {
|
||
package_name: {
|
||
"enabled": True,
|
||
"soFiles": [gadget_so_ref], # Include gadget SO file
|
||
"injectionMethod": "standard",
|
||
"gadgetConfig": gadget_config,
|
||
"useGlobalGadget": False
|
||
}
|
||
},
|
||
"globalGadgetConfig": None
|
||
}
|
||
|
||
# Generate gadget config content based on mode
|
||
if gadget_config['mode'] == 'server':
|
||
gadget_config_content = {
|
||
"interaction": {
|
||
"type": "listen",
|
||
"address": gadget_config['address'],
|
||
"port": gadget_config['port'],
|
||
"on_port_conflict": gadget_config['onPortConflict'],
|
||
"on_load": gadget_config['onLoad']
|
||
}
|
||
}
|
||
else: # script mode
|
||
gadget_config_content = {
|
||
"interaction": {
|
||
"type": "script",
|
||
"path": gadget_config['scriptPath']
|
||
}
|
||
}
|
||
|
||
return (
|
||
json.dumps(module_config, indent=2, ensure_ascii=False),
|
||
json.dumps(gadget_config_content, indent=2, ensure_ascii=False)
|
||
)
|
||
|
||
|
||
def quick_test(adb: ADBHelper, port: int = 27042):
|
||
"""Quick test connectivity after configuration"""
|
||
print("\n=== Quick Test ===")
|
||
print("Testing Frida connectivity...\n")
|
||
|
||
# Check if frida is installed
|
||
result = subprocess.run(['which', 'frida'], capture_output=True, text=True)
|
||
if result.returncode != 0:
|
||
print("⚠️ Frida CLI not found. Please install it with:")
|
||
print(" pip install frida-tools")
|
||
return False
|
||
|
||
# Test 1: Basic connection test
|
||
print("Test 1: Basic connection...")
|
||
# 使用 stdin 输入命令和 exit,避免交互式 REPL 导致超时
|
||
test_input = "console.log('Connected! PID:', Process.id)\nexit\n"
|
||
test_cmd = ['frida', '-H', f'127.0.0.1:{port}', 'Gadget']
|
||
|
||
result = subprocess.run(test_cmd, input=test_input, capture_output=True, text=True, timeout=10)
|
||
if result.returncode == 0 and 'Connected!' in result.stdout:
|
||
print("✓ Connection successful!")
|
||
# 提取并显示 PID
|
||
for line in result.stdout.split('\n'):
|
||
if 'Connected! PID:' in line:
|
||
print(f" {line.strip()}")
|
||
break
|
||
else:
|
||
print("✗ Connection failed")
|
||
print(f" Error: {result.stderr.strip() if result.stderr else 'Unknown error'}")
|
||
print("\nTroubleshooting:")
|
||
print(" 1. Check if the target app is running")
|
||
print(" 2. Verify port forwarding: adb forward tcp:{} tcp:{}".format(port, port))
|
||
print(" 3. Check logcat for errors: adb logcat -s Gadget:*")
|
||
return False
|
||
|
||
# Test 2: Enumerate modules
|
||
print("\nTest 2: Enumerate loaded modules...")
|
||
test_input = "Process.enumerateModules().slice(0, 5).forEach(m => console.log(' -', m.name))\nexit\n"
|
||
test_cmd = ['frida', '-H', f'127.0.0.1:{port}', 'Gadget']
|
||
|
||
result = subprocess.run(test_cmd, input=test_input, capture_output=True, text=True, timeout=10)
|
||
if result.returncode == 0:
|
||
print("✓ Modules enumerated:")
|
||
# 提取并显示模块列表
|
||
in_output = False
|
||
for line in result.stdout.split('\n'):
|
||
if ' -' in line:
|
||
in_output = True
|
||
print(line)
|
||
elif in_output and line.strip() == '':
|
||
break
|
||
else:
|
||
print("✗ Failed to enumerate modules")
|
||
print(f" Error: {result.stderr.strip()}")
|
||
|
||
return True
|
||
|
||
|
||
def setup_port_forward(adb: ADBHelper, port: int) -> bool:
|
||
"""Setup ADB port forwarding"""
|
||
print(f"\n=== Setting up port forwarding ===")
|
||
print(f"Forwarding local port {port} to device port {port}...")
|
||
|
||
result = adb.run(['forward', f'tcp:{port}', f'tcp:{port}'], check=False)
|
||
if result.returncode == 0:
|
||
print(f"✓ Port forwarding established: tcp:{port} -> tcp:{port}")
|
||
return True
|
||
else:
|
||
print(f"✗ Failed to setup port forwarding")
|
||
print(f" Error: {result.stderr}")
|
||
return False
|
||
|
||
|
||
def check_and_set_selinux(adb: ADBHelper) -> bool:
|
||
"""Check SELinux status and set to Permissive if needed"""
|
||
print("\n=== Checking SELinux Status ===")
|
||
|
||
# Check current SELinux status
|
||
result = adb.run(['shell', 'getenforce'], check=False)
|
||
if result.returncode != 0:
|
||
print("⚠️ Failed to check SELinux status")
|
||
return True # Continue anyway
|
||
|
||
status = result.stdout.strip()
|
||
print(f"Current SELinux mode: {status}")
|
||
|
||
if status == 'Permissive':
|
||
print("✓ SELinux is already in Permissive mode")
|
||
return True
|
||
elif status == 'Enforcing':
|
||
print("\n⚠️ SELinux is in Enforcing mode")
|
||
print(" Zygisk modules cannot read config files when SELinux is Enforcing.")
|
||
print(" We need to set it to Permissive mode.")
|
||
|
||
# Ask user for confirmation
|
||
while True:
|
||
choice = input("\nSet SELinux to Permissive? (Y/n): ").strip().lower()
|
||
if choice in ['', 'y', 'yes']:
|
||
break
|
||
elif choice in ['n', 'no']:
|
||
print("\nWarning: Continuing with SELinux Enforcing may cause injection to fail.")
|
||
print("You can manually set it later with: adb shell \"su -c 'setenforce 0'\"")
|
||
return True
|
||
else:
|
||
print("Please enter 'y' or 'n'")
|
||
|
||
# Set SELinux to Permissive
|
||
print("\nSetting SELinux to Permissive...")
|
||
result = adb.run(['shell', 'su', '-c', 'setenforce 0'], check=False)
|
||
|
||
if result.returncode == 0:
|
||
print("✓ SELinux set to Permissive mode")
|
||
print(" Note: This setting will reset after reboot")
|
||
return True
|
||
else:
|
||
print("✗ Failed to set SELinux to Permissive")
|
||
print(f" Error: {result.stderr.strip()}")
|
||
print("\nPlease manually run: adb shell \"su -c 'setenforce 0'\"")
|
||
return False
|
||
else:
|
||
print(f"Unknown SELinux status: {status}")
|
||
return True
|
||
|
||
|
||
def restart_app(adb: ADBHelper, package_name: str):
|
||
"""Restart the target application"""
|
||
print(f"\n=== Restarting Application ===")
|
||
|
||
# Force stop
|
||
print(f"Stopping {package_name}...")
|
||
result = adb.run(['shell', 'am', 'force-stop', package_name], check=False)
|
||
if result.returncode == 0:
|
||
print("✓ App stopped")
|
||
else:
|
||
print("⚠️ Failed to stop app")
|
||
|
||
# Get main activity
|
||
print(f"\nGetting launch activity...")
|
||
result = adb.run(['shell', 'pm', 'dump', package_name, '|', 'grep', '-A', '1', 'android.intent.action.MAIN'], check=False)
|
||
|
||
# Try to start the app
|
||
print(f"Starting {package_name}...")
|
||
result = adb.run(['shell', 'monkey', '-p', package_name, '-c', 'android.intent.category.LAUNCHER', '1'], check=False)
|
||
|
||
if result.returncode == 0:
|
||
print("✓ App started")
|
||
print(" Note: The app should now load with Frida Gadget injected")
|
||
return True
|
||
else:
|
||
print("⚠️ Failed to start app automatically")
|
||
print(" Please start the app manually from the device")
|
||
return False
|
||
|
||
|
||
def main():
|
||
"""Main entry point"""
|
||
# Parse command line arguments
|
||
parser = argparse.ArgumentParser(
|
||
description='Zygisk-MyInjector Auto Config Tool',
|
||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||
)
|
||
parser.add_argument(
|
||
'-p', '--port',
|
||
type=int,
|
||
choices=DEFAULT_PORTS,
|
||
help=f'Preset Gadget port (choices: {", ".join(map(str, DEFAULT_PORTS))})'
|
||
)
|
||
args = parser.parse_args()
|
||
|
||
print("=" * 60)
|
||
print("Zygisk-MyInjector Auto Config Tool")
|
||
print("=" * 60)
|
||
|
||
# Step 1: Select device
|
||
device_id = select_device()
|
||
if not device_id:
|
||
sys.exit(1)
|
||
|
||
adb = ADBHelper(device_id)
|
||
|
||
# Step 2: Check and set SELinux
|
||
if not check_and_set_selinux(adb):
|
||
print("\nError: Failed to configure SELinux")
|
||
print("The injection may fail. Please fix SELinux manually and try again.")
|
||
sys.exit(1)
|
||
|
||
# Step 3: Select package
|
||
package_name = select_package(adb)
|
||
if not package_name:
|
||
sys.exit(1)
|
||
|
||
# Step 4: Configure gadget
|
||
gadget_config = configure_gadget(preset_port=args.port)
|
||
if not gadget_config:
|
||
sys.exit(1)
|
||
|
||
# Step 5: Ensure Frida Gadget is installed
|
||
if not ensure_gadget_installed(adb, gadget_config['gadgetName']):
|
||
print("\nError: Failed to install Frida Gadget")
|
||
print("Please manually download and install libgadget.so")
|
||
sys.exit(1)
|
||
|
||
# Step 6: Generate config files
|
||
print("\n=== Generating Configuration Files ===")
|
||
config_json, gadget_config_json = generate_config_files(package_name, gadget_config)
|
||
|
||
print("\nGenerated config.json:")
|
||
print(config_json)
|
||
print("\nGenerated gadget config:")
|
||
print(gadget_config_json)
|
||
|
||
# Step 7: Save to temp files
|
||
temp_dir = tempfile.mkdtemp(prefix='frida_gadget_config_')
|
||
|
||
config_file = os.path.join(temp_dir, 'config.json')
|
||
gadget_name = gadget_config['gadgetName'].replace('.so', '.config.so')
|
||
gadget_config_file = os.path.join(temp_dir, gadget_name)
|
||
|
||
with open(config_file, 'w', encoding='utf-8') as f:
|
||
f.write(config_json)
|
||
|
||
with open(gadget_config_file, 'w', encoding='utf-8') as f:
|
||
f.write(gadget_config_json)
|
||
|
||
print(f"\nConfig files saved to: {temp_dir}")
|
||
|
||
# Step 8: Push to device
|
||
print("\n=== Pushing Files to Device ===")
|
||
|
||
remote_config = '/data/local/tmp/zygisk_config.json'
|
||
remote_gadget_config = f'/data/local/tmp/{gadget_name}'
|
||
|
||
if not adb.push_file(config_file, remote_config):
|
||
print("Error: Failed to push config.json")
|
||
sys.exit(1)
|
||
print(f"✓ Pushed config.json -> {remote_config}")
|
||
|
||
if not adb.push_file(gadget_config_file, remote_gadget_config):
|
||
print("Error: Failed to push gadget config")
|
||
sys.exit(1)
|
||
print(f"✓ Pushed gadget config -> {remote_gadget_config}")
|
||
|
||
# Step 9: Send broadcast
|
||
print("\n=== Sending Broadcast to Apply Config ===")
|
||
|
||
success = adb.send_broadcast(
|
||
action='com.jiqiu.configapp.APPLY_CONFIG',
|
||
component='com.jiqiu.configapp/.ConfigApplyReceiver',
|
||
extras={
|
||
'package_name': package_name,
|
||
'tmp_config_path': remote_config,
|
||
'tmp_gadget_config_path': remote_gadget_config
|
||
}
|
||
)
|
||
|
||
if success:
|
||
print("\n✓ Configuration applied successfully!")
|
||
print(f"\nThe app '{package_name}' has been configured.")
|
||
|
||
# 自动完成工作流程
|
||
print("\n=== Completing Workflow ===")
|
||
|
||
# Step 1: Restart app
|
||
restart_app(adb, package_name)
|
||
|
||
# Step 2: Setup port forwarding
|
||
port = gadget_config.get('port', 27042)
|
||
if setup_port_forward(adb, port):
|
||
# Step 3: Quick test
|
||
import time
|
||
print("\nWaiting 3 seconds for app to initialize...")
|
||
time.sleep(3)
|
||
|
||
try:
|
||
test_success = quick_test(adb, port)
|
||
if test_success:
|
||
# 彩色打印 frida 命令
|
||
print("\n" + "="*60)
|
||
print("\033[1;32m✓ All tests passed!\033[0m\n")
|
||
print("\033[1;36mYou can now use Frida with the following commands:\033[0m\n")
|
||
|
||
# Interactive mode
|
||
print("\033[1;33m# Interactive REPL:\033[0m")
|
||
print(f"\033[1;32mfrida -H 127.0.0.1:{port} Gadget\033[0m\n")
|
||
|
||
# Execute script
|
||
print("\033[1;33m# Execute JavaScript code:\033[0m")
|
||
print(f"\033[1;32mfrida -H 127.0.0.1:{port} Gadget -e 'YOUR_CODE_HERE'\033[0m\n")
|
||
|
||
# Load script file
|
||
print("\033[1;33m# Load script file:\033[0m")
|
||
print(f"\033[1;32mfrida -H 127.0.0.1:{port} Gadget -l your_script.js\033[0m\n")
|
||
|
||
print("="*60)
|
||
except Exception as e:
|
||
print(f"\n⚠️ Test failed: {e}")
|
||
print("\nYou can manually test with:")
|
||
if gadget_config['mode'] == 'server':
|
||
print(f"\033[1;32mfrida -H 127.0.0.1:{port} Gadget -l your_script.js\033[0m")
|
||
else:
|
||
print("\n✗ Failed to apply configuration")
|
||
print("Please check logcat for details:")
|
||
print(f" adb -s {device_id} logcat -s ConfigApplyReceiver:* ConfigManager:*")
|
||
sys.exit(1)
|
||
|
||
# Clean up temp directory
|
||
try:
|
||
shutil.rmtree(temp_dir)
|
||
except Exception:
|
||
pass # Ignore cleanup errors
|
||
|
||
|
||
if __name__ == '__main__':
|
||
try:
|
||
main()
|
||
except KeyboardInterrupt:
|
||
print("\n\nOperation cancelled by user")
|
||
sys.exit(130)
|