feat: publish workphone SDK deployment and API docs
This commit is contained in:
319
sdk/scripts/setup_noroot.sh
Executable file
319
sdk/scripts/setup_noroot.sh
Executable file
@@ -0,0 +1,319 @@
|
||||
#!/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 "部署完成!"
|
||||
Reference in New Issue
Block a user