Skip to content

[bug] fix legacy WMPF support broken by the 25715 refactor - #288

Closed
tom613951 wants to merge 2 commits into
evi0s:mainfrom
tom613951:fix/legacy-scene-chain-25510
Closed

tom613951 wants to merge 2 commits into
evi0s:mainfrom
tom613951:fix/legacy-scene-chain-25510

Conversation

@tom613951

@tom613951 tom613951 commented Sep 21, 2026 •

Copy link
Copy Markdown

Fix legacy WMPF support broken by the 25715 refactor

Summary

Two independent defects break legacy WMPF builds (anything configured with
SceneOffsets, e.g. 25510 / 25558 / 25560) on current main:

  1. frida/hook.js — every AppletIndexContainer::OnLoadStart call throws
    ReferenceError: 'remoteDebugParametersPtr' is not defined, so mini-programs
    never enter remote-debug mode.
  2. frida/autodetect/win32.js — --auto-detect rejects these builds with
    legacy scene path: expected one candidate, found 0.

Both were introduced by 2970a6c ("[version] new version 25715") and its
companion 9be09da ("[auto-detect] update auto-detect scripts"). That commit
also carries the note TODO: auto-detect should be updated, and the later
e73175d ("fix legacy wmpf version detection") addressed only
src/platform/win32.ts, which handles version detection rather than hook
execution or offset recovery.

Fix 1 — frida/hook.js (handleOnLoadStart)

The refactor split the previously single-pass legacy chain into two halves and
introduced two defects in the config.SceneOffsets branch:

  1. Wrong variable name. The final hop read remoteDebugParametersPtr, which
    is never declared (only miniappLaunchConfigPtr, remoteDebugConfigPtr and
    miniappScenePtr exist).

  2. Chain split at the wrong point. The pre-refactor implementation walked
    all six offsets in one go:

    const miniappConfigPtr = a1
        .add(sceneOffsets[0]).readPointer()
        .add(sceneOffsets[1]).readPointer();
    const miniappScenePtr = miniappConfigPtr
        .add(sceneOffsets[2]).readPointer()
        .add(sceneOffsets[3]).readPointer()
        .add(sceneOffsets[4]).readPointer()
        .add(sceneOffsets[5]);

    The refactored version instead terminated miniappLaunchConfigPtr after an
    extra dereference of offset[2] and continued from offset[3]. That
    inserts one dereference too many and lands on the wrong struct, so even
    after fixing the variable name the scene read would still be wrong.

Restore the legacy branch to a single six-hop pass, keep remoteDebugConfigPtr
assigned so the code below stays defined for both branches, and document why
the legacy branch returns early (on these builds remote-debug mode is enabled
by the args[1] |= 0x1 write in patchOnLoadStart, and there is no
WebSocketURLStringOffset / RemoteDebugModeOffset field to patch).

The modern branch (MiniAppConfigStructOffsets, 25715+) is untouched.

Fix 2 — frida/autodetect/win32.js (recoverRemoteGuard)

recoverRemoteGuard assumed the scene and mode comparisons always share a base
expression (item.baseKey === scene.baseKey). On these builds they do not: the
scene value lives behind a deep pointer chain, while the remote-debug flag is
read straight off the function's first argument.

Disassembly of the detected helper on 25510 shows the mismatch directly:

mov rsi, rcx                          ; first argument saved
mov rax, [rcx + 8]                    ; offset[2] = 8
mov rcx, [rax + 0x5c0]                ; offset[3] = 1472
mov rcx, [rcx + 0x10]                 ; offset[4] = 16
cmp dword ptr [rcx + 0x1c8], 0x44d    ; scene, offset[5] = 456, immediate 1101
cmp byte  ptr [rsi + 0x29], 1         ; mode, different base (rsi, not rcx)

The scene comparison resolves to argumentIndex 0 with offsets
[8, 1472, 16], while the mode comparison resolves to a different base with
an empty chain, so baseKey can never match and no candidate is produced.

