rebuild_wechathook_20260706.sh
8.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/bin/bash
# rebuild_wechathook.sh — compile WeChatHook, repackage, redeploy to device
# Does not need the LSPosed source tree — uses bundled XposedBridge stubs.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ADB="${ADB:-$SCRIPT_DIR/../platform-tools/adb}"
[ ! -f "$ADB" ] && ADB=adb
ANDROID_HOME="${ANDROID_HOME:-/opt/homebrew/share/android-commandlinetools}"
ANDROID_JAR="$ANDROID_HOME/platforms/android-36/android.jar"
D8="$ANDROID_HOME/build-tools/36.0.0/d8"
APKSIGNER="$ANDROID_HOME/build-tools/36.0.0/apksigner"
ZIPALIGN="$ANDROID_HOME/build-tools/36.0.0/zipalign"
JAVA_HOME="${JAVA_HOME:-/opt/homebrew/opt/openjdk@17}"
JAVAC="$JAVA_HOME/bin/javac"
KEYTOOL="$JAVA_HOME/bin/keytool"
GOLDEN_APK="$SCRIPT_DIR/wechathook_v2_signed.apk"
DEBUG_KS="$HOME/.android/debug.keystore"
STUBS_DIR="/tmp/xposed_stubs"
BUILD_DIR="/tmp/wechathook_rebuild"
# ── Write XposedBridge stubs (minimal, for compilation only) ──────────────────
echo "== Writing XposedBridge stubs =="
mkdir -p "$STUBS_DIR/de/robv/android/xposed/callbacks"
cat > "$STUBS_DIR/de/robv/android/xposed/XposedBridge.java" << 'JAVA'
package de.robv.android.xposed;
public class XposedBridge {
public static void log(String text) {}
public static void log(Throwable t) {}
public static XC_MethodHook.Unhook hookMethod(
java.lang.reflect.Member hookMethod, XC_MethodHook callback) { return null; }
}
JAVA
cat > "$STUBS_DIR/de/robv/android/xposed/XC_MethodHook.java" << 'JAVA'
package de.robv.android.xposed;
public abstract class XC_MethodHook {
public class Unhook { public void unhook() {} }
public static class MethodHookParam {
public Object thisObject;
public Object[] args;
public Object getResult() { return null; }
public void setResult(Object result) {}
public Throwable getThrowable() { return null; }
public boolean hasThrowable() { return false; }
}
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
}
JAVA
cat > "$STUBS_DIR/de/robv/android/xposed/XposedHelpers.java" << 'JAVA'
package de.robv.android.xposed;
public class XposedHelpers {
public static Class<?> findClass(String className, ClassLoader classLoader) { return null; }
public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args) { return null; }
public static Object callMethod(Object obj, String methodName, Object... args) { return null; }
public static Object callMethod(Object obj, String methodName, Class<?>[] paramTypes, Object... args) { return null; }
public static Object newInstance(Class<?> clazz, Object... args) { return null; }
public static Object newInstance(Class<?> clazz, Class<?>[] paramTypes, Object... args) { return null; }
public static Object getObjectField(Object obj, String fieldName) { return null; }
public static void setObjectField(Object obj, String fieldName, Object value) {}
public static XC_MethodHook.Unhook findAndHookMethod(
String className, ClassLoader cl, String methodName, Object... paramTypesAndCallback) { return null; }
public static XC_MethodHook.Unhook findAndHookMethod(
Class<?> clazz, String methodName, Object... paramTypesAndCallback) { return null; }
public static XC_MethodHook.Unhook findAndHookConstructor(
String className, ClassLoader cl, Object... paramTypesAndCallback) { return null; }
public static class InvocationTargetError extends Error {
public InvocationTargetError(String msg, Throwable cause) { super(msg, cause); }
}
}
JAVA
cat > "$STUBS_DIR/de/robv/android/xposed/IXposedHookLoadPackage.java" << 'JAVA'
package de.robv.android.xposed;
public interface IXposedHookLoadPackage {
void handleLoadPackage(de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam lpparam) throws Throwable;
}
JAVA
cat > "$STUBS_DIR/de/robv/android/xposed/callbacks/XC_LoadPackage.java" << 'JAVA'
package de.robv.android.xposed.callbacks;
public class XC_LoadPackage {
public static class LoadPackageParam {
public String packageName;
public String processName;
public ClassLoader classLoader;
public android.content.pm.ApplicationInfo appInfo;
public boolean isFirstApplication;
}
}
JAVA
# ── Compile ────────────────────────────────────────────────────────────────────
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR/compiled" "$BUILD_DIR/dex" "$BUILD_DIR/pack"
echo "== Compiling XposedBridge stubs =="
"$JAVAC" -source 11 -target 11 -cp "$ANDROID_JAR" \
-d "$BUILD_DIR/compiled" \
$(find "$STUBS_DIR" -name "*.java") 2>/dev/null
echo "== Compiling WeChatHook.java =="
"$JAVAC" -source 11 -target 11 \
-cp "$ANDROID_JAR:$BUILD_DIR/compiled" \
-d "$BUILD_DIR/compiled" \
"$SCRIPT_DIR/wechathook_src/WeChatHook.java"
echo "== Converting to DEX =="
"$D8" --min-api 30 --output "$BUILD_DIR/dex/" \
$(find "$BUILD_DIR/compiled/com/example/wechathook" -name "*.class")
# ── Package ───────────────────────────────────────────────────────────────────
echo "== Packing APK =="
cp "$GOLDEN_APK" "$BUILD_DIR/pack/wechathook.apk"
# Replace DEX in place (the golden APK already has correct manifest + xposed_init)
cd "$BUILD_DIR/pack"
zip -u wechathook.apk -j "$BUILD_DIR/dex/classes.dex" > /dev/null
"$ZIPALIGN" -v -f 4 wechathook.apk wechathook_aligned.apk > /dev/null
# ── Sign with Android debug keystore (same as golden APK) ────────────────────
echo "== Signing =="
OUT="$BUILD_DIR/wechathook_signed.apk"
"$APKSIGNER" sign \
--ks "$DEBUG_KS" \
--ks-key-alias androiddebugkey \
--ks-pass pass:android \
--key-pass pass:android \
--out "$OUT" \
wechathook_aligned.apk 2>/dev/null
echo "APK size: $(wc -c < "$OUT") bytes"
# ── Deploy to device ──────────────────────────────────────────────────────────
echo "== Deploying to device =="
# Push to stable path
$ADB push "$OUT" /data/local/tmp/wechathook.apk
# Install via PKMS (use -r to update in-place if same sig; if first time, just install)
INSTALL_OUT=$($ADB shell "su 0 sh -c 'pm install -r /data/local/tmp/wechathook.apk 2>&1'" | tr -d '\r')
echo "pm install: $INSTALL_OUT"
if echo "$INSTALL_OUT" | grep -q "FAILED"; then
echo "Retrying without -r (signature mismatch or first install)..."
$ADB shell "su 0 sh -c 'pm uninstall com.example.wechathook 2>&1 || true'"
$ADB shell "su 0 sh -c 'pm install /data/local/tmp/wechathook.apk 2>&1'"
fi
# Get new PKMS path
NEW_PATH=$($ADB shell "pm path com.example.wechathook 2>/dev/null | tr -d '\r' | sed 's/package://'" 2>/dev/null)
echo "New PKMS path: $NEW_PATH"
# Update lspd DB (device must be unlocked — Termux CE storage must be accessible)
SQLITE3="LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib /data/data/com.termux/files/usr/bin/sqlite3"
DB="/data/adb/lspd/config/modules_config.db"
$ADB shell "su -c \"printf \\\"INSERT OR REPLACE INTO modules(module_pkg_name,apk_path,enabled) VALUES('com.example.wechathook','$NEW_PATH',1);\\n\
INSERT OR IGNORE INTO scope(mid,app_pkg_name,user_id) SELECT mid,'com.tencent.mm',0 FROM modules WHERE module_pkg_name='com.example.wechathook';\\n\
SELECT mid,module_pkg_name,apk_path FROM modules;\\n\\\" > /data/local/tmp/update.sql\""
$ADB shell "su -c '$SQLITE3 $DB < /data/local/tmp/update.sql 2>&1'"
# Ensure scope file exists
$ADB shell "su -c 'mkdir -p /data/adb/lspd/config/modules/com.example.wechathook && echo com.tencent.mm > /data/adb/lspd/config/modules/com.example.wechathook/scope && chmod 644 /data/adb/lspd/config/modules/com.example.wechathook/scope'"
# WAL checkpoint + sync to all .bak files
$ADB shell "su -c '$SQLITE3 $DB \"PRAGMA wal_checkpoint(TRUNCATE);\" && \
cp $DB ${DB}.bak && cp $DB ${DB}.bak2 && cp $DB ${DB}.bak3 && cp $DB ${DB}.bak4 && echo baks_done'"
# Restart WeChat to pick up new module (no reboot needed)
$ADB shell "su 0 sh -c 'am force-stop com.tencent.mm'"
sleep 2
$ADB shell "am start -n com.tencent.mm/.ui.LauncherUI 2>/dev/null || true"
echo ""
echo "== Done. Test with: =="
echo " $ADB shell \"su 0 sh -c 'am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to filehelper --es text test'\""