Name Last Update
..
wechathook_src Loading commit data...
xhshook_src Loading commit data...
CLAUDE.md Loading commit data...
README.md Loading commit data...
STATUS.md Loading commit data...
analyze_wechathook.py Loading commit data...
apply_a16_fix.sh Loading commit data...
do_not_break.md Loading commit data...
hermes_task.md Loading commit data...
ipc_bridge.cpp Loading commit data...
ipc_bridge.h Loading commit data...
rebuild_wechathook.sh Loading commit data...
rebuild_xhshook.sh Loading commit data...
termux_build.sh Loading commit data...
tts_voice_send.sh Loading commit data...
update_wechathook_db.sh Loading commit data...
update_xhshook_db.sh Loading commit data...
wechat_send.sh Loading commit data...
wechathook_v2_signed.apk Loading commit data...
xhshook_v2_signed.apk Loading commit data...

README.md

LSPosed A16 Fix — JNINativeInterface Runtime Size Mismatch

Summary

LSPosed (JingMatrix fork v1.11.0 / Vector v2.0) crashes zygote64 on Android 16 with:

JNI DETECTED ERROR IN APPLICATION: negative buffer capacity in NewDirectByteBuffer
from int com.android.internal.os.Zygote.nativeForkSystemServer

Magisk detects repeated zygote crashes and auto-rolls-back Zygisk. Modules never inject.

Root Cause

zygisk/src/main/cpp/ipc_bridge.cpp line 557:

memcpy(&native_interface_hook_, env->functions, sizeof(JNINativeInterface));

sizeof(JNINativeInterface) is a compile-time constant from the NDK used to build LSPosed (NDK 26 = ~2376 bytes).

Android 16 ART added new JNI functions to the JNINativeInterface structure (e.g. NewDirectByteBuffer overloads). The runtime structure is larger than the compile-time size.

The memcpy truncates the table — function pointers for new JNI functions beyond the compile-time boundary become uninitialized garbage values. ART later calls them → crash.

Key detail: reserved0 in JNINativeInterface stores the actual runtime size (set by ART). The truncated copy preserves reserved0 (it's at offset 0), so ART's SetTableOverride sees the correct size and treats garbage as valid function pointers.

Fix

Replace compile-time sizeof(JNINativeInterface) with runtime reserved0, and dynamically allocate the copy.

File 1: zygisk/src/main/cpp/include/ipc_bridge.h

-    JNINativeInterface native_interface_hook_{};
+    JNINativeInterface* native_interface_hook_ = nullptr;
+    size_t table_size_ = 0;

File 2: zygisk/src/main/cpp/ipc_bridge.cpp

-    memcpy(&native_interface_hook_, env->functions, sizeof(JNINativeInterface));
+    table_size_ = (size_t)env->functions->reserved0;
+    if (table_size_ == 0 || table_size_ < sizeof(JNINativeInterface))
+        table_size_ = sizeof(JNINativeInterface);
+    native_interface_hook_ = (JNINativeInterface*)malloc(table_size_);
+    memcpy(native_interface_hook_, env->functions, table_size_);

-    call_boolean_method_v_backup_ = env->functions->CallBooleanMethodV;
+    call_boolean_method_v_backup_ = native_interface_hook_->CallBooleanMethodV;

-    native_interface_hook_.CallBooleanMethodV = ...
+    native_interface_hook_->CallBooleanMethodV = ...

-    set_table_override_func(&native_interface_hook_);
+    set_table_override_func(native_interface_hook_);

How to Build

# 1. Clone
git clone --depth 1 --recursive https://github.com/JingMatrix/LSPosed.git
cd LSPosed

# 2. Apply fix
bash apply_a16_fix.sh .

# 3. Build (requires Android SDK + NDK)
export ANDROID_HOME=/path/to/android-sdk
./gradlew :zygisk:assembleRelease

# Output: zygisk/build/outputs/apk/release/

How to Deploy on Device

# Extract the fixed zygisk .so from the APK
unzip -o zygisk-release.apk -d zygisk_out

# Push to device and replace
adb push zygisk_out/lib/arm64-v8a/libzygisk.so /data/local/tmp/
adb shell su -c 'cp /data/local/tmp/libzygisk.so /data/adb/modules/zygisk_vector/zygisk/arm64-v8a.so'

# Ensure Zygisk is enabled
adb shell su -c 'magisk --sqlite "INSERT OR REPLACE INTO settings (key,value) VALUES (\"zygisk\",1);"'

# Reboot
adb reboot

Verification (after reboot)

# 1. Daemon running?
adb shell su -c 'pidof lspd'

# 2. Zygisk injected into zygote?
adb shell su -c 'cat /proc/$(pidof zygote64 | awk "{print \$1}")/maps | grep zygisk' | head -3

# 3. Module injected into target app?
adb shell su -c 'cat /proc/$(pidof com.xingin.xhs)/maps | grep xhshook'

Background

Full reverse engineering session: Hermes Agent on Pixel 10 Pro Fold, Android 16, XHS 9.35.1.

Campaign goal: MITM capture xy-common-params (ECFAAF/ECFAAF01 signatures) from edith.xiaohongshu.com.

Confirmed: edith uses Google Mainline Cronet (libmainlinecronet.141.0.7340.3.so, 6MB) with static BoringSSL. Frida Interceptor cannot hook static-linked symbols. Only viable bypass is LSPosed CertificatePinner hook → MITM proxy capture.

Other achievements from the session:

  • Java TrustManager bypass works for non-edith domains (crash/as/t2/mediacloud)
  • sentry abort Interceptor works (Frida survives 35+ seconds)
  • --pause mode confirms edith goes through TrustManager (timing race is the issue)
  • xhshook module pre-built at /data/adb/modules/xhshook/xhshook.apk