Layout:
- src/ .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
MxAsbClient, probes, tests, harnesses. Executable spec.
- design/ Architectural plan for the Rust port (M0–M6), error
model, protocol invariants, risks (R1–R16), adversarial
review log (review.md).
- rust/ Rust workspace. M0 skeleton + M1 codec parity.
mxaccess-codec: 215 unit tests + 2 cross-implementation
parity tests (byte-identical against .NET reference).
Other crates are M0 stubs awaiting M2+.
- captures/ Frida + netsh + pcap evidence per CLAUDE.md
("captures are evidence, not throwaway logs").
- analysis/ Decompiled C# (frida/proxy/decompiled-*),
Ghidra exports for native DLLs (`exports/` only —
working state at `projects/` and AVEVA's input
binaries at `input/` are gitignored).
- docs/ Reverse-engineering reference docs.
- tools/ Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/ Rust CI: fmt + build + test + clippy on Windows.
- LICENSE MIT (Joseph Doherty, 2026).
Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly
Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 KiB
MXAccess capture run - 2026-04-25
This run used the primary installed MXAccess interop assembly:
C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll
The harness is in src\MxTraceHarness and builds as net481 x86 because
MXAccess activates the 32-bit LMXProxy.LMXProxyServer COM server.
Galaxy repository inputs
The tag selector is:
analysis\sql\select_capture_tags.sql
It connects to the Galaxy repository described in
C:\Users\dohertj2\Desktop\lmxopcua\gr\connectioninfo.md:
Server=localhost; Database=ZB; Integrated Security=SSPI
The output from this run is saved at:
analysis\db\capture-tag-candidates.tsv
Selected test tags:
| Runtime reference | Type | Security | Notes |
|---|---|---|---|
TestChildObject.TestBool |
Boolean | Operate / 1 | scalar read/subscription |
TestChildObject.TestInt |
Integer | Operate / 1 | scalar read/subscription/write |
TestChildObject.TestString |
String | Operate / 1 | scalar read/subscription |
TestChildObject.TestStringArray[] |
String array, length 10 | Operate / 1 | array read/subscription |
NoSuchObject_999.NoSuchAttr |
invalid | n/a | negative resolution path |
Important naming result: array attributes must be passed to MXAccess with the
[] suffix. TestChildObject.TestStringArray produced
MxCategoryConfigurationError, detail 1003; TestChildObject.TestStringArray[]
returned System.String[] with quality 192.
Harness behavior captured
Built command:
dotnet build src\MxTraceHarness\MxTraceHarness.csproj -c Release
Executable:
src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe
Scenarios now implemented:
| Scenario | High-level sequence |
|---|---|
register |
Register, wait, Unregister |
add-remove |
Register, AddItem, wait, RemoveItem, Unregister |
subscribe |
Register, AddItem, AdviseSupervisory, wait callbacks, UnAdvise, RemoveItem, Unregister |
write |
Register, AddItem, AdviseSupervisory, wait initial callback, Write, wait OnWriteComplete, cleanup |
Write precondition found in this run:
Writeimmediately throwsE_INVALIDARGif called afterAddItemonly.Writesucceeds afterAdviseSupervisoryhas established the item connection.- The fourth
Writeargument should follow the existing bridge convention: pass the Galaxysecurity_classificationvalue for the attribute. ForTestChildObject.TestInt, that value is1.
Successful same-value write:
captures\010-write-test-int-advised-same-value\harness.log
Observed sequence:
mx.event.data-change: TestChildObject.TestInt = 99, quality 192
mx.write.begin: value 99, UserId/security classification 1
mx.write.end
mx.event.write-complete: MxCategoryOk, MxSourceRespondingAutomationObject, detail 0
Captures produced
Every capture folder contains harness.log, netsh.etl, network.pcapng, and
tool stdout/stderr files unless noted.
| Folder | Result |
|---|---|
captures\001-register |
register/unregister success |
captures\002-add-remove-scalar |
scalar AddItem/RemoveItem success |
captures\003-subscribe-scalars |
bool/int/string initial data changes, quality 192 |
captures\004-subscribe-array-runtime-name |
array without [] returns configuration error detail 1003 |
captures\005-subscribe-array-bracketed-name |
array with [] returns System.String[], length 10, quality 192 |
captures\006-add-invalid |
invalid AddItem still returns an item handle; no validation until advise |
captures\007-subscribe-invalid |
invalid subscribe returns configuration error detail 6 |
captures\008-write-test-int-same-value |
write without advise throws E_INVALIDARG using fourth arg 0 |
captures\009-write-test-int-same-value-security-1 |
write without advise still throws E_INVALIDARG using fourth arg 1 |
captures\010-write-test-int-advised-same-value |
advised same-value write succeeds |
captures\011-pktmon-subscribe-scalar-loopback-probe |
pktmon probe; still did not expose ::1 NMX loopback traffic |
Converted pcap summaries:
analysis\network\pcap-summary.txt
captures\011-pktmon-subscribe-scalar-loopback-probe\pcap-summary.txt
Network capture status
netsh trace and pktmon both captured external/background traffic and can be
converted to pcapng. They did not expose the local loopback session that
NmxSvc.exe keeps open on IPv6 loopback.
Current NmxSvc.exe socket evidence during the run:
TCP 10.100.0.48:5026 LISTEN
UDP 10.100.0.48:5026
TCP ::1:49829 <-> ::1:49704 ESTABLISHED
Interpretation: the payload needed for a native managed client is probably on
the local ::1 connection between the 32-bit MXAccess stack and NmxSvc.exe,
not on the physical NIC path. Capturing that requires one of:
- working Npcap loopback capture,
- API Monitor / debugger tracing at Winsock or COM method boundaries,
- ETW provider capture if the AVEVA NMX components emit enough payload detail,
- direct lower-level COM tracing around
INmx4.PutRequest2/GetResponse2andIDataClientmethods.
Wireshark 4.6.4 and etl2pcapng 1.11.0 were installed.
Update after interactive install: Npcap 1.87 is now installed and working.
dumpcap -D lists \Device\NPF_Loopback (Adapter for loopback traffic capture).
The verification capture is:
captures\012-npcap-loopback-subscribe-scalar\
Files:
loopback.pcapng
harness.log
nmx-loopback-frames.tsv
The focused loopback capture includes the active MXAccess/NMX conversation:
::1:59335 <-> ::1:49704
803 frames, about 84 kB, duration 7.6793 s
It also saw the pre-existing service connection:
::1:49829 <-> ::1:49704
4 frames, about 526 bytes
This confirms Npcap loopback capture is the correct mechanism for collecting the actual local NMX payloads needed for protocol reconstruction.
Npcap loopback protocol captures
The repeatable runner is:
analysis\scripts\run_loopback_capture.ps1
Focused captures completed after Npcap verification:
| Folder | Result |
|---|---|
captures\013-loopback-subscribe-scalars |
good bool/int/string subscribe |
captures\014-loopback-subscribe-array-bracketed |
good string-array subscribe using [] suffix |
captures\015-loopback-subscribe-invalid |
invalid reference subscribe |
captures\016-loopback-write-test-int-advised |
advised same-value write succeeds |
Extraction and summary helpers:
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\analyze_write_window.py
analysis\scripts\diff_write_window_records.py
analysis\scripts\run_frida_mx_trace.ps1
analysis\scripts\extract_frida_trace.py
analysis\scripts\summarize_dcerpc.py
analysis\network\dcerpc-loopback-summary.tsv
analysis\network\write-window-tcp-payloads.tsv
Important packet result: the ::1:<ephemeral> <-> ::1:49704 traffic is DCE/RPC,
not a simple tag-string socket protocol. The observed interface UUIDs are:
4e0c90df-e39d-4164-a421-ace89484c602
1981974b-6bf7-46cb-9640-0260bbb551ba
Those UUIDs were not found as direct keys under the checked COM registry interface, CLSID, or TypeLib areas. The likely decode targets are therefore the native proxy/stub binaries:
C:\Program Files (x86)\ArchestrA\Framework\Bin\NmxSvcps.dll
C:\Program Files (x86)\ArchestrA\Framework\Bin\WWProxyStub.dll
Good scalar subscribe, good array subscribe, and successful advised write share
the same main 49704 DCE/RPC shape: 165 ctx-1 opnum-3 request/response pairs,
10 ctx-0/ctx-1 setup groups, and 3 ctx-1 opnum-5 pairs. The invalid subscribe
adds one more setup group and 11 more opnum-3 pairs, matching the observed
behavior that invalid references fail during advise/resolution, not AddItem.
The successful write has a second important observation. The harness write call
occurred at relative time about 10.34s, and the write-complete callback at
about 10.55s. No 49704 DCE/RPC frames appear in that exact window. The
active payload is primarily on:
127.0.0.1:57415 <-> 127.0.0.1:57433
That stream is compact binary traffic with small control messages and apparent little-endian length prefixes. This means the managed replacement likely needs to reproduce both the DCE/RPC coordination path and a separate local binary callback/request channel.
The write-window stream is extracted as:
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 has a mixed-record framing:
- 12-byte control records:
int32 code_or_status,int32 token_low,int32 token_high. - Data records:
uint32 body_length, followed by the data body. - Positive control values can announce one or more following data records.
-1appears as a normal acknowledgement/status control.-2appears around write-window status/control exchanges.
Additional differential write captures:
| Folder | Result |
|---|---|
captures\017-loopback-write-test-int-100 |
value changed 99 -> 100; pcap usable |
captures\018-loopback-write-test-int-101 |
write succeeded, but pcap is header-only and should not be used |
captures\019-loopback-write-test-int-101-rerun |
pcap usable, but same-value write because value was already 101 |
captures\020-loopback-write-test-int-102 |
value changed 101 -> 102; pcap usable |
captures\021-loopback-write-test-int-sequence-103-105 |
same-session sequence changed 102 -> 103 -> 104 -> 105; pcap usable |
captures\022-frida-write-test-int-sequence-106-108 |
Frida hooks validated Ghidra RVAs; first buffer dump helper was incomplete |
captures\023-frida-write-test-int-sequence-109-111 |
Frida hooks captured raw write values in LmxProxy and NmxAdptr buffers |
captures\024-frida-write-test-bool-sequence |
bool write matrix: VT_BOOL, 37-byte PutRequest, value slot at offset 18 |
captures\025-frida-write-test-float-sequence |
float write matrix: 40-byte PutRequest, float32 at offset 18 |
captures\026-frida-write-test-double-sequence |
double write matrix: 44-byte PutRequest, float64 at offset 18 |
captures\027-frida-write-test-string-sequence |
string write matrix: UTF-16LE payload at offset 26 |
captures\028-frida-write-test-datetime-sequence |
datetime write matrix: outbound UTF-16LE display string; callback FILETIME |
captures\029-frida-write-test-int-array |
int array write succeeds; packed int32 values at PutRequest offset 28 |
captures\030-frida-write-test-bool-array |
bool array write succeeds, but alternating requested values returned as paired values; needs follow-up |
captures\031-frida-write-test-float-array |
float array write succeeds; packed float32 values at PutRequest offset 28 |
captures\032-frida-write-test-double-array |
double array write succeeds; packed float64 values at PutRequest offset 28 |
captures\033-frida-write-test-string-array |
string array write succeeds; per-element UTF-16LE records |
captures\034-frida-write-test-datetime-array |
datetime array write succeeds, but body dump was truncated by the old 256-byte Frida cap |
captures\035-frida-write-test-datetime-array-full |
datetime array rerun with full 4096-byte Frida dump cap |
captures\036-frida-write-secured-test-int |
WriteSecured against Operate int rejected with 0x80004021 before value-bearing body |
captures\037-frida-write-secured2-test-int |
WriteSecured2 against Operate int rejected with E_INVALIDARG before value-bearing body |
captures\038-frida-write-secured-protectedvalue |
WriteSecured against real SecuredWrite bool rejected with 0x80004021 |
captures\039-frida-write-secured-verified-protectedvalue1 |
WriteSecured against real VerifiedWrite bool rejected with 0x80004021 |
captures\040-frida-write-normal-secured-protectedvalue |
normal Write with user/security 2 succeeds against SecuredWrite bool |
captures\041-frida-write-normal-verified-protectedvalue1 |
normal Write with user/security 3 succeeds against VerifiedWrite bool |
captures\042-frida-write2-test-int-timestamp |
Write2 succeeds; int at offset 18, FILETIME at offset 24 |
captures\043-frida-loopback-write-test-int-115 |
combined Frida plus Npcap loopback capture; exact adapter bodies are not present verbatim in TCP streams |
captures\044-frida-loopback-write-test-int-123456789 |
combined Frida plus Npcap loopback capture with distinctive value; raw scalar also absent from full pcap payload scan |
The same-session sequence was captured with:
--scenario=write --tag=TestChildObject.TestInt --type=int --values=103,104,105 --user-id=1 --write-delay-ms=1000 --write-interval-ms=700 --duration=5
Generated write-window analyses:
captures\017-loopback-write-test-int-100\write-window-mixed-records.tsv
captures\020-loopback-write-test-int-102\write-window-mixed-records.tsv
captures\021-loopback-write-test-int-sequence-103-105\write-window-mixed-records.tsv
analysis\network\write-window-body-diff-017-vs-020.tsv
analysis\network\write-window-body-diff-021-w0-vs-w1.tsv
analysis\network\write-window-body-diff-021-w1-vs-w2.tsv
The sequence capture is important because it keeps one MXAccess session alive
while changing only the requested int value. The decoded local-stream records
around write-complete still do not contain 103, 104, or 105 as plain
little-endian int32 payloads. The bytes that move consistently in the visible
records are request/session counters and checksummed or opaque body fields.
The pcap-only mixed-stream layer did not isolate the scalar value. Headless
Ghidra plus Frida then located it one native layer higher:
CLMXProxyServer::Write variant A RVA 0x12c0c
CNmxAdapter::PutRequest RVA 0x15169
CNmxAdapter::TransferData RVA 0x10996
CNmxAdapter::ProcessDataReceived RVA 0x112da
In captures\023-frida-write-test-int-sequence-109-111, the raw scalar values
are visible as little-endian int32:
| Function | Body size | Value offsets |
|---|---|---|
CLMXProxyServer::Write |
call args | args[5] = 109, 110, 111 |
CNmxAdapter::PutRequest |
40 |
offset 18 |
CNmxAdapter::TransferData |
86 |
offset 64 |
CNmxAdapter::ProcessDataReceived |
88 |
offset 84 |
The later Frida captures generalize the body format:
| Type | PutRequest |
TransferData |
Callback/update | Encoding |
|---|---|---|---|---|
| bool | size 37, offset 18 |
size 83, offset 64 |
size 85, offset 84 |
VT_BOOL; true ff ff ff 00 in write body and ff in data-change body; false 00 ff ff 00 and 00 |
| int | size 40, offset 18 |
size 86, offset 64 |
size 88, offset 84 |
little-endian int32 |
| float | size 40, offset 18 |
size 86, offset 64 |
size 88, offset 84 |
little-endian float32 |
| double | size 44, offset 18 |
size 90, offset 64 |
size 92, offset 84 |
little-endian float64 |
| string | size 58 or 60, offset 26 |
size 104 or 106, offset 72 |
size 106 or 108, offset 92 |
UTF-16LE |
| datetime | size 86, offset 26 |
size 132, offset 72 |
size 98, offset 88 |
outbound UTF-16LE display string; callback FILETIME |
Full matrix:
analysis\frida\write-body-matrix.tsv
Array matrix:
analysis\frida\write-array-body-matrix.tsv
Write-mode matrix:
analysis\frida\write-mode-matrix.tsv
Frida-to-TCP mapper:
analysis\scripts\run_frida_loopback_capture.ps1
analysis\scripts\map_frida_to_tcp.py
analysis\scripts\parse_dcerpc_streams.py
captures\043-frida-loopback-write-test-int-115\frida-to-tcp-map.tsv
captures\044-frida-loopback-write-test-int-123456789\frida-to-tcp-map.tsv
Array body summary:
| Type | PutRequest |
TransferData |
Callback/update | Encoding |
|---|---|---|---|---|
| int[] | size 86, first value offset 28 |
size 132, first value offset 74 |
size 134, first value offset 94 |
descriptor kind 0x42, count 10, width 4, packed int32 |
| bool[] | size 66, first value offset 28 |
size 112, first value offset 74 |
size 114, first value offset 94 |
descriptor kind 0x41, count 10, width 2; observed value pairing needs follow-up |
| float[] | size 86, first value offset 28 |
size 132, first value offset 74 |
size 134, first value offset 94 |
descriptor kind 0x43, count 10, width 4, packed float32 |
| double[] | size 126, first value offset 28 |
size 172, first value offset 74 |
size 174, first value offset 94 |
descriptor kind 0x44, count 10, width 8, packed float64 |
| string[] | size 256, first string bytes at 41 |
size 302, first string bytes at 87 |
size 304, first string bytes at 107 |
descriptor kind 0x45, per-element UTF-16LE variable records |
| datetime[] | size 596, first string bytes at 41 |
size 642, first string bytes at 87 |
size 214, first FILETIME at 94 |
outbound per-element display strings; callback/update FILETIME sequence |
Secured/verified write result: the public WriteSecured and WriteSecured2
methods are exposed by COM but did not produce value-bearing requests in these
captures. Actual SecuredWrite and VerifiedWrite attributes accepted normal
Write calls when the fourth argument matched the Galaxy security
classification (2 or 3).
Write2 result: the timestamped int write still uses a 40-byte body with the
value at offset 18. A FILETIME timestamp is embedded at PutRequest offset
24, TransferData offset 70, and callback/update offset 75.
Transport correlation result: in capture 043, the raw int32 value 115
appears in TCP streams, but the exact Frida PutRequest, TransferData, and
callback bodies do not. The ::1:49704 hits around the scalar align with
DCE/RPC metadata/call IDs rather than the native adapter body.
Capture 044 uses 123456789 to avoid that ambiguity. The raw scalar is not
found in a full pcap payload scan, in parsed ::1:49704 DCE/RPC stubs, or in
the mixed local stream. This confirms the wire representation is transformed or
encoded before TCP.
Detailed notes are in:
docs\Loopback-Protocol-Findings.md
docs\NMX-COM-Contracts.md
Protocol facts established
Registerreturns session handle1for these short-lived runs.AddItemallocates local item handles but does not prove the reference exists.AdviseSupervisorytriggers item resolution and initial data/status callback.- Good scalar reads return:
MXSTATUS_PROXY.success = -1category = MxCategoryOkdetectedBy = MxSourceRequestingLmxdetail = 0- quality
192
- Invalid subscribe returns:
- value
null - quality
0 category = MxCategoryConfigurationErrordetectedBy = MxSourceRequestingLmxdetail = 6
- value
- Array name missing
[]returns:- value
null - quality
0 category = MxCategoryConfigurationErrordetectedBy = MxSourceRespondingAutomationObjectdetail = 1003
- value
- Successful write completion returns:
success = -1category = MxCategoryOkdetectedBy = MxSourceRespondingAutomationObjectdetail = 0
Later targeted non-core type captures:
062-frida-subscribe-intl-shortdesc:TestChildObject.ShortDesc(InternationalizedString) resolves and advises. A callback record used normal string wire kind0x05with compact empty payload04 00 00 00.063-frida-subscribe-elapsed-time-deadband:TestMachine_001.TestAlarm001.Alarm.TimeDeadband(ElapsedTime) resolves and advises. A callback record used wire kind0x07with four-byte zero payload.064-frida-subscribe-intl-percentand065-frida-subscribe-intl-mb: non-empty internationalized-string references resolved and advised but did not emit value callbacks during the capture window.066through069: timestampedWrite2captures for bool, float, double, and string. Float, double, and string use the same fixed/variable timestamped suffix shape as int. Timestamped bool uses wire kind0x01, one value byte, then the normal timestamp suffix.070through072: timestampedWrite2captures for int[], bool[], and string[]. These match the existing managed array timestamp encoder. The bool-array capture preserves the earlier MXAccess marshaling behavior where requested alternating bools arrive as paired true/false values.073,074, and076: timestampedWrite2captures for float[], double[], and datetime[]. These match the managed array timestamp encoder. The initial075datetime[] run used the wrong tag name and did not emit the expected0x37write body;076is the valid capture forTestDateTimeArray[].mxaccess-suspend-*/mxaccess-activate-*:SuspendandActivatethrow0x80070057beforeAdviseSupervisory. The advised variants succeed on scan-state targets: suspend returns pending/requesting-LMX, activate returns ok/requesting-LMX. Frida captures077and078show no additional NMX request body after the public method call, so this behavior appears local to the MXAccess/LMX layer once the item has been advised.079-frida-add-buffered-advise-testint:AddBufferedItem("TestInt", "TestChildObject")followed byAdviseSupervisorysends an item-control0x10reference-registration body forTestInt.property(buffer)in contextTestChildObject. It does not send a normal0x1fadvise body for the buffered handle.080-frida-buffered-external-write-testint: while the buffered handle is advised, adding normal writer handles in the same session sends normal reference-registration bodies, but noOnBufferedDataChangepayload was observed. This capture supplied the stable normal and buffered0x10registration bodies, plus the matching0x11registration-result frames, used byNmxReferenceRegistrationMessageandNmxReferenceRegistrationResultMessagetests.085-frida-subscribe-property-bufferand086-frida-write-property-buffer: direct literalTestChildObject.TestInt.property(buffer)add/advise follows normalAdviseSupervisory, notAddBufferedItem; the item-control0x10registration uses the full literal item with an empty context and receives a0x11registration result containing the same Base Runtime Object internal error text. NoOnBufferedDataChangeevent was fired.087-frida-authenticate-administrator-emptyand088-frida-authenticate-invalid-empty: password-redacted auth hook showsCLMXProxyServer.AuthenticateUserreturningS_OKand user ID1for bothAdministratorand an invalid user name with password length0. The only NMX traffic observed after the auth call is the normal unregister-time system reference cleanup, not an auth request body.111-frida-write-secured-auth-protectedvalueand112-frida-write-secured-auth-verified-protectedvalue1: pre-authenticating withAuthenticateUserreturns user handle1, but publicWriteSecuredstill returns0x80004021before any value-bearing NMX write body is emitted.113-frida-write-secured2-auth-protectedvalue,114-frida-write-secured2-auth-protectedvalue-false,115-frida-write-secured2-auth-protectedvalue-true, and116-frida-write-secured2-auth-verified-protectedvalue1: authenticated publicWriteSecured2succeeds and emits NMX command0x38over normal transfer kindWrite(3). The decoded boolean body layout is: command and 14-byte reference-handle projection, boolean wire kind0x01, a four-byte boolean scalar whose first byte carries the value, FILETIME timestamp, a 16-byte current-user authenticator token, UTF-16LE client-name byte length, null-terminated UTF-16LE client name, a 16-byte verifier token,0xffff, client token, and write index. The verifier token is all zeros whenVerifierUserIdis0and equals the authenticated token when the verifier handle is1.117-frida-write-secured2-auth-testint: after pre-authentication, publicWriteSecured2also succeeds againstTestChildObject.TestIntand emits command0x38with integer wire kind0x02. This proves the secured2 body is not boolean-only: it reuses the normal timestamped0x37value and timestamp prefix, then appends current-user token, client name, verifier token,0xffff, client token, and write index.089-frida-write-testint-wrong-type: writing stringnot_an_intto integerTestChildObject.TestIntsends a normal0x37write body using string wire kind0x05. NMX responds with a length-prefixed completion-only status body whose inner completion byte is0x41; MXAccess returnsS_OKfromWritebut does not fireOnWriteCompleteduring the harness wait.090-frida-write-invalid-reference: invalid reference add/advise produces the expected0x10registration and0x11registration-result failure. The later publicWritecall returnsS_OKbut no value-bearing0x37body or write-complete event is observed.091-frida-write-testint-double-type,092-frida-write-testbool-string-type, and093-frida-write-testdatetime-string-type: double-to-int writes emit a length-prefixed completion-only byte0x00, but MXAccess still does not fireOnWriteComplete; string-to-bool and string-to-time wrong-type writes match the string-to-int failure pattern and emit completion byte0x41.analysis/ghidra/exports/LmxProxy.dll.buffered-decompile.md: decompile ofAddBufferedItem,Fire_OnBufferedDataChange, andSetBufferedUpdateInterval. It confirms the.property(buffer)suffix, the buffered item-record marker, the seven-argument event firing path, and 100 ms tick rounding for buffered update intervals.analysis/ghidra/exports/LmxProxy.dll.buffered-event-xrefs.mdandanalysis/ghidra/exports/LmxProxy.dll.buffered-event-caller-decompile.md:Fire_OnBufferedDataChangehas one direct caller,FUN_1001657f. That function is the same nativeOnDataChange callback receivedpath used for normal data changes. It looks up the item record and branches on a buffered item flag at offset0x28: normal items call the_ILMXProxyServerEventsOnDataChangehelper, while buffered items convert the value to value, quality, and timestamp SAFEARRAY variants and call_ILMXProxyServerEvents2::Fire_OnBufferedDataChange.analysis/ghidra/exports/LmxProxy.dll.buffered-value-conversion-decompile.md: decompile of the buffered value conversion helper shows the public buffered event value argument is a SAFEARRAY of values, the quality argument is aVT_I2SAFEARRAY, the timestamp argument is aVT_UI8/FILETIME SAFEARRAY, and the status argument is the normalMXSTATUS_PROXY[]SAFEARRAY. This confirms the event shape even though a live buffered payload has not yet been produced.analysis/ghidra/exports/LmxProxy.dll.auth-decompile.md: decompile ofAuthenticateUserandArchestrAUserToId. Both increment a session-local user counter and store a mapping from that generated handle to a GUID/token identity before returning the generated handle to the public API caller.analysis/ghidra/exports/LmxProxy.dll.events-decompile.md: decompile ofFire_OnWriteCompleteandFire_OperationComplete. Both build the same three-argument COM event payload: server handle, item handle, and oneMXSTATUS_PROXYSAFEARRAY.OnWriteCompletedispatches event ID2;OperationCompletedispatches event ID3. No capture in this set emittedmx.event.operation-complete.analysis/ghidra/exports/LmxProxy.dll.event-xrefs.mdandanalysis/ghidra/exports/LmxProxy.dll.event-callers-decompile.md: generated with headless Ghidra to identify the event helper callers.Fire_OnWriteCompleteis called only fromFUN_10016b50, which logsOnSetAttributeResult callback received.Fire_OperationCompleteis called only fromFUN_10016d4b, which logsOperationComplete callback received. This confirms that mapping write completion statuses to both public events would be incorrect;OperationCompleteneeds a distinct native callback capture before the managed compatibility event can fire.analysis/ghidra/exports/LmxProxy.dll.operation-candidates-decompile.md: decompile of public operation candidates.RemoveItemperforms local item cleanup, whileSuspendandActivatequery anIMxScanOnDemandinterface and synchronously call vtable offsets0x0cand0x10, respectively. These paths did not reveal a call toFire_OperationComplete, matching captures077and078, which returned status structs but emitted no operation event.118-frida-suspend-advised-scanstate-longand119-frida-activate-advised-scanstate-long: reran advisedDevAppEngine.ScanStatesuspend/activate with direct hooks onCUserConnectionCallback.OnSetAttributeResultandCUserConnectionCallback.OperationComplete. The hooks installed, but neither callback entry point was called during the 20 second waits. This strengthens the conclusion that these public scan-on-demand calls return local status and do not trigger the publicOperationCompleteevent on this node.094-frida-buffered-separate-writer: the harness was adjusted sobuffered-external-writeregisters a separate writer server handle before adding/advising/writingTestChildObject.TestInt. The capture still produced nomx.event.buffered-data-changeand noFire_OnBufferedDataChangeFrida entry. It did show the buffered0x10registration/0x11result forTestInt.property(buffer)in contextTestChildObject, plus normal writer subscription/data callbacks. This rules out same-server-handle writer reuse as the reason buffered callbacks were absent.120-frida-buffered-history-testhistoryvalue: first historized-attribute buffered attempt againstTestHistoryValue, but the harness argument used--item-contextinstead of its actual--contextswitch, so MXAccess registeredTestHistoryValue.property(buffer)with an empty context. This is retained only as a harness-option correction.121-frida-buffered-history-testhistoryvalue-context: repeated the capture with--context=TestMachine_001. GR identifiesTestMachine_001.TestHistoryValueas a deployed, historized integer dynamic attribute. Native MXAccess emitted the expected buffered0x10registration and0x11result forTestHistoryValue.property(buffer)in contextTestMachine_001, and the separate writer session successfully wrote201,202, and203throughTestMachine_001.TestHistoryValue. No publicmx.event.buffered-data-changeand noFire_OnBufferedDataChangeFrida entry were observed.122-frida-buffered-history-testhistoryvalue-plainadvise: added a harness--plain-adviseprobe switch and repeated the same historized buffered scenario using publicAdviseinstead ofAdviseSupervisory. The registration/result bodies matched the context-bearing buffered shape, writer writes succeeded, and writer-session normal0x32data callbacks were seen, but the buffered subscriber still did not enterFire_OnBufferedDataChange. This makes the remaining buffered gap a runtime/source-delivery condition, not a plain-versus-supervisory advise mismatch.095-frida-write-elapsed-int: writing1000as anInt32toTestMachine_001.TestAlarm001.Alarm.TimeDeadbandemitted a normal0x37write body with integer wire kind0x02; MXAccess did not emit a special elapsed write kind for an integer caller value.096-frida-write-intl-string: writing"hello-native"as astringtoTestChildObject.ShortDescemitted a normal0x37write body with string wire kind0x05. The runtime returned a completion-only status byte0xef, so success semantics still need more captures, but outbound encoding is now defined for the caller-variant path.097-frida-write-bool-array-pattern: first attempted a bool-array pattern with--values, which the harness interprets as ten separate writes of one-element arrays. This capture is useful only as a harness argument reminder.098-frida-write-bool-array-pattern-10: writing one ten-elementbool[]value with the requested patterntrue,false,false,true,true,false,true,false,false,trueconfirmed the x86 MXAccess COM automation path still emits a paired/shifted VARIANT_BOOL-style wire payload:true,true,false,false,false,false,true,true,true,true. The NMX array descriptor remains0x41, count10, width2. The managed encoder keeps direct per-elementbool[]encoding for native .NET callers, while the observed x86 COM projection is preserved as a golden compatibility capture.099-frida-plain-advise-testint: publicCLMXProxyServer.AdviseforTestChildObject.TestIntemitted the same item-control0x1fbody shape as the earlierAdviseSupervisoryscalar subscription capture, including the trailing option value3. The wrapper's sharedAdvise/AdviseSupervisorypath now has capture support for this scalar case.100-frida-subscribe-string-array: subscribing toTestChildObject.TestStringArray[]produced a subscription-status callback with wire kind0x45, count10, width code4, and string-array element records, but the observed callback buffer stopped inside the final"JJ10"element after"JJ1". MXAccess did not raise a publicOnDataChangeevent.101-frida-write-string-array-update: writingKA1;KB2;KC3;KD4;KE5;KF6;KG7;KH8;KI9;KJ10emitted a complete outbound0x37string-array body. The following callback again carried a0x45string-array record but stopped inside the final"KJ10"element after"KJ1", and no public data-change event was observed. Current managed decoding treats this malformed callback value as incomplete rather than fabricating a string array.102-frida-subscribe-intl-shortdesc-after-write: subscribing toTestChildObject.ShortDescafter the earlier string write still produced the compact empty string callback form (wire kind 0x05, record length4) and no public data-change event. The earlier096write returned completion-only0xef, so it did not establish a non-emptyInternationalizedStringcallback value.103-frida-subscribe-elapsed-after-write: subscribing toTestMachine_001.TestAlarm001.Alarm.TimeDeadbandproduced a non-zeroElapsedTimecallback value using wire kind0x07followed by a 4-byte little-endian millisecond count (00 e4 0b 54,0x540be400). This confirms the existing elapsed-time callback decoder works for non-zero values as well as the earlier zero capture.
Managed x64 live probes
probe-remqi-managed: a .NET 10 x64 process using managed NTLM activation successfully resolvedINmxService2, completedRemQueryInterface, and returned partner version6.probe-session-write: direct managedWriteAsynctoTestChildObject.TestIntreturns success throughINmxService2.TransferData. The managed sender now uses the captured transfer kind for normal writes (Write, value3) rather than the earlier item-control kind.probe-session-subscribe: fixed local engine IDs caused stale registration collisions andUnAdvisefailures (0x80041101). The managed defaults now derive the local engine ID from the current process ID; unique engine IDs receive both0x32subscription-status and0x33data-update callbacks and clean up without that failure.- The live managed subscriber receives status-only scalar callbacks for
TestChildObject.TestInt: the raw0x33body contains status, quality, timestamp, and wire kind0x02, but no four-byte integer payload. The same status-only result occurs when an x86 MXAccess writer changes the tag while the managed subscriber is active. This narrows the missing value-delivery piece to the pre-advise AddItem/metadata registration path that MXAccess emits as0x17, not to the scalar callback decoder. - A managed encoder for the observed
0x17metadata body reproduces the capture exactly andNmxSvcaccepts it before advise, but it still does not turn the managed scalar callback into a value-bearing record. The0x17body is therefore documented as a captured metadata primitive, not a complete AddItem/value-subscription registration. - Replaying the observed pre-advise
0x17metadata body from the managed NTLM/DCOM path now reproduces the MXAccess-like callback sequence in x64: a 706-byte0x40metadata response containingDevPlatform.GR.TimeOfLastDeploy,DevPlatform.GR.TimeOfLastConfigChange, and the textAn internal error occurred in the Base Runtime Object; a 151-byte0x32metadata status callback with two datetime records; the 92-byte operation status frame; then the normal 108-byte scalar0x32status-only callback forTestChildObject.TestInt. This proves the pre-advise metadata primitive is live and decoded enough to inspect, but its runtime result is an internal base-object error rather than successful value-subscription state. - A same-session managed subscribe-then-write probe also remains status-only:
after
SubscribeAsync(TestChildObject.TestInt), managedWriteAsync(331)returns success, then the callback is0x33with wire kind0x02but no integer payload. This rules out cross-client delivery as the only cause; the native library still needs additional LMX item/write state beyond the current GR handle, NMX advise, and direct NMX write bodies. - Ghidra review of
CReferenceStringResolutionServiceexplains why replaying only the wire-visible metadata request is insufficient. The resolver comparesTimeOfLastDeployandTimeOfLastConfigChangeagainst pending reference status details, and on a usable GR subscription status it calls an in-processOnSetAttributeResultpath directly before removing the pending request. That state mutation is not produced by the current managed x64 replay, which only sends theNmxSvcrequest and receives callbacks. - Fixed Frida captures
104and105corrected the stack argument mapping forPrebindReferenceandUserRegisterPreboundReference. NativeTestChildObject.ShortDescreturns public prebound/reference handles of1, sends0x1fadvise with transfer kindItemControl(2), and itsIMxReference.GetMxHandlevalue uses property id10even though the GR attribute category is11. - After changing the managed sender to use transfer kind
2for0x1fadvise and changing the GR resolver to synthesize value handles with property id10, .NET 10 x64SubscribeAsync(TestChildObject.ShortDesc)receives the native-equivalent 112-byte callback: command0x32, status/detail3/0, quality0x00C0, wire kind0x05, and empty string value. This proves the primary value-subscription path can be reproduced in full managed code for at least one x86 value-bearing capture. - A managed-client regression check now asserts the distinct transfer kinds:
normal
0x37writes use transfer kindWrite(3), while0x1fadvise uses transfer kindItemControl(2). The generatedShortDesc"hello-native"write body matches capture096byte-for-byte, including the value handle with property id10. - Live x64 subscribe-write probes after that correction matched the native
outcomes:
TestChildObject.ShortDescstill returns completion-only byte0xef, which is therefore native-equivalent for the currentInternationalizedStringcaller path rather than a managed-only transport failure;TestChildObject.TestIntreturns completion-only byte0x00and a status-only0x33update callback with wire kind0x02. - Fresh x86 MXAccess baseline captures on the current VM state:
106-native-subscribe-testint-currentand107-native-write-testint-currentshow public MXAccess also raises nomx.event.data-changeforTestChildObject.TestIntsubscribe-only or subscribe-then-write, even though the write call returns success. Older captures003,011,012,017, and018prove this tag previously emitted value-bearing public events, so the current status-only managed result is not by itself a managed transport failure; it reflects the current runtime/Galaxy state or deployment value-delivery state. - Fresh current-state capture
108-native-subscribe-scalar-currentsubscribed toTestBool,TestInt,TestFloat,TestString, andShortDescthrough native x86 MXAccess and raised no publicmx.event.data-changefor any of them. Because native x86 also suppresses the decoded emptyShortDescadapter callback at the public event layer,MxNativeCompatibilityServernow suppresses emptyInternationalizedStringDataChangedpromotion while leaving the low-levelMxNativeSession.CallbackReceivedevent intact. - The new .NET 10 x64 compatibility probe
--probe-compatibility-subscribevalidates that public-facade behavior:TestChildObject.ShortDescandTestChildObject.TestIntboth reportedcompat_data_changes=0, matching fresh native x86 public-event behavior on the current VM state. - The companion
--probe-compatibility-subscribe-writeprobe validates public write-path facade behavior:TestChildObject.TestInt = 793andTestChildObject.ShortDesc = hello-compatboth completed the managed write call but reportedcompat_subscribe_write_data_changes=0andcompat_subscribe_write_completes=0, matching the current native public behavior where completion-only NMX statuses do not surface asOnWriteComplete. - Invalid-reference parity is now modeled in the compatibility facade:
NoSuchObject_999.NoSuchAttrreturns an item handle fromAddItem, andAdviseSupervisoryemits one public data-change withvalue=null,quality=0,status_success=0,ConfigurationError,RequestingLmx, and detail6, matching captures007and015. A subsequent compatibility write to that invalid handle returns without adding a public write-complete event, matching capture090. - A mixed multi-item .NET 10 x64 compatibility probe with
ShortDesc,TestInt, andNoSuchObject_999.NoSuchAttrvalidated handle routing in one server session: items1and2produced zero public changes on the current VM state, while item3produced exactly one configuration-error data-change. - Compatibility write argument validation now mirrors two x86 error captures:
normal
WriteafterAddItembut before advise returns0x80070057, as in captures008/009, and normalWriteagainst anAddBufferedItemhandle returns0x80070057, as inmxaccess-add-buffered-write-testint-context.log. captures\109-native-post-remove-errorsrecords stale item-handle parity afterRemoveItem. Native x86 MXAccess returnsArgumentException0x80070057forAdvise,AdviseSupervisory,UnAdvise,Write,Write2,Suspend,Activate, and a secondRemoveItemagainst the removed handle. The .NET 10 x64--probe-compatibility-post-removeprobe now reports the same0x80070057result for all of those operations.captures\110-native-invalid-handle-errorsrecords invalid server-handle and cross-server item-handle parity. Native x86 MXAccess returnsArgumentException0x80070057forAddItem,AddItem2,RemoveItem,Advise,AdviseSupervisory,UnAdvise,Write,Write2,Suspend,Activate, andUnregisterwhen the server handle is invalid, and also forRemoveItem,Advise, andWritewhen the item handle belongs to another server. The .NET 10 x64--probe-compatibility-invalid-handlesprobe now matches those HRESULTs.- Literal property-reference parity is now covered for the observed buffer
property. Captures
085/086showTestChildObject.TestInt.property(buffer)resolving to baseTestIntwith property id0x32and native handle01 00 01 00 02 00 05 00 36 d7 02 00 9b 00 32 00 3e da 00 00.GalaxyRepositoryTagResolvernow recognizes that suffix, and the .NET 10 x64 compatibility subscribe/write probe for the literal reference reports zero public data-changes and zero public write-completes, matching the native public behavior in those captures. AddItem2context resolution has been broadened in the compatibility layer. The live .NET 10 x64 probe now covers the captured simple context formTestInt+TestChildObject, a dotted primitive attributeAlarm.TimeDeadband+TestMachine_001.TestAlarm001, and a context-relative property referenceTestInt.property(buffer)+TestChildObject. All three add and advise successfully; no public data-change is promoted on the current VM state.- A mixed .NET 10 x64 compatibility write probe now validates per-item routing
for
TestChildObject.TestInt,TestChildObject.ShortDesc,TestChildObject.TestInt.property(buffer), andNoSuchObject_999.NoSuchAttrin one server session. The three valid/literal items reportdata_changes=0andwrite_completes=0on the current VM state, while the invalid reference reports exactly one configuration-error data-change and no write-complete. This confirms the compatibility facade does not misattribute suppressed completion-only writes or invalid-reference callbacks across item handles. - A first post-fix scalar matrix shows more decoder/runtime work remains:
TestChildObject.TestStringreceives a0x05string record but the current body ends before the declared string payload is complete, so the decoder reportsvalue=null;TestChildObject.TestFloatreceives wire kind0x03without enough bytes for a float payload;TestChildObject.TestBoolreceives a 105-byte frame that stops inside the timestamp/kind area and is surfaced asUnparsedCallbackReceived. These look like status/incomplete initial-value callbacks on this node rather than handle-generation failures, becauseShortDescnow matches the native value-bearing path. - Managed recovery lifecycle probe:
MxNativeClient.Probe --probe-session-recover --recover-attempts=2 --recover-delay-ms=100 --tag=TestChildObject.TestInt --value=323ran from .NET 10 x64. It subscribed successfully, reported one recovery-started event, zero failed-attempt events, one recovery-completed event, preserved one active subscription after recovery, and wrote through the recovered session. - Recovery callback policy is now explicit in the managed API: callbacks are
passed through during reconnect/replay and marked with
IsDuringRecoveryinstead of being suppressed or buffered. More live evidence is still needed to catch actual replay-window callback delivery on larger subscription sets. - Managed multi-subscription recovery probe:
MxNativeClient.Probe --probe-session-recover-multi --recover-attempts=2 --recover-delay-ms=100subscribed toTestChildObject.TestInt,TestChildObject.ShortDesc,TestMachine_001.ProtectedValue, andTestMachine_001.ProtectedValue1. Recovery replay preserved all four subscriptions. The run observed two data callbacks and four unparsed callbacks overall, but zero data, operation-status, reference-registration, or unparsed callbacks withIsDuringRecovery=true. - Managed multi-subscription recovery churn probe:
MxNativeClient.Probe --probe-session-recover-multi --recover-attempts=2 --recover-delay-ms=100 --recover-concurrent-writes --recover-write-start=330 --recover-write-count=5 --recover-write-delay-ms=10used a separate writer session to writeTestChildObject.TestIntvalues330-334during recovery. Two writes landed before the recovery-completed event. The recovering session preserved all four subscriptions, observed four data callbacks overall, and marked two data callbacks withIsDuringRecovery=true; operation-status, reference-registration, and unparsed recovery-window counts remained zero. - OperationComplete trigger analysis update: decompiled interop exposes
IMxCallback2.OperationComplete(int lCallbackId, ref MxStatus, string)plus theIDataConsumer.ActivateSuspend/ProcessActivateSuspend2path returningItemActiveResponse. PublicLMXProxyServerClass.SuspendandActivatecaptures118/119used directIMxScanOnDemandcalls and did not enter the callback. The next useful native trigger search should target DataConsumer activate/suspend completion, not another plain public suspend/activate run. aaMxDataConsumer.dllimport/probe: registry CLSID{85209FB2-0BA1-4594-BBC4-59D3DDAB823D}maps toMxDataConsumer ClassinC:\Program Files (x86)\Common Files\ArchestrA\Services\aaMxDataConsumer.dll.tlbimpgeneratedanalysis\interop\Interop.aaMxDataConsumer.dll, andilspycmddecompiled it intoanalysis\decompiled-interop\Interop.aaMxDataConsumer. The newMxDataConsumerProbex86 harness can instantiateDataConsumerClass, callRegisterCallback, resolve namespace strings to ID1, and callResolveReference,subscribe,ActivateSuspend, andProcessActivateSuspend2. Tested namespace strings remain disconnected (IsConnected=0), no registration or subscription records are returned, andProcessActivateSuspend2returns0x8007139F(ERROR_INVALID_STATE). This confirms the COM surface is callable but still missing the bootstrap needed to attach it to the live namespace.MxDataConsumerProbe --probe-dataclientattempted to createDataClientClassfrom the same imported type library. Creation failed with0x80040154(REGDB_E_CLASSNOTREG) because CLSID{73BC4121-FF89-4762-901C-206E2BD9FE87}is not registered on this node. The ASB deployment config showsServiceHost1atnet.tcp://localhost:4000/andDefault_ZB_MxDataProviderpublishingIASBIData,IASBIDataV2, andIDataV3, but a registered client/factory is still needed before endpoint connection probes can run.- Headless Ghidra plus ILSpy on
C:\Program Files (x86)\Common Files\ArchestrA\Services\aaMxDataConsumer.dllshows that its DataClient side is a mixed-mode wrapper around managed ASB proxies.CDataClientCLI.CreateConnectionsets the namespace string on aDataClientProxyand starts the auto-connect worker;DataClientProxycallsCIDataVersionAdapterFactory.GetIDataAdapter(accessName), which callsIDataProxySelector.SelectProxyForLatestEndpoint(accessName, new AsbMxDataSettings(), out error). ASBIDataV2Adapter.dllfrom the GAC containsIDataProxySelector. It first checksASBDataV2Proxy.FindIDataEndpoint(accessName, DiscoveryScope.Global); if any endpoints are discovered it returnsASBDataV2Proxy, otherwise it falls back toASBDataProxyV1.ASBDataV2Proxysearches LDS using scopedomainname/<accessName>/global.- New x64
AsbProxyProberesults: access nameZBdiscovers oneIASBIDataV2endpoint,net.tcp://desktop-6jl3kko/ASBService/Default_ZB_MxDataProvider/IDataV2, with listen URIs on the host name and local IPs. Access namesDefault_ZB_MxDataProvider,Galaxy,localhost, andZB2discover no IDataV2/IData endpoints.AsbProxyProbe --access=ZB --connectsuccessfully opens the ASB proxy from an x64 managed process;Connectreturns true, channel state isOpened, andPublishWriteCompletereturns success with zero pending writes. AsbProxyProbe --access=ZB --connect --register --read --write-int=401 --tag=TestChildObject.TestIntproves the direct ASB value path. The tag registers with item id18446462598732840961, reads as ASB type4(Int32) value334, accepts a write of401, and reads back401. The immediate write result is global success with per-item0x0000001F(OperationWouldBlock), thenPublishWriteCompletereturns0x00000020(PublishComplete) with the submitted write handle0xA5B20001and final per-item success.- A follow-up x86
MxDataConsumerProbe --namespace=ZBrun hung inside the COM wrapper and was stopped. Direct ASB proxy probing is now the preferred path for validating data-service functionality and any future OperationComplete trigger without relying on the standalone mixed-mode DataConsumer COM object. - Pure .NET 10 x64 ASB port update:
MxAsbClient.Probe --tag=TestChildObject.TestInt --write-int=412now completes the core register/read/write/complete data-service flow without AVEVA assembly references. It connects/authenticates through ASB system auth, retries the observed one-wayAuthenticateMestartup race inRegisterItems, reads the tag, accepts the write with immediate per-item0x0000001F, reads back412, and decodesPublishWriteCompleteresult0x00000020, count1, the submitted handle, and final per-item success. The saved evidence isanalysis\proxy\mxasbclient-probe-stage21-register-retry.txt. - Pure .NET 10 ASB unregister update:
MxAsbClient.Probe --tag=TestChildObject.TestIntnow callsUnregisterItemswith the registered item identity. The pure client and installedASBDataV2Proxycompare run both return global success0x00000000and per-item0x0000000B(OperationFailed) for this provider. Evidence:analysis\proxy\mxasbclient-probe-stage23-unregister-id.txtandanalysis\proxy\asbproxyprobe-unregister-compare.txt. - Pure .NET 10 ASB multi-item update:
MxAsbClient.Probe --tag=TestChildObject.TestInt --tag=TestMachine_001.TestHistoryValueregisters and reads both tags in two-item requests. Both tags return per-item register/read success; values decode as ASBTypeInt32previews412and303. Evidence:analysis\proxy\mxasbclient-probe-stage24-multi-read.txt.
Next capture steps
- Decode
NmxSvcps.dllandWWProxyStub.dllto recover interface names, opnum signatures, and NDR stub layouts for the observed DCE/RPC UUIDs. - Extract all localhost binary streams, not just port
49704, and correlate them to harness method/callback timestamps. - Trace
LmxProxy.dll,Lmx.dll,NmxAdptr.dll, andNmxSvc.exewith API Monitor around:send,recv,WSASend,WSARecv,INmx4.PutRequest2,INmx4.GetResponse2,IDataClient.RegisterItems2,IDataClient.Write2, andIDataClient.PublishWriteComplete2. - Decide whether a future strict MXAccess COM-compatibility mode should
intentionally reproduce the x86
SAFEARRAY VT_BOOLvalue-pairing behavior from capture098; the native managed path currently uses direct per-element bool encoding. - Build managed encoder/decoder tests from the scalar, array, and write-mode matrix TSVs.
- Decode DCE/RPC/NDR and mixed-stream records structurally; raw byte searching has confirmed that adapter bodies are not copied verbatim to TCP.