Full read-only SDK (src/AVEVA.Historian.Client) implementing the CLAUDE.md required
surface against AVEVA Historian's binary WCF protocol — no native AVEVA runtime
dependency. All operations live-verified against a local Historian:
- ProbeAsync, ReadRawAsync, ReadAggregateAsync, ReadAtTimeAsync, ReadEventsAsync
- BrowseTagNamesAsync, GetTagMetadataAsync (17 native data-type codes mapped)
- GetConnectionStatusAsync, GetStoreForwardStatusAsync, GetSystemParameterAsync
- 108/108 unit + integration tests pass
Includes the reverse-engineering toolkit (tools/AVEVA.Historian.ReverseEngineering)
used to decode the protocol: WCF probes, IL inspection via dnlib, and IL-rewrite
instrumentation (instrument-wcf-{write,read}message etc.) plus the .NET Framework
trace harness (tools/AVEVA.Historian.NativeTraceHarness) for parity testing.
Sanitized handoff evidence under docs/reverse-engineering/. Native AVEVA binaries
(current/, aveva-install-x64/, aveva-install-x86/) are gitignored — fetch separately
from the AVEVA installer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Frida Hook Pass: aahClientManaged Integrated Read
Scenario:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Attach-AahClientManagedFridaCapture.ps1 -TagName OtOpcUaParityTest_001.Counter -LookbackMinutes 1440 -MaxRows 1
Artifacts:
- Script:
scripts\frida\aahclientmanaged-open-query.js - Runner:
scripts\Attach-AahClientManagedFridaCapture.ps1 - Latest capture:
docs\reverse-engineering\frida-aahclientmanaged-attach-read-latest.ndjson
Result
The attach-based workflow is the reliable Frida path for this mixed CLR/native assembly:
- Start Windows PowerShell.
- Load
current\aahClientManaged.dll. - Sleep briefly before creating
HistorianAccess. - Attach Frida to the already-loaded process.
- Install hooks at candidate RVAs.
- Continue the native integrated read.
Frida confirmed:
- target process architecture:
x64 - loaded module:
aahClientManaged.dll - module base for latest run:
0x7ffd4a600000 - module size:
17166336 - path:
C:\Users\dohertj2\Desktop\histsdk\current\aahClientManaged.dll
Hooks were installed for candidate method RVAs including:
CClientInfo.SerializeOpenConnectionInParams*CHistoryConnectionWCF.OpenConnection*HistorianClient.StartQueryHistorianClient.StartDataQueryClientApp.StartDataQueryQuery.StartDataQueryCRetrievalConnectionWCF.StartQuery2QueryColumnSelector.SelectNonSummaryColumnsQueryColumnSelector.Save<SCrtMemFile>QueryColumnSelector.GetColumnSelectorFlagsHistorianClient.GetNextRow<DataQueryResultRow>HistorianClient.GetNextRow<EventQueryResultRow>
No hook entry/leave events fired during the successful read.
Interpretation
The methods command RVAs are not reliable executable hook targets for the
successful path. They are CLR/mixed-mode method body locations or stubs, not the
actual runtime addresses invoked after CLR/JIT/mixed-mode dispatch. Frida can
see the module and can install some interceptors at base + RVA, but those
addresses are not reached by the wrapper scenario.
The successful read still proves the scenario:
- integrated auth opens
GetConnectionStatussettlesHistoryQuery.StartQuerysucceedsMoveNextreturns one row forOtOpcUaParityTest_001.Counter
An expanded pass based on decompiled call targets produced the same result:
hooks installed for MethodDef RVAs such as HistorianClient.StartDataQuery
(0x4160C4), Query.StartDataQuery (0x41CACC),
QueryColumnSelector.SelectNonSummaryColumns (0x1EE34), and
HistorianClient.GetNextRow<DataQueryResultRow> (0x42F818), but no
entry/leave callbacks fired. Treat MethodDef RVAs from the mixed-mode assembly
as insufficient for Frida interception without resolving the actual CLR/native
dispatch target.
Runtime method-pointer dumps from
tools\AVEVA.Historian.NativeTraceHarness --dump-method-pointers ... provide a
better target class, but not stable RVAs. RuntimeHelpers.PrepareMethod plus
MethodHandle.GetFunctionPointer() exposes process-local CLR/JIT entry
addresses for methods such as <Module>.HistorianClient.StartDataQuery,
<Module>.CRetrievalConnectionWCF.StartQuery2, and
<Module>.HistorianClient.GetNextRow<class DataQueryResultRow>. The current
artifacts mark all of these as FunctionPointerInModule = false, so they must
be discovered in the same process that will be hooked.
scripts\Attach-NativeTraceHarnessRuntimePointerCapture.ps1 now automates that
same-process attempt. It starts NativeTraceHarness, writes a runtime pointer
snapshot immediately before StartQuery, pauses, generates a temporary Frida
script with the absolute addresses, and attaches to the still-paused process.
The latest local direct history run installed 37 absolute hooks from
runtime-method-pointers-before-history-start-latest.json; the read succeeded,
but no hook enter/leave callbacks fired. This rules out raw Frida
interception of MethodHandle.GetFunctionPointer() addresses as the next
primary capture route.
Decompiled Query Call Facts
ArchestrA.HistoryQuery.StartQuery:
- Calls
BaseQuery.GetClient. - Calls
EndQuerybefore starting a new query. - Converts tag names to a native UTF-16 pointer vector.
- Builds a selected-column stream as:
ushortvalue1- 8-byte
QueryColumnSelectorpayload
- Calls
HistorianClient.StartDataQuerywith retrieval mode, query format0, summary type0, tag count, tag pointer vector, UTC FILETIME start/end, resolution, deadbands, time zoneUTC, version/interpolation/timestamp/ quality/value-selector/aggregation enum values, option/filter buffers, selected-column byte count and pointer, max states, output query handle, andSError.
ArchestrA.EventQuery.StartQuery:
- Calls
BaseQuery.GetClient. - Calls
EndQuerybefore starting a new event query. - Normalizes
EventCount = 0to100000and enables a continue-query mode. - Calls
HistorianClient.StartEventQuerywith UTC FILETIME start/end, event count, skip count, event order asushort, query type value1, filter pointer, time zoneUTC, output query handle, andSError.
Confirmed Managed Contract Shape
Decompilation confirms these WCF byte-buffer contracts:
HistoryServiceContract.IHistoryServiceContract2.OpenConnection2HistoryServiceContract.IHistoryServiceContract2.ExchangeKeyHistoryServiceContract.IHistoryServiceContract2.ValidateClientCredentialRetrievalServiceContract.IRetrievalServiceContract2.StartQuery2RetrievalServiceContract.IRetrievalServiceContract2.GetNextQueryResultBuffer2RetrievalServiceContract.IRetrievalServiceContract2.EndQuery2
StartQuery2 signature:
bool StartQuery2(
uint clientHandle,
ushort queryRequestType,
uint requestSize,
byte[] pRequestBuff,
out uint responseSize,
out byte[] pResponseBuff,
ref uint queryHandle,
out uint errSize,
out byte[] err)
GetNextQueryResultBuffer2 signature:
bool GetNextQueryResultBuffer2(
uint clientHandle,
uint queryHandle,
out uint resultSize,
out byte[] pResultBuff,
out uint errSize,
out byte[] err)
Next Hook Direction
Use profiler/API-boundary interception rather than raw Frida function-pointer interception:
- CLR profiler / managed method rewrite for
HistoryServiceContract.*andRetrievalServiceContract.*calls. - API Monitor or Detours-style hooks at the lower native/WCF DLL boundary.
- Focus first on capturing managed byte-array arguments to:
OpenConnection2ExchangeKeyValidateClientCredentialStartQuery2GetNextQueryResultBuffer2
WCF Diagnostics Attempt
tools\AVEVA.Historian.NativeTraceHarness attempted classic .NET Framework
System.ServiceModel diagnostics and message logging while running the same
successful integrated read. No .svclog file was produced, even though the
scenario succeeded. This is negative evidence that the native wrapper path does
not expose a managed WCF client pipeline in the harness AppDomain that can be
captured with ordinary WCF config diagnostics. A profiler/method-rewrite hook
remains the likely buffer-capture route.
Event Add-Tag Hook Pass
Scenario:
.\tools\AVEVA.Historian.NativeTraceHarness\bin\Debug\net481\AVEVA.Historian.NativeTraceHarness.exe --scenario event --server-name localhost --tcp-port 32568 --connection-wait-seconds 15 --pre-open-sleep-seconds 15 --max-rows 1 --lookback-minutes 1440
Artifacts:
- Script:
scripts\frida\aahclientmanaged-open-query.js - Latest capture:
docs\reverse-engineering\frida-aahclientmanaged-event-addtag-latest.ndjson - Native result:
docs\reverse-engineering\native-event-addtag-frida-child.json
The native event query still succeeded and returned one event row. The expanded hook list installed hooks for the event default-tag path:
HistorianAccess.CreateDefaultEventTagat method RVA0x43c2d4HistorianAccess.AddTagInternalat method RVA0x43be68HistorianClient.AddHistorianTagat method RVA0x417c18HistorianClient.ConvertEventTagToTagMetadataat method RVA0x417b68CTagMetadata.Save<SByteStream<SCrtMemFile>>at method RVA0x1044dc
The capture has hook-install events and some hook-install failures for other
candidates, but still no enter/leave events. This confirms the same limitation
as the history pass: MethodDef RVAs from this mixed-mode assembly are not the
actual runtime entry points for the successful wrapper path. The next capture
mechanism needs CLR method rewriting/profiling or a lower native boundary such
as Winsock/API Monitor, not raw base + RVA Frida interceptors.
Event Winsock/IPС Pass
tools\AVEVA.Historian.NativeTraceHarness now supports
--pre-load-sleep-seconds, allowing Frida to attach before
aahClientManaged.dll is loaded. Running the event scenario with that preload
pause produced:
docs\reverse-engineering\winsock-event-preload-localhost-latest.ndjson- native event open succeeded
StartQuerysucceeded- one event row returned
- no Historian-port
connect/send/recvevents - no tracked named-pipe or interesting file
ReadFile/WriteFilepayloads
For the local event scenario, the successful wrapper path is therefore not visible through the current Winsock or named-pipe hook set. Keep the preload pause because it is useful for future lower-level hooks, but the next byte capture should target CLR method arguments, API Monitor at the native/WCF DLL boundary, or a real remote/relay path where traffic leaves the process.
The remote relay path does expose event-mode transport, but it still stops at
the security layer. With endpoint-host rewriting enabled, event mode sends
/HistCert over application/ssl-tls, then repeats /Hist-Integrated over
application/negotiate with NTLMSSP messages. The server returns a short reject
record before the native harness reaches connected state, so this path has
endpoint/security evidence but still no query payload evidence.
aahClient Export Hook Pass
scripts\frida\aahclient-exports.js and
scripts\Attach-NativeTraceHarnessAahClientExportCapture.ps1 hook the
procedural mdas_* exports from aahClient.dll if that DLL is loaded. The
first local direct history run attached before aahClientManaged.dll load and
the native read succeeded, but Frida never observed aahClient.dll being loaded
and installed no export hooks. A separate -DumpLoadedModules run of the older
PowerShell harness also showed only aahClientManaged.dll among the current
AVEVA DLL set during a successful wrapper read.
That rules out aahClient.dll exports as the immediate capture boundary for
the active wrapper path. The mdas_* exports may still describe a separate
native client ABI, but they are not the calls currently made by
HistoryQuery.StartQuery in this harness.
System Boundary Hook Pass
scripts\frida\aahclientmanaged-system-boundary.js and
scripts\Attach-NativeTraceHarnessSystemBoundaryCapture.ps1 hook the imported
system/API boundary used by aahClientManaged.dll: file I/O, NtCreateFile,
NtReadFile, NtWriteFile, NtDeviceIoControlFile, DNS, exported Winsock
connect/send/recv APIs, WSAIoctl, mswsock extension exports, Secur32,
Crypt32, and NetAPI.
Local direct history and same-machine remote-IP reads still succeeded without file/pipe/socket/security callbacks beyond hook installation. This reinforces that the local Historian path is optimized below the query surface we need.
The Debian relay run adds one sharper fact. The relay accepted connections from
the Windows host, and the Windows TCP owner monitor attributed the established
connection to the AVEVA.Historian.NativeTraceHarness PID. Even with hooks
installed in that same PID before OpenConnection, no exported Winsock,
WSAIoctl, mswsock, or NtDeviceIoControlFile callbacks fired. The relay
connection still reset before the harness reached ConnectedToServer = true.
That makes raw Frida export hooks insufficient for the remaining transport capture. The next local capture mechanism should be ETW/netsh/WFP/kernel-level network tracing, API Monitor/Detours below the wrapper, or CLR profiler/IL instrumentation inside the mixed-mode assembly.