The former frida/version-detect.js did not have this constraint: it located
the scene by scanning opcode patterns (81 b9 ?? ?? ?? ?? 4d 04 00 00), which
is why auto-detect used to work on these builds. 9be09da replaced that with
structural recovery and lost the coverage.

Legacy detection only needs the scene side — SceneOffsets is assembled from
rootOffsets + sceneOffset — so fall back to an unpaired scene candidate
instead of rejecting the build. A matching mode field is still preferred when
one exists, so 25715 and other modern builds behave exactly as before. The
modern path additionally refuses a null mode offset so the fallback can never
produce a partial modern config.

Why both paths can coexist

Both layouts describe the same pointer progression; they only differ in how
it is sliced:

legacy                                    modern
a1 +o0 -> +o1 ->                          a1 +LC0 -> +LC1 -> +LC2
  +o2 -> +o3 -> +o4 -> +o5                  +RD0 -> +RD1 -> +SceneOffset

Dispatching on config.SceneOffsets (as the existing code already does) is
sufficient to support both.

Testing

Environment: Windows 11 x64, WeChat with WMPF 25510 (flue.dll under
%appdata%\Tencent\xwechat\XPlugin\Plugins\RadiumWMPF\25510\extracted\runtime).

Fix 1 — npx ts-node src/index.ts --debug-frida

Before:

[frida] script loaded, WMPF version: 25510, pid: 23140
[frida client] { type: 'error',
  description: "ReferenceError: 'remoteDebugParametersPtr' is not defined",
  stack: "... at handleOnLoadStart (/script1.js:87)" }
(repeats on every mini-program load)

After:

[frida] script loaded, WMPF version: 25510, pid: 23140
[frida client] [inteceptor] AppletIndexContainer::OnLoadStart onEnter, indexContainer.this: 0x8cc07de2880
[frida client] [hook] scene: 1256
[frida client] [hook] hook scene condition -> 1101
[miniapp] miniapp client connected

Zero ReferenceError / TypeError / type: 'error' entries in the run.

Fix 2 — npx ts-node src/index.ts --auto-detect --debug-frida

Before:

[frida] auto-detecting hook offsets...
Error: [frida] auto-detect failed: Error: legacy scene path: expected one candidate, found 0
    at findLegacyScenePath (/script1.js:831)
    at detectLegacy (/script1.js:839)
    at detect (/script1.js:904)

After:

[frida] auto-detecting hook offsets...
[frida] detected hook offsets: {"Version":25510,"LoadStartHookOffset":"0x2ce4640","CDPFilterHookOffset":"0x3974ff0","SceneOffsets":[64,1536,8,1472,16,456]}
[frida] script loaded, WMPF version: 25510, pid: 23140
[frida client] [hook] scene: 1256
[frida client] [hook] hook scene condition -> 1101
[miniapp] miniapp client connected

The recovered offsets match the hand-verified
frida/config/win32/addresses.25510.json exactly:

Field addresses.25510.json auto-detected
LoadStartHookOffset 0x2ce4640 0x2ce4640
CDPFilterHookOffset 0x3974ff0 0x3974ff0
SceneOffsets [64, 1536, 8, 1472, 16, 456] [64, 1536, 8, 1472, 16, 456]

Also confirmed on the same build that the change does not regress concurrency:
three mini-programs opened simultaneously hold three established connections to
the debug server on port 9421, and all three platforms' credentials are
captured in the same probe round.

Notes for the reviewer

  • No config files changed. frida/config/win32/addresses.25510.json was already
    correct and is unchanged.
  • The two fixes are independent and could be split into separate PRs if that is
    preferred; they are combined here because both are required for the same
    build to work end to end.
  • --auto-detect now works on this build, so the TODO: auto-detect should be updated item from 2970a6c is at least partially addressed. Whether other
    legacy versions need further heuristic adjustments is unknown, as only 25510
    was available for testing.

