#!/bin/bash # ============================================================ # 无 Root 全自动部署脚本 # 一键完成:检测设备 → 注入 Gadget → 安装 → 连通 → 控制微信 # ============================================================ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SDK_DIR="$(dirname "$SCRIPT_DIR")" GADGET_SO="$SCRIPT_DIR/frida-gadget-17.8.1-android-arm64.so" KEYSTORE="$SCRIPT_DIR/debug.keystore" WECHAT_PKG="com.tencent.mm" GADGET_PORT=27042 GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' CYAN='\033[0;36m' NC='\033[0m' log() { echo -e "${GREEN}[✓]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; } err() { echo -e "${RED}[✗]${NC} $1"; exit 1; } info() { echo -e "${CYAN}[→]${NC} $1"; } # ───────────────────────────────────── # Phase 1: 等待设备 # ───────────────────────────────────── info "Phase 1: 等待 ADB 设备连接..." SERIAL="" for i in $(seq 1 60); do DEV=$(adb devices 2>/dev/null | grep -w "device" | head -1 | awk '{print $1}') if [ -n "$DEV" ]; then SERIAL="$DEV" break fi printf "." sleep 2 done echo "" [ -z "$SERIAL" ] && err "等待 120 秒仍未检测到设备,请检查 USB 连接和 USB 调试" log "设备已连接: $SERIAL" ADB="adb -s $SERIAL" # 设备基本信息 MODEL=$($ADB shell getprop ro.product.model 2>/dev/null | tr -d '\r') ANDROID=$($ADB shell getprop ro.build.version.release 2>/dev/null | tr -d '\r') log "型号: $MODEL | Android: $ANDROID" # ───────────────────────────────────── # Phase 2: 检查微信 # ───────────────────────────────────── info "Phase 2: 检查微信状态..." WX_VER=$($ADB shell dumpsys package $WECHAT_PKG 2>/dev/null | grep versionName | head -1 | awk -F= '{print $2}' | tr -d '\r ') [ -z "$WX_VER" ] && err "微信未安装" log "微信版本: $WX_VER" # 检查是否已经是注入版 ALREADY_INJECTED=$($ADB shell "run-as $WECHAT_PKG ls lib/arm64/libfrida-gadget.so 2>/dev/null" 2>/dev/null | grep -c "gadget" || echo "0") if [ "$ALREADY_INJECTED" != "0" ]; then log "微信已包含 Frida Gadget,跳过注入步骤" SKIP_INJECT=1 else SKIP_INJECT=0 fi # ───────────────────────────────────── # Phase 3: 注入 Frida Gadget(如需) # ───────────────────────────────────── if [ "$SKIP_INJECT" = "0" ]; then info "Phase 3: 提取并注入微信 APK..." WORK_DIR="$SCRIPT_DIR/gadget_work" rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" # 提取 APK APK_PATH=$($ADB shell pm path $WECHAT_PKG 2>/dev/null | head -1 | sed 's/package://' | tr -d '\r\n') log "微信 APK: $APK_PATH" info "拉取 APK(可能需要几分钟)..." $ADB pull "$APK_PATH" "$WORK_DIR/wechat_original.apk" 2>&1 | tail -1 APK_SIZE=$(ls -lh "$WORK_DIR/wechat_original.apk" | awk '{print $5}') log "APK 大小: $APK_SIZE" # 反编译(只反编译资源,不反编译代码以加快速度) info "反编译 APK..." apktool d -f -s -o "$WORK_DIR/wechat_dec" "$WORK_DIR/wechat_original.apk" 2>&1 | tail -2 # 注入 gadget LIB_DIR="$WORK_DIR/wechat_dec/lib/arm64-v8a" mkdir -p "$LIB_DIR" cp "$GADGET_SO" "$LIB_DIR/libfrida-gadget.so" log "frida-gadget.so 已注入到 lib/arm64-v8a/" # Gadget 配置(listen 模式,自动 resume) cat > "$LIB_DIR/libfrida-gadget.config.so" << 'EOF' { "interaction": { "type": "listen", "address": "0.0.0.0", "port": 27042, "on_port_conflict": "pick-next", "on_load": "resume" } } EOF log "Gadget 配置: listen 模式, 端口 $GADGET_PORT" # 修改 AndroidManifest MANIFEST="$WORK_DIR/wechat_dec/AndroidManifest.xml" sed -i.bak 's/android:extractNativeLibs="false"/android:extractNativeLibs="true"/g' "$MANIFEST" 2>/dev/null || true log "AndroidManifest: extractNativeLibs=true" # 在 smali 中注入 loadLibrary info "注入 System.loadLibrary(\"frida-gadget\")..." # 查找 Application 类 APP_CLASS=$(grep -oP 'android:name="\K[^"]+(?=.*application)' "$MANIFEST" 2>/dev/null | head -1 || true) [ -z "$APP_CLASS" ] && APP_CLASS="com.tencent.mm.app.MMApplication" SMALI_REL=$(echo "$APP_CLASS" | sed 's/\./\//g') SMALI_FILE="" for d in "$WORK_DIR/wechat_dec"/smali*; do [ -f "$d/${SMALI_REL}.smali" ] && SMALI_FILE="$d/${SMALI_REL}.smali" && break done if [ -n "$SMALI_FILE" ]; then python3 << PYEOF import re, sys path = "$SMALI_FILE" with open(path, 'r') as f: code = f.read() load = ' const-string v0, "frida-gadget"\n invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\n' # 注入到 static initializer clinit = re.search(r'(\.method\s+.*?static\s+.*?.*?\n.*?\.locals\s+\d+\n)', code, re.DOTALL) if clinit: pos = clinit.end() code = code[:pos] + load + code[pos:] with open(path, 'w') as f: f.write(code) print("[✓] 注入到 ") sys.exit(0) # 注入到 attachBaseContext 或 onCreate for method_name in ['attachBaseContext', 'onCreate']: pat = rf'(\.method\s+.*?{method_name}\(.*?\n(?:.*?\n)*? \.locals\s+\d+\n)' m = re.search(pat, code) if m: pos = m.end() code = code[:pos] + load + code[pos:] with open(path, 'w') as f: f.write(code) print(f"[✓] 注入到 {method_name}") sys.exit(0) print("[!] 未找到合适注入点,gadget 将通过 native lib 自动加载") PYEOF else warn "未找到 Application smali: $APP_CLASS" fi # 重新打包 info "重新打包 APK..." apktool b -o "$WORK_DIR/wechat_unsigned.apk" "$WORK_DIR/wechat_dec" 2>&1 | tail -2 # 签名 info "签名 APK..." jarsigner -sigalg SHA256withRSA -digestalg SHA-256 \ -keystore "$KEYSTORE" -storepass android -keypass android \ "$WORK_DIR/wechat_unsigned.apk" sdkkey 2>&1 | tail -2 cp "$WORK_DIR/wechat_unsigned.apk" "$WORK_DIR/wechat_patched.apk" PATCHED_SIZE=$(ls -lh "$WORK_DIR/wechat_patched.apk" | awk '{print $5}') log "签名完成: $PATCHED_SIZE" # 备份微信数据(重要!) info "备份微信数据..." $ADB shell "pm clear $WECHAT_PKG 2>/dev/null" || true warn "注意:重新安装会清除微信登录状态,需要重新登录" # 卸载 + 安装 info "卸载原版微信..." $ADB shell pm uninstall $WECHAT_PKG 2>&1 || true info "安装注入版微信(可能需要几分钟)..." $ADB install "$WORK_DIR/wechat_patched.apk" 2>&1 | tail -1 log "微信安装完成" fi # ───────────────────────────────────── # Phase 4: 启动微信 + 端口转发 # ───────────────────────────────────── info "Phase 4: 启动微信..." $ADB shell am force-stop $WECHAT_PKG 2>/dev/null || true sleep 1 $ADB shell am start -n $WECHAT_PKG/.ui.LauncherUI 2>&1 | tail -1 log "微信已启动,等待 Gadget 初始化..." sleep 8 info "设置端口转发..." $ADB forward tcp:$GADGET_PORT tcp:$GADGET_PORT 2>&1 log "端口转发: localhost:$GADGET_PORT → device:$GADGET_PORT" # ───────────────────────────────────── # Phase 5: 验证 Frida 连接 # ───────────────────────────────────── info "Phase 5: 验证 Frida Gadget 连接..." sleep 3 python3 << 'PYEOF' import frida import time import sys host = "127.0.0.1:27042" max_retries = 5 for attempt in range(max_retries): try: mgr = frida.get_device_manager() device = mgr.add_remote_device(host) session = device.attach("Gadget") print(f"[✓] Frida Gadget 连接成功(尝试 {attempt+1})") # 加载 Hook 脚本测试 script = session.create_script('rpc.exports = { ping: function() { return "pong_from_gadget"; } };') script.load() result = script.exports_sync.ping() print(f"[✓] RPC 测试: ping → {result}") script.unload() session.detach() print("[✓] Frida Gadget 工作正常!") sys.exit(0) except Exception as e: print(f"[!] 尝试 {attempt+1}/{max_retries}: {e}") time.sleep(3) print("[✗] Frida Gadget 连接失败") sys.exit(1) PYEOF FRIDA_OK=$? # ───────────────────────────────────── # Phase 6: 加载微信 Hook 脚本 # ───────────────────────────────────── if [ "$FRIDA_OK" = "0" ]; then info "Phase 6: 加载微信 Hook 脚本..." python3 << PYEOF import frida, json, sys, os host = "127.0.0.1:$GADGET_PORT" script_path = "$SDK_DIR/agent/hook/wechat_hook_v2.js" if not os.path.exists(script_path): print(f"[!] Hook 脚本不存在: {script_path}") sys.exit(1) with open(script_path, 'r') as f: source = f.read() mgr = frida.get_device_manager() device = mgr.add_remote_device(host) session = device.attach("Gadget") script = session.create_script(source) def on_message(message, data): if message['type'] == 'send': payload = message.get('payload', {}) if payload.get('type') == 'hook_event': print(f" 事件: {payload.get('event_type', '?')} | {json.dumps(payload.get('data', {}), ensure_ascii=False)[:100]}") elif payload.get('type') == 'log': print(f" 日志: [{payload.get('tag','')}] {payload.get('message','')}") elif message['type'] == 'error': print(f" 错误: {message.get('description','')}") script.on('message', on_message) script.load() rpc = script.exports_sync try: pong = rpc.ping() print(f"[✓] Hook 脚本已加载,ping={pong}") except Exception as e: print(f"[!] ping 失败: {e}") try: info = rpc.get_process_info({}) print(f"[✓] 微信进程: {json.dumps(info, ensure_ascii=False)}") except Exception as e: print(f"[!] get_process_info: {e}") try: status = rpc.get_hook_status({}) print(f"[✓] Hook 状态: {json.dumps(status, ensure_ascii=False)}") except Exception as e: print(f"[!] get_hook_status: {e}") print("") print("=" * 50) print("[✓] 全部完成!微信 Hook 通道已连通") print("=" * 50) print(f" Frida Gadget: {host}") print(f" Hook 脚本: {os.path.basename(script_path)}") print(f" 连接模式: 无 Root (Gadget)") print(f" SDK 服务端: http://localhost:8899") print("=" * 50) script.unload() session.detach() PYEOF else warn "Frida 连接未成功,微信可能需要登录后再试" fi echo "" log "部署完成!"