Initial commit: managed .NET 10 AVEVA Historian SDK + reverse-engineering toolkit
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>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# Capture Workflow
|
||||
|
||||
Use the reverse-engineering CLI to keep captures repeatable:
|
||||
|
||||
```powershell
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- manifest
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- exports current\aahClient.dll
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- mark history-raw
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- wcf-probe 10.100.0.48 32568
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- wcf-tag-info 10.100.0.48 32568 OtOpcUaParityTest_001.Counter
|
||||
```
|
||||
|
||||
To probe the certificate-secured history endpoint with fully managed WCF/MDAS:
|
||||
|
||||
```powershell
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- wcf-cert-probe localhost 32568
|
||||
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- wcf-cert-probe 10.100.0.48 32568 localhost
|
||||
```
|
||||
|
||||
The optional final argument supplies the expected endpoint DNS identity. On the
|
||||
current development Historian, the remote endpoint presents certificate identity
|
||||
`localhost`, so the explicit identity is required when connecting by IP address.
|
||||
|
||||
Windows built-in packet capture may miss local Historian traffic. For local
|
||||
native-wrapper probes, use the Frida harness:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Attach-NativeTraceHarnessWinsockCapture.ps1 -Scenario history -ServerName localhost -RetrievalMode Full -TagName OtOpcUaParityTest_001.Counter -LookbackMinutes 1440 -MaxRows 1
|
||||
```
|
||||
|
||||
The Frida harness attaches before `OpenConnection` and hooks:
|
||||
|
||||
- Winsock `connect`, `WSAConnect`, `send`, `recv`, `WSASend`, and `WSARecv`
|
||||
- `CreateFileW`, `ReadFile`, `WriteFile`, and `CloseHandle`
|
||||
- `NtCreateFile`, `NtReadFile`, and `NtWriteFile`
|
||||
|
||||
For native trace harness captures that must hook before `aahClientManaged.dll`
|
||||
loads, pass a preload pause:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Attach-NativeTraceHarnessWinsockCapture.ps1 -Scenario event -PreLoadSleepSeconds 8 -AttachDelaySeconds 0 -OutputPath .\docs\reverse-engineering\winsock-event-preload-localhost-latest.ndjson
|
||||
```
|
||||
|
||||
The latest preload local event pass still produced no Winsock or tracked pipe
|
||||
payloads even though native event open and `StartQuery` succeeded.
|
||||
|
||||
Artifacts should be treated as diagnostic metadata. The script records byte
|
||||
counts and short hex prefixes only; do not add raw credential/session buffers
|
||||
to the repo.
|
||||
|
||||
Current local result: `localhost`, `127.0.0.1`, and the machine LAN IP all
|
||||
complete native reads without observed client-process socket or pipe payloads.
|
||||
That suggests local native reads are not exercising the remote transport path.
|
||||
|
||||
To force the native client onto a remote TCP path through the Debian test box:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Run-DebianHistorianRelayCapture.ps1 -SshUser dohertj2 -SshHost 10.100.0.35 -TargetHost 10.100.0.48 -OutputPath .\docs\reverse-engineering\debian-relay-history-latest.ndjson -HarnessOutputPath .\docs\reverse-engineering\native-trace-harness-via-debian-relay-latest.json
|
||||
```
|
||||
|
||||
This starts a temporary Python TCP relay on `10.100.0.35:32568` forwarding to
|
||||
`10.100.0.48:32568`, runs the native harness against `10.100.0.35`, pulls back
|
||||
the relay log, and cleans up the remote process. The relay logs connection
|
||||
events, byte counts, and 16-byte hex prefixes only.
|
||||
|
||||
Current relay result: the native client reaches the remote Net.TCP/WCF preamble
|
||||
and authentication exchange, but the relayed session is rejected before
|
||||
`OpenConnection` becomes connected. This gives transport evidence but not query
|
||||
request/response buffers yet. Matching ArchestrA logs identify the relayed
|
||||
target as `Server(10.100.0.35)` and show `Transport with Certificate` security,
|
||||
so the relay is not transparent at the certificate/identity layer.
|
||||
|
||||
For event mode, the rewritten relay shows the same security boundary but a
|
||||
clear endpoint sequence:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Run-DebianHistorianRelayCapture.ps1 -SshUser dohertj2 -SshHost 10.100.0.35 -TargetHost 10.100.0.48 -RewriteEndpointHost -Scenario event -OutputPath .\docs\reverse-engineering\debian-relay-rewrite-event-latest.ndjson -HarnessOutputPath .\docs\reverse-engineering\native-trace-harness-via-debian-relay-rewrite-event-latest.json
|
||||
```
|
||||
|
||||
Observed event relay sequence:
|
||||
|
||||
- `/HistCert` preamble with `application/ssl-tls`
|
||||
- TLS-style records
|
||||
- repeated `/Hist-Integrated` preambles with `application/negotiate`
|
||||
- NTLMSSP type 1/2/3 messages
|
||||
- 13-byte server rejection/reset before connected state
|
||||
|
||||
Adding `--direct-connection` for event mode does not bypass the relay; event
|
||||
direct emits the same endpoint sequence and still fails before connected state.
|
||||
|
||||
To test whether native connection flags change that security choice, add extra
|
||||
harness arguments:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Run-DebianHistorianRelayCapture.ps1 -SshUser dohertj2 -SshHost 10.100.0.35 -TargetHost 10.100.0.48 -OutputPath .\docs\reverse-engineering\debian-relay-history-direct-latest.ndjson -HarnessOutputPath .\docs\reverse-engineering\native-trace-harness-via-debian-relay-direct-latest.json -HarnessExtraArgs @("--direct-connection")
|
||||
```
|
||||
|
||||
Observed result: once the reverse harness forces the private
|
||||
`directConnection` backing field, the native read succeeds and the relay records
|
||||
only its own startup line. This is useful for native parity snapshots, but it
|
||||
bypasses the remote transport evidence needed for the managed driver.
|
||||
|
||||
To collect Windows packet metadata for the relay path without storing raw
|
||||
payload bytes:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Run-PktmonDebianRelayCapture.ps1 -Scenario history -SshUser dohertj2 -SshHost 10.100.0.35 -TargetHost 10.100.0.48 -TagName OtOpcUaParityTest_001.Counter -LookbackMinutes 1440 -MaxRows 1 -OutputPrefix .\docs\reverse-engineering\pktmon-debian-relay-history-latest
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
- adds a pktmon TCP filter for `10.100.0.35:32568`
|
||||
- starts pktmon with flags `0x00e`, intentionally omitting raw packet bytes
|
||||
- runs the Debian relay harness scenario
|
||||
- converts the ETL to text/stat metadata
|
||||
- deletes the ETL file before writing the summary
|
||||
|
||||
Current pktmon result: the metadata capture records TCP flows between
|
||||
`10.100.0.48` and `10.100.0.35:32568` with no payload bytes retained. This is
|
||||
useful for timing, directions, ports, and reset behavior, but still not enough
|
||||
to reconstruct query buffers.
|
||||
|
||||
To correlate relay TCP ownership on Windows while running Frida system-boundary
|
||||
hooks:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Attach-SystemBoundaryViaDebianRelay.ps1 -Scenario history -SshUser dohertj2 -SshHost 10.100.0.35 -TargetHost 10.100.0.48 -TagName OtOpcUaParityTest_001.Counter -LookbackMinutes 1440 -MaxRows 1 -OutputPath .\docs\reverse-engineering\system-boundary-via-debian-relay-history-latest.ndjson
|
||||
```
|
||||
|
||||
Current system-boundary relay result: the Windows TCP owner monitor attributes
|
||||
the established relay connection to `AVEVA.Historian.NativeTraceHarness`, but
|
||||
Frida hooks on exported Winsock calls, `WSAIoctl`, `mswsock`, file APIs, and
|
||||
`NtDeviceIoControlFile` still record no transport callbacks. Treat this as
|
||||
negative evidence for further export-level Frida work.
|
||||
|
||||
For each scenario:
|
||||
|
||||
1. Start Wireshark and API Monitor.
|
||||
2. Emit a `mark <scenario-name>` line and note the timestamp.
|
||||
3. Run the same operation through the native SDK/client.
|
||||
4. Save raw captures outside the repo.
|
||||
5. Add only sanitized binary frames or decoded notes under `fixtures/protocol`.
|
||||
|
||||
The production SDK must not reference this harness or any AVEVA native binary.
|
||||
|
||||
After a capture is sanitized, add parser tests before enabling the corresponding
|
||||
operation in `Historian2020ProtocolDialect`.
|
||||
Reference in New Issue
Block a user