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,215 @@
|
||||
'use strict';
|
||||
|
||||
const moduleName = 'aahClient.dll';
|
||||
const maxDumpBytes = 256;
|
||||
const interestingExportFragments = [
|
||||
'mdas_OpenConnection',
|
||||
'mdas_CloseConnection',
|
||||
'mdas_StartDataRetrievalQuery',
|
||||
'mdas_GetNextDataQueryResult',
|
||||
'mdas_StartEventDataRetrievalQuery',
|
||||
'mdas_GetNextEventDataQueryResult',
|
||||
'mdas_StartBlockRetrievalQuery',
|
||||
'mdas_GetNextBlockQueryResult',
|
||||
'mdas_EndQuery'
|
||||
];
|
||||
|
||||
let hooksInstalled = false;
|
||||
|
||||
function emit(kind, payload) {
|
||||
payload.kind = kind;
|
||||
payload.pid = Process.id;
|
||||
payload.tid = Process.getCurrentThreadId();
|
||||
payload.timestamp = new Date().toISOString();
|
||||
console.log('FRIDA_EVENT ' + JSON.stringify(payload));
|
||||
}
|
||||
|
||||
function toHex(bytes) {
|
||||
const parts = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const text = bytes[i].toString(16);
|
||||
parts.push(text.length === 1 ? '0' + text : text);
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function asciiPreview(bytes) {
|
||||
let text = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const b = bytes[i];
|
||||
text += b >= 32 && b <= 126 ? String.fromCharCode(b) : '.';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function utf16Preview(bytes) {
|
||||
let text = '';
|
||||
const count = Math.min(bytes.length - (bytes.length % 2), 128);
|
||||
for (let i = 0; i < count; i += 2) {
|
||||
const code = bytes[i] | (bytes[i + 1] << 8);
|
||||
text += code >= 32 && code <= 126 ? String.fromCharCode(code) : '.';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function isReadablePointer(value) {
|
||||
if (value.isNull() || value.compare(ptr('0x10000')) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const range = Process.findRangeByAddress(value);
|
||||
return range !== null && range.protection.indexOf('r') !== -1;
|
||||
}
|
||||
|
||||
function dumpPointer(value) {
|
||||
const range = Process.findRangeByAddress(value);
|
||||
const available = range === null ? 0 : Math.min(maxDumpBytes, range.base.add(range.size).sub(value).toNumber());
|
||||
if (available <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const raw = Memory.readByteArray(value, available);
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bytes = Array.from(new Uint8Array(raw));
|
||||
return {
|
||||
address: value.toString(),
|
||||
rangeBase: range.base.toString(),
|
||||
rangeSize: range.size,
|
||||
protection: range.protection,
|
||||
byteCount: bytes.length,
|
||||
hexPrefix: toHex(bytes.slice(0, Math.min(bytes.length, 128))),
|
||||
asciiPrefix: asciiPreview(bytes.slice(0, Math.min(bytes.length, 128))),
|
||||
utf16Prefix: utf16Preview(bytes.slice(0, Math.min(bytes.length, 256)))
|
||||
};
|
||||
}
|
||||
|
||||
function inspectArgs(args, count) {
|
||||
const inspected = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const value = args[i];
|
||||
const item = {
|
||||
index: i,
|
||||
value: value.toString()
|
||||
};
|
||||
|
||||
if (isReadablePointer(value)) {
|
||||
try {
|
||||
item.memory = dumpPointer(value);
|
||||
} catch (e) {
|
||||
item.memoryError = String(e);
|
||||
}
|
||||
}
|
||||
|
||||
inspected.push(item);
|
||||
}
|
||||
|
||||
return inspected;
|
||||
}
|
||||
|
||||
function isInterestingExport(name) {
|
||||
for (const fragment of interestingExportFragments) {
|
||||
if (name.indexOf(fragment) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function installHooks() {
|
||||
if (hooksInstalled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let module = null;
|
||||
try {
|
||||
module = Process.getModuleByName(moduleName);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hooksInstalled = true;
|
||||
const exports = module.enumerateExports().filter(e => e.type === 'function' && isInterestingExport(e.name));
|
||||
emit('module-loaded', {
|
||||
module: module.name,
|
||||
base: module.base.toString(),
|
||||
size: module.size,
|
||||
path: module.path,
|
||||
exportCount: exports.length,
|
||||
exports: exports.map(e => ({ name: e.name, address: e.address.toString() }))
|
||||
});
|
||||
|
||||
for (const exported of exports) {
|
||||
try {
|
||||
Interceptor.attach(exported.address, {
|
||||
onEnter(args) {
|
||||
this.name = exported.name;
|
||||
this.address = exported.address.toString();
|
||||
this.argsSnapshot = inspectArgs(args, 18);
|
||||
emit('enter', {
|
||||
function: this.name,
|
||||
address: this.address,
|
||||
args: this.argsSnapshot
|
||||
});
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('leave', {
|
||||
function: this.name,
|
||||
address: this.address,
|
||||
retval: retval.toString(),
|
||||
args: this.argsSnapshot
|
||||
});
|
||||
}
|
||||
});
|
||||
emit('hooked', { function: exported.name, address: exported.address.toString() });
|
||||
} catch (e) {
|
||||
emit('hook-error', { function: exported.name, address: exported.address.toString(), error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isInterestingModule(module) {
|
||||
const name = module.name.toLowerCase();
|
||||
const path = module.path.toLowerCase();
|
||||
return name.indexOf('aah') !== -1 || path.indexOf('histsdk') !== -1 || path.indexOf('aveva') !== -1;
|
||||
}
|
||||
|
||||
emit('startup', {
|
||||
arch: Process.arch,
|
||||
platform: Process.platform,
|
||||
modules: Process.enumerateModules()
|
||||
.filter(isInterestingModule)
|
||||
.map(m => ({ name: m.name, base: m.base.toString(), size: m.size, path: m.path }))
|
||||
});
|
||||
|
||||
try {
|
||||
Process.attachModuleObserver({
|
||||
onAdded(module) {
|
||||
if (isInterestingModule(module)) {
|
||||
emit('module-added', {
|
||||
name: module.name,
|
||||
base: module.base.toString(),
|
||||
size: module.size,
|
||||
path: module.path
|
||||
});
|
||||
}
|
||||
|
||||
installHooks();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
emit('module-observer-error', { error: String(e) });
|
||||
}
|
||||
|
||||
if (!installHooks()) {
|
||||
const timer = setInterval(() => {
|
||||
if (installHooks()) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// 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)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,318 @@
|
||||
'use strict';
|
||||
|
||||
const moduleName = 'aahClientManaged.dll';
|
||||
const knownModulePaths = [
|
||||
moduleName,
|
||||
'C:\\Users\\dohertj2\\Desktop\\histsdk\\current\\aahClientManaged.dll'
|
||||
];
|
||||
const maxDumpBytes = 256;
|
||||
|
||||
const targets = [
|
||||
['CServiceUtility.SaveOpenConnectionParams', 0x2f0d54],
|
||||
['CClientInfo.SerializeOpenConnectionInParams', 0x2f0928],
|
||||
['CClientInfo.SerializeOpenConnectionInParams2', 0x2f0690],
|
||||
['CClientInfo.SerializeOpenConnectionInParams2Content', 0x2f03a4],
|
||||
['CClientInfo.SerializeOpenConnectionInParams3', 0x2f00c0],
|
||||
['CClientInfo.SerializeOpenConnectionInParams3Content', 0x2efdac],
|
||||
['CClientInfo.SerializeOpenConnectionInParams4', 0x2f2e88],
|
||||
['CClientInfo.EncryptWithClientKey', 0x0],
|
||||
['CHistoryConnectionWCF.GetClientKey', 0x2f3dc4],
|
||||
['CHistoryConnectionWCF.OpenConnection', 0x2ec9c8],
|
||||
['CHistoryConnectionWCF.OpenConnection2', 0x2fdeb8],
|
||||
['CHistoryConnectionWCF.OpenConnection3', 0x2fedb4],
|
||||
['CHistoryConnectionWCF.RegisterTags', 0x2f6f78],
|
||||
['CHistoryConnectionWCF.ValidateClientCredential', 0x302e90],
|
||||
['HistorianClient.OpenConnection', 0x4170e8],
|
||||
['HistorianAccess.AddTagInternal', 0x43be68],
|
||||
['HistorianAccess.CreateDefaultEventTag', 0x43c2d4],
|
||||
['HistorianClient.AddHistorianTag', 0x417c18],
|
||||
['HistorianClient.ConvertEventTagToTagMetadata', 0x417b68],
|
||||
['HistorianClient.StartQuery', 0x415bbc],
|
||||
['HistorianClient.StartEventQuery', 0x41811c],
|
||||
['HistorianClient.StartDataQuery', 0x4160c4],
|
||||
['CTagMetadata.Save<SByteStream<SCrtMemFile>>', 0x1044dc],
|
||||
['ClientApp.StartDataQuery', 0x400f9c],
|
||||
['ClientApp.StartEventQuery', 0x4015a4],
|
||||
['Query.StartDataQuery', 0x41cacc],
|
||||
['CRetrievalConnectionWCF.StartQuery2', 0x36eb48],
|
||||
['CRetrievalConnectionWCF.StartEventQuery', 0x370324],
|
||||
['HistoryQuery.StartQuery', 0x44012c],
|
||||
['EventQuery.StartQuery', 0x43035c],
|
||||
['Query.StartEventQuery', 0x41db4c],
|
||||
['EventQueryFilters.Save<SCrtMemFile>', 0x41d38c],
|
||||
['EventQueryRequest.Save<SCrtMemFile>', 0x41d48c],
|
||||
['QueryColumnSelector.ctor.default', 0x1b94c],
|
||||
['QueryColumnSelector.SelectNonSummaryColumns', 0x1ee34],
|
||||
['QueryColumnSelector.Save<SCrtMemFile>', 0x41b8d8],
|
||||
['QueryColumnSelector.GetColumnSelectorFlags', 0x41e110],
|
||||
['HistorianClient.GetNextRow<DataQueryResultRow>', 0x42f818],
|
||||
['DataQueryResultBuffer.GetNextRow<SByteStream<SCrtMemFile>>', 0x42f6f8],
|
||||
['Query.GetNextRow', 0x42f744],
|
||||
['HistorianClient.GetNextRow<EventQueryResultRow>', 0x430e10],
|
||||
['EventQueryResultBuffer.GetNextRow<SByteStream<SCrtMemFile>>', 0x430a3c],
|
||||
['Event.Query.GetNextRow', 0x430af4]
|
||||
].filter(t => t[1] !== 0);
|
||||
|
||||
let startupLogged = false;
|
||||
let hooksInstalled = false;
|
||||
let getModuleHandleW = null;
|
||||
|
||||
function emit(kind, payload) {
|
||||
payload.kind = kind;
|
||||
payload.pid = Process.id;
|
||||
payload.tid = Process.getCurrentThreadId();
|
||||
payload.timestamp = new Date().toISOString();
|
||||
console.log('FRIDA_EVENT ' + JSON.stringify(payload));
|
||||
}
|
||||
|
||||
function isReadablePointer(value) {
|
||||
if (value.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.compare(ptr('0x10000')) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const range = Process.findRangeByAddress(value);
|
||||
return range !== null && range.protection.indexOf('r') !== -1;
|
||||
}
|
||||
|
||||
function toHex(bytes) {
|
||||
const parts = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const text = bytes[i].toString(16);
|
||||
parts.push(text.length === 1 ? '0' + text : text);
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function asciiPreview(bytes) {
|
||||
let text = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const b = bytes[i];
|
||||
text += b >= 32 && b <= 126 ? String.fromCharCode(b) : '.';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function utf16Preview(bytes) {
|
||||
let text = '';
|
||||
const count = Math.min(bytes.length - (bytes.length % 2), 96);
|
||||
for (let i = 0; i < count; i += 2) {
|
||||
const code = bytes[i] | (bytes[i + 1] << 8);
|
||||
text += code >= 32 && code <= 126 ? String.fromCharCode(code) : '.';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function dumpPointer(value) {
|
||||
const range = Process.findRangeByAddress(value);
|
||||
const available = range === null ? 0 : Math.min(maxDumpBytes, range.base.add(range.size).sub(value).toNumber());
|
||||
if (available <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const raw = Memory.readByteArray(value, available);
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bytes = Array.from(new Uint8Array(raw));
|
||||
return {
|
||||
address: value.toString(),
|
||||
rangeBase: range.base.toString(),
|
||||
rangeSize: range.size,
|
||||
protection: range.protection,
|
||||
byteCount: bytes.length,
|
||||
hexPrefix: toHex(bytes.slice(0, Math.min(bytes.length, 96))),
|
||||
asciiPrefix: asciiPreview(bytes.slice(0, Math.min(bytes.length, 96))),
|
||||
utf16Prefix: utf16Preview(bytes.slice(0, Math.min(bytes.length, 192)))
|
||||
};
|
||||
}
|
||||
|
||||
function inspectArgs(args, count) {
|
||||
const inspected = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const value = args[i];
|
||||
const item = {
|
||||
index: i,
|
||||
value: value.toString()
|
||||
};
|
||||
|
||||
if (isReadablePointer(value)) {
|
||||
try {
|
||||
item.memory = dumpPointer(value);
|
||||
} catch (e) {
|
||||
item.memoryError = String(e);
|
||||
}
|
||||
}
|
||||
|
||||
inspected.push(item);
|
||||
}
|
||||
|
||||
return inspected;
|
||||
}
|
||||
|
||||
function hookTarget(base, name, rva) {
|
||||
const address = base.add(rva);
|
||||
try {
|
||||
Interceptor.attach(address, {
|
||||
onEnter(args) {
|
||||
this.name = name;
|
||||
this.argsSnapshot = inspectArgs(args, 10);
|
||||
emit('enter', {
|
||||
function: name,
|
||||
rva: '0x' + rva.toString(16),
|
||||
address: address.toString(),
|
||||
args: this.argsSnapshot
|
||||
});
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('leave', {
|
||||
function: this.name,
|
||||
retval: retval.toString(),
|
||||
args: this.argsSnapshot
|
||||
});
|
||||
}
|
||||
});
|
||||
emit('hooked', { function: name, rva: '0x' + rva.toString(16), address: address.toString() });
|
||||
} catch (e) {
|
||||
emit('hook-error', { function: name, rva: '0x' + rva.toString(16), address: address.toString(), error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
function installHooks() {
|
||||
if (!startupLogged) {
|
||||
startupLogged = true;
|
||||
emit('startup', {
|
||||
arch: Process.arch,
|
||||
platform: Process.platform,
|
||||
modules: Process.enumerateModules()
|
||||
.filter(m => m.name.toLowerCase().indexOf('aah') !== -1 || m.path.toLowerCase().indexOf('histsdk') !== -1)
|
||||
.map(m => ({ name: m.name, base: m.base.toString(), size: m.size, path: m.path }))
|
||||
});
|
||||
}
|
||||
|
||||
const modules = Process.enumerateModules().filter(m => m.name.toLowerCase() === moduleName.toLowerCase());
|
||||
let module = modules.length > 0 ? modules[0] : null;
|
||||
let base = module === null ? null : module.base;
|
||||
|
||||
if (base === null && getModuleHandleW !== null) {
|
||||
for (const candidate of knownModulePaths) {
|
||||
const handle = getModuleHandleW(Memory.allocUtf16String(candidate));
|
||||
if (!handle.isNull()) {
|
||||
base = handle;
|
||||
emit('module-handle-found', { module: candidate, base: base.toString() });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (base === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hooksInstalled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hooksInstalled = true;
|
||||
emit('module-loaded', { module: moduleName, base: base.toString(), arch: Process.arch, platform: Process.platform });
|
||||
for (const [name, rva] of targets) {
|
||||
hookTarget(base, name, rva);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isInterestingModule(module) {
|
||||
const name = module.name.toLowerCase();
|
||||
const path = module.path.toLowerCase();
|
||||
return name.indexOf('aah') !== -1
|
||||
|| name.indexOf('historian') !== -1
|
||||
|| path.indexOf('histsdk') !== -1
|
||||
|| path.indexOf('aveva') !== -1;
|
||||
}
|
||||
|
||||
try {
|
||||
Process.attachModuleObserver({
|
||||
onAdded(module) {
|
||||
if (isInterestingModule(module)) {
|
||||
emit('module-added', {
|
||||
name: module.name,
|
||||
base: module.base.toString(),
|
||||
size: module.size,
|
||||
path: module.path
|
||||
});
|
||||
}
|
||||
|
||||
installHooks();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
emit('module-observer-error', { error: String(e) });
|
||||
}
|
||||
|
||||
try {
|
||||
const kernel32 = Process.getModuleByName('kernel32.dll');
|
||||
getModuleHandleW = new NativeFunction(kernel32.getExportByName('GetModuleHandleW'), 'pointer', ['pointer']);
|
||||
const hookLoader = (exportName, pathArgIndex) => {
|
||||
const fn = kernel32.getExportByName(exportName);
|
||||
Interceptor.attach(fn, {
|
||||
onEnter(args) {
|
||||
this.exportName = exportName;
|
||||
this.path = args[pathArgIndex].isNull() ? '' : args[pathArgIndex].readUtf16String();
|
||||
},
|
||||
onLeave(retval) {
|
||||
const lower = (this.path || '').toLowerCase();
|
||||
if (lower.indexOf('aah') !== -1 || lower.indexOf('historian') !== -1 || lower.indexOf('histsdk') !== -1 || lower.indexOf('aveva') !== -1) {
|
||||
emit('load-library', { api: this.exportName, path: this.path, result: retval.toString() });
|
||||
}
|
||||
installHooks();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
hookLoader('LoadLibraryW', 0);
|
||||
hookLoader('LoadLibraryExW', 0);
|
||||
} catch (e) {
|
||||
emit('load-library-hook-error', { error: String(e) });
|
||||
}
|
||||
|
||||
try {
|
||||
const ntdll = Process.getModuleByName('ntdll.dll');
|
||||
const ldrLoadDll = ntdll.getExportByName('LdrLoadDll');
|
||||
Interceptor.attach(ldrLoadDll, {
|
||||
onEnter(args) {
|
||||
this.path = '';
|
||||
try {
|
||||
const unicodeString = args[2];
|
||||
const length = unicodeString.readU16();
|
||||
const buffer = unicodeString.add(Process.pointerSize * 2).readPointer();
|
||||
if (!buffer.isNull() && length > 0) {
|
||||
this.path = buffer.readUtf16String(length / 2);
|
||||
}
|
||||
} catch (e) {
|
||||
this.path = '<read-error:' + String(e) + '>';
|
||||
}
|
||||
},
|
||||
onLeave(retval) {
|
||||
const lower = (this.path || '').toLowerCase();
|
||||
if (lower.indexOf('aah') !== -1 || lower.indexOf('historian') !== -1 || lower.indexOf('histsdk') !== -1 || lower.indexOf('aveva') !== -1) {
|
||||
emit('ldr-load-dll', { path: this.path, result: retval.toString() });
|
||||
}
|
||||
installHooks();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
emit('ldr-load-dll-hook-error', { error: String(e) });
|
||||
}
|
||||
|
||||
if (!installHooks()) {
|
||||
const timer = setInterval(() => {
|
||||
if (installHooks()) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
@@ -0,0 +1,863 @@
|
||||
'use strict';
|
||||
|
||||
const maxPrefixBytes = 96;
|
||||
const handles = new Map();
|
||||
const sockets = new Map();
|
||||
const pendingHooks = [];
|
||||
const installedHooks = new Set();
|
||||
let getPeerName = null;
|
||||
|
||||
function emit(kind, payload) {
|
||||
payload.kind = kind;
|
||||
payload.pid = Process.id;
|
||||
payload.tid = Process.getCurrentThreadId();
|
||||
payload.timestamp = new Date().toISOString();
|
||||
console.log('FRIDA_SYS ' + JSON.stringify(payload));
|
||||
}
|
||||
|
||||
function toHex(bytes) {
|
||||
const parts = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const text = bytes[i].toString(16);
|
||||
parts.push(text.length === 1 ? '0' + text : text);
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function asciiPreview(bytes) {
|
||||
let text = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const b = bytes[i];
|
||||
text += b >= 32 && b <= 126 ? String.fromCharCode(b) : '.';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function payloadSummary(buffer, length, includePrefix) {
|
||||
if (includePrefix === undefined) {
|
||||
includePrefix = true;
|
||||
}
|
||||
|
||||
const byteCount = Math.max(0, Math.min(length, maxPrefixBytes));
|
||||
if (!includePrefix || buffer.isNull() || byteCount === 0) {
|
||||
return { byteCount: length, prefixByteCount: 0, prefixHex: '', asciiPrefix: '' };
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = buffer.readByteArray(byteCount);
|
||||
const bytes = Array.from(new Uint8Array(raw));
|
||||
return {
|
||||
byteCount: length,
|
||||
prefixByteCount: byteCount,
|
||||
prefixHex: toHex(bytes),
|
||||
asciiPrefix: asciiPreview(bytes)
|
||||
};
|
||||
} catch (e) {
|
||||
return { byteCount: length, prefixByteCount: 0, prefixHex: '', asciiPrefix: '', error: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function socketKey(socket) {
|
||||
return socket.toString();
|
||||
}
|
||||
|
||||
function readPortNetworkOrder(address) {
|
||||
const hi = address.readU8();
|
||||
const lo = address.add(1).readU8();
|
||||
return (hi << 8) | lo;
|
||||
}
|
||||
|
||||
function parseSockaddr(address) {
|
||||
if (address.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const family = address.readU16();
|
||||
if (family === 2) {
|
||||
return {
|
||||
family: 'IPv4',
|
||||
address: [address.add(4).readU8(), address.add(5).readU8(), address.add(6).readU8(), address.add(7).readU8()].join('.'),
|
||||
port: readPortNetworkOrder(address.add(2))
|
||||
};
|
||||
}
|
||||
|
||||
if (family === 23) {
|
||||
const parts = [];
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
parts.push(((address.add(8 + i).readU8() << 8) | address.add(8 + i + 1).readU8()).toString(16));
|
||||
}
|
||||
|
||||
return { family: 'IPv6', address: parts.join(':'), port: readPortNetworkOrder(address.add(2)) };
|
||||
}
|
||||
|
||||
return { family: String(family), address: null, port: null };
|
||||
} catch (e) {
|
||||
return { error: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function ensurePeer(socket) {
|
||||
const key = socketKey(socket);
|
||||
if (sockets.has(key) || getPeerName === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const address = Memory.alloc(128);
|
||||
const length = Memory.alloc(4);
|
||||
length.writeU32(128);
|
||||
if (getPeerName(socket, address, length) === 0) {
|
||||
const peer = parseSockaddr(address);
|
||||
if (peer !== null) {
|
||||
sockets.set(key, peer);
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
}
|
||||
}
|
||||
|
||||
function readWsaBuffers(lpBuffers, count) {
|
||||
const result = [];
|
||||
const stride = Process.pointerSize === 8 ? 16 : 8;
|
||||
const pointerOffset = Process.pointerSize === 8 ? 8 : 4;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const item = lpBuffers.add(i * stride);
|
||||
const length = item.readU32();
|
||||
const buffer = item.add(pointerOffset).readPointer();
|
||||
result.push(payloadSummary(buffer, length));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function readSecBufferDesc(secBufferDesc) {
|
||||
if (secBufferDesc.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const version = secBufferDesc.readU32();
|
||||
const bufferCount = secBufferDesc.add(4).readU32();
|
||||
const buffersPointer = secBufferDesc.add(Process.pointerSize === 8 ? 8 : 8).readPointer();
|
||||
const buffers = [];
|
||||
const stride = Process.pointerSize === 8 ? 16 : 12;
|
||||
const pointerOffset = Process.pointerSize === 8 ? 8 : 8;
|
||||
|
||||
for (let i = 0; i < Math.min(bufferCount, 8); i++) {
|
||||
const item = buffersPointer.add(i * stride);
|
||||
const length = item.readU32();
|
||||
const type = item.add(4).readU32();
|
||||
const buffer = item.add(pointerOffset).readPointer();
|
||||
buffers.push({
|
||||
index: i,
|
||||
type,
|
||||
payload: payloadSummary(buffer, length)
|
||||
});
|
||||
}
|
||||
|
||||
return { version, bufferCount, buffers };
|
||||
} catch (e) {
|
||||
return { error: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function shouldCaptureFilePayload(path) {
|
||||
if (path === undefined || path === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return path.indexOf('\\pipe\\') !== -1 || path.indexOf('\\Device\\Afd') !== -1;
|
||||
}
|
||||
|
||||
function hookExport(moduleName, exportName, callbacks) {
|
||||
const key = moduleName.toLowerCase() + '!' + exportName;
|
||||
if (installedHooks.has(key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
let module = null;
|
||||
const targetModuleName = moduleName.toLowerCase();
|
||||
for (const candidate of Process.enumerateModules()) {
|
||||
if (candidate.name.toLowerCase() === targetModuleName) {
|
||||
module = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (module === null) {
|
||||
module = Process.getModuleByName(moduleName);
|
||||
}
|
||||
|
||||
const address = module.getExportByName(exportName);
|
||||
Interceptor.attach(address, callbacks);
|
||||
installedHooks.add(key);
|
||||
emit('hooked', { api: exportName, module: moduleName, address: address.toString() });
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (String(e).indexOf('unable to find module') !== -1) {
|
||||
if (pendingHooks.filter(h => h.key === key).length === 0) {
|
||||
pendingHooks.push({ key, moduleName, exportName, callbacks });
|
||||
emit('hook-pending', { api: exportName, module: moduleName });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String(e).indexOf('unable to find export') !== -1) {
|
||||
installedHooks.add(key);
|
||||
emit('hook-missing-export', { api: exportName, module: moduleName, error: String(e) });
|
||||
return false;
|
||||
}
|
||||
|
||||
emit('hook-error', { api: exportName, module: moduleName, error: String(e) });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function installPendingHooks() {
|
||||
for (const hook of pendingHooks.slice()) {
|
||||
hookExport(hook.moduleName, hook.exportName, hook.callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
function readUnicodeString(unicodeString) {
|
||||
if (unicodeString.isNull()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const length = unicodeString.readU16();
|
||||
const bufferOffset = Process.pointerSize === 8 ? 8 : 4;
|
||||
const buffer = unicodeString.add(bufferOffset).readPointer();
|
||||
if (buffer.isNull() || length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return buffer.readUtf16String(length / 2);
|
||||
} catch (e) {
|
||||
return '<unicode-read-error:' + String(e) + '>';
|
||||
}
|
||||
}
|
||||
|
||||
function readObjectAttributesName(objectAttributes) {
|
||||
if (objectAttributes.isNull()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const objectNameOffset = Process.pointerSize === 8 ? 16 : 8;
|
||||
const unicodeString = objectAttributes.add(objectNameOffset).readPointer();
|
||||
return readUnicodeString(unicodeString);
|
||||
} catch (e) {
|
||||
return '<object-attributes-read-error:' + String(e) + '>';
|
||||
}
|
||||
}
|
||||
|
||||
function rememberHandle(handle, path, api) {
|
||||
if (!handle.equals(ptr('-1')) && !handle.isNull()) {
|
||||
handles.set(handle.toString(), path || '<empty>');
|
||||
emit('handle-open', { api, handle: handle.toString(), path: path || '<empty>' });
|
||||
}
|
||||
}
|
||||
|
||||
function forgetHandle(handle, api, retval) {
|
||||
const key = handle.toString();
|
||||
const path = handles.get(key);
|
||||
if (path !== undefined) {
|
||||
emit('handle-close', { api, handle: key, path, retval: retval.toString() });
|
||||
handles.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
emit('startup', {
|
||||
arch: Process.arch,
|
||||
platform: Process.platform,
|
||||
modules: Process.enumerateModules()
|
||||
.filter(m => {
|
||||
const lower = m.name.toLowerCase() + ' ' + m.path.toLowerCase();
|
||||
return lower.indexOf('aah') !== -1 || lower.indexOf('historian') !== -1 || lower.indexOf('histsdk') !== -1;
|
||||
})
|
||||
.map(m => ({ name: m.name, base: m.base.toString(), size: m.size, path: m.path }))
|
||||
});
|
||||
|
||||
try {
|
||||
Process.attachModuleObserver({
|
||||
onAdded(module) {
|
||||
const lower = module.name.toLowerCase() + ' ' + module.path.toLowerCase();
|
||||
if (lower.indexOf('aah') !== -1 || lower.indexOf('historian') !== -1 || lower.indexOf('histsdk') !== -1 || lower.indexOf('secur') !== -1 || lower.indexOf('netapi') !== -1) {
|
||||
emit('module-added', { name: module.name, base: module.base.toString(), size: module.size, path: module.path });
|
||||
}
|
||||
|
||||
installPendingHooks();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
emit('module-observer-error', { error: String(e) });
|
||||
}
|
||||
|
||||
const pendingTimer = setInterval(() => {
|
||||
installPendingHooks();
|
||||
if (pendingHooks.filter(h => !installedHooks.has(h.key)).length === 0) {
|
||||
clearInterval(pendingTimer);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
hookExport('kernel32.dll', 'CreateFileW', {
|
||||
onEnter(args) {
|
||||
this.path = args[0].isNull() ? '' : args[0].readUtf16String();
|
||||
},
|
||||
onLeave(retval) {
|
||||
rememberHandle(retval, this.path, 'CreateFileW');
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'CloseHandle', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
},
|
||||
onLeave(retval) {
|
||||
forgetHandle(this.handle, 'CloseHandle', retval);
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'ReadFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.buffer = args[1];
|
||||
this.requested = args[2].toInt32();
|
||||
this.readPointer = args[3];
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let read = null;
|
||||
try {
|
||||
read = this.readPointer.isNull() ? null : this.readPointer.readU32();
|
||||
} catch (_) {
|
||||
read = null;
|
||||
}
|
||||
|
||||
emit('read-file', {
|
||||
api: 'ReadFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.requested,
|
||||
resultBytes: read,
|
||||
retval: retval.toInt32(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, read === null ? 0 : read), shouldCaptureFilePayload(this.path))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'WriteFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.buffer = args[1];
|
||||
this.requested = args[2].toInt32();
|
||||
this.writtenPointer = args[3];
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let written = null;
|
||||
try {
|
||||
written = this.writtenPointer.isNull() ? null : this.writtenPointer.readU32();
|
||||
} catch (_) {
|
||||
written = null;
|
||||
}
|
||||
|
||||
emit('write-file', {
|
||||
api: 'WriteFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.requested,
|
||||
resultBytes: written,
|
||||
retval: retval.toInt32(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, written === null ? this.requested : written), shouldCaptureFilePayload(this.path))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtCreateFile', {
|
||||
onEnter(args) {
|
||||
this.fileHandlePointer = args[0];
|
||||
this.path = readObjectAttributesName(args[2]);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
rememberHandle(this.fileHandlePointer.readPointer(), this.path, 'NtCreateFile');
|
||||
} catch (e) {
|
||||
emit('handle-open-error', { api: 'NtCreateFile', path: this.path, error: String(e) });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtReadFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.ioStatusBlock = args[4];
|
||||
this.buffer = args[5];
|
||||
this.requested = args[6].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let transferred = null;
|
||||
try {
|
||||
transferred = this.ioStatusBlock.add(Process.pointerSize).readPointer().toUInt32();
|
||||
} catch (_) {
|
||||
transferred = retval.toInt32() >= 0 ? this.requested : 0;
|
||||
}
|
||||
|
||||
emit('nt-read-file', {
|
||||
api: 'NtReadFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.requested,
|
||||
resultBytes: transferred,
|
||||
ntstatus: retval.toString(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, transferred || 0), shouldCaptureFilePayload(this.path))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtWriteFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.ioStatusBlock = args[4];
|
||||
this.buffer = args[5];
|
||||
this.requested = args[6].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let transferred = null;
|
||||
try {
|
||||
transferred = this.ioStatusBlock.add(Process.pointerSize).readPointer().toUInt32();
|
||||
} catch (_) {
|
||||
transferred = this.requested;
|
||||
}
|
||||
|
||||
emit('nt-write-file', {
|
||||
api: 'NtWriteFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.requested,
|
||||
resultBytes: transferred,
|
||||
ntstatus: retval.toString(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, transferred || this.requested), shouldCaptureFilePayload(this.path))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtDeviceIoControlFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.ioStatusBlock = args[4];
|
||||
this.ioControlCode = args[5].toUInt32();
|
||||
this.inputBuffer = args[6];
|
||||
this.inputLength = args[7].toInt32();
|
||||
this.outputBuffer = args[8];
|
||||
this.outputLength = args[9].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
let transferred = null;
|
||||
try {
|
||||
transferred = this.ioStatusBlock.add(Process.pointerSize).readPointer().toUInt32();
|
||||
} catch (_) {
|
||||
transferred = null;
|
||||
}
|
||||
|
||||
emit('nt-device-io-control', {
|
||||
api: 'NtDeviceIoControlFile',
|
||||
handle: this.handle.toString(),
|
||||
path: handles.get(this.handle.toString()),
|
||||
ioControlCode: '0x' + this.ioControlCode.toString(16),
|
||||
inputBytes: this.inputLength,
|
||||
outputBytes: this.outputLength,
|
||||
transferredBytes: transferred,
|
||||
ntstatus: retval.toString(),
|
||||
input: payloadSummary(this.inputBuffer, Math.max(0, this.inputLength), shouldCaptureFilePayload(handles.get(this.handle.toString()))),
|
||||
output: payloadSummary(this.outputBuffer, retval.toInt32() >= 0 ? Math.max(0, transferred === null ? this.outputLength : transferred) : 0, shouldCaptureFilePayload(handles.get(this.handle.toString())))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'GetAddrInfoW', {
|
||||
onEnter(args) {
|
||||
this.nodeName = args[0].isNull() ? '' : args[0].readUtf16String();
|
||||
this.serviceName = args[1].isNull() ? '' : args[1].readUtf16String();
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('dns', { api: 'GetAddrInfoW', nodeName: this.nodeName, serviceName: this.serviceName, retval: retval.toInt32() });
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const ws2 = Process.getModuleByName('ws2_32.dll');
|
||||
getPeerName = new NativeFunction(ws2.getExportByName('getpeername'), 'int', ['pointer', 'pointer', 'pointer']);
|
||||
} catch (e) {
|
||||
emit('getpeername-error', { error: String(e) });
|
||||
}
|
||||
|
||||
hookExport('ws2_32.dll', 'connect', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.peer = parseSockaddr(args[1]);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.peer !== null) {
|
||||
sockets.set(socketKey(this.socket), this.peer);
|
||||
}
|
||||
emit('socket-connect', { api: 'connect', socket: socketKey(this.socket), peer: this.peer, retval: retval.toInt32() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'WSAConnect', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.peer = parseSockaddr(args[1]);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.peer !== null) {
|
||||
sockets.set(socketKey(this.socket), this.peer);
|
||||
}
|
||||
emit('socket-connect', { api: 'WSAConnect', socket: socketKey(this.socket), peer: this.peer, retval: retval.toInt32() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'send', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffer = args[1];
|
||||
this.length = args[2].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
ensurePeer(this.socket);
|
||||
emit('socket-send', {
|
||||
api: 'send',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
requestedBytes: this.length,
|
||||
resultBytes: retval.toInt32(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, Math.min(this.length, retval.toInt32())))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'recv', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffer = args[1];
|
||||
},
|
||||
onLeave(retval) {
|
||||
const length = retval.toInt32();
|
||||
if (length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensurePeer(this.socket);
|
||||
emit('socket-recv', {
|
||||
api: 'recv',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
resultBytes: length,
|
||||
payload: payloadSummary(this.buffer, length)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'WSASend', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffers = args[1];
|
||||
this.count = args[2].toInt32();
|
||||
this.sentPointer = args[3];
|
||||
this.summaries = readWsaBuffers(this.buffers, this.count);
|
||||
},
|
||||
onLeave(retval) {
|
||||
let sent = null;
|
||||
try {
|
||||
sent = this.sentPointer.isNull() ? null : this.sentPointer.readU32();
|
||||
} catch (_) {
|
||||
sent = null;
|
||||
}
|
||||
|
||||
ensurePeer(this.socket);
|
||||
emit('socket-send', {
|
||||
api: 'WSASend',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
retval: retval.toInt32(),
|
||||
resultBytes: sent,
|
||||
buffers: this.summaries
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'WSARecv', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffers = args[1];
|
||||
this.count = args[2].toInt32();
|
||||
this.receivedPointer = args[3];
|
||||
},
|
||||
onLeave(retval) {
|
||||
let received = null;
|
||||
try {
|
||||
received = this.receivedPointer.isNull() ? null : this.receivedPointer.readU32();
|
||||
} catch (_) {
|
||||
received = null;
|
||||
}
|
||||
|
||||
ensurePeer(this.socket);
|
||||
emit('socket-recv', {
|
||||
api: 'WSARecv',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
retval: retval.toInt32(),
|
||||
resultBytes: received,
|
||||
buffers: readWsaBuffers(this.buffers, this.count)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'WSAIoctl', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.controlCode = args[1].toUInt32();
|
||||
this.inputBuffer = args[2];
|
||||
this.inputLength = args[3].toInt32();
|
||||
this.outputBuffer = args[4];
|
||||
this.outputLength = args[5].toInt32();
|
||||
this.bytesReturnedPointer = args[6];
|
||||
},
|
||||
onLeave(retval) {
|
||||
let bytesReturned = null;
|
||||
try {
|
||||
bytesReturned = this.bytesReturnedPointer.isNull() ? null : this.bytesReturnedPointer.readU32();
|
||||
} catch (_) {
|
||||
bytesReturned = null;
|
||||
}
|
||||
|
||||
ensurePeer(this.socket);
|
||||
emit('socket-ioctl', {
|
||||
api: 'WSAIoctl',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
controlCode: '0x' + this.controlCode.toString(16),
|
||||
inputBytes: this.inputLength,
|
||||
outputBytes: this.outputLength,
|
||||
bytesReturned,
|
||||
retval: retval.toInt32(),
|
||||
input: payloadSummary(this.inputBuffer, Math.max(0, this.inputLength)),
|
||||
output: payloadSummary(this.outputBuffer, Math.max(0, bytesReturned === null ? 0 : bytesReturned))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('mswsock.dll', 'ConnectEx', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.peer = parseSockaddr(args[1]);
|
||||
this.sendBuffer = args[3];
|
||||
this.sendBytes = args[4].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.peer !== null) {
|
||||
sockets.set(socketKey(this.socket), this.peer);
|
||||
}
|
||||
emit('socket-connect', {
|
||||
api: 'ConnectEx',
|
||||
socket: socketKey(this.socket),
|
||||
peer: this.peer,
|
||||
retval: retval.toInt32(),
|
||||
initialPayload: payloadSummary(this.sendBuffer, Math.max(0, this.sendBytes))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('mswsock.dll', 'TransmitPackets', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.packetArray = args[1];
|
||||
this.elementCount = args[2].toInt32();
|
||||
this.sendSize = args[3].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
ensurePeer(this.socket);
|
||||
emit('socket-send', {
|
||||
api: 'TransmitPackets',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
elementCount: this.elementCount,
|
||||
sendSize: this.sendSize,
|
||||
retval: retval.toInt32()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('secur32.dll', 'AcquireCredentialsHandleW', {
|
||||
onEnter(args) {
|
||||
this.principal = args[0].isNull() ? '' : args[0].readUtf16String();
|
||||
this.packageName = args[1].isNull() ? '' : args[1].readUtf16String();
|
||||
this.credentialUse = args[2].toUInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('secur32', {
|
||||
api: 'AcquireCredentialsHandleW',
|
||||
packageName: this.packageName,
|
||||
principalPresent: this.principal.length > 0,
|
||||
credentialUse: this.credentialUse,
|
||||
status: retval.toString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('sspicli.dll', 'AcquireCredentialsHandleW', {
|
||||
onEnter(args) {
|
||||
this.principal = args[0].isNull() ? '' : args[0].readUtf16String();
|
||||
this.packageName = args[1].isNull() ? '' : args[1].readUtf16String();
|
||||
this.credentialUse = args[2].toUInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('secur32', {
|
||||
api: 'AcquireCredentialsHandleW',
|
||||
module: 'sspicli.dll',
|
||||
packageName: this.packageName,
|
||||
principalPresent: this.principal.length > 0,
|
||||
credentialUse: this.credentialUse,
|
||||
status: retval.toString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('secur32.dll', 'InitializeSecurityContextW', {
|
||||
onEnter(args) {
|
||||
this.target = args[2].isNull() ? '' : args[2].readUtf16String();
|
||||
this.requestFlags = args[3].toUInt32();
|
||||
this.inputToken = readSecBufferDesc(args[6]);
|
||||
this.outputTokenPointer = args[9];
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('secur32', {
|
||||
api: 'InitializeSecurityContextW',
|
||||
target: this.target,
|
||||
requestFlags: this.requestFlags,
|
||||
inputToken: this.inputToken,
|
||||
outputToken: readSecBufferDesc(this.outputTokenPointer),
|
||||
status: retval.toString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('sspicli.dll', 'InitializeSecurityContextW', {
|
||||
onEnter(args) {
|
||||
this.target = args[2].isNull() ? '' : args[2].readUtf16String();
|
||||
this.requestFlags = args[3].toUInt32();
|
||||
this.inputToken = readSecBufferDesc(args[6]);
|
||||
this.outputTokenPointer = args[9];
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('secur32', {
|
||||
api: 'InitializeSecurityContextW',
|
||||
module: 'sspicli.dll',
|
||||
target: this.target,
|
||||
requestFlags: this.requestFlags,
|
||||
inputToken: this.inputToken,
|
||||
outputToken: readSecBufferDesc(this.outputTokenPointer),
|
||||
status: retval.toString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('secur32.dll', 'AcceptSecurityContext', {
|
||||
onEnter(args) {
|
||||
this.requestFlags = args[3].toUInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('secur32', { api: 'AcceptSecurityContext', requestFlags: this.requestFlags, status: retval.toString() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('sspicli.dll', 'AcceptSecurityContext', {
|
||||
onEnter(args) {
|
||||
this.requestFlags = args[3].toUInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('secur32', { api: 'AcceptSecurityContext', module: 'sspicli.dll', requestFlags: this.requestFlags, status: retval.toString() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('secur32.dll', 'QuerySecurityContextToken', {
|
||||
onLeave(retval) {
|
||||
emit('secur32', { api: 'QuerySecurityContextToken', status: retval.toString() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('sspicli.dll', 'QuerySecurityContextToken', {
|
||||
onLeave(retval) {
|
||||
emit('secur32', { api: 'QuerySecurityContextToken', module: 'sspicli.dll', status: retval.toString() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('crypt32.dll', 'CryptBinaryToStringW', {
|
||||
onEnter(args) {
|
||||
this.inputBytes = args[1].toUInt32();
|
||||
this.flags = args[2].toUInt32();
|
||||
this.outputLengthPointer = args[4];
|
||||
},
|
||||
onLeave(retval) {
|
||||
let outputChars = null;
|
||||
try {
|
||||
outputChars = this.outputLengthPointer.isNull() ? null : this.outputLengthPointer.readU32();
|
||||
} catch (_) {
|
||||
outputChars = null;
|
||||
}
|
||||
emit('crypt32', { api: 'CryptBinaryToStringW', inputBytes: this.inputBytes, flags: this.flags, outputChars, retval: retval.toInt32() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('crypt32.dll', 'CryptStringToBinaryW', {
|
||||
onEnter(args) {
|
||||
this.inputChars = args[1].toUInt32();
|
||||
this.flags = args[2].toUInt32();
|
||||
this.outputLengthPointer = args[4];
|
||||
},
|
||||
onLeave(retval) {
|
||||
let outputBytes = null;
|
||||
try {
|
||||
outputBytes = this.outputLengthPointer.isNull() ? null : this.outputLengthPointer.readU32();
|
||||
} catch (_) {
|
||||
outputBytes = null;
|
||||
}
|
||||
emit('crypt32', { api: 'CryptStringToBinaryW', inputChars: this.inputChars, flags: this.flags, outputBytes, retval: retval.toInt32() });
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('netapi32.dll', 'NetUserGetLocalGroups', {
|
||||
onEnter(args) {
|
||||
this.server = args[0].isNull() ? '' : args[0].readUtf16String();
|
||||
this.user = args[1].isNull() ? '' : args[1].readUtf16String();
|
||||
},
|
||||
onLeave(retval) {
|
||||
emit('netapi', { api: 'NetUserGetLocalGroups', server: this.server, user: this.user, status: retval.toString() });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,506 @@
|
||||
'use strict';
|
||||
|
||||
const historianPort = 32568;
|
||||
const maxPrefixBytes = 16;
|
||||
const sockets = new Map();
|
||||
const handles = new Map();
|
||||
let getPeerName = null;
|
||||
|
||||
function emit(kind, payload) {
|
||||
payload.kind = kind;
|
||||
payload.pid = Process.id;
|
||||
payload.tid = Process.getCurrentThreadId();
|
||||
payload.timestamp = new Date().toISOString();
|
||||
console.log('FRIDA_NET ' + JSON.stringify(payload));
|
||||
}
|
||||
|
||||
function socketKey(socket) {
|
||||
return socket.toString();
|
||||
}
|
||||
|
||||
function readPortNetworkOrder(address) {
|
||||
const hi = address.readU8();
|
||||
const lo = address.add(1).readU8();
|
||||
return (hi << 8) | lo;
|
||||
}
|
||||
|
||||
function parseSockaddr(address) {
|
||||
if (address.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const family = address.readU16();
|
||||
if (family === 2) {
|
||||
const port = readPortNetworkOrder(address.add(2));
|
||||
const a = [
|
||||
address.add(4).readU8(),
|
||||
address.add(5).readU8(),
|
||||
address.add(6).readU8(),
|
||||
address.add(7).readU8()
|
||||
].join('.');
|
||||
return { family: 'IPv4', address: a, port };
|
||||
}
|
||||
|
||||
if (family === 23) {
|
||||
const port = readPortNetworkOrder(address.add(2));
|
||||
const parts = [];
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
const value = (address.add(8 + i).readU8() << 8) | address.add(8 + i + 1).readU8();
|
||||
parts.push(value.toString(16));
|
||||
}
|
||||
return { family: 'IPv6', address: parts.join(':'), port };
|
||||
}
|
||||
|
||||
return { family: String(family), address: null, port: null };
|
||||
} catch (e) {
|
||||
return { error: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function toHex(bytes) {
|
||||
const parts = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const text = bytes[i].toString(16);
|
||||
parts.push(text.length === 1 ? '0' + text : text);
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function payloadSummary(buffer, length) {
|
||||
const byteCount = Math.max(0, Math.min(length, maxPrefixBytes));
|
||||
if (buffer.isNull() || byteCount === 0) {
|
||||
return { byteCount: length, prefixByteCount: 0, prefixHex: '' };
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = Memory.readByteArray(buffer, byteCount);
|
||||
return {
|
||||
byteCount: length,
|
||||
prefixByteCount: byteCount,
|
||||
prefixHex: toHex(Array.from(new Uint8Array(raw)))
|
||||
};
|
||||
} catch (e) {
|
||||
return { byteCount: length, prefixByteCount: 0, prefixHex: '', error: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function isHistorianSocket(socket) {
|
||||
ensurePeer(socket);
|
||||
const peer = sockets.get(socketKey(socket));
|
||||
return peer !== undefined && peer.port === historianPort;
|
||||
}
|
||||
|
||||
function ensurePeer(socket) {
|
||||
const key = socketKey(socket);
|
||||
if (sockets.has(key) || getPeerName === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const address = Memory.alloc(128);
|
||||
const length = Memory.alloc(4);
|
||||
length.writeU32(128);
|
||||
const result = getPeerName(socket, address, length);
|
||||
if (result === 0) {
|
||||
const peer = parseSockaddr(address);
|
||||
if (peer !== null) {
|
||||
sockets.set(key, peer);
|
||||
if (peer.port === historianPort) {
|
||||
emit('peer-discovered', { socket: key, peer });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
emit('peer-discovery-error', { socket: key, error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
function hookExport(moduleName, exportName, callbacks) {
|
||||
try {
|
||||
const module = Process.getModuleByName(moduleName);
|
||||
const address = module.getExportByName(exportName);
|
||||
Interceptor.attach(address, callbacks);
|
||||
emit('hooked', { api: exportName, module: moduleName, address: address.toString() });
|
||||
} catch (e) {
|
||||
emit('hook-error', { api: exportName, module: moduleName, error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
function isInterestingPath(path) {
|
||||
const lower = (path || '').toLowerCase();
|
||||
return lower.indexOf('\\pipe\\') !== -1
|
||||
|| lower.indexOf('historian') !== -1
|
||||
|| lower.indexOf('aveva') !== -1
|
||||
|| lower.indexOf('wonderware') !== -1
|
||||
|| lower.indexOf('aah') !== -1
|
||||
|| lower.indexOf('ww') !== -1;
|
||||
}
|
||||
|
||||
function readUnicodeString(unicodeString) {
|
||||
if (unicodeString.isNull()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const length = unicodeString.readU16();
|
||||
const bufferOffset = Process.pointerSize === 8 ? 8 : 4;
|
||||
const buffer = unicodeString.add(bufferOffset).readPointer();
|
||||
if (buffer.isNull() || length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return buffer.readUtf16String(length / 2);
|
||||
} catch (e) {
|
||||
return '<unicode-read-error:' + String(e) + '>';
|
||||
}
|
||||
}
|
||||
|
||||
function readObjectAttributesName(objectAttributes) {
|
||||
if (objectAttributes.isNull()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const objectNameOffset = Process.pointerSize === 8 ? 16 : 8;
|
||||
const unicodeString = objectAttributes.add(objectNameOffset).readPointer();
|
||||
return readUnicodeString(unicodeString);
|
||||
} catch (e) {
|
||||
return '<object-attributes-read-error:' + String(e) + '>';
|
||||
}
|
||||
}
|
||||
|
||||
emit('startup', { arch: Process.arch, platform: Process.platform, historianPort });
|
||||
|
||||
try {
|
||||
const ws2 = Process.getModuleByName('ws2_32.dll');
|
||||
getPeerName = new NativeFunction(ws2.getExportByName('getpeername'), 'int', ['pointer', 'pointer', 'pointer']);
|
||||
} catch (e) {
|
||||
emit('getpeername-error', { error: String(e) });
|
||||
}
|
||||
|
||||
hookExport('ws2_32.dll', 'connect', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.peer = parseSockaddr(args[1]);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() === 0 && this.peer !== null) {
|
||||
sockets.set(socketKey(this.socket), this.peer);
|
||||
}
|
||||
|
||||
if (this.peer !== null && this.peer.port === historianPort) {
|
||||
emit('connect', {
|
||||
socket: socketKey(this.socket),
|
||||
peer: this.peer,
|
||||
retval: retval.toInt32()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'WSAConnect', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.peer = parseSockaddr(args[1]);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() === 0 && this.peer !== null) {
|
||||
sockets.set(socketKey(this.socket), this.peer);
|
||||
}
|
||||
|
||||
if (this.peer !== null && this.peer.port === historianPort) {
|
||||
emit('connect', {
|
||||
api: 'WSAConnect',
|
||||
socket: socketKey(this.socket),
|
||||
peer: this.peer,
|
||||
retval: retval.toInt32()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'closesocket', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.peer = sockets.get(socketKey(this.socket));
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.peer !== undefined && this.peer.port === historianPort) {
|
||||
emit('close', { socket: socketKey(this.socket), peer: this.peer, retval: retval.toInt32() });
|
||||
}
|
||||
|
||||
sockets.delete(socketKey(this.socket));
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'send', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffer = args[1];
|
||||
this.length = args[2].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (isHistorianSocket(this.socket)) {
|
||||
emit('send', {
|
||||
api: 'send',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
requestedBytes: this.length,
|
||||
resultBytes: retval.toInt32(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, Math.min(this.length, retval.toInt32())))
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'recv', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffer = args[1];
|
||||
},
|
||||
onLeave(retval) {
|
||||
const length = retval.toInt32();
|
||||
if (length > 0 && isHistorianSocket(this.socket)) {
|
||||
emit('recv', {
|
||||
api: 'recv',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
resultBytes: length,
|
||||
payload: payloadSummary(this.buffer, length)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function readWsaBuffers(lpBuffers, count) {
|
||||
const result = [];
|
||||
const stride = Process.pointerSize === 8 ? 16 : 8;
|
||||
const pointerOffset = Process.pointerSize === 8 ? 8 : 4;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const item = lpBuffers.add(i * stride);
|
||||
const length = item.readU32();
|
||||
const buffer = item.add(pointerOffset).readPointer();
|
||||
result.push(payloadSummary(buffer, length));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
hookExport('ws2_32.dll', 'WSASend', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffers = args[1];
|
||||
this.count = args[2].toInt32();
|
||||
this.sentPointer = args[3];
|
||||
this.summaries = isHistorianSocket(this.socket) ? readWsaBuffers(this.buffers, this.count) : [];
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (isHistorianSocket(this.socket)) {
|
||||
let sent = null;
|
||||
try {
|
||||
sent = this.sentPointer.isNull() ? null : this.sentPointer.readU32();
|
||||
} catch (_) {
|
||||
sent = null;
|
||||
}
|
||||
|
||||
emit('send', {
|
||||
api: 'WSASend',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
retval: retval.toInt32(),
|
||||
resultBytes: sent,
|
||||
buffers: this.summaries
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ws2_32.dll', 'WSARecv', {
|
||||
onEnter(args) {
|
||||
this.socket = args[0];
|
||||
this.buffers = args[1];
|
||||
this.count = args[2].toInt32();
|
||||
this.receivedPointer = args[3];
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (isHistorianSocket(this.socket)) {
|
||||
let received = null;
|
||||
try {
|
||||
received = this.receivedPointer.isNull() ? null : this.receivedPointer.readU32();
|
||||
} catch (_) {
|
||||
received = null;
|
||||
}
|
||||
|
||||
emit('recv', {
|
||||
api: 'WSARecv',
|
||||
socket: socketKey(this.socket),
|
||||
peer: sockets.get(socketKey(this.socket)),
|
||||
retval: retval.toInt32(),
|
||||
resultBytes: received,
|
||||
buffers: readWsaBuffers(this.buffers, this.count)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'CreateFileW', {
|
||||
onEnter(args) {
|
||||
this.path = args[0].isNull() ? '' : args[0].readUtf16String();
|
||||
this.interesting = isInterestingPath(this.path);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.interesting && !retval.equals(ptr('-1'))) {
|
||||
handles.set(retval.toString(), this.path);
|
||||
emit('ipc-open', {
|
||||
api: 'CreateFileW',
|
||||
handle: retval.toString(),
|
||||
path: this.path
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'CloseHandle', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path !== undefined) {
|
||||
emit('ipc-close', {
|
||||
api: 'CloseHandle',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
retval: retval.toInt32()
|
||||
});
|
||||
handles.delete(this.handle.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'WriteFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.buffer = args[1];
|
||||
this.length = args[2].toInt32();
|
||||
this.writtenPointer = args[3];
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path !== undefined) {
|
||||
let written = null;
|
||||
try {
|
||||
written = this.writtenPointer.isNull() ? null : this.writtenPointer.readU32();
|
||||
} catch (_) {
|
||||
written = null;
|
||||
}
|
||||
|
||||
emit('ipc-write', {
|
||||
api: 'WriteFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.length,
|
||||
resultBytes: written,
|
||||
retval: retval.toInt32(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, written === null ? this.length : written))
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('kernel32.dll', 'ReadFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.buffer = args[1];
|
||||
this.length = args[2].toInt32();
|
||||
this.readPointer = args[3];
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path !== undefined) {
|
||||
let read = null;
|
||||
try {
|
||||
read = this.readPointer.isNull() ? null : this.readPointer.readU32();
|
||||
} catch (_) {
|
||||
read = null;
|
||||
}
|
||||
|
||||
emit('ipc-read', {
|
||||
api: 'ReadFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.length,
|
||||
resultBytes: read,
|
||||
retval: retval.toInt32(),
|
||||
payload: payloadSummary(this.buffer, Math.max(0, read === null ? 0 : read))
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtCreateFile', {
|
||||
onEnter(args) {
|
||||
this.fileHandlePointer = args[0];
|
||||
this.path = readObjectAttributesName(args[2]);
|
||||
this.interesting = isInterestingPath(this.path);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.interesting && retval.toInt32() >= 0) {
|
||||
try {
|
||||
const handle = this.fileHandlePointer.readPointer();
|
||||
handles.set(handle.toString(), this.path);
|
||||
emit('ipc-open', {
|
||||
api: 'NtCreateFile',
|
||||
handle: handle.toString(),
|
||||
path: this.path,
|
||||
ntstatus: retval.toString()
|
||||
});
|
||||
} catch (e) {
|
||||
emit('ipc-open-error', { api: 'NtCreateFile', path: this.path, error: String(e) });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtWriteFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.buffer = args[5];
|
||||
this.length = args[6].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path !== undefined) {
|
||||
emit('ipc-write', {
|
||||
api: 'NtWriteFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.length,
|
||||
ntstatus: retval.toString(),
|
||||
payload: payloadSummary(this.buffer, this.length)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hookExport('ntdll.dll', 'NtReadFile', {
|
||||
onEnter(args) {
|
||||
this.handle = args[0];
|
||||
this.path = handles.get(this.handle.toString());
|
||||
this.buffer = args[5];
|
||||
this.length = args[6].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.path !== undefined) {
|
||||
emit('ipc-read', {
|
||||
api: 'NtReadFile',
|
||||
handle: this.handle.toString(),
|
||||
path: this.path,
|
||||
requestedBytes: this.length,
|
||||
ntstatus: retval.toString(),
|
||||
payload: payloadSummary(this.buffer, retval.toInt32() >= 0 ? this.length : 0)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user