Files
workphone-sdk/sdk/scripts/setup_noroot.sh

320 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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+.*?<clinit>.*?\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("[✓] 注入到 <clinit>")
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 "部署完成!"