# Loopback protocol findings This note captures the first Npcap loopback decode pass for the MXAccess to LMX/NMX path. ## Capture set The repeatable capture runner is: ```text analysis\scripts\run_loopback_capture.ps1 ``` It starts `dumpcap` on the Npcap loopback adapter and runs the x86 MXAccess harness: ```text src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe ``` Focused loopback captures: | Folder | Scenario | | --- | --- | | `captures\013-loopback-subscribe-scalars` | subscribe `TestBool`, `TestInt`, `TestString` | | `captures\014-loopback-subscribe-array-bracketed` | subscribe `TestStringArray[]` | | `captures\015-loopback-subscribe-invalid` | subscribe invalid reference | | `captures\016-loopback-write-test-int-advised` | advised same-value write to `TestInt` | Per-capture generated files: | File | Purpose | | --- | --- | | `loopback.pcapng` | raw Npcap loopback capture | | `harness.log` | timestamped MXAccess method and callback log | | `dcerpc-49704.tsv` | tshark DCE/RPC frame index for the NMX service port | | `nmx-conversations.tsv` | extracted IPv6 conversations involving port `49704` | | `nmx-payload-packets.tsv` | payload packet index for the selected `49704` conversation | | `nmx-stream-*.bin` | reassembled directional payloads for the selected `49704` conversation | | `tcp-conversations.tsv` | all TCP payload conversations ranked by byte count | | `tcp-payload-packets.tsv` | payload packet index for the top TCP conversations | | `tcp-stream-*.bin` | reassembled directional payloads for the top TCP conversations | Cross-capture summaries: ```text analysis\scripts\extract_nmx_loopback.py analysis\scripts\extract_tcp_conversations.py analysis\scripts\decode_tcp_payload_packets.py analysis\scripts\decode_mixed_local_stream.py analysis\scripts\summarize_dcerpc.py analysis\network\dcerpc-loopback-summary.tsv analysis\network\write-window-tcp-payloads.tsv ``` ## DCE/RPC service path Wireshark decodes the main `::1: <-> ::1:49704` traffic as DCE/RPC. The captures show these DCE/RPC interface UUIDs: | UUID | Observed use | | --- | --- | | `4e0c90df-e39d-4164-a421-ace89484c602` | initial bind context, opnum `0` calls | | `1981974b-6bf7-46cb-9640-0260bbb551ba` | altered context, main opnums `0`, `2`, `3`, `5` | The UUIDs were not present as direct keys under the checked COM registry areas: ```text HKCR\Interface HKCR\Wow6432Node\Interface HKCR\CLSID HKCR\Wow6432Node\CLSID HKCR\TypeLib HKCR\Wow6432Node\TypeLib ``` This makes `NmxSvcps.dll` and `WWProxyStub.dll` high-value next targets. They are likely proxy/stub components, but their interfaces are not exposed through the simple COM registry lookup above. ## DCE/RPC shape For good scalar subscribe, good array subscribe, and successful advised write, the main `49704` DCE/RPC shape is identical: ```text 165 request/response pairs on ctx 1, opnum 3 10 request/response groups on ctx 0 opnum 0, ctx 1 opnum 0, ctx 1 opnum 2 3 request/response pairs on ctx 1, opnum 5 ``` The invalid subscribe adds one extra group: ```text 176 request/response pairs on ctx 1, opnum 3 11 request/response groups on ctx 0 opnum 0, ctx 1 opnum 0, ctx 1 opnum 2 3 request/response pairs on ctx 1, opnum 5 ``` Interpretation: invalid item resolution does not fail at `AddItem`; it drives additional NMX/RPC activity during `AdviseSupervisory`, matching the harness callbacks where invalid names return configuration errors only after advise. ## Write path split The successful write capture has the standard main `49704` DCE/RPC conversation: ```text ::1:55840 <-> ::1:49704 803 frames, about 84 kB ``` It also has a short additional `49704` DCE/RPC conversation: ```text ::1:49768 <-> ::1:49704 192 frames, about 19 kB ``` The harness write timestamps were: ```text 2026-04-25T05:13:58.0479762Z mx.write.begin 2026-04-25T05:13:58.0489758Z mx.write.end 2026-04-25T05:13:58.2561934Z mx.event.write-complete ``` With the pcap first frame at epoch `1777094027.708322300`, this places the write call around relative time `10.34s` and the write-complete callback around relative time `10.55s`. No DCE/RPC frames on port `49704` occur in the `10.30s` to `10.62s` window. Instead, the active payload in that exact window is primarily TCP stream `0`: ```text 127.0.0.1:57415 <-> 127.0.0.1:57433 ``` That stream is not decoded by Wireshark as DCE/RPC. Its payload has a compact binary framing pattern with frequent 12-byte control messages and small length-prefixed payloads. Example payload prefixes around the write window include little-endian values such as: ```text 1a 00 00 00 ... 16 00 00 00 ... 22 00 00 00 ... 1e 00 00 00 ... 3f 00 00 00 ... ``` Interpretation: the native implementation probably uses DCE/RPC for service activation/session coordination and a separate local binary channel for at least part of the advised update/write-complete path. The managed replacement must account for both layers. The stream has been extracted as: ```text captures\016-loopback-write-test-int-advised\tcp-stream-127_0_0_1_57415-to-127_0_0_1_57433.bin captures\016-loopback-write-test-int-advised\tcp-stream-127_0_0_1_57433-to-127_0_0_1_57415.bin ``` The stream is now decoded with packet-boundary and mixed-record helpers: ```text analysis\scripts\decode_tcp_payload_packets.py analysis\scripts\decode_mixed_local_stream.py analysis\scripts\analyze_write_window.py analysis\scripts\diff_write_window_records.py ``` The mixed-record model is: - 12-byte control records: `int32 code_or_status`, `int32 token_low`, `int32 token_high`. - Data records: `uint32 body_length`, followed by the data body. - A positive control value can announce one or more following data records. - `-1` appears as acknowledgement/status. - `-2` appears as a bidirectional status/control marker around write windows. Usable value-change write captures added after this decode pass: | Folder | Result | | --- | --- | | `captures\017-loopback-write-test-int-100` | `TestInt` changed `99 -> 100` | | `captures\020-loopback-write-test-int-102` | `TestInt` changed `101 -> 102` | | `captures\021-loopback-write-test-int-sequence-103-105` | same-session writes `103`, `104`, `105` | The detailed COM contract and managed-client implication note is: ```text docs\NMX-COM-Contracts.md ``` ## Same-session write sequence `captures\021-loopback-write-test-int-sequence-103-105` writes three int values through one registered/advised MXAccess session: ```text 103 at 2026-04-25T05:53:06.9746508Z 104 at 2026-04-25T05:53:07.6963047Z 105 at 2026-04-25T05:53:08.4180133Z ``` Each write produced a good data-change callback followed by a good write-complete callback. The top local payload stream remained: ```text 127.0.0.1:57415 <-> 127.0.0.1:57433 ``` The analyzer output is: ```text captures\021-loopback-write-test-int-sequence-103-105\write-window-mixed-records.tsv analysis\network\write-window-body-diff-021-w0-vs-w1.tsv analysis\network\write-window-body-diff-021-w1-vs-w2.tsv ``` Within the `-0.10s` to `+0.12s` write-complete windows, the repeated records are the same families already seen in the single-write captures: ```text 54 8f 63 40 e2 5e 31 40 ... 26-byte data body 1c 21 18 d0 c4 6f 33 bb ... 34-byte data body 98 04 33 cb 0c b4 7c 38 ... 67-byte data body 44 6b 99 d8 ec 1b bd b5 ... 52-byte data body 55 ce ff 62 b2 1b 3a 50 ... 30-byte data body ``` The raw int values `103`, `104`, and `105` were not isolated from the pcap-only mixed stream decode. The most consistent byte changes in that layer are counter-like fields at offset `14` in the 26-byte and 30-byte data bodies, offset `0` in the 22-byte and 26-byte response bodies, and related token fields in adjacent 12-byte controls. Those fields advance with the local message sequence and should not be treated as the application value. The application value was later isolated one layer higher with Frida hooks placed using headless Ghidra RVAs. See: ```text docs\Ghidra-Headless-Analysis.md captures\023-frida-write-test-int-sequence-109-111\frida-events.tsv ``` ## Plaintext result The selected `49704` stream binaries did not contain the test tag names in ASCII or UTF-16LE. The captured protocol is therefore not a simple plaintext tag-reference transport. ## Current reconstruction hypothesis The implementation path is: ```text ArchestrA.MXAccess.dll -> LmxProxy.dll COM in-proc server -> local DCE/RPC to NmxSvc.exe on ::1:49704 -> local binary channels for request/callback data -> LMX/NMX runtime and Galaxy-derived security/type metadata ``` The next useful work is to decode the proxy/stub interfaces and the local binary stream structure, then tie decoded calls back to MXAccess harness events.