The 25715 refactor (2970a6c) reworked handleOnLoadStart for the new flue
layout and broke the config.SceneOffsets branch:

1. The final hop referenced an undeclared variable
   (remoteDebugParametersPtr), throwing
   "ReferenceError: 'remoteDebugParametersPtr' is not defined" on every
   AppletIndexContainer::OnLoadStart call.

2. The chain was split after an extra dereference of offset[2], so the
   legacy six-hop chain was walked incorrectly and landed on the wrong
   struct even once the name was fixed.

Because the exception aborts onEnter, miniappScenePtr was never computed,
writeInt(1101) never ran, and mini-programs never entered remote-debug
mode. The debug server still started and reported the correct WMPF version,
so the breakage only became visible when a mini-program was launched.

Restore the legacy branch to a single six-hop pass, matching the
pre-refactor implementation, and keep remoteDebugConfigPtr assigned so the
code below works for both branches. The modern branch
(MiniAppConfigStructOffsets) is unchanged.

Verified on Windows x64 with WMPF 25510: previously every mini-program load
threw the ReferenceError; now the hook reports "[hook] scene: 1256" and
rewrites it to 1101.
recoverRemoteGuard assumed the scene and mode comparisons always share a
base expression (baseKey === scene.baseKey). Legacy builds such as 25510
do not: the scene value sits behind a deep pointer chain while the
remote-debug flag is read straight off the function's first argument.

On 25510 the detected helper looks like:

  mov rsi, rcx
  mov rax, [rcx + 8]
  mov rcx, [rax + 0x5c0]
  mov rcx, [rcx + 0x10]
  cmp dword ptr [rcx + 0x1c8], 0x44d
  cmp byte  ptr [rsi + 0x29], 1

so the scene comparison resolves to argumentIndex 0 with offsets
[8, 1472, 16] while the mode comparison resolves to a different base with
an empty chain. baseKey can never match, no candidate is produced, and
detectLegacy fails with "expected one candidate, found 0".

The former frida/version-detect.js had no such constraint: it located the
scene by scanning opcode patterns, which is why auto-detect used to work
on these builds.

Legacy detection only needs the scene side (SceneOffsets is assembled from
rootOffsets + sceneOffset), so fall back to an unpaired scene candidate
instead of rejecting the build. A paired mode field is still preferred
when present, so modern builds are unaffected, and the modern path now
rejects a null mode offset so the fallback cannot emit a partial config.

Recovered offsets on 25510 now match the hand-verified
addresses.25510.json exactly.
@tom613951 tom613951 changed the title [bug] fix legacy scene pointer chain broken by the 25715 refactor [bug] fix legacy WMPF support broken by the 25715 refactor Sep 21, 2026
@evi0s

evi0s commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Thank you for your contribution. However, the bug was already fixed in PR #286. Additionally, the auto-detect was already updated and tested, and I cannot see breaking changes in this PR regarding auto-detection methods. Technically, the auto-detect feature is for the later version that the repo does not have a corresponding config, that is, the previous builds are not take into the consideration of the scope that auto-detect feature would concern. Still, I really appreciate your time and contribution, thank you!

@tom613951

Copy link
Copy Markdown
Author

Thanks for the explanation — you're right on both counts.

  1. #286 already fixed the hook.js legacy branch. I had assumed the different slicing was behaviourally different, but the resulting scene address is identical (*(*(*(*(*(a1+o0)+o1)+o2)+o3)+o4)+o5), so my change was redundant. Sorry for the noise.

  2. I misread the scope of auto-detect. Exercising --auto-detect against a build that already has a hand-verified config was the wrong way to use it, so the expected one candidate, found 0 failure I hit is expected behaviour rather than a regression worth fixing.

Closing this. Thanks for the quick review.

@tom613951 tom613951 closed this Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants