Files
histsdk/scripts/frida/aahclientaccesspoint-valcl-context.js
T
dohertj2 c95824a65d 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>
2026-05-04 06:31:48 -04:00

232 lines
5.8 KiB
JavaScript

// Sanitized server-side AVEVA Historian ValCl context probe.
// Logs pointers, GUID bytes, token lengths, round flags, and return values only.
'use strict';
const moduleName = 'aahClientAccessPoint.exe';
const imageBase = ptr('0x00400000');
const moduleBase = Module.findBaseAddress(moduleName);
function emit(event) {
event.timestampUtc = new Date().toISOString();
console.log(JSON.stringify(event));
}
function addrFromVa(va) {
if (moduleBase === null) {
return null;
}
return moduleBase.add(ptr(va).sub(imageBase));
}
function safePtr(value) {
if (value === null || value === undefined) {
return null;
}
try {
return ptr(value).toString();
} catch (_) {
return null;
}
}
function readU8(pointer) {
try {
if (pointer.isNull()) return null;
return pointer.readU8();
} catch (_) {
return null;
}
}
function readU32(pointer) {
try {
if (pointer.isNull()) return null;
return pointer.readU32();
} catch (_) {
return null;
}
}
function readGuid(pointer) {
try {
if (pointer.isNull()) return null;
const bytes = pointer.readByteArray(16);
if (bytes === null) return null;
return Array.prototype.map.call(new Uint8Array(bytes), b => ('0' + b.toString(16)).slice(-2)).join('');
} catch (_) {
return null;
}
}
function readServerBufferSummary(buffer) {
try {
if (buffer.isNull()) {
return { buffer: safePtr(buffer) };
}
const data = buffer.add(0x48).readPointer();
const length = buffer.add(0x4c).readU32();
let roundByte = null;
let wrappedTokenLength = null;
if (!data.isNull() && length >= 5) {
roundByte = readU8(data);
wrappedTokenLength = readU32(data.add(1));
}
return {
buffer: safePtr(buffer),
data: safePtr(data),
length: length,
roundByte: roundByte,
wrappedTokenLength: wrappedTokenLength
};
} catch (error) {
return {
buffer: safePtr(buffer),
readError: String(error)
};
}
}
function hook(name, va, callbacks) {
const address = addrFromVa(va);
if (address === null) {
emit({ event: 'hook.error', name: name, reason: 'module-not-loaded', module: moduleName });
return;
}
try {
Interceptor.attach(address, callbacks);
emit({ event: 'hook.installed', name: name, va: va, address: address.toString() });
} catch (error) {
emit({ event: 'hook.error', name: name, va: va, address: address.toString(), reason: String(error) });
}
}
emit({
event: 'script.loaded',
module: moduleName,
moduleBase: moduleBase === null ? null : moduleBase.toString()
});
hook('CServerNode.ProcessServerToken', '0x00526E00', {
onEnter(args) {
this.thisPtr = this.context.ecx;
this.contextGuid = args[0];
this.serverBuffer = args[1];
this.continuePtr = args[2];
this.errorPtr = args[3];
emit({
event: 'ProcessServerToken.enter',
thisPtr: safePtr(this.thisPtr),
contextGuidPtr: safePtr(this.contextGuid),
contextGuidBytes: readGuid(this.contextGuid),
serverBuffer: readServerBufferSummary(this.serverBuffer),
continuePtr: safePtr(this.continuePtr),
errorPtr: safePtr(this.errorPtr)
});
},
onLeave(retval) {
emit({
event: 'ProcessServerToken.leave',
retval: retval.toInt32(),
continueValue: readU8(this.continuePtr)
});
}
});
hook('ContextSetup.0050FFC0', '0x0050FFC0', {
onEnter(args) {
this.thisPtr = this.context.ecx;
this.contextGuid = args[0];
emit({
event: 'ContextSetup.enter',
thisPtr: safePtr(this.thisPtr),
contextGuidPtr: safePtr(this.contextGuid),
contextGuidBytes: readGuid(this.contextGuid)
});
},
onLeave(retval) {
emit({ event: 'ContextSetup.leave', retval: retval.toInt32() });
}
});
hook('ContextLookup.00517AB0', '0x00517AB0', {
onEnter(args) {
this.thisPtr = this.context.ecx;
this.outPair = args[0];
this.contextGuid = args[1];
emit({
event: 'ContextLookup.enter',
thisPtr: safePtr(this.thisPtr),
outPair: safePtr(this.outPair),
contextGuidPtr: safePtr(this.contextGuid),
contextGuidBytes: readGuid(this.contextGuid)
});
},
onLeave(retval) {
let first = null;
let second = null;
try {
if (!this.outPair.isNull()) {
first = this.outPair.readPointer();
second = this.outPair.add(Process.pointerSize).readPointer();
}
} catch (_) {
first = null;
second = null;
}
emit({
event: 'ContextLookup.leave',
retval: safePtr(retval),
contextObject: safePtr(first),
contextSharedState: safePtr(second)
});
}
});
hook('AcquireCredentialsHelper.00505AE0', '0x00505AE0', {
onEnter(args) {
this.thisPtr = this.context.ecx;
this.errorPtr = args[0];
emit({
event: 'AcquireCredentialsHelper.enter',
contextObject: safePtr(this.thisPtr),
errorPtr: safePtr(this.errorPtr)
});
},
onLeave(retval) {
emit({ event: 'AcquireCredentialsHelper.leave', retval: retval.toInt32() });
}
});
hook('AcceptSecurityContextHelper.00505C00', '0x00505C00', {
onEnter(args) {
this.thisPtr = this.context.ecx;
this.firstRound = args[0].toInt32() & 0xff;
this.tokenLength = args[1].toUInt32();
this.tokenPtr = args[2];
this.continuePtr = args[3];
this.serverCredentialPtr = args[4];
this.errorPtr = args[5];
emit({
event: 'AcceptSecurityContextHelper.enter',
contextObject: safePtr(this.thisPtr),
firstRound: this.firstRound,
tokenLength: this.tokenLength,
tokenPtr: safePtr(this.tokenPtr),
continuePtr: safePtr(this.continuePtr),
serverCredentialPtr: safePtr(this.serverCredentialPtr),
errorPtr: safePtr(this.errorPtr)
});
},
onLeave(retval) {
emit({
event: 'AcceptSecurityContextHelper.leave',
retval: retval.toInt32(),
continueValue: readU8(this.continuePtr)
});
}
});