c95824a65d
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>
6879 lines
282 KiB
C#
6879 lines
282 KiB
C#
using System.Buffers.Binary;
|
|
using System.Reflection.Metadata;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
using System.Reflection.PortableExecutable;
|
|
using System.Net;
|
|
using System.Net.Security;
|
|
using System.ServiceModel;
|
|
using System.ServiceModel.Channels;
|
|
using System.ServiceModel.Security;
|
|
using System.Security.Principal;
|
|
using System.Security.Cryptography;
|
|
using System.Runtime.Versioning;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using AVEVA.Historian.Client.Wcf;
|
|
using AVEVA.Historian.Client.Wcf.Contracts;
|
|
using dnlib.DotNet;
|
|
using dnlib.DotNet.Emit;
|
|
|
|
if (args.Length == 0 || args[0] is "-h" or "--help" or "help")
|
|
{
|
|
PrintHelp();
|
|
return 0;
|
|
}
|
|
|
|
try
|
|
{
|
|
return args[0].ToLowerInvariant() switch
|
|
{
|
|
"exports" => PrintExports(args),
|
|
"manifest" => WriteManifest(args),
|
|
"methods" => PrintManagedMethods(args),
|
|
"dnlib-method" => InspectDnlibMethod(args),
|
|
"method-refs" => PrintMethodReferences(args),
|
|
"field-constant" => PrintFieldConstantReferences(args),
|
|
"instrument-startdataquery" => InstrumentStartDataQuery(args),
|
|
"instrument-getnextrow" => InstrumentGetNextRow(args),
|
|
"instrument-wcf-readquery" => InstrumentWcfReadQuery(args),
|
|
"instrument-wcf-openconnection" => InstrumentWcfOpenConnection(args),
|
|
"instrument-historianclient-openconnection" => InstrumentHistorianClientOpenConnection(args),
|
|
"instrument-cserverclient-gethandle" => InstrumentCServerClientGetHandle(args),
|
|
"instrument-retrieval-console-startquery" => InstrumentRetrievalConsoleStartQuery(args),
|
|
"instrument-retrieval-upper-startquery" => InstrumentRetrievalUpperStartQuery(args),
|
|
"instrument-cclientcommon-startquery" => InstrumentCClientCommonStartQuery(args),
|
|
"instrument-cclientbase-openconnection" => InstrumentCClientBaseOpenConnection(args),
|
|
"instrument-starteventquery" => InstrumentStartEventQuery(args),
|
|
"instrument-getnexteventrow" => InstrumentGetNextEventRow(args),
|
|
"instrument-openconnection2" => InstrumentOpenConnection2(args),
|
|
"instrument-starttagquery" => InstrumentStartTagQuery(args),
|
|
"instrument-tagquery-gettaginfo" => InstrumentTagQueryGetTagInfo(args),
|
|
"instrument-openconnection3" => InstrumentOpenConnection3(args),
|
|
"instrument-cclientinfo-context" => InstrumentCClientInfoContext(args),
|
|
"instrument-wcf-auth-context" => InstrumentWcfAuthContext(args),
|
|
"instrument-wcf-writemessage" => InstrumentWcfWriteMessage(args),
|
|
"instrument-wcf-readmessage" => InstrumentWcfReadMessage(args),
|
|
"mark" => WriteMarker(args),
|
|
"wcf-probe" => ProbeWcf(args),
|
|
"wcf-cert-probe" => ProbeWcfCertificate(args),
|
|
"wcf-status" => ProbeWcfStatus(args),
|
|
"wcf-open" => OpenWcfSession(args),
|
|
"wcf-open2" => OpenWcfSession2(args),
|
|
"wcf-auth-context" => ProbeWcfAuthContext(args),
|
|
"wcf-auth-valcl" => ProbeWcfValidateClientCredential(args),
|
|
"wcf-tag-info" => ProbeWcfTagInfo(args),
|
|
"wcf-like-tag-browse" => ProbeWcfLikeTagBrowse(args),
|
|
"wcf-tag-query" => ProbeWcfTagQuery(args),
|
|
"wcf-start-query" => StartWcfQuery(args),
|
|
"wcf-start-event-query" => StartWcfEventQuery(args),
|
|
"wcf-register-event-tag" => RegisterEventTagAndStartQuery(args),
|
|
"wcf-add-event-tag" => AddEventTagAndStartQuery(args),
|
|
_ => UnknownCommand(args[0])
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine(Environment.GetEnvironmentVariable("AVEVA_HISTORIAN_RE_STACK") == "1" ? ex.ToString() : ex.Message);
|
|
return 1;
|
|
}
|
|
|
|
static int PrintExports(string[] args)
|
|
{
|
|
string path = args.Length > 1 ? args[1] : Path.Combine("current", "aahClient.dll");
|
|
bool json = args.Any(static a => a.Equals("--json", StringComparison.OrdinalIgnoreCase));
|
|
|
|
NativeBinaryInventory inventory = NativeBinaryInventory.Read(path);
|
|
if (json)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(inventory, CreateJsonOptions()));
|
|
return 0;
|
|
}
|
|
|
|
Console.WriteLine($"Path: {inventory.Path}");
|
|
Console.WriteLine($"SHA256: {inventory.Sha256}");
|
|
Console.WriteLine($"Exports: {inventory.Exports.Count}");
|
|
foreach (string export in inventory.Exports)
|
|
{
|
|
Console.WriteLine(export);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int WriteManifest(string[] args)
|
|
{
|
|
string output = args.Length > 1 ? args[1] : Path.Combine("docs", "reverse-engineering", "capture-manifest.json");
|
|
string root = Directory.GetCurrentDirectory();
|
|
string current = Path.Combine(root, "current");
|
|
|
|
CaptureManifest manifest = new(
|
|
GeneratedUtc: DateTimeOffset.UtcNow,
|
|
Binaries:
|
|
[
|
|
NativeBinaryInventory.Read(Path.Combine(current, "aahClient.dll")),
|
|
NativeBinaryInventory.Read(Path.Combine(current, "aahClientCommon.dll")),
|
|
NativeBinaryInventory.Read(Path.Combine(current, "aahClientManaged.dll"))
|
|
],
|
|
Scenarios: CaptureScenario.Defaults);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(output))!);
|
|
File.WriteAllText(output, JsonSerializer.Serialize(manifest, CreateJsonOptions()));
|
|
Console.WriteLine(output);
|
|
return 0;
|
|
}
|
|
|
|
static int PrintManagedMethods(string[] args)
|
|
{
|
|
string path = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string? filter = args.Length > 2 ? args[2] : null;
|
|
bool json = args.Any(static a => a.Equals("--json", StringComparison.OrdinalIgnoreCase));
|
|
|
|
IReadOnlyList<ManagedMethodInventory> methods = ManagedMethodInventory.Read(path, filter);
|
|
if (json)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(methods, CreateJsonOptions()));
|
|
return 0;
|
|
}
|
|
|
|
foreach (ManagedMethodInventory method in methods)
|
|
{
|
|
Console.WriteLine($"{method.Token} {method.Rva} {method.DeclaringType}.{method.Name}");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InspectDnlibMethod(string[] args)
|
|
{
|
|
string path = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string filter = args.Length > 2 ? args[2] : "HistoryQuery.StartQuery";
|
|
string? writeCopy = GetOption(args, "--write-copy");
|
|
bool includeInstructions = HasOption(args, "--instructions");
|
|
string? instructionWindow = GetOption(args, "--instruction-window");
|
|
(uint? Start, uint? End) window = ParseInstructionWindow(instructionWindow);
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(path);
|
|
List<object> methods = [];
|
|
foreach (TypeDef type in module.GetTypes())
|
|
{
|
|
foreach (MethodDef method in type.Methods)
|
|
{
|
|
if (!MethodMatches(method, filter))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
methods.Add(new
|
|
{
|
|
DeclaringType = type.FullName,
|
|
method.Name,
|
|
Token = "0x" + method.MDToken.Raw.ToString("X8"),
|
|
Rva = "0x" + method.RVA.ToString("X"),
|
|
method.IsStatic,
|
|
method.IsPublic,
|
|
HasBody = method.HasBody,
|
|
InstructionCount = method.Body?.Instructions.Count ?? 0,
|
|
Locals = method.Body?.Variables
|
|
.Select(static variable => new
|
|
{
|
|
Index = variable.Index,
|
|
Type = variable.Type?.ToString()
|
|
})
|
|
.ToArray(),
|
|
Calls = method.Body?.Instructions
|
|
.Where(static instruction => instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt)
|
|
.Select(static instruction => new
|
|
{
|
|
Offset = "0x" + instruction.Offset.ToString("X4"),
|
|
OpCode = instruction.OpCode.Name,
|
|
Operand = instruction.Operand?.ToString(),
|
|
Token = TryFormatDnlibToken(instruction.Operand)
|
|
})
|
|
.ToArray(),
|
|
Instructions = includeInstructions
|
|
? method.Body?.Instructions
|
|
.Where(instruction => IsInInstructionWindow(instruction.Offset, window))
|
|
.Select(static instruction => new
|
|
{
|
|
Offset = "0x" + instruction.Offset.ToString("X4"),
|
|
OpCode = instruction.OpCode.Name,
|
|
Operand = instruction.Operand?.ToString(),
|
|
Token = TryFormatDnlibToken(instruction.Operand)
|
|
})
|
|
.ToArray()
|
|
: null
|
|
});
|
|
}
|
|
}
|
|
|
|
object output = new
|
|
{
|
|
Path = Path.GetFullPath(path),
|
|
Filter = filter,
|
|
IsILOnly = module.IsILOnly,
|
|
IsMixedMode = !module.IsILOnly,
|
|
Methods = methods
|
|
};
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(output, CreateJsonOptions()));
|
|
|
|
if (!string.IsNullOrWhiteSpace(writeCopy))
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(writeCopy!))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(writeCopy);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(writeCopy);
|
|
}
|
|
|
|
Console.Error.WriteLine($"Wrote copy: {writeCopy}");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int PrintMethodReferences(string[] args)
|
|
{
|
|
string path = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string tokenText = args.Length > 2 ? args[2] : throw new ArgumentException("Usage: method-refs [dll-path] [target-token]");
|
|
uint targetToken = ParseMetadataToken(tokenText);
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(path);
|
|
List<object> references = [];
|
|
foreach (TypeDef type in module.GetTypes())
|
|
{
|
|
foreach (MethodDef method in type.Methods)
|
|
{
|
|
if (!method.HasBody)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (Instruction instruction in method.Body.Instructions)
|
|
{
|
|
if (instruction.Operand is not ITokenOperand tokenOperand ||
|
|
tokenOperand.MDToken.Raw != targetToken)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
references.Add(new
|
|
{
|
|
DeclaringType = type.FullName,
|
|
Method = method.Name.ToString(),
|
|
MethodToken = "0x" + method.MDToken.Raw.ToString("X8"),
|
|
Offset = "0x" + instruction.Offset.ToString("X4"),
|
|
instruction.OpCode.Name,
|
|
Operand = instruction.Operand?.ToString()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Path = Path.GetFullPath(path),
|
|
TargetToken = "0x" + targetToken.ToString("X8"),
|
|
References = references
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int PrintFieldConstantReferences(string[] args)
|
|
{
|
|
string path = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
if (args.Length <= 2 || !int.TryParse(args[2], out int fieldOffset))
|
|
{
|
|
throw new ArgumentException("Usage: field-constant [dll-path] [decimal-field-offset]");
|
|
}
|
|
|
|
int window = args.Length > 3 && int.TryParse(args[3], out int parsedWindow)
|
|
? Math.Max(parsedWindow, 0)
|
|
: 8;
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(path);
|
|
List<object> references = [];
|
|
foreach (TypeDef type in module.GetTypes())
|
|
{
|
|
foreach (MethodDef method in type.Methods)
|
|
{
|
|
if (!method.HasBody)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IList<Instruction> instructions = method.Body.Instructions;
|
|
for (int index = 0; index < instructions.Count; index++)
|
|
{
|
|
if (!TryGetLdcI4Value(instructions[index], out int value) || value != fieldOffset)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int start = Math.Max(0, index - window);
|
|
int end = Math.Min(instructions.Count - 1, index + window);
|
|
references.Add(new
|
|
{
|
|
DeclaringType = type.FullName,
|
|
Method = method.Name,
|
|
MethodToken = "0x" + method.MDToken.Raw.ToString("X8"),
|
|
Offset = "0x" + instructions[index].Offset.ToString("X4"),
|
|
Window = instructions
|
|
.Skip(start)
|
|
.Take(end - start + 1)
|
|
.Select(static instruction => new
|
|
{
|
|
Offset = "0x" + instruction.Offset.ToString("X4"),
|
|
OpCode = instruction.OpCode.Name,
|
|
Operand = instruction.Operand?.ToString(),
|
|
Token = TryFormatDnlibToken(instruction.Operand)
|
|
})
|
|
.ToArray()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Path = Path.GetFullPath(path),
|
|
FieldOffset = fieldOffset,
|
|
References = references
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool TryGetLdcI4Value(Instruction instruction, out int value)
|
|
{
|
|
if (instruction.OpCode == OpCodes.Ldc_I4)
|
|
{
|
|
value = (int)instruction.Operand;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_S)
|
|
{
|
|
value = (sbyte)instruction.Operand;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_0)
|
|
{
|
|
value = 0;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_1)
|
|
{
|
|
value = 1;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_2)
|
|
{
|
|
value = 2;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_3)
|
|
{
|
|
value = 3;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_4)
|
|
{
|
|
value = 4;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_5)
|
|
{
|
|
value = 5;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_6)
|
|
{
|
|
value = 6;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_7)
|
|
{
|
|
value = 7;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_8)
|
|
{
|
|
value = 8;
|
|
return true;
|
|
}
|
|
|
|
if (instruction.OpCode == OpCodes.Ldc_I4_M1)
|
|
{
|
|
value = -1;
|
|
return true;
|
|
}
|
|
|
|
value = 0;
|
|
return false;
|
|
}
|
|
|
|
static int InstrumentStartDataQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef startDataQuery = FindMethod(module, 0x0600574B);
|
|
MethodDef getData = FindMethod(module, 0x0600100D);
|
|
MethodDef getLength = FindMethod(module, 0x0600100C);
|
|
if (!startDataQuery.HasBody)
|
|
{
|
|
throw new InvalidOperationException("Query.StartDataQuery has no method body.");
|
|
}
|
|
|
|
Local streamVariable = startDataQuery.Body.Variables.First(variable => variable.Index == 21);
|
|
Local clientHandleCandidate = startDataQuery.Body.Variables.First(variable => variable.Index == 10);
|
|
Instruction requestInsertAfter = startDataQuery.Body.Instructions.First(instruction => instruction.Offset == 0x01C2);
|
|
int requestInsertIndex = startDataQuery.Body.Instructions.IndexOf(requestInsertAfter) + 1;
|
|
Instruction handleInsertAfter = startDataQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0206);
|
|
int handleInsertIndex = startDataQuery.Body.Instructions.IndexOf(handleInsertAfter) + 1;
|
|
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
|
|
Instruction[] requestInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "StartDataQuery.Request"),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getData),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getLength),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
|
|
Instruction[] handleInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "StartDataQuery.ClientHandleCandidate"),
|
|
Instruction.Create(OpCodes.Ldloc_S, clientHandleCandidate),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(startDataQuery, handleInsertIndex, handleInjected);
|
|
InsertInstructions(startDataQuery, requestInsertIndex, requestInjected);
|
|
|
|
startDataQuery.Body.MaxStack = (ushort)Math.Max((int)startDataQuery.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + startDataQuery.MDToken.Raw.ToString("X8"),
|
|
RequestInsertedAfterOffset = "0x01C2",
|
|
HandleInsertedAfterOffset = "0x0206",
|
|
LoggerAssembly = "AVEVA.Historian.ReverseInstrumentation",
|
|
LoggerType = "AVEVA.Historian.ReverseInstrumentation.CaptureLogger",
|
|
LoggerMethods = new[] { "LogBuffer", "LogUInt32" },
|
|
StreamVariable = "V_21",
|
|
ClientHandleCandidateVariable = "V_10",
|
|
UsesGetData = "0x" + getData.MDToken.Raw.ToString("X8"),
|
|
UsesGetLength = "0x" + getLength.MDToken.Raw.ToString("X8")
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentGetNextRow(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-getnextrow", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef getNextRow = FindMethod(module, 0x0600588D);
|
|
if (!getNextRow.HasBody)
|
|
{
|
|
throw new InvalidOperationException("HistorianClient.GetNextRow<DataQueryResultRow> has no method body.");
|
|
}
|
|
|
|
Instruction insertAfter = getNextRow.Body.Instructions.First(instruction => instruction.Offset == 0x005B);
|
|
int insertIndex = getNextRow.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "GetNextRow.QueryHandle"),
|
|
Instruction.Create(OpCodes.Ldarg_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "GetNextRow.DataQueryResultRow"),
|
|
Instruction.Create(OpCodes.Ldarg_2),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 512),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
getNextRow.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
getNextRow.Body.MaxStack = (ushort)Math.Max((int)getNextRow.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + getNextRow.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x005B",
|
|
LoggerMethods = new[] { "LogUInt32", "LogBuffer" },
|
|
LoggedScalar = "arg.1 queryHandle",
|
|
LoggedPointer = "arg.2 DataQueryResultRow*",
|
|
CapturedLength = 512
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentWcfReadQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-wcf-readquery", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef startQuery2 = FindMethod(module, 0x06004A0D);
|
|
MethodDef getNextQueryResultBuffer2 = FindMethod(module, 0x06004A0E);
|
|
if (!startQuery2.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CRetrievalConnectionWCF.StartQuery2 has no method body.");
|
|
}
|
|
|
|
if (!getNextQueryResultBuffer2.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CRetrievalConnectionWCF.GetNextQueryResultBuffer2 has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
MemberRefUser logByteArray = CreateLogByteArrayRef(module);
|
|
|
|
Local startResponseSize = startQuery2.Body.Variables.First(variable => variable.Index == 23);
|
|
Local startResponseBuffer = startQuery2.Body.Variables.First(variable => variable.Index == 39);
|
|
Local startRequestSize = startQuery2.Body.Variables.First(variable => variable.Index == 44);
|
|
Local startQueryHandle = startQuery2.Body.Variables.First(variable => variable.Index == 47);
|
|
Local startErrorSize = startQuery2.Body.Variables.First(variable => variable.Index == 52);
|
|
Local startErrorBuffer = startQuery2.Body.Variables.First(variable => variable.Index == 53);
|
|
Local startSuccess = startQuery2.Body.Variables.First(variable => variable.Index == 0);
|
|
Instruction startCall = startQuery2.Body.Instructions.First(instruction => instruction.Offset == 0x0293);
|
|
int startInsertIndex = startQuery2.Body.Instructions.IndexOf(startCall) + 1;
|
|
Instruction[] startInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Stloc, startSuccess),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, startSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.ClientHandle"),
|
|
Instruction.Create(OpCodes.Ldarg_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.QueryRequestType"),
|
|
Instruction.Create(OpCodes.Ldarg_2),
|
|
Instruction.Create(OpCodes.Ldind_U2),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.RequestSize"),
|
|
Instruction.Create(OpCodes.Ldloc, startRequestSize),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.ResponseSize"),
|
|
Instruction.Create(OpCodes.Ldloc, startResponseSize),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.ResponseBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, startResponseBuffer),
|
|
Instruction.Create(OpCodes.Call, logByteArray),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.QueryHandle"),
|
|
Instruction.Create(OpCodes.Ldloc, startQueryHandle),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.ErrorSize"),
|
|
Instruction.Create(OpCodes.Ldloc, startErrorSize),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.StartQuery2.ErrorBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, startErrorBuffer),
|
|
Instruction.Create(OpCodes.Call, logByteArray)
|
|
];
|
|
|
|
InsertInstructions(startQuery2, startInsertIndex, startInjected);
|
|
|
|
Local nextResultSize = getNextQueryResultBuffer2.Body.Variables.First(variable => variable.Index == 42);
|
|
Local nextResultBuffer = getNextQueryResultBuffer2.Body.Variables.First(variable => variable.Index == 20);
|
|
Local nextErrorSize = getNextQueryResultBuffer2.Body.Variables.First(variable => variable.Index == 47);
|
|
Local nextErrorBuffer = getNextQueryResultBuffer2.Body.Variables.First(variable => variable.Index == 48);
|
|
Local nextSuccess = getNextQueryResultBuffer2.Body.Variables.First(variable => variable.Index == 0);
|
|
Instruction nextStoreSuccess = getNextQueryResultBuffer2.Body.Instructions.First(instruction => instruction.Offset == 0x0252);
|
|
int nextInsertIndex = getNextQueryResultBuffer2.Body.Instructions.IndexOf(nextStoreSuccess) + 1;
|
|
Instruction[] nextInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, nextSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.ClientHandle"),
|
|
Instruction.Create(OpCodes.Ldarg_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.QueryHandle"),
|
|
Instruction.Create(OpCodes.Ldarg_2),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.ResultSize"),
|
|
Instruction.Create(OpCodes.Ldloc, nextResultSize),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.ResultBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, nextResultBuffer),
|
|
Instruction.Create(OpCodes.Call, logByteArray),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.ErrorSize"),
|
|
Instruction.Create(OpCodes.Ldloc, nextErrorSize),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetNextQueryResultBuffer2.ErrorBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, nextErrorBuffer),
|
|
Instruction.Create(OpCodes.Call, logByteArray)
|
|
];
|
|
|
|
InsertInstructions(getNextQueryResultBuffer2, nextInsertIndex, nextInjected);
|
|
|
|
startQuery2.Body.MaxStack = (ushort)Math.Max((int)startQuery2.Body.MaxStack, 10);
|
|
getNextQueryResultBuffer2.Body.MaxStack = (ushort)Math.Max((int)getNextQueryResultBuffer2.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethods = new[]
|
|
{
|
|
"0x" + startQuery2.MDToken.Raw.ToString("X8"),
|
|
"0x" + getNextQueryResultBuffer2.MDToken.Raw.ToString("X8")
|
|
},
|
|
StartQuery2InsertedAfterOffset = "0x0293",
|
|
GetNextQueryResultBuffer2InsertedAfterOffset = "0x0252",
|
|
LoggerMethods = new[] { "LogUInt32", "LogByteArray" },
|
|
CapturedValues = new[]
|
|
{
|
|
"StartQuery2 success/clientHandle/queryRequestType/requestSize/responseSize/responseBytes/queryHandle/errorSize/errorBytes",
|
|
"GetNextQueryResultBuffer2 success/clientHandle/queryHandle/resultSize/resultBytes/errorSize/errorBytes"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentWcfOpenConnection(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-wcf-openconnection", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef openConnection = FindMethod(module, 0x06004042);
|
|
if (!openConnection.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.OpenConnection has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
MemberRefUser logString = CreateLogStringRef(module);
|
|
Local storageSession = openConnection.Body.Variables.First(variable => variable.Index == 40);
|
|
Local returnCode = openConnection.Body.Variables.First(variable => variable.Index == 52);
|
|
|
|
dnlib.DotNet.Parameter handleParameter = (dnlib.DotNet.Parameter)openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x0301).Operand;
|
|
dnlib.DotNet.Parameter serverStatusParameter = (dnlib.DotNet.Parameter)openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x0305).Operand;
|
|
Instruction openCall = openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x0307);
|
|
int insertIndex = openConnection.Body.Instructions.IndexOf(openCall) + 1;
|
|
|
|
// The return code must stay on the stack for the existing ToSError call.
|
|
// Insert the real instructions manually so the duplicated return code stays
|
|
// below the logger arguments until the original call consumes it.
|
|
Instruction[] openInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Stloc, returnCode),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.OpenConnection.ReturnCode"),
|
|
Instruction.Create(OpCodes.Ldloc, returnCode),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.OpenConnection.Handle"),
|
|
Instruction.Create(OpCodes.Ldarg_S, handleParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.OpenConnection.ServerStatus"),
|
|
Instruction.Create(OpCodes.Ldarg_S, serverStatusParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.OpenConnection.StorageSession"),
|
|
Instruction.Create(OpCodes.Ldloc, storageSession),
|
|
Instruction.Create(OpCodes.Call, logString)
|
|
];
|
|
|
|
InsertInstructions(openConnection, insertIndex, openInjected);
|
|
openConnection.Body.MaxStack = (ushort)Math.Max((int)openConnection.Body.MaxStack, 10);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + openConnection.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x0307",
|
|
LoggerMethods = new[] { "LogUInt32", "LogString" },
|
|
CapturedValues = new[]
|
|
{
|
|
"OpenConnection returnCode/handle/serverStatus/storageSession"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentHistorianClientOpenConnection(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-historianclient-openconnection", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef openConnection = FindMethod(module, 0x060055D8);
|
|
if (!openConnection.HasBody)
|
|
{
|
|
throw new InvalidOperationException("HistorianClient.OpenConnection has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
Local successValue = new(module.CorLibTypes.UInt32);
|
|
openConnection.Body.Variables.Add(successValue);
|
|
Instruction calli = openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x0057);
|
|
int insertIndex = openConnection.Body.Instructions.IndexOf(calli) + 1;
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Stloc, successValue),
|
|
Instruction.Create(OpCodes.Ldstr, "HistorianClient.OpenConnection.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, successValue),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "HistorianClient.OpenConnection.Handle"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4_8),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(openConnection, insertIndex, injected);
|
|
openConnection.Body.MaxStack = (ushort)Math.Max((int)openConnection.Body.MaxStack, 10);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + openConnection.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x0057",
|
|
LoggerMethod = "LogUInt32",
|
|
CapturedValues = new[]
|
|
{
|
|
"HistorianClient.OpenConnection success/handle"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentCServerClientGetHandle(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-cserverclient-gethandle", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef getHandle = FindMethod(module, 0x060017F9);
|
|
if (!getHandle.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahClientCommon.CServerClient.GetHandle has no method body.");
|
|
}
|
|
|
|
Local returnValue = new(module.CorLibTypes.UInt32);
|
|
getHandle.Body.Variables.Add(returnValue);
|
|
|
|
Instruction insertAfter = getHandle.Body.Instructions.First(instruction => instruction.Offset == 0x0008);
|
|
int insertIndex = getHandle.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Stloc, returnValue),
|
|
Instruction.Create(OpCodes.Ldstr, "CServerClient.GetHandle.Return"),
|
|
Instruction.Create(OpCodes.Ldloc, returnValue),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(getHandle, insertIndex, injected);
|
|
getHandle.Body.MaxStack = (ushort)Math.Max((int)getHandle.Body.MaxStack, 4);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + getHandle.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x0008",
|
|
LoggerMethod = "LogUInt32",
|
|
CapturedValue = "CServerClient.GetHandle.Return",
|
|
NativeFieldOffset = 2528
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentRetrievalConsoleStartQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-retrieval-console-startquery", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef startQuery = FindMethod(module, 0x06002527);
|
|
if (!startQuery.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CRetrievalConsoleClient.StartQuery has no method body.");
|
|
}
|
|
|
|
Local lockedPointerValue = startQuery.Body.Variables.First(variable => variable.Index == 14);
|
|
Local lockedScalarValue = startQuery.Body.Variables.First(variable => variable.Index == 13);
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
|
|
Instruction entryInstruction = startQuery.Body.Instructions.First();
|
|
int entryInsertIndex = startQuery.Body.Instructions.IndexOf(entryInstruction);
|
|
Instruction[] entryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "RetrievalConsole.StartQuery.Arg1"),
|
|
Instruction.Create(OpCodes.Ldarg_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction pointerStore = startQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0177);
|
|
int pointerInsertIndex = startQuery.Body.Instructions.IndexOf(pointerStore) + 1;
|
|
Instruction[] pointerInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "RetrievalConsole.StartQuery.LockedPointerValue"),
|
|
Instruction.Create(OpCodes.Ldloc_S, lockedPointerValue),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction scalarStore = startQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0189);
|
|
int scalarInsertIndex = startQuery.Body.Instructions.IndexOf(scalarStore) + 1;
|
|
Instruction[] scalarInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "RetrievalConsole.StartQuery.LockedScalarValue"),
|
|
Instruction.Create(OpCodes.Ldloc_S, lockedScalarValue),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(startQuery, scalarInsertIndex, scalarInjected);
|
|
InsertInstructions(startQuery, pointerInsertIndex, pointerInjected);
|
|
InsertInstructions(startQuery, entryInsertIndex, entryInjected);
|
|
|
|
startQuery.Body.MaxStack = (ushort)Math.Max((int)startQuery.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + startQuery.MDToken.Raw.ToString("X8"),
|
|
EntryInsertedBeforeOffset = "0x0000",
|
|
LockedPointerInsertedAfterOffset = "0x0177",
|
|
LockedScalarInsertedAfterOffset = "0x0189",
|
|
LoggerMethod = "LogUInt32",
|
|
CapturedValues = new[]
|
|
{
|
|
"CRetrievalConsoleClient.StartQuery arg.1",
|
|
"V_14 dereferenced after SExternallyLocked<unsigned long>.Get at this+176",
|
|
"V_13 after SExternallyLocked<unsigned long>.Get at this+192"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentRetrievalUpperStartQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-retrieval-upper-startquery", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef retrievalStartQuery2 = FindMethod(module, 0x06002482);
|
|
MethodDef srvStartQuery = FindMethod(module, 0x060024E1);
|
|
if (!retrievalStartQuery2.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahClientCommon.CRetrieval.StartQuery2 has no method body.");
|
|
}
|
|
|
|
if (!srvStartQuery.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahClientCommon.CSrvRetrievalConnection.StartQuery has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
|
|
dnlib.DotNet.Parameter retrievalQueryHandleParameter = (dnlib.DotNet.Parameter)retrievalStartQuery2.Body.Instructions
|
|
.First(instruction => instruction.Offset == 0x0061).Operand;
|
|
Local retrievalRequestSize = retrievalStartQuery2.Body.Variables.First(variable => variable.Index == 10);
|
|
Local retrievalSuccess = retrievalStartQuery2.Body.Variables.First(variable => variable.Index == 4);
|
|
|
|
int retrievalEntryIndex = retrievalStartQuery2.Body.Instructions.IndexOf(retrievalStartQuery2.Body.Instructions.First());
|
|
Instruction[] retrievalEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CRetrieval.StartQuery2.QueryHandleArg"),
|
|
Instruction.Create(OpCodes.Ldarg_S, retrievalQueryHandleParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction retrievalSizeStore = retrievalStartQuery2.Body.Instructions.First(instruction => instruction.Offset == 0x0079);
|
|
int retrievalSizeIndex = retrievalStartQuery2.Body.Instructions.IndexOf(retrievalSizeStore) + 1;
|
|
Instruction[] retrievalSizeInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CRetrieval.StartQuery2.RequestSize"),
|
|
Instruction.Create(OpCodes.Ldloc_S, retrievalRequestSize),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction retrievalSuccessStore = retrievalStartQuery2.Body.Instructions.First(instruction => instruction.Offset == 0x00E6);
|
|
int retrievalSuccessIndex = retrievalStartQuery2.Body.Instructions.IndexOf(retrievalSuccessStore) + 1;
|
|
Instruction[] retrievalSuccessInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CRetrieval.StartQuery2.CSrvSuccess"),
|
|
Instruction.Create(OpCodes.Ldloc_S, retrievalSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(retrievalStartQuery2, retrievalSuccessIndex, retrievalSuccessInjected);
|
|
InsertInstructions(retrievalStartQuery2, retrievalSizeIndex, retrievalSizeInjected);
|
|
InsertInstructions(retrievalStartQuery2, retrievalEntryIndex, retrievalEntryInjected);
|
|
|
|
Local srvSuccess = srvStartQuery.Body.Variables.First(variable => variable.Index == 1);
|
|
|
|
int srvEntryIndex = srvStartQuery.Body.Instructions.IndexOf(srvStartQuery.Body.Instructions.First());
|
|
Instruction[] srvEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CSrvRetrieval.StartQuery.QueryHandleArg"),
|
|
Instruction.Create(OpCodes.Ldarg_2),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction srvResolveStore = srvStartQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0085);
|
|
int srvResolveIndex = srvStartQuery.Body.Instructions.IndexOf(srvResolveStore) + 1;
|
|
Instruction[] srvResolveInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CSrvRetrieval.StartQuery.ResolveSuccess"),
|
|
Instruction.Create(OpCodes.Ldloc_S, srvSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction srvConsoleStore = srvStartQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0132);
|
|
int srvConsoleIndex = srvStartQuery.Body.Instructions.IndexOf(srvConsoleStore) + 1;
|
|
Instruction[] srvConsoleInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CSrvRetrieval.StartQuery.ConsoleSuccess"),
|
|
Instruction.Create(OpCodes.Ldloc_S, srvSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(srvStartQuery, srvConsoleIndex, srvConsoleInjected);
|
|
InsertInstructions(srvStartQuery, srvResolveIndex, srvResolveInjected);
|
|
InsertInstructions(srvStartQuery, srvEntryIndex, srvEntryInjected);
|
|
|
|
retrievalStartQuery2.Body.MaxStack = (ushort)Math.Max((int)retrievalStartQuery2.Body.MaxStack, 8);
|
|
srvStartQuery.Body.MaxStack = (ushort)Math.Max((int)srvStartQuery.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethods = new[]
|
|
{
|
|
"0x" + retrievalStartQuery2.MDToken.Raw.ToString("X8"),
|
|
"0x" + srvStartQuery.MDToken.Raw.ToString("X8")
|
|
},
|
|
LoggerMethod = "LogUInt32",
|
|
CapturedValues = new[]
|
|
{
|
|
"CRetrieval.StartQuery2 queryHandle/requestSize/CSrvSuccess",
|
|
"CSrvRetrieval.StartQuery queryHandle/ResolveSuccess/ConsoleSuccess"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentCClientCommonStartQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-cclientcommon-startquery", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef startQuery = FindMethod(module, 0x06002E86);
|
|
if (!startQuery.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahClientCommon.CClientCommon.StartQuery has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
Local clientHandle = new(module.CorLibTypes.UInt32);
|
|
startQuery.Body.Variables.Add(clientHandle);
|
|
Local callSuccess = startQuery.Body.Variables.First(variable => variable.Index == 5);
|
|
|
|
dnlib.DotNet.Parameter queryHandleParameter = (dnlib.DotNet.Parameter)startQuery.Body.Instructions
|
|
.First(instruction => instruction.Offset == 0x01AD).Operand;
|
|
|
|
int entryIndex = startQuery.Body.Instructions.IndexOf(startQuery.Body.Instructions.First());
|
|
Instruction[] entryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientCommon.StartQuery.QueryHandleBeforeCall"),
|
|
Instruction.Create(OpCodes.Ldarg_S, queryHandleParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction clientHandleCall = startQuery.Body.Instructions.First(instruction => instruction.Offset == 0x01A3);
|
|
int clientHandleIndex = startQuery.Body.Instructions.IndexOf(clientHandleCall) + 1;
|
|
Instruction[] clientHandleInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Stloc, clientHandle),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientCommon.StartQuery.ClientHandleForConnection"),
|
|
Instruction.Create(OpCodes.Ldloc, clientHandle),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction successStore = startQuery.Body.Instructions.First(instruction => instruction.Offset == 0x01C1);
|
|
int successIndex = startQuery.Body.Instructions.IndexOf(successStore) + 1;
|
|
Instruction[] successInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientCommon.StartQuery.CallSuccess"),
|
|
Instruction.Create(OpCodes.Ldloc_S, callSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientCommon.StartQuery.QueryHandleAfterCall"),
|
|
Instruction.Create(OpCodes.Ldarg_S, queryHandleParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
InsertInstructions(startQuery, successIndex, successInjected);
|
|
InsertInstructions(startQuery, clientHandleIndex, clientHandleInjected);
|
|
InsertInstructions(startQuery, entryIndex, entryInjected);
|
|
|
|
startQuery.Body.MaxStack = (ushort)Math.Max((int)startQuery.Body.MaxStack, 10);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + startQuery.MDToken.Raw.ToString("X8"),
|
|
ClientHandleInsertedAfterOffset = "0x01A3",
|
|
SuccessInsertedAfterOffset = "0x01C1",
|
|
LoggerMethod = "LogUInt32",
|
|
CapturedValues = new[]
|
|
{
|
|
"CClientCommon.StartQuery client handle returned by CClient vtable call at offset 0x01A3",
|
|
"queryHandle before/after WCF StartQuery vtable call at offset 0x01BC",
|
|
"StartQuery call success flag"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentCClientBaseOpenConnection(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-cclientbase-openconnection", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef openConnection = FindMethod(module, 0x0600388D);
|
|
if (!openConnection.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahClientCommon.CClientBase.OpenConnection has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
Local handleValue = new(module.CorLibTypes.UInt32);
|
|
openConnection.Body.Variables.Add(handleValue);
|
|
Local openSuccess = openConnection.Body.Variables.First(variable => variable.Index == 11);
|
|
MethodSig initialGetHandleCallSite = (MethodSig)openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x007D).Operand;
|
|
|
|
Instruction initialHandleCall = openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x007D);
|
|
int initialHandleIndex = openConnection.Body.Instructions.IndexOf(initialHandleCall) + 1;
|
|
Instruction[] initialHandleInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Stloc, handleValue),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.OpenConnection.InitialHandle"),
|
|
Instruction.Create(OpCodes.Ldloc, handleValue),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
|
|
Instruction firstOpenStore = openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x048A);
|
|
int firstOpenIndex = openConnection.Body.Instructions.IndexOf(firstOpenStore) + 1;
|
|
Instruction[] firstOpenInjected = CreateCClientBaseOpenConnectionLogInstructions(
|
|
"CClientBase.OpenConnection.PrimaryOpenSuccess",
|
|
"CClientBase.OpenConnection.HandleAfterPrimaryOpen",
|
|
openSuccess,
|
|
handleValue,
|
|
initialGetHandleCallSite,
|
|
logUInt32);
|
|
|
|
Instruction secondOpenStore = openConnection.Body.Instructions.First(instruction => instruction.Offset == 0x06D9);
|
|
int secondOpenIndex = openConnection.Body.Instructions.IndexOf(secondOpenStore) + 1;
|
|
Instruction[] secondOpenInjected = CreateCClientBaseOpenConnectionLogInstructions(
|
|
"CClientBase.OpenConnection.SecondaryOpenSuccess",
|
|
"CClientBase.OpenConnection.HandleAfterSecondaryOpen",
|
|
openSuccess,
|
|
handleValue,
|
|
initialGetHandleCallSite,
|
|
logUInt32);
|
|
|
|
InsertInstructions(openConnection, secondOpenIndex, secondOpenInjected);
|
|
InsertInstructions(openConnection, firstOpenIndex, firstOpenInjected);
|
|
InsertInstructions(openConnection, initialHandleIndex, initialHandleInjected);
|
|
|
|
openConnection.Body.MaxStack = (ushort)Math.Max((int)openConnection.Body.MaxStack, 14);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + openConnection.MDToken.Raw.ToString("X8"),
|
|
InitialHandleInsertedAfterOffset = "0x007D",
|
|
PrimaryOpenInsertedAfterOffset = "0x048A",
|
|
SecondaryOpenInsertedAfterOffset = "0x06D9",
|
|
LoggerMethod = "LogUInt32",
|
|
CapturedValues = new[]
|
|
{
|
|
"initial vtable offset 24 handle",
|
|
"primary/secondary open success flags",
|
|
"vtable offset 24 handle after each open branch"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static Instruction[] CreateCClientBaseOpenConnectionLogInstructions(
|
|
string successPhase,
|
|
string handlePhase,
|
|
Local openSuccess,
|
|
Local handleValue,
|
|
MethodSig getHandleCallSite,
|
|
MemberRefUser logUInt32)
|
|
{
|
|
return
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, successPhase),
|
|
Instruction.Create(OpCodes.Ldloc_S, openSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Dup),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)24),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Calli, getHandleCallSite),
|
|
Instruction.Create(OpCodes.Stloc, handleValue),
|
|
Instruction.Create(OpCodes.Ldstr, handlePhase),
|
|
Instruction.Create(OpCodes.Ldloc, handleValue),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
}
|
|
|
|
static int InstrumentWcfWriteMessage(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-wcf-writemessage", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
// aahMDASEncoder.ClientMessageEncoder.WriteMessage(Message, int32, BufferManager, int32) -> ArraySegment<byte>
|
|
MethodDef writeMessage = FindMethod(module, 0x06005E65);
|
|
if (!writeMessage.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahMDASEncoder.ClientMessageEncoder.WriteMessage has no method body.");
|
|
}
|
|
|
|
// V_0 holds the ArraySegment<byte> result of the inner encoder.
|
|
Local segmentLocal = writeMessage.Body.Variables.First(v => v.Index == 0);
|
|
|
|
// Inject right after stloc.0 at IL_0010 (so V_0 is populated, before the binding-element accounting runs).
|
|
Instruction insertAfter = writeMessage.Body.Instructions.First(i => i.Offset == 0x0010);
|
|
int insertIndex = writeMessage.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
|
|
// Find existing get_Count MemberRef (already used in the method at IL_0024) to reuse the imported generic instance.
|
|
MemberRef getCountRef = writeMessage.Body.Instructions
|
|
.Where(i => i.Operand is IMethod m && m.Name == "get_Count" && m.DeclaringType?.FullName?.StartsWith("System.ArraySegment") == true)
|
|
.Select(i => (MemberRef)i.Operand!)
|
|
.First();
|
|
|
|
ITypeDefOrRef arraySegmentType = getCountRef.DeclaringType;
|
|
// ArraySegment<T>::get_Array returns T[] in metadata — we need the generic-var form, not byte[].
|
|
MemberRefUser getArrayRef = new(
|
|
module,
|
|
"get_Array",
|
|
MethodSig.CreateInstance(new SZArraySig(new GenericVar(0))),
|
|
arraySegmentType);
|
|
MemberRefUser getOffsetRef = new(
|
|
module,
|
|
"get_Offset",
|
|
MethodSig.CreateInstance(module.CorLibTypes.Int32),
|
|
arraySegmentType);
|
|
|
|
MemberRefUser logByteArraySegment = CreateLogByteArraySegmentRef(module);
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "WCF.WriteMessage.Body"),
|
|
Instruction.Create(OpCodes.Ldloca_S, segmentLocal),
|
|
Instruction.Create(OpCodes.Call, getArrayRef),
|
|
Instruction.Create(OpCodes.Ldloca_S, segmentLocal),
|
|
Instruction.Create(OpCodes.Call, getOffsetRef),
|
|
Instruction.Create(OpCodes.Ldloca_S, segmentLocal),
|
|
Instruction.Create(OpCodes.Call, getCountRef),
|
|
Instruction.Create(OpCodes.Call, logByteArraySegment)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
writeMessage.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
writeMessage.Body.MaxStack = (ushort)Math.Max((int)writeMessage.Body.MaxStack, 8);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + writeMessage.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x0010",
|
|
LoggerMethod = "LogByteArraySegment",
|
|
SegmentLocal = "V_0 (ArraySegment<byte>)",
|
|
Phase = "WCF.WriteMessage.Body"
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentWcfReadMessage(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-wcf-readmessage", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
// aahMDASEncoder.ClientMessageEncoder.ReadMessage(ArraySegment<byte>, BufferManager, string) -> Message
|
|
MethodDef readMessage = FindMethod(module, 0x06005E63);
|
|
if (!readMessage.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahMDASEncoder.ClientMessageEncoder.ReadMessage has no method body.");
|
|
}
|
|
|
|
// arg.1 = incoming ArraySegment<byte>. Inject at method entry so we capture the buffer
|
|
// regardless of whether the compressed-path (DecompressBuffer + V_1) or the
|
|
// uncompressed-path (uses arg.1 directly at IL_009C) is taken.
|
|
dnlib.DotNet.Parameter segmentParam = readMessage.Parameters[1];
|
|
int insertIndex = 0;
|
|
Instruction firstInstruction = readMessage.Body.Instructions[0];
|
|
|
|
// Reuse imported ArraySegment<byte> get_Count MemberRef (already in the body) for its DeclaringType.
|
|
MemberRef getCountRef = readMessage.Body.Instructions
|
|
.Where(i => i.Operand is IMethod m && m.Name == "get_Count" && m.DeclaringType?.FullName?.StartsWith("System.ArraySegment") == true)
|
|
.Select(i => (MemberRef)i.Operand!)
|
|
.First();
|
|
|
|
ITypeDefOrRef arraySegmentType = getCountRef.DeclaringType;
|
|
MemberRefUser getArrayRef = new(
|
|
module,
|
|
"get_Array",
|
|
MethodSig.CreateInstance(new SZArraySig(new GenericVar(0))),
|
|
arraySegmentType);
|
|
MemberRefUser getOffsetRef = new(
|
|
module,
|
|
"get_Offset",
|
|
MethodSig.CreateInstance(module.CorLibTypes.Int32),
|
|
arraySegmentType);
|
|
|
|
MemberRefUser logByteArraySegment = CreateLogByteArraySegmentRef(module);
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "WCF.ReadMessage.Body"),
|
|
Instruction.Create(OpCodes.Ldarga_S, segmentParam),
|
|
Instruction.Create(OpCodes.Call, getArrayRef),
|
|
Instruction.Create(OpCodes.Ldarga_S, segmentParam),
|
|
Instruction.Create(OpCodes.Call, getOffsetRef),
|
|
Instruction.Create(OpCodes.Ldarga_S, segmentParam),
|
|
Instruction.Create(OpCodes.Call, getCountRef),
|
|
Instruction.Create(OpCodes.Call, logByteArraySegment)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
readMessage.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
readMessage.Body.MaxStack = (ushort)Math.Max((int)readMessage.Body.MaxStack, 8);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + readMessage.MDToken.Raw.ToString("X8"),
|
|
InsertedAtOffset = "0x" + firstInstruction.Offset.ToString("X4"),
|
|
LoggerMethod = "LogByteArraySegment",
|
|
SegmentSource = "arg.1 (ArraySegment<byte>, method entry — captures both compressed and uncompressed paths)",
|
|
Phase = "WCF.ReadMessage.Body"
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentStartEventQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-starteventquery", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef startEventQuery = FindMethod(module, 0x0600574A);
|
|
MethodDef getData = FindMethod(module, 0x0600100D);
|
|
MethodDef getLength = FindMethod(module, 0x0600100C);
|
|
if (!startEventQuery.HasBody)
|
|
{
|
|
throw new InvalidOperationException("Query.StartEventQuery has no method body.");
|
|
}
|
|
|
|
Local streamVariable = startEventQuery.Body.Variables.First(variable => variable.Index == 14);
|
|
Instruction insertAfter = startEventQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0096);
|
|
int insertIndex = startEventQuery.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "StartEventQuery.Request"),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getData),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getLength),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
startEventQuery.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
startEventQuery.Body.MaxStack = (ushort)Math.Max((int)startEventQuery.Body.MaxStack, 8);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + startEventQuery.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x0096",
|
|
LoggerMethod = "LogBuffer",
|
|
StreamVariable = "V_14",
|
|
UsesGetData = "0x" + getData.MDToken.Raw.ToString("X8"),
|
|
UsesGetLength = "0x" + getLength.MDToken.Raw.ToString("X8")
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentGetNextEventRow(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-getnexteventrow", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef getNextRow = FindMethod(module, 0x06005965);
|
|
if (!getNextRow.HasBody)
|
|
{
|
|
throw new InvalidOperationException("HistorianClient.GetNextRow<EventQueryResultRow> has no method body.");
|
|
}
|
|
|
|
Instruction insertAfter = getNextRow.Body.Instructions.First(instruction => instruction.Offset == 0x005B);
|
|
int insertIndex = getNextRow.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "GetNextRow.EventQueryResultRow"),
|
|
Instruction.Create(OpCodes.Ldarg_2),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2048),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
getNextRow.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
getNextRow.Body.MaxStack = (ushort)Math.Max((int)getNextRow.Body.MaxStack, 8);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + getNextRow.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x005B",
|
|
LoggerMethod = "LogBuffer",
|
|
LoggedPointer = "arg.2 EventQueryResultRow*",
|
|
CapturedLength = 2048
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentStartTagQuery(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-starttagquery", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef startTagQuery = FindMethod(module, 0x06004A15);
|
|
MethodDef getData = FindMethod(module, 0x0600100D);
|
|
MethodDef getLength = FindMethod(module, 0x0600100C);
|
|
if (!startTagQuery.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CRetrievalConnectionWCF.StartTagQuery has no method body.");
|
|
}
|
|
|
|
Local streamVariable = startTagQuery.Body.Variables.First(variable => variable.Index == 61);
|
|
Instruction insertAfter = startTagQuery.Body.Instructions.First(instruction => instruction.Offset == 0x02B5);
|
|
int insertIndex = startTagQuery.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
MemberRefUser logByteArray = CreateLogByteArrayRef(module);
|
|
MemberRefUser logString = CreateLogStringRef(module);
|
|
|
|
Local handleStringVariable = startTagQuery.Body.Variables.First(variable => variable.Index == 38);
|
|
Instruction handleInsertAfter = startTagQuery.Body.Instructions.First(instruction => instruction.Offset == 0x022F);
|
|
int handleInsertIndex = startTagQuery.Body.Instructions.IndexOf(handleInsertAfter) + 1;
|
|
Instruction[] handleInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "StartTagQuery.Handle"),
|
|
Instruction.Create(OpCodes.Ldloc_S, handleStringVariable),
|
|
Instruction.Create(OpCodes.Call, logString)
|
|
];
|
|
|
|
foreach (Instruction instruction in handleInjected.Reverse())
|
|
{
|
|
startTagQuery.Body.Instructions.Insert(handleInsertIndex, instruction);
|
|
}
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "StartTagQuery.Request"),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getData),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getLength),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
startTagQuery.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
Local responseVariable = startTagQuery.Body.Variables.First(variable => variable.Index == 47);
|
|
Instruction responseInsertAfter = startTagQuery.Body.Instructions.First(instruction => instruction.Offset == 0x0311);
|
|
int responseInsertIndex = startTagQuery.Body.Instructions.IndexOf(responseInsertAfter) + 1;
|
|
Instruction[] responseInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "StartTagQuery.Response"),
|
|
Instruction.Create(OpCodes.Ldloc_S, responseVariable),
|
|
Instruction.Create(OpCodes.Call, logByteArray)
|
|
];
|
|
|
|
foreach (Instruction instruction in responseInjected.Reverse())
|
|
{
|
|
startTagQuery.Body.Instructions.Insert(responseInsertIndex, instruction);
|
|
}
|
|
|
|
startTagQuery.Body.MaxStack = (ushort)Math.Max((int)startTagQuery.Body.MaxStack, 8);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + startTagQuery.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x02B5",
|
|
HandleInsertedAfterOffset = "0x022F",
|
|
ResponseInsertedAfterOffset = "0x0311",
|
|
LoggerMethod = "LogBuffer",
|
|
HandleLoggerMethod = "LogString",
|
|
ResponseLoggerMethod = "LogByteArray",
|
|
HandleVariable = "V_38",
|
|
StreamVariable = "V_61",
|
|
ResponseVariable = "V_47",
|
|
UsesGetData = "0x" + getData.MDToken.Raw.ToString("X8"),
|
|
UsesGetLength = "0x" + getLength.MDToken.Raw.ToString("X8")
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentOpenConnection2(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-openconnection2", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef openConnection2 = FindMethod(module, 0x06004057);
|
|
if (!openConnection2.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.OpenConnection2 has no method body.");
|
|
}
|
|
|
|
MemberRefUser logByteArray = CreateLogByteArrayRef(module);
|
|
|
|
Local requestVariable = openConnection2.Body.Variables.First(variable => variable.Index == 38);
|
|
Instruction requestInsertAfter = openConnection2.Body.Instructions.First(instruction => instruction.Offset == 0x0375);
|
|
int requestInsertIndex = openConnection2.Body.Instructions.IndexOf(requestInsertAfter) + 1;
|
|
Instruction[] requestInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection2.Request"),
|
|
Instruction.Create(OpCodes.Ldloc_S, requestVariable),
|
|
Instruction.Create(OpCodes.Call, logByteArray)
|
|
];
|
|
|
|
foreach (Instruction instruction in requestInjected.Reverse())
|
|
{
|
|
openConnection2.Body.Instructions.Insert(requestInsertIndex, instruction);
|
|
}
|
|
|
|
Local responseVariable = openConnection2.Body.Variables.First(variable => variable.Index == 20);
|
|
Local errorVariable = openConnection2.Body.Variables.First(variable => variable.Index == 39);
|
|
Instruction responseInsertAfter = openConnection2.Body.Instructions.First(instruction => instruction.Offset == 0x03AB);
|
|
int responseInsertIndex = openConnection2.Body.Instructions.IndexOf(responseInsertAfter) + 1;
|
|
Instruction[] responseInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection2.Response"),
|
|
Instruction.Create(OpCodes.Ldloc_S, responseVariable),
|
|
Instruction.Create(OpCodes.Call, logByteArray),
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection2.Error"),
|
|
Instruction.Create(OpCodes.Ldloc_S, errorVariable),
|
|
Instruction.Create(OpCodes.Call, logByteArray)
|
|
];
|
|
|
|
foreach (Instruction instruction in responseInjected.Reverse())
|
|
{
|
|
openConnection2.Body.Instructions.Insert(responseInsertIndex, instruction);
|
|
}
|
|
|
|
openConnection2.Body.MaxStack = (ushort)Math.Max((int)openConnection2.Body.MaxStack, 8);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + openConnection2.MDToken.Raw.ToString("X8"),
|
|
RequestInsertedAfterOffset = "0x0375",
|
|
ResponseInsertedAfterOffset = "0x03AB",
|
|
LoggerMethod = "LogByteArray",
|
|
RequestVariable = "V_38",
|
|
ResponseVariable = "V_20",
|
|
ErrorVariable = "V_39"
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentOpenConnection3(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-openconnection3", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef openConnection3 = FindMethod(module, 0x06004059);
|
|
if (!openConnection3.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.OpenConnection3 has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
MemberRefUser logByteArray = CreateLogByteArrayRef(module);
|
|
|
|
Local success = openConnection3.Body.Variables.First(variable => variable.Index == 0);
|
|
Local request = openConnection3.Body.Variables.First(variable => variable.Index == 51);
|
|
Local response = openConnection3.Body.Variables.First(variable => variable.Index == 26);
|
|
Local error = openConnection3.Body.Variables.First(variable => variable.Index == 52);
|
|
|
|
Instruction responseInsertAfter = openConnection3.Body.Instructions.First(instruction => instruction.Offset == 0x039D);
|
|
int responseInsertIndex = openConnection3.Body.Instructions.IndexOf(responseInsertAfter) + 1;
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection3.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, success),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection3.Request"),
|
|
Instruction.Create(OpCodes.Ldloc_S, request),
|
|
Instruction.Create(OpCodes.Call, logByteArray),
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection3.Response"),
|
|
Instruction.Create(OpCodes.Ldloc_S, response),
|
|
Instruction.Create(OpCodes.Call, logByteArray),
|
|
Instruction.Create(OpCodes.Ldstr, "OpenConnection3.Error"),
|
|
Instruction.Create(OpCodes.Ldloc_S, error),
|
|
Instruction.Create(OpCodes.Call, logByteArray)
|
|
];
|
|
|
|
InsertInstructions(openConnection3, responseInsertIndex, injected);
|
|
openConnection3.Body.MaxStack = (ushort)Math.Max((int)openConnection3.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + openConnection3.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x039D",
|
|
LoggerMethods = new[] { "LogUInt32", "LogByteArray" },
|
|
CapturedValues = new[] { "OpenConnection3 success/request/response/error byte arrays" },
|
|
RequestVariable = "V_51",
|
|
ResponseVariable = "V_26",
|
|
ErrorVariable = "V_52"
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentCClientInfoContext(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-cclientinfo-context", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef setContextKey = FindMethod(module, 0x0600386E);
|
|
MethodDef initializeClientKey = FindMethod(module, 0x06003868);
|
|
MethodDef serializeOpenConnectionInParams4 = FindMethod(module, 0x06004003);
|
|
MethodDef openConnection3 = FindMethod(module, 0x06004059);
|
|
MethodDef configureOpenConnection = FindMethod(module, 0x0600388C);
|
|
if (!setContextKey.HasBody || !initializeClientKey.HasBody || !serializeOpenConnectionInParams4.HasBody || !openConnection3.HasBody || !configureOpenConnection.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CClientInfo context/key serializer/open/configure methods must have method bodies.");
|
|
}
|
|
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
|
|
Instruction[] setContextKeyInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientInfo.SetContextKey.Argument"),
|
|
Instruction.Create(OpCodes.Ldarg_1),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(setContextKey, 0, setContextKeyInjected);
|
|
|
|
Instruction[] serializeInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientInfo.SerializeOpenConnectionInParams4.ContextField"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 1240),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientInfo.SerializeOpenConnectionInParams4.ClientKeyField"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 1208),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientInfo.SerializeOpenConnectionInParams4.SelectorField"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 1608),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(serializeOpenConnectionInParams4, 0, serializeInjected);
|
|
|
|
Instruction[] openConnection3Injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CHistoryConnectionWCF.OpenConnection3.ThisMemory"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 4096),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CHistoryConnectionWCF.OpenConnection3.ServerConnectionMemory"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 4096),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CHistoryConnectionWCF.OpenConnection3.ClientInfoMemory"),
|
|
Instruction.Create(OpCodes.Ldarg_1),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2048),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(openConnection3, 0, openConnection3Injected);
|
|
|
|
Instruction configureAuthCall = configureOpenConnection.Body.Instructions.First(instruction => instruction.Offset == 0x038C);
|
|
int configureBeforeAuthIndex = configureOpenConnection.Body.Instructions.IndexOf(configureAuthCall);
|
|
Instruction[] configureBeforeAuthInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextBeforeAuthenticate"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2112),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.ContextBeforeAuthenticate"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2176),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(configureOpenConnection, configureBeforeAuthIndex, configureBeforeAuthInjected);
|
|
|
|
Instruction configureCopyBlock = configureOpenConnection.Body.Instructions.First(instruction => instruction.Offset == 0x03FD);
|
|
int configureAfterCopyIndex = configureOpenConnection.Body.Instructions.IndexOf(configureCopyBlock) + 1;
|
|
Instruction[] configureAfterCopyInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextAfterAuthenticate"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2112),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextAfterAuthenticate.Pointer8Target"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2120),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextAfterAuthenticate.Pointer16Target"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2128),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextAfterAuthenticate.Pointer16Target.Pointer0Target"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2128),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextAfterAuthenticate.Pointer16Target.Pointer8Target"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2128),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Ldc_I4_8),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CClientContextAfterAuthenticate.Pointer16Target.Pointer64Target"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2128),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Ldc_I4, 64),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 256),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.ContextAfterAuthenticate"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 2176),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "CClientBase.ConfigureOpenConnection.CopiedContextField"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 1480),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(configureOpenConnection, configureAfterCopyIndex, configureAfterCopyInjected);
|
|
|
|
Instruction initializeInsertAfter = initializeClientKey.Body.Instructions.First(instruction => instruction.Offset == 0x0017);
|
|
int initializeInsertIndex = initializeClientKey.Body.Instructions.IndexOf(initializeInsertAfter) + 1;
|
|
Instruction[] initializeInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "CClientInfo.InitializeClientKey.Generated"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 1208),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)16),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(initializeClientKey, initializeInsertIndex, initializeInjected);
|
|
|
|
setContextKey.Body.MaxStack = (ushort)Math.Max((int)setContextKey.Body.MaxStack, 8);
|
|
initializeClientKey.Body.MaxStack = (ushort)Math.Max((int)initializeClientKey.Body.MaxStack, 8);
|
|
serializeOpenConnectionInParams4.Body.MaxStack = (ushort)Math.Max((int)serializeOpenConnectionInParams4.Body.MaxStack, 8);
|
|
openConnection3.Body.MaxStack = (ushort)Math.Max((int)openConnection3.Body.MaxStack, 8);
|
|
configureOpenConnection.Body.MaxStack = (ushort)Math.Max((int)configureOpenConnection.Body.MaxStack, 8);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethods = new[]
|
|
{
|
|
"0x" + setContextKey.MDToken.Raw.ToString("X8"),
|
|
"0x" + initializeClientKey.MDToken.Raw.ToString("X8"),
|
|
"0x" + serializeOpenConnectionInParams4.MDToken.Raw.ToString("X8"),
|
|
"0x" + openConnection3.MDToken.Raw.ToString("X8"),
|
|
"0x" + configureOpenConnection.MDToken.Raw.ToString("X8")
|
|
},
|
|
LoggerMethod = "LogBuffer",
|
|
CapturedValues = new[]
|
|
{
|
|
"CClientInfo.SetContextKey argument GUID bytes",
|
|
"CClientInfo.InitializeClientKey generated GUID bytes",
|
|
"CClientInfo.SerializeOpenConnectionInParams4 context/client-key/selector fields",
|
|
"CHistoryConnectionWCF.OpenConnection3 connection/client-info memory windows",
|
|
"CClientBase.ConfigureOpenConnection context field before/after AuthenticateClient",
|
|
"CClientBase.ConfigureOpenConnection embedded CClientContext 256-byte window before/after AuthenticateClient",
|
|
"CClientBase.ConfigureOpenConnection post-auth CClientContext readable pointer targets at offsets 8/16",
|
|
"CClientBase.ConfigureOpenConnection post-auth CClientContext +16 nested pointer targets at offsets 0/8/64"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentWcfAuthContext(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-wcf-auth-context", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef validateClient = FindMethod(module, 0x06004044);
|
|
MethodDef getClientKey = FindMethod(module, 0x06004041);
|
|
MethodDef validateClientCredential = FindMethod(module, 0x06004062);
|
|
MethodDef configureTcpProxy = FindMethod(module, 0x06004095);
|
|
MethodDef configurePipeProxy = FindMethod(module, 0x06004096);
|
|
MethodDef historyInitializeProxy = FindMethod(module, 0x06004079);
|
|
MethodDef initialize = FindMethod(module, 0x0600403C);
|
|
MethodDef setHistoryWcfProxy = FindMethod(module, 0x06004022);
|
|
MethodDef historyConnectionCtor = FindMethod(module, 0x06004036);
|
|
MethodDef createHistoryConnectionWcf = FindMethod(module, 0x0600402C);
|
|
MethodDef getInterfaceVersion = FindMethod(module, 0x0600403E);
|
|
if (!validateClient.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.ValidateClient has no method body.");
|
|
}
|
|
if (!getClientKey.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.GetClientKey has no method body.");
|
|
}
|
|
if (!validateClientCredential.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.ValidateClientCredential has no method body.");
|
|
}
|
|
if (!configureTcpProxy.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CWcfConfig.ConfigureTcpProxy has no method body.");
|
|
}
|
|
if (!configurePipeProxy.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CWcfConfig.ConfigurePipeProxy has no method body.");
|
|
}
|
|
if (!historyInitializeProxy.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.InitializeProxy<IHistoryServiceContract2> has no method body.");
|
|
}
|
|
if (!initialize.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.Initialize has no method body.");
|
|
}
|
|
if (!setHistoryWcfProxy.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.SetHistoryWCFProxy has no method body.");
|
|
}
|
|
if (!historyConnectionCtor.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF constructor has no method body.");
|
|
}
|
|
if (!createHistoryConnectionWcf.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CClientCommon.CreateHistoryConnectionWCF has no method body.");
|
|
}
|
|
if (!getInterfaceVersion.HasBody)
|
|
{
|
|
throw new InvalidOperationException("CHistoryConnectionWCF.GetInterfaceVersion has no method body.");
|
|
}
|
|
|
|
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
|
MemberRefUser logStringSummary = CreateLogStringSummaryRef(module);
|
|
MemberRefUser logByteArraySummary = CreateLogByteArraySummaryRef(module);
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
|
|
Local validateSuccess = validateClient.Body.Variables.First(variable => variable.Index == 0);
|
|
Local validateUserName = validateClient.Body.Variables.First(variable => variable.Index == 21);
|
|
Local validateProcessName = validateClient.Body.Variables.First(variable => variable.Index == 22);
|
|
Local validateHostName = validateClient.Body.Variables.First(variable => variable.Index == 23);
|
|
Local validateErrorBytes = validateClient.Body.Variables.First(variable => variable.Index == 42);
|
|
Local mdasHandle = validateClient.Body.Variables.First(variable => variable.Index == 45);
|
|
dnlib.DotNet.Parameter validateProcessIdParameter = (dnlib.DotNet.Parameter)validateClient.Body.Instructions.First(instruction => instruction.Offset == 0x02D1).Operand;
|
|
dnlib.DotNet.Parameter validateServerStatusParameter = (dnlib.DotNet.Parameter)validateClient.Body.Instructions.First(instruction => instruction.Offset == 0x02D7).Operand;
|
|
|
|
Instruction validateClient2Call = validateClient.Body.Instructions.First(instruction => instruction.Offset == 0x02DB);
|
|
int validateClient2BeforeIndex = validateClient.Body.Instructions.IndexOf(validateClient2Call);
|
|
Instruction[] validateClient2Before =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.HandleString"),
|
|
Instruction.Create(OpCodes.Ldloc, validateClient.Body.Variables.First(variable => variable.Index == 41)),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.HostName"),
|
|
Instruction.Create(OpCodes.Ldloc, validateHostName),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.ProcessName"),
|
|
Instruction.Create(OpCodes.Ldloc, validateProcessName),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.UserName"),
|
|
Instruction.Create(OpCodes.Ldloc, validateUserName),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.ProcessId"),
|
|
Instruction.Create(OpCodes.Ldarg_S, validateProcessIdParameter),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClient, validateClient2BeforeIndex, validateClient2Before);
|
|
|
|
Instruction validateClient2Store = validateClient.Body.Instructions.First(instruction => instruction.Offset == 0x02E0);
|
|
int validateClient2AfterIndex = validateClient.Body.Instructions.IndexOf(validateClient2Store) + 1;
|
|
Instruction[] validateClient2After =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, validateSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.ErrorBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, validateErrorBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient2.ServerStatus"),
|
|
Instruction.Create(OpCodes.Ldarg_S, validateServerStatusParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClient, validateClient2AfterIndex, validateClient2After);
|
|
|
|
Instruction getMdasHandleStore = validateClient.Body.Instructions.First(instruction => instruction.Offset == 0x031D);
|
|
int getMdasHandleAfterIndex = validateClient.Body.Instructions.IndexOf(getMdasHandleStore) + 1;
|
|
Instruction[] getMdasHandleAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient.GetMdasHandle.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, validateSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient.GetMdasHandle.MdasHandle"),
|
|
Instruction.Create(OpCodes.Ldloc, mdasHandle),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClient, getMdasHandleAfterIndex, getMdasHandleAfter);
|
|
|
|
Instruction legacyValidateStore = validateClient.Body.Instructions.First(instruction => instruction.Offset == 0x035F);
|
|
int legacyValidateAfterIndex = validateClient.Body.Instructions.IndexOf(legacyValidateStore) + 1;
|
|
Instruction[] legacyValidateAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient.Legacy.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, validateSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient.Legacy.MdasHandle"),
|
|
Instruction.Create(OpCodes.Ldloc, mdasHandle),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClient.Legacy.ServerStatus"),
|
|
Instruction.Create(OpCodes.Ldarg_S, validateServerStatusParameter),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClient, legacyValidateAfterIndex, legacyValidateAfter);
|
|
|
|
Local exchangeSuccess = getClientKey.Body.Variables.First(variable => variable.Index == 0);
|
|
Local sharedSecretBytes = getClientKey.Body.Variables.First(variable => variable.Index == 22);
|
|
Local exchangeErrorBytes = getClientKey.Body.Variables.First(variable => variable.Index == 40);
|
|
Local exchangeOutputBytes = getClientKey.Body.Variables.First(variable => variable.Index == 41);
|
|
Local exchangeHandleString = getClientKey.Body.Variables.First(variable => variable.Index == 42);
|
|
Local publicKeyBytes = getClientKey.Body.Variables.First(variable => variable.Index == 43);
|
|
|
|
Instruction exchangeCall = getClientKey.Body.Instructions.First(instruction => instruction.Offset == 0x02B3);
|
|
int exchangeBeforeIndex = getClientKey.Body.Instructions.IndexOf(exchangeCall);
|
|
Instruction[] exchangeBefore =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ExchangeKey.HandleString"),
|
|
Instruction.Create(OpCodes.Ldloc, exchangeHandleString),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ExchangeKey.PublicKey"),
|
|
Instruction.Create(OpCodes.Ldloc, publicKeyBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary)
|
|
];
|
|
InsertInstructions(getClientKey, exchangeBeforeIndex, exchangeBefore);
|
|
|
|
Instruction exchangeStore = getClientKey.Body.Instructions.First(instruction => instruction.Offset == 0x02B8);
|
|
int exchangeAfterIndex = getClientKey.Body.Instructions.IndexOf(exchangeStore) + 1;
|
|
Instruction[] exchangeAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ExchangeKey.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, exchangeSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ExchangeKey.OutputBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, exchangeOutputBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ExchangeKey.ErrorBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, exchangeErrorBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary)
|
|
];
|
|
InsertInstructions(getClientKey, exchangeAfterIndex, exchangeAfter);
|
|
|
|
Instruction sharedSecretStore = getClientKey.Body.Instructions.First(instruction => instruction.Offset == 0x02E8);
|
|
int sharedSecretAfterIndex = getClientKey.Body.Instructions.IndexOf(sharedSecretStore) + 1;
|
|
Instruction[] sharedSecretAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ExchangeKey.SharedSecret"),
|
|
Instruction.Create(OpCodes.Ldloc, sharedSecretBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary)
|
|
];
|
|
InsertInstructions(getClientKey, sharedSecretAfterIndex, sharedSecretAfter);
|
|
|
|
Local valClSuccess = validateClientCredential.Body.Variables.First(variable => variable.Index == 0);
|
|
Local valClOutputBytes = validateClientCredential.Body.Variables.First(variable => variable.Index == 5);
|
|
Local valClInputBytes = validateClientCredential.Body.Variables.First(variable => variable.Index == 23);
|
|
Local valClErrorBytes = validateClientCredential.Body.Variables.First(variable => variable.Index == 41);
|
|
Local valClHandleString = validateClientCredential.Body.Variables.First(variable => variable.Index == 42);
|
|
Local valClExistingProxyFaulted = validateClientCredential.Body.Variables.First(variable => variable.Index == 55);
|
|
Local valClReconnectRequired = validateClientCredential.Body.Variables.First(variable => variable.Index == 53);
|
|
dnlib.DotNet.Parameter valClErrorParameter = (dnlib.DotNet.Parameter)validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x006F).Operand;
|
|
|
|
Instruction valClStartStore = validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x0076);
|
|
int valClStartAfterIndex = validateClientCredential.Body.Instructions.IndexOf(valClStartStore) + 1;
|
|
Instruction[] valClStartAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.OperationStart2.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, valClSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.OperationStart2.ErrorCode"),
|
|
Instruction.Create(OpCodes.Ldarg_S, valClErrorParameter),
|
|
Instruction.Create(OpCodes.Ldc_I4_8),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.OperationStart2.ErrorType"),
|
|
Instruction.Create(OpCodes.Ldarg_S, valClErrorParameter),
|
|
Instruction.Create(OpCodes.Ldc_I4_S, (sbyte)12),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.ThisWindow.AfterOperationStart2"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 736),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(validateClientCredential, valClStartAfterIndex, valClStartAfter);
|
|
|
|
Instruction[] valClEntryState =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.ThisWindow.Entry"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 736),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.HistoryProxySlotTarget"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 608),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 128),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.BindingSlotTarget"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 616),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 128),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.WindowsIdentitySlotTarget"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 640),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 128),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.HistoryProxySlotNonZero"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 608),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I8),
|
|
Instruction.Create(OpCodes.Ldc_I4_0),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Cgt_Un),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.HistoryProxyCountField"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 648),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.HistoryProxyReadyFlag"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 669),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_U1),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClientCredential, 0, valClEntryState);
|
|
|
|
Instruction valClExistingProxyStore = validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x00BE);
|
|
int valClExistingProxyAfterIndex = validateClientCredential.Body.Instructions.IndexOf(valClExistingProxyStore) + 1;
|
|
Instruction[] valClExistingProxyAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.ExistingProxyFaulted"),
|
|
Instruction.Create(OpCodes.Ldloc, valClExistingProxyFaulted),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClientCredential, valClExistingProxyAfterIndex, valClExistingProxyAfter);
|
|
|
|
Instruction valClReconnectStore = validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x00F6);
|
|
int valClReconnectAfterIndex = validateClientCredential.Body.Instructions.IndexOf(valClReconnectStore) + 1;
|
|
Instruction[] valClReconnectAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.ReconnectRequiredFlag"),
|
|
Instruction.Create(OpCodes.Ldloc, valClReconnectRequired),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(validateClientCredential, valClReconnectAfterIndex, valClReconnectAfter);
|
|
|
|
Instruction valClInitializeProxyCall = validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x0197);
|
|
int valClInitializeProxyAfterIndex = validateClientCredential.Body.Instructions.IndexOf(valClInitializeProxyCall) + 1;
|
|
Instruction[] valClInitializeProxyAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Stloc, valClSuccess),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.InitializeProxy.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, valClSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldloc, valClSuccess)
|
|
];
|
|
InsertInstructions(validateClientCredential, valClInitializeProxyAfterIndex, valClInitializeProxyAfter);
|
|
|
|
Instruction valClCall = validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x030B);
|
|
int valClBeforeIndex = validateClientCredential.Body.Instructions.IndexOf(valClCall);
|
|
Instruction[] valClBefore =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.HandleString"),
|
|
Instruction.Create(OpCodes.Ldloc, valClHandleString),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.InputBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, valClInputBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary)
|
|
];
|
|
InsertInstructions(validateClientCredential, valClBeforeIndex, valClBefore);
|
|
|
|
Instruction valClStore = validateClientCredential.Body.Instructions.First(instruction => instruction.Offset == 0x0310);
|
|
int valClAfterIndex = validateClientCredential.Body.Instructions.IndexOf(valClStore) + 1;
|
|
Instruction[] valClAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, valClSuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.OutputBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, valClOutputBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ValidateClientCredential.ErrorBytes"),
|
|
Instruction.Create(OpCodes.Ldloc, valClErrorBytes),
|
|
Instruction.Create(OpCodes.Call, logByteArraySummary)
|
|
];
|
|
InsertInstructions(validateClientCredential, valClAfterIndex, valClAfter);
|
|
|
|
Local configureEndpoint = configureTcpProxy.Body.Variables.First(variable => variable.Index == 0);
|
|
dnlib.DotNet.Parameter configureEncryptionTypeParameter = (dnlib.DotNet.Parameter)configureTcpProxy.Body.Instructions.First(instruction => instruction.Offset == 0x0037).Operand;
|
|
dnlib.DotNet.Parameter configureIntegratedSecurityParameter = (dnlib.DotNet.Parameter)configureTcpProxy.Body.Instructions.First(instruction => instruction.Offset == 0x0048).Operand;
|
|
Instruction[] configureTcpEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigureTcpProxy.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(configureTcpProxy, 0, configureTcpEntryInjected);
|
|
|
|
Instruction configureEndpointFinal = configureTcpProxy.Body.Instructions.First(instruction => instruction.Offset == 0x00CE);
|
|
int configureEndpointIndex = configureTcpProxy.Body.Instructions.IndexOf(configureEndpointFinal) + 1;
|
|
Instruction[] configureEndpointInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigureTcpProxy.Endpoint"),
|
|
Instruction.Create(OpCodes.Ldloc, configureEndpoint),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigureTcpProxy.EncryptionType"),
|
|
Instruction.Create(OpCodes.Ldarg_S, configureEncryptionTypeParameter),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigureTcpProxy.IntegratedSecurity"),
|
|
Instruction.Create(OpCodes.Ldarg_S, configureIntegratedSecurityParameter),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(configureTcpProxy, configureEndpointIndex, configureEndpointInjected);
|
|
|
|
Local configurePipeEndpoint = configurePipeProxy.Body.Variables.First(variable => variable.Index == 0);
|
|
dnlib.DotNet.Parameter configurePipeCompressionParameter = (dnlib.DotNet.Parameter)configurePipeProxy.Body.Instructions.First(instruction => instruction.Offset == 0x000E).Operand;
|
|
Instruction[] configurePipeEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigurePipeProxy.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(configurePipeProxy, 0, configurePipeEntryInjected);
|
|
|
|
Instruction configurePipeEndpointFinal = configurePipeProxy.Body.Instructions.First(instruction => instruction.Offset == 0x002F);
|
|
int configurePipeEndpointIndex = configurePipeProxy.Body.Instructions.IndexOf(configurePipeEndpointFinal) + 1;
|
|
Instruction[] configurePipeEndpointInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigurePipeProxy.Endpoint"),
|
|
Instruction.Create(OpCodes.Ldloc, configurePipeEndpoint),
|
|
Instruction.Create(OpCodes.Call, logStringSummary),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.ConfigurePipeProxy.Compression"),
|
|
Instruction.Create(OpCodes.Ldarg_S, configurePipeCompressionParameter),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(configurePipeProxy, configurePipeEndpointIndex, configurePipeEndpointInjected);
|
|
|
|
Instruction[] historyInitializeProxyEntry =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.InitializeProxy.History.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.InitializeProxy.History.ConnectionMode"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 216),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(historyInitializeProxy, 0, historyInitializeProxyEntry);
|
|
|
|
Instruction historyInitializeProxyPipeCall = historyInitializeProxy.Body.Instructions.First(instruction => instruction.Offset == 0x0098);
|
|
int historyInitializeProxyPipeIndex = historyInitializeProxy.Body.Instructions.IndexOf(historyInitializeProxyPipeCall);
|
|
Instruction[] historyInitializeProxyBeforePipe =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.InitializeProxy.History.BeforePipeConfigure"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(historyInitializeProxy, historyInitializeProxyPipeIndex, historyInitializeProxyBeforePipe);
|
|
|
|
Instruction historyInitializeProxyTcpCall = historyInitializeProxy.Body.Instructions.First(instruction => instruction.Offset == 0x038E);
|
|
int historyInitializeProxyTcpIndex = historyInitializeProxy.Body.Instructions.IndexOf(historyInitializeProxyTcpCall);
|
|
Instruction[] historyInitializeProxyBeforeTcp =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.InitializeProxy.History.BeforeTcpConfigure"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(historyInitializeProxy, historyInitializeProxyTcpIndex, historyInitializeProxyBeforeTcp);
|
|
|
|
Local initializeHistoryProxySuccess = new(module.CorLibTypes.Boolean);
|
|
Local initializeTransactionProxySuccess = new(module.CorLibTypes.Boolean);
|
|
initialize.Body.Variables.Add(initializeHistoryProxySuccess);
|
|
initialize.Body.Variables.Add(initializeTransactionProxySuccess);
|
|
Instruction[] initializeEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.Initialize.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(initialize, 0, initializeEntryInjected);
|
|
|
|
Instruction initializeHistoryProxyCall = initialize.Body.Instructions.First(instruction => instruction.Offset == 0x00AF);
|
|
int initializeHistoryProxyAfterIndex = initialize.Body.Instructions.IndexOf(initializeHistoryProxyCall) + 1;
|
|
Instruction[] initializeHistoryProxyAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Stloc, initializeHistoryProxySuccess),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.Initialize.HistoryInitializeProxy.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, initializeHistoryProxySuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldloc, initializeHistoryProxySuccess)
|
|
];
|
|
InsertInstructions(initialize, initializeHistoryProxyAfterIndex, initializeHistoryProxyAfter);
|
|
|
|
Instruction initializeHistorySetCall = initialize.Body.Instructions.First(instruction => instruction.Offset == 0x00CA);
|
|
int initializeHistorySetAfterIndex = initialize.Body.Instructions.IndexOf(initializeHistorySetCall) + 1;
|
|
Instruction[] initializeHistorySetAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.Initialize.HistoryProxyCountField"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 648),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_I4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.Initialize.HistoryProxyReadyFlag"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 669),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_U1),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(initialize, initializeHistorySetAfterIndex, initializeHistorySetAfter);
|
|
|
|
Instruction initializeTransactionProxyCall = initialize.Body.Instructions.First(instruction => instruction.Offset == 0x0114);
|
|
int initializeTransactionProxyAfterIndex = initialize.Body.Instructions.IndexOf(initializeTransactionProxyCall) + 1;
|
|
Instruction[] initializeTransactionProxyAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Stloc, initializeTransactionProxySuccess),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.Initialize.TransactionInitializeProxy.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, initializeTransactionProxySuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldloc, initializeTransactionProxySuccess)
|
|
];
|
|
InsertInstructions(initialize, initializeTransactionProxyAfterIndex, initializeTransactionProxyAfter);
|
|
|
|
Instruction[] setHistoryEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.SetHistoryWCFProxy.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(setHistoryWcfProxy, 0, setHistoryEntryInjected);
|
|
|
|
Instruction[] ctorEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.Ctor.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(historyConnectionCtor, 0, ctorEntryInjected);
|
|
|
|
Instruction[] createHistoryEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.CreateHistoryConnectionWCF.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32)
|
|
];
|
|
InsertInstructions(createHistoryConnectionWcf, 0, createHistoryEntryInjected);
|
|
|
|
Local getVersionFirstInitializeProxySuccess = new(module.CorLibTypes.Boolean);
|
|
Local getVersionSecondInitializeProxySuccess = new(module.CorLibTypes.Boolean);
|
|
getInterfaceVersion.Body.Variables.Add(getVersionFirstInitializeProxySuccess);
|
|
getInterfaceVersion.Body.Variables.Add(getVersionSecondInitializeProxySuccess);
|
|
|
|
Instruction[] getVersionEntryInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.Entry"),
|
|
Instruction.Create(OpCodes.Ldc_I4_1),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.ThisWindow.Entry"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 736),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(getInterfaceVersion, 0, getVersionEntryInjected);
|
|
|
|
Instruction getVersionFirstInitializeProxyCall = getInterfaceVersion.Body.Instructions.First(instruction => instruction.Offset == 0x0200);
|
|
int getVersionFirstInitializeProxyAfterIndex = getInterfaceVersion.Body.Instructions.IndexOf(getVersionFirstInitializeProxyCall) + 1;
|
|
Instruction[] getVersionFirstInitializeProxyAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Stloc, getVersionFirstInitializeProxySuccess),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.FirstInitializeProxy.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, getVersionFirstInitializeProxySuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldloc, getVersionFirstInitializeProxySuccess)
|
|
];
|
|
InsertInstructions(getInterfaceVersion, getVersionFirstInitializeProxyAfterIndex, getVersionFirstInitializeProxyAfter);
|
|
|
|
Instruction getVersionFirstSetManagedPtrCall = getInterfaceVersion.Body.Instructions.First(instruction => instruction.Offset == 0x0259);
|
|
int getVersionFirstSetManagedPtrAfterIndex = getInterfaceVersion.Body.Instructions.IndexOf(getVersionFirstSetManagedPtrCall) + 1;
|
|
Instruction[] getVersionFirstSetManagedPtrAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.FirstSetManagedPtr.ReadyFlag"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 669),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_U1),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.ThisWindow.AfterFirstSetManagedPtr"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 736),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(getInterfaceVersion, getVersionFirstSetManagedPtrAfterIndex, getVersionFirstSetManagedPtrAfter);
|
|
|
|
Instruction getVersionSecondInitializeProxyCall = getInterfaceVersion.Body.Instructions.First(instruction => instruction.Offset == 0x044B);
|
|
int getVersionSecondInitializeProxyAfterIndex = getInterfaceVersion.Body.Instructions.IndexOf(getVersionSecondInitializeProxyCall) + 1;
|
|
Instruction[] getVersionSecondInitializeProxyAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Stloc, getVersionSecondInitializeProxySuccess),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.SecondInitializeProxy.Success"),
|
|
Instruction.Create(OpCodes.Ldloc, getVersionSecondInitializeProxySuccess),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldloc, getVersionSecondInitializeProxySuccess)
|
|
];
|
|
InsertInstructions(getInterfaceVersion, getVersionSecondInitializeProxyAfterIndex, getVersionSecondInitializeProxyAfter);
|
|
|
|
Instruction getVersionSecondSetManagedPtrCall = getInterfaceVersion.Body.Instructions.First(instruction => instruction.Offset == 0x04A6);
|
|
int getVersionSecondSetManagedPtrAfterIndex = getInterfaceVersion.Body.Instructions.IndexOf(getVersionSecondSetManagedPtrCall) + 1;
|
|
Instruction[] getVersionSecondSetManagedPtrAfter =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.SecondSetManagedPtr.ReadyFlag"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Ldc_I4, 669),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Add),
|
|
Instruction.Create(OpCodes.Ldind_U1),
|
|
Instruction.Create(OpCodes.Conv_U4),
|
|
Instruction.Create(OpCodes.Call, logUInt32),
|
|
Instruction.Create(OpCodes.Ldstr, "Wcf.GetInterfaceVersion.ThisWindow.AfterSecondSetManagedPtr"),
|
|
Instruction.Create(OpCodes.Ldarg_0),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 736),
|
|
Instruction.Create(OpCodes.Conv_U8),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
InsertInstructions(getInterfaceVersion, getVersionSecondSetManagedPtrAfterIndex, getVersionSecondSetManagedPtrAfter);
|
|
|
|
validateClient.Body.MaxStack = (ushort)Math.Max((int)validateClient.Body.MaxStack, 10);
|
|
getClientKey.Body.MaxStack = (ushort)Math.Max((int)getClientKey.Body.MaxStack, 10);
|
|
validateClientCredential.Body.MaxStack = (ushort)Math.Max((int)validateClientCredential.Body.MaxStack, 10);
|
|
configureTcpProxy.Body.MaxStack = (ushort)Math.Max((int)configureTcpProxy.Body.MaxStack, 10);
|
|
configurePipeProxy.Body.MaxStack = (ushort)Math.Max((int)configurePipeProxy.Body.MaxStack, 10);
|
|
historyInitializeProxy.Body.MaxStack = (ushort)Math.Max((int)historyInitializeProxy.Body.MaxStack, 10);
|
|
initialize.Body.MaxStack = (ushort)Math.Max((int)initialize.Body.MaxStack, 10);
|
|
setHistoryWcfProxy.Body.MaxStack = (ushort)Math.Max((int)setHistoryWcfProxy.Body.MaxStack, 10);
|
|
historyConnectionCtor.Body.MaxStack = (ushort)Math.Max((int)historyConnectionCtor.Body.MaxStack, 10);
|
|
createHistoryConnectionWcf.Body.MaxStack = (ushort)Math.Max((int)createHistoryConnectionWcf.Body.MaxStack, 10);
|
|
getInterfaceVersion.Body.MaxStack = (ushort)Math.Max((int)getInterfaceVersion.Body.MaxStack, 10);
|
|
validateClient.Body.SimplifyBranches();
|
|
getClientKey.Body.SimplifyBranches();
|
|
validateClientCredential.Body.SimplifyBranches();
|
|
configureTcpProxy.Body.SimplifyBranches();
|
|
configurePipeProxy.Body.SimplifyBranches();
|
|
historyInitializeProxy.Body.SimplifyBranches();
|
|
initialize.Body.SimplifyBranches();
|
|
setHistoryWcfProxy.Body.SimplifyBranches();
|
|
historyConnectionCtor.Body.SimplifyBranches();
|
|
createHistoryConnectionWcf.Body.SimplifyBranches();
|
|
getInterfaceVersion.Body.SimplifyBranches();
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethods = new[]
|
|
{
|
|
"0x" + validateClient.MDToken.Raw.ToString("X8"),
|
|
"0x" + getClientKey.MDToken.Raw.ToString("X8"),
|
|
"0x" + validateClientCredential.MDToken.Raw.ToString("X8"),
|
|
"0x" + configureTcpProxy.MDToken.Raw.ToString("X8"),
|
|
"0x" + configurePipeProxy.MDToken.Raw.ToString("X8"),
|
|
"0x" + historyInitializeProxy.MDToken.Raw.ToString("X8"),
|
|
"0x" + initialize.MDToken.Raw.ToString("X8"),
|
|
"0x" + setHistoryWcfProxy.MDToken.Raw.ToString("X8"),
|
|
"0x" + historyConnectionCtor.MDToken.Raw.ToString("X8"),
|
|
"0x" + createHistoryConnectionWcf.MDToken.Raw.ToString("X8"),
|
|
"0x" + getInterfaceVersion.MDToken.Raw.ToString("X8")
|
|
},
|
|
LoggerMethods = new[] { "LogUInt32", "LogStringSummary", "LogByteArraySummary", "LogBuffer" },
|
|
CapturedValues = new[]
|
|
{
|
|
"ValidateClient2 sanitized handle/host/process/user/process-id/success/error/server-status",
|
|
"Legacy ValidateClient branch sanitized mdas handle/server-status",
|
|
"ExchangeKey sanitized handle/public-key/output/error/shared-secret metadata",
|
|
"ValidateClientCredential sanitized handle/input/output/error metadata",
|
|
"ConfigureTcpProxy sanitized endpoint/security metadata",
|
|
"ConfigurePipeProxy sanitized endpoint metadata",
|
|
"CHistoryConnectionWCF.InitializeProxy branch markers",
|
|
"CHistoryConnectionWCF.Initialize proxy setup markers",
|
|
"CHistoryConnectionWCF.SetHistoryWCFProxy entry marker",
|
|
"CHistoryConnectionWCF constructor entry marker",
|
|
"CClientCommon.CreateHistoryConnectionWCF entry marker",
|
|
"ValidateClientCredential proxy field presence markers",
|
|
"CHistoryConnectionWCF.GetInterfaceVersion proxy setup markers",
|
|
"CHistoryConnectionWCF object memory windows around GetInterfaceVersion and ValidateClientCredential",
|
|
"ValidateClientCredential managed-pointer target windows for history proxy, binding, and Windows identity slots"
|
|
}
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int InstrumentTagQueryGetTagInfo(string[] args)
|
|
{
|
|
string sourcePath = args.Length > 1 ? args[1] : Path.Combine("current", "aahClientManaged.dll");
|
|
string outputPath = args.Length > 2 ? args[2] : Path.Combine("artifacts", "reverse-engineering", "instrumented-tagquery-gettaginfo", "aahClientManaged.dll");
|
|
|
|
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
|
MethodDef getTagInfo = FindMethod(module, 0x06006310);
|
|
MethodDef getTagInfos = FindMethod(module, 0x06002EC9);
|
|
MethodDef getData = FindMethod(module, 0x0600100D);
|
|
MethodDef getLength = FindMethod(module, 0x0600100C);
|
|
if (!getTagInfo.HasBody)
|
|
{
|
|
throw new InvalidOperationException("ArchestrA.TagQuery.GetTagInfo has no method body.");
|
|
}
|
|
if (!getTagInfos.HasBody)
|
|
{
|
|
throw new InvalidOperationException("aahClientCommon.CClientCommon.GetTagInfos has no method body.");
|
|
}
|
|
|
|
Local vectorVariable = getTagInfo.Body.Variables.First(variable => variable.Index == 7);
|
|
Instruction insertAfter = getTagInfo.Body.Instructions.First(instruction => instruction.Offset == 0x0068);
|
|
int insertIndex = getTagInfo.Body.Instructions.IndexOf(insertAfter) + 1;
|
|
MemberRefUser logStdVector = CreateLogStdVectorRef(module);
|
|
MemberRefUser logBuffer = CreateLogBufferRef(module);
|
|
|
|
Instruction[] injected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "TagQuery.GetTagInfo.CTagMetadataVector"),
|
|
Instruction.Create(OpCodes.Ldloca_S, vectorVariable),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldc_I4, 224),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Ldc_I4, 8),
|
|
Instruction.Create(OpCodes.Conv_I8),
|
|
Instruction.Create(OpCodes.Call, logStdVector)
|
|
];
|
|
|
|
foreach (Instruction instruction in injected.Reverse())
|
|
{
|
|
getTagInfo.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
|
|
getTagInfo.Body.MaxStack = (ushort)Math.Max((int)getTagInfo.Body.MaxStack, 10);
|
|
|
|
Local streamVariable = getTagInfos.Body.Variables.First(variable => variable.Index == 23);
|
|
Instruction responseInsertAfter = getTagInfos.Body.Instructions.First(instruction => instruction.Offset == 0x01B3);
|
|
int responseInsertIndex = getTagInfos.Body.Instructions.IndexOf(responseInsertAfter) + 1;
|
|
Instruction[] responseInjected =
|
|
[
|
|
Instruction.Create(OpCodes.Ldstr, "TagQuery.GetTagInfo.ResponseStream"),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getData),
|
|
Instruction.Create(OpCodes.Conv_I),
|
|
Instruction.Create(OpCodes.Ldloca_S, streamVariable),
|
|
Instruction.Create(OpCodes.Call, getLength),
|
|
Instruction.Create(OpCodes.Call, logBuffer)
|
|
];
|
|
|
|
foreach (Instruction instruction in responseInjected.Reverse())
|
|
{
|
|
getTagInfos.Body.Instructions.Insert(responseInsertIndex, instruction);
|
|
}
|
|
|
|
getTagInfos.Body.MaxStack = (ushort)Math.Max((int)getTagInfos.Body.MaxStack, 10);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
|
|
if (module.IsILOnly)
|
|
{
|
|
module.Write(outputPath);
|
|
}
|
|
else
|
|
{
|
|
module.NativeWrite(outputPath);
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Source = Path.GetFullPath(sourcePath),
|
|
Output = Path.GetFullPath(outputPath),
|
|
InstrumentedMethod = "0x" + getTagInfo.MDToken.Raw.ToString("X8"),
|
|
InstrumentedResponseMethod = "0x" + getTagInfos.MDToken.Raw.ToString("X8"),
|
|
InsertedAfterOffset = "0x0068",
|
|
ResponseInsertedAfterOffset = "0x01B3",
|
|
LoggerMethod = "LogStdVector",
|
|
ResponseLoggerMethod = "LogBuffer",
|
|
VectorVariable = "V_7",
|
|
ResponseStreamVariable = "V_23",
|
|
ElementSize = 224,
|
|
MaxElements = 8,
|
|
UsesGetData = "0x" + getData.MDToken.Raw.ToString("X8"),
|
|
UsesGetLength = "0x" + getLength.MDToken.Raw.ToString("X8")
|
|
}, CreateJsonOptions()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static MemberRefUser CreateLogBufferRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogBuffer",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
module.CorLibTypes.IntPtr,
|
|
module.CorLibTypes.UInt64),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogByteArrayRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogByteArray",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
new SZArraySig(module.CorLibTypes.Byte)),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogByteArraySegmentRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogByteArraySegment",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
new SZArraySig(module.CorLibTypes.Byte),
|
|
module.CorLibTypes.Int32,
|
|
module.CorLibTypes.Int32),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogByteArraySummaryRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogByteArraySummary",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
new SZArraySig(module.CorLibTypes.Byte)),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogStringRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogString",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
module.CorLibTypes.String),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogStringSummaryRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogStringSummary",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
module.CorLibTypes.String),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogUInt32Ref(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogUInt32",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
module.CorLibTypes.UInt32),
|
|
loggerType);
|
|
}
|
|
|
|
static MemberRefUser CreateLogStdVectorRef(ModuleDefMD module)
|
|
{
|
|
AssemblyRefUser loggerAssembly = new("AVEVA.Historian.ReverseInstrumentation")
|
|
{
|
|
Version = new Version(1, 0, 0, 0),
|
|
PublicKeyOrToken = new PublicKeyToken("aac9d3a181469d4d")
|
|
};
|
|
loggerAssembly.Attributes &= ~dnlib.DotNet.AssemblyAttributes.PublicKey;
|
|
TypeRefUser loggerType = new(module, "AVEVA.Historian.ReverseInstrumentation", "CaptureLogger", loggerAssembly);
|
|
return new MemberRefUser(
|
|
module,
|
|
"LogStdVector",
|
|
MethodSig.CreateStatic(
|
|
module.CorLibTypes.Void,
|
|
module.CorLibTypes.String,
|
|
module.CorLibTypes.IntPtr,
|
|
module.CorLibTypes.UInt64,
|
|
module.CorLibTypes.UInt64),
|
|
loggerType);
|
|
}
|
|
|
|
static void InsertInstructions(MethodDef method, int insertIndex, IReadOnlyList<Instruction> instructions)
|
|
{
|
|
foreach (Instruction instruction in instructions.Reverse())
|
|
{
|
|
method.Body.Instructions.Insert(insertIndex, instruction);
|
|
}
|
|
}
|
|
|
|
static MethodDef FindMethod(ModuleDefMD module, uint metadataToken)
|
|
{
|
|
foreach (TypeDef type in module.GetTypes())
|
|
{
|
|
foreach (MethodDef method in type.Methods)
|
|
{
|
|
if (method.MDToken.Raw == metadataToken)
|
|
{
|
|
return method;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException($"Method token not found: 0x{metadataToken:X8}");
|
|
}
|
|
|
|
static (uint? Start, uint? End) ParseInstructionWindow(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return (null, null);
|
|
}
|
|
|
|
string[] parts = value.Split(':');
|
|
if (parts.Length != 2)
|
|
{
|
|
throw new ArgumentException("Instruction window must use start:end offsets, for example 0x0180:0x0230.");
|
|
}
|
|
|
|
return (ParseOffset(parts[0]), ParseOffset(parts[1]));
|
|
}
|
|
|
|
static uint ParseOffset(string value)
|
|
{
|
|
value = value.Trim();
|
|
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return uint.Parse(value[2..], System.Globalization.NumberStyles.HexNumber);
|
|
}
|
|
|
|
return uint.Parse(value, System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
static uint ParseMetadataToken(string value)
|
|
{
|
|
value = value.Trim();
|
|
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return uint.Parse(value[2..], System.Globalization.NumberStyles.HexNumber);
|
|
}
|
|
|
|
return uint.Parse(value, System.Globalization.NumberStyles.HexNumber);
|
|
}
|
|
|
|
static bool IsInInstructionWindow(uint offset, (uint? Start, uint? End) window)
|
|
{
|
|
return (!window.Start.HasValue || offset >= window.Start.Value)
|
|
&& (!window.End.HasValue || offset <= window.End.Value);
|
|
}
|
|
|
|
static bool MethodMatches(MethodDef method, string filter)
|
|
{
|
|
if (filter.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
|
&& uint.TryParse(filter[2..], System.Globalization.NumberStyles.HexNumber, null, out uint hexToken))
|
|
{
|
|
return method.MDToken.Raw == hexToken;
|
|
}
|
|
|
|
if (uint.TryParse(filter, out uint decimalToken))
|
|
{
|
|
return method.MDToken.Raw == decimalToken;
|
|
}
|
|
|
|
string fullName = method.DeclaringType.FullName + "." + method.Name;
|
|
return fullName.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
|
|
}
|
|
|
|
static string? TryFormatDnlibToken(object? operand)
|
|
{
|
|
return operand switch
|
|
{
|
|
ITokenOperand tokenOperand => "0x" + tokenOperand.MDToken.Raw.ToString("X8"),
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
static int WriteMarker(string[] args)
|
|
{
|
|
if (args.Length < 2)
|
|
{
|
|
throw new ArgumentException("Usage: mark <scenario-name>");
|
|
}
|
|
|
|
CaptureMarker marker = new(args[1], DateTimeOffset.UtcNow, Environment.ProcessId);
|
|
Console.WriteLine(JsonSerializer.Serialize(marker, CreateJsonOptions()));
|
|
return 0;
|
|
}
|
|
|
|
static int ProbeWcf(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(5);
|
|
|
|
List<WcfProbeResult> results = [];
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.History))
|
|
{
|
|
results.Add(ProbeService<IHistoryServiceContract2>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.HistoryCertificate))
|
|
{
|
|
results.Add(ProbeService<IHistoryServiceContract2>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.HistoryIntegrated))
|
|
{
|
|
results.Add(ProbeService<IHistoryServiceContract2>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.Retrieval))
|
|
{
|
|
results.Add(ProbeService<IRetrievalServiceContract>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.Status))
|
|
{
|
|
results.Add(ProbeService<IStatusServiceContract>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.Storage))
|
|
{
|
|
results.Add(ProbeService<IStorageServiceContract>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.Transaction))
|
|
{
|
|
results.Add(ProbeService<ITransactionServiceContract>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static int ProbeWcfCertificate(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
string? dnsIdentity = args.Length > 3 ? args[3] : null;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(5);
|
|
|
|
List<WcfProbeResult> results = [];
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.HistoryCertificate))
|
|
{
|
|
results.Add(ProbeCertificateService<IHistoryServiceContract2>(host, port, path, static channel =>
|
|
{
|
|
uint returnCode = channel.GetInterfaceVersion(out uint version);
|
|
return new WcfProbeCallResult(returnCode, version);
|
|
}, timeout, dnsIdentity));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static int ProbeWcfStatus(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(5);
|
|
|
|
List<WcfStatusResult> results = [];
|
|
string? systemParameterName = args.Length > 3 ? args[3] : null;
|
|
foreach (string path in GetCandidatePaths(HistorianWcfServiceNames.Status))
|
|
{
|
|
results.Add(CallStatusService(host, port, path, timeout, systemParameterName));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static WcfStatusResult CallStatusService(string host, int port, string path, TimeSpan timeout, string? systemParameterName)
|
|
{
|
|
Uri endpoint = HistorianWcfBindingFactory.CreateEndpointAddress(host, port, path).Uri;
|
|
ChannelFactory<IStatusServiceContract2>? factory = null;
|
|
IStatusServiceContract2? channel = null;
|
|
try
|
|
{
|
|
factory = new ChannelFactory<IStatusServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
new EndpointAddress(endpoint));
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
uint interfaceReturnCode = channel.GetInterfaceVersion(out uint interfaceVersion);
|
|
|
|
uint? serverTimeReturnCode = null;
|
|
uint? systemTimeSize = null;
|
|
int? systemTimeByteCount = null;
|
|
string? systemTimeBase64 = null;
|
|
string? parsedServerTime = null;
|
|
string? serverTimeError = null;
|
|
try
|
|
{
|
|
serverTimeReturnCode = channel.GetServerTime(out byte[]? systemTime, out uint actualSystemTimeSize);
|
|
systemTimeSize = actualSystemTimeSize;
|
|
systemTimeByteCount = systemTime?.Length;
|
|
if (systemTime is not null)
|
|
{
|
|
systemTimeBase64 = Convert.ToBase64String(systemTime);
|
|
parsedServerTime = HistorianStatusProtocol.TryReadSystemTime(systemTime)?.ToString("O");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
serverTimeError = FormatException(ex);
|
|
}
|
|
|
|
uint? timeZoneReturnCode = null;
|
|
string? systemTimeZoneName = null;
|
|
string? timeZoneNameError = null;
|
|
try
|
|
{
|
|
timeZoneReturnCode = channel.GetSystemTimeZoneName(0, out systemTimeZoneName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
timeZoneNameError = FormatException(ex);
|
|
}
|
|
|
|
uint? caseSensitiveReturnCode = null;
|
|
bool? isCaseSensitive = null;
|
|
string? caseSensitiveError = null;
|
|
try
|
|
{
|
|
caseSensitiveReturnCode = channel.IsDBCaseSensitive(0, out bool actualIsCaseSensitive);
|
|
isCaseSensitive = actualIsCaseSensitive;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
caseSensitiveError = FormatException(ex);
|
|
}
|
|
|
|
bool? systemParameterSuccess = null;
|
|
string? systemParameterValue = null;
|
|
uint? systemParameterErrorSize = null;
|
|
string? systemParameterErrorBase64 = null;
|
|
byte? systemParameterNativeErrorType = null;
|
|
uint? systemParameterNativeErrorCode = null;
|
|
string? systemParameterNativeErrorName = null;
|
|
string? systemParameterError = null;
|
|
if (!string.IsNullOrWhiteSpace(systemParameterName))
|
|
{
|
|
try
|
|
{
|
|
bool success = channel.GetSystemParameter(
|
|
0,
|
|
systemParameterName,
|
|
out string actualParameterValue,
|
|
out uint actualErrorSize,
|
|
out byte[] errorBuffer);
|
|
HistorianNativeError? nativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
systemParameterSuccess = success;
|
|
systemParameterValue = actualParameterValue;
|
|
systemParameterErrorSize = actualErrorSize;
|
|
systemParameterErrorBase64 = errorBuffer is null ? null : Convert.ToBase64String(errorBuffer);
|
|
systemParameterNativeErrorType = nativeError?.Type;
|
|
systemParameterNativeErrorCode = nativeError?.Code;
|
|
systemParameterNativeErrorName = nativeError?.Name;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
systemParameterError = FormatException(ex);
|
|
}
|
|
}
|
|
|
|
return new WcfStatusResult(
|
|
path,
|
|
endpoint.ToString(),
|
|
interfaceReturnCode == 0,
|
|
interfaceReturnCode,
|
|
interfaceVersion,
|
|
serverTimeReturnCode,
|
|
systemTimeSize,
|
|
systemTimeByteCount,
|
|
systemTimeBase64,
|
|
parsedServerTime,
|
|
serverTimeError,
|
|
timeZoneReturnCode,
|
|
systemTimeZoneName,
|
|
timeZoneNameError,
|
|
caseSensitiveReturnCode,
|
|
isCaseSensitive,
|
|
caseSensitiveError,
|
|
systemParameterName,
|
|
systemParameterSuccess,
|
|
systemParameterValue,
|
|
systemParameterErrorSize,
|
|
systemParameterErrorBase64,
|
|
systemParameterNativeErrorType,
|
|
systemParameterNativeErrorCode,
|
|
systemParameterNativeErrorName,
|
|
systemParameterError,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfStatusResult(path, endpoint.ToString(), false, null, null, null, null, null, null, null, null, null, null, null, null, null, null, systemParameterName, null, null, null, null, null, null, null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
|
|
static int OpenWcfSession2(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(5);
|
|
|
|
WcfOpen2Attempt[] attempts =
|
|
[
|
|
new("legacy-v1-empty-v0-mode2", host, HistorianWcfServiceNames.History, string.Empty, [], 4, 0, 2),
|
|
new("legacy-v1-empty-v4-mode2", host, HistorianWcfServiceNames.History, string.Empty, [], 4, 4, 2),
|
|
new("legacy-v1-empty-v11-mode2", host, HistorianWcfServiceNames.History, string.Empty, [], 4, 11, 2),
|
|
new("legacy-v1-integrated-windows-v0-mode1026-hist", host, HistorianWcfServiceNames.History, string.Empty, [], 4, 0, 1026, UseWindowsTransportSecurity: true),
|
|
new("legacy-v1-integrated-windows-v0-mode1026-hist-integrated", host, HistorianWcfServiceNames.HistoryIntegrated, string.Empty, [], 4, 0, 1026, UseWindowsTransportSecurity: true),
|
|
new("legacy-v1-integrated-windows-v4-mode1026-hist-integrated", host, HistorianWcfServiceNames.HistoryIntegrated, string.Empty, [], 4, 4, 1026, UseWindowsTransportSecurity: true),
|
|
new("legacy-v1-integrated-windows-v11-mode1026-hist-integrated", host, HistorianWcfServiceNames.HistoryIntegrated, string.Empty, [], 4, 11, 1026, UseWindowsTransportSecurity: true),
|
|
new("native-v3-integrated-windows-mode1026-hist-integrated", host, HistorianWcfServiceNames.HistoryIntegrated, string.Empty, [], 4, 11, 1026, UseWindowsTransportSecurity: true, UseNativeVersion3Buffer: true),
|
|
new("native-v6-openconnection3-integrated-windows-mode1026-hist-integrated", host, HistorianWcfServiceNames.HistoryIntegrated, string.Empty, [], 4, 11, 1026, UseWindowsTransportSecurity: true, UseNativeOpenConnection3Version6Buffer: true),
|
|
new("native-v6-openconnection3-cert-mode1026-histcert", host, HistorianWcfServiceNames.HistoryCertificate, string.Empty, [], 4, 11, 1026, UseCertificateTransportSecurity: true, UseNativeOpenConnection3Version6Buffer: true),
|
|
new("legacy-v1-empty-v0-mode0", host, HistorianWcfServiceNames.History, string.Empty, [], 4, 0, 0),
|
|
new("legacy-v1-windows-user-v0-mode2", host, HistorianWcfServiceNames.History, Environment.UserName, [], 4, 0, 2),
|
|
.. CreateCredentialOpen2Attempts(host)
|
|
];
|
|
|
|
List<WcfOpen2Result> results = [];
|
|
foreach (WcfOpen2Attempt attempt in attempts)
|
|
{
|
|
results.Add(OpenHistorySession2(port, attempt, timeout));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static int OpenWcfSession(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(5);
|
|
List<WcfOpenResult> results = [];
|
|
|
|
WcfOpenAttempt[] attempts =
|
|
[
|
|
new("empty", host, string.Empty, [], 0, string.Empty, 4, 0),
|
|
new("zero-password-buffer", host, string.Empty, [0], 0, string.Empty, 4, 0),
|
|
new("wide-empty-password-buffer", host, string.Empty, new byte[1026], 0, string.Empty, 4, 0),
|
|
new("wide-version-1", host, string.Empty, new byte[1026], 0, string.Empty, 4, 1),
|
|
new("wide-version-2", host, string.Empty, new byte[1026], 0, string.Empty, 4, 2),
|
|
new("packet-u16-1", host, string.Empty, CreateVersionedBuffer(1, 2), 0, string.Empty, 4, 0),
|
|
new("packet-u16-2", host, string.Empty, CreateVersionedBuffer(2, 2), 0, string.Empty, 4, 0),
|
|
new("packet-u16-3", host, string.Empty, CreateVersionedBuffer(3, 2), 0, string.Empty, 4, 0),
|
|
new("packet-u16-4", host, string.Empty, CreateVersionedBuffer(4, 2), 0, string.Empty, 4, 0),
|
|
new("packet-u32-1", host, string.Empty, CreateVersionedBuffer(1, 4), 0, string.Empty, 4, 0),
|
|
new("packet-u32-2", host, string.Empty, CreateVersionedBuffer(2, 4), 0, string.Empty, 4, 0),
|
|
new("packet-u32-3", host, string.Empty, CreateVersionedBuffer(3, 4), 0, string.Empty, 4, 0),
|
|
new("packet-u32-4", host, string.Empty, CreateVersionedBuffer(4, 4), 0, string.Empty, 4, 0),
|
|
new("type-0", host, string.Empty, new byte[1026], 0, string.Empty, 0, 0),
|
|
new("type-1", host, string.Empty, new byte[1026], 0, string.Empty, 1, 0),
|
|
new("type-2", host, string.Empty, new byte[1026], 0, string.Empty, 2, 0),
|
|
new("type-3", host, string.Empty, new byte[1026], 0, string.Empty, 3, 0),
|
|
new("type-5", host, string.Empty, new byte[1026], 0, string.Empty, 5, 0),
|
|
new("type-6", host, string.Empty, new byte[1026], 0, string.Empty, 6, 0),
|
|
new("type-7", host, string.Empty, new byte[1026], 0, string.Empty, 7, 0),
|
|
new("guid-session", host, string.Empty, new byte[1026], 0, Guid.Empty.ToString("D"), 4, 0),
|
|
new("windows-user", host, Environment.UserName, new byte[1026], 0, string.Empty, 4, 0),
|
|
new("machine-host", Environment.MachineName, string.Empty, new byte[1026], 0, string.Empty, 4, 0)
|
|
];
|
|
|
|
foreach (WcfOpenAttempt attempt in attempts)
|
|
{
|
|
results.Add(OpenHistorySession(port, attempt, timeout));
|
|
if (results[^1].Success)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static int StartWcfQuery(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
string tagName = args.Length > 3 ? args[3] : "OtOpcUaParityTest_001.Counter";
|
|
bool useWindowsRetrievalTransport = args.Any(static arg => arg.Equals("--retrieval-windows", StringComparison.OrdinalIgnoreCase));
|
|
int? maxAttempts = TryReadIntOption(args, "--max-attempts");
|
|
int timeoutSeconds = TryReadIntOption(args, "--timeout-seconds") ?? 10;
|
|
if (maxAttempts <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(args), "--max-attempts must be greater than zero.");
|
|
}
|
|
|
|
if (timeoutSeconds <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(args), "--timeout-seconds must be greater than zero.");
|
|
}
|
|
|
|
DateTime endUtc = DateTime.UtcNow;
|
|
DateTime startUtc = endUtc.AddDays(-1);
|
|
TimeSpan timeout = TimeSpan.FromSeconds(timeoutSeconds);
|
|
|
|
List<WcfStartQueryResult> results = [];
|
|
IEnumerable<HistorianDataQueryRequest> attempts = CreateStartQueryAttempts(tagName, startUtc, endUtc);
|
|
if (maxAttempts is not null)
|
|
{
|
|
attempts = attempts.Take(maxAttempts.Value);
|
|
}
|
|
|
|
foreach (HistorianDataQueryRequest request in attempts)
|
|
{
|
|
results.Add(StartWcfQueryAttempt(host, port, timeout, request, useWindowsRetrievalTransport));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static IEnumerable<HistorianDataQueryRequest> CreateStartQueryAttempts(string tagName, DateTime startUtc, DateTime endUtc)
|
|
{
|
|
yield return new HistorianDataQueryRequest([tagName], startUtc, endUtc, MaxStates: 100, BatchSize: 1, Option: string.Empty)
|
|
{
|
|
QueryType = 2
|
|
};
|
|
|
|
foreach (uint queryType in Enumerable.Range(0, 15).Select(static value => (uint)value))
|
|
{
|
|
yield return new HistorianDataQueryRequest([tagName], startUtc, endUtc, MaxStates: 100, BatchSize: 1, Option: string.Empty)
|
|
{
|
|
QueryType = queryType,
|
|
ColumnSelectorFlags = 0x0000_0000_0003_FFFF
|
|
};
|
|
}
|
|
|
|
foreach (string option in new[] { "NoOption", string.Empty })
|
|
{
|
|
yield return new HistorianDataQueryRequest([tagName], startUtc, endUtc, MaxStates: 100, BatchSize: 1, Option: option)
|
|
{
|
|
QueryType = 2,
|
|
ColumnSelectorFlags = 0x0000_0000_0000_FFFF
|
|
};
|
|
}
|
|
|
|
foreach (string option in new[] { "NoOption", string.Empty })
|
|
{
|
|
yield return new HistorianDataQueryRequest([tagName], startUtc, endUtc, MaxStates: 100, BatchSize: 1, Option: option)
|
|
{
|
|
QueryType = 2,
|
|
ColumnSelectorFlags = ulong.MaxValue
|
|
};
|
|
}
|
|
|
|
HistorianRedundantEndpoint localEndpoint = new(EndpointName: string.Empty, Endpoints: [new HistorianEndpoint(Environment.MachineName, string.Empty)]);
|
|
yield return new HistorianDataQueryRequest([tagName], startUtc, endUtc, MaxStates: 100, BatchSize: 1, Option: string.Empty)
|
|
{
|
|
QueryType = 2,
|
|
ColumnSelectorFlags = ulong.MaxValue,
|
|
MdsEndpoint = localEndpoint,
|
|
StorageEndpoint = localEndpoint
|
|
};
|
|
|
|
yield return new HistorianDataQueryRequest([tagName], startUtc, endUtc, MaxStates: 100, BatchSize: 1, Option: string.Empty)
|
|
{
|
|
QueryType = 2,
|
|
ColumnSelectorFlags = ulong.MaxValue,
|
|
MdsEndpoint = new HistorianRedundantEndpoint(Environment.MachineName, [new HistorianEndpoint(Environment.MachineName, string.Empty)]),
|
|
StorageEndpoint = new HistorianRedundantEndpoint(Environment.MachineName, [new HistorianEndpoint(Environment.MachineName, string.Empty)])
|
|
};
|
|
}
|
|
|
|
static int ProbeWcfTagInfo(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
string tagName = args.Length > 3 ? args[3] : "OtOpcUaParityTest_001.Counter";
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
|
|
WcfTagInfoResult result = ProbeWcfTagInfoAttempt(host, port, timeout, tagName);
|
|
Console.WriteLine(JsonSerializer.Serialize(result, CreateJsonOptions()));
|
|
return result.Success ? 0 : 1;
|
|
}
|
|
|
|
static int ProbeWcfLikeTagBrowse(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
string filter = args.Length > 3 ? args[3] : "OtOpcUaParityTest%";
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
|
|
object result = ProbeWcfLikeTagBrowseAttempt(host, port, timeout, filter);
|
|
Console.WriteLine(JsonSerializer.Serialize(result, CreateJsonOptions()));
|
|
return IsTruthyProperty(result, "Success") ? 0 : 1;
|
|
}
|
|
|
|
static int ProbeWcfTagQuery(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
string filter = args.Length > 3 ? args[3] : "TagName eq 'OtOpcUaParityTest_001.Counter'";
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
|
|
WcfTagQueryResult result = ProbeWcfTagQueryAttempt(host, port, timeout, filter);
|
|
Console.WriteLine(JsonSerializer.Serialize(result, CreateJsonOptions()));
|
|
return result.Success ? 0 : 1;
|
|
}
|
|
|
|
static WcfTagQueryResult ProbeWcfTagQueryAttempt(string host, int port, TimeSpan timeout, string filter)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract3>? retrievalFactory = null;
|
|
IRetrievalServiceContract3? retrievalChannel = null;
|
|
IReadOnlyList<HistorianTagQueryAttempt> startAttempts =
|
|
[
|
|
HistorianTagQueryProtocol.CreateStartTagQueryAttempt(filter),
|
|
HistorianTagQueryProtocol.CreateStartTagQueryHeaderOnlyAttempt()
|
|
];
|
|
try
|
|
{
|
|
WcfOpen2Attempt openAttempt = new(
|
|
"legacy-v1-integrated-windows-v11-mode1026-hist-integrated",
|
|
host,
|
|
HistorianWcfServiceNames.HistoryIntegrated,
|
|
string.Empty,
|
|
[],
|
|
4,
|
|
11,
|
|
1026,
|
|
UseWindowsTransportSecurity: true);
|
|
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null || openOut is null)
|
|
{
|
|
return new WcfTagQueryResult(
|
|
filter,
|
|
startAttempts[0].RequestBuffer.Length,
|
|
startAttempts[0].RequestSha256,
|
|
false,
|
|
openError is null ? null : checked((uint)openError.Length),
|
|
openNativeError?.Code,
|
|
[],
|
|
"Open2 failed or returned an unrecognized output shape.");
|
|
}
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract3>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
List<WcfTagQueryHandleResult> handleResults = [];
|
|
foreach (HistorianTagQueryAttempt startAttempt in startAttempts)
|
|
{
|
|
foreach ((string handleVariant, string handle) in CreateTagQueryHandleCandidates(openOutput, openOut))
|
|
{
|
|
handleResults.Add(ProbeTagQueryHandle(retrievalChannel, handleVariant, handle, startAttempt));
|
|
}
|
|
}
|
|
|
|
return new WcfTagQueryResult(
|
|
filter,
|
|
startAttempts[0].RequestBuffer.Length,
|
|
startAttempts[0].RequestSha256,
|
|
handleResults.Any(static result => result.StartSuccess),
|
|
null,
|
|
null,
|
|
handleResults,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfTagQueryResult(
|
|
filter,
|
|
startAttempts[0].RequestBuffer.Length,
|
|
startAttempts[0].RequestSha256,
|
|
false,
|
|
null,
|
|
null,
|
|
[],
|
|
FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static IReadOnlyList<(string Variant, string Handle)> CreateTagQueryHandleCandidates(
|
|
HistorianLegacyOpen2Output openOutput,
|
|
byte[] openOut)
|
|
{
|
|
List<(string Variant, string Handle)> candidates =
|
|
[
|
|
("numeric-open2-handle", openOutput.Handle.ToString()),
|
|
("storage-session-id", openOutput.StorageSessionId.ToString("D")),
|
|
("fresh-guid-uppercase", Guid.NewGuid().ToString("D").ToUpperInvariant())
|
|
];
|
|
|
|
string? suppliedHandle = Environment.GetEnvironmentVariable("HISTORIAN_TAG_QUERY_HANDLE");
|
|
if (!string.IsNullOrWhiteSpace(suppliedHandle))
|
|
{
|
|
candidates.Add(("env-historian-tag-query-handle", suppliedHandle));
|
|
}
|
|
|
|
foreach ((string variant, string handle) in CreateOpen2GuidHandleCandidates(openOut))
|
|
{
|
|
if (!candidates.Any(candidate => candidate.Handle.Equals(handle, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
candidates.Add((variant, handle));
|
|
}
|
|
}
|
|
|
|
if (openOut.Length >= 16)
|
|
{
|
|
candidates.Add(("open-output-first16-hex", Convert.ToHexString(openOut.AsSpan(0, 16)).ToLowerInvariant()));
|
|
candidates.Add(("open-output-first16-base64", Convert.ToBase64String(openOut.AsSpan(0, 16).ToArray())));
|
|
}
|
|
|
|
candidates.Add(("open-output-full-hex", Convert.ToHexString(openOut).ToLowerInvariant()));
|
|
candidates.Add(("open-output-full-base64", Convert.ToBase64String(openOut)));
|
|
return candidates;
|
|
}
|
|
|
|
static WcfTagQueryHandleResult ProbeTagQueryHandle(
|
|
IRetrievalServiceContract3 retrievalChannel,
|
|
string handleVariant,
|
|
string handle,
|
|
HistorianTagQueryAttempt startAttempt)
|
|
{
|
|
uint? parsedQueryHandle = null;
|
|
uint? parsedTagCount = null;
|
|
List<WcfQueryTagResult> queryAttempts = [];
|
|
uint queryIdForEnd = 0;
|
|
try
|
|
{
|
|
bool startSuccess = retrievalChannel.StartTagQuery(
|
|
handle,
|
|
startAttempt.RequestBuffer,
|
|
out byte[]? startResponse,
|
|
out byte[]? startError);
|
|
HistorianNativeError? startNativeError = startError is null ? null : HistorianOpen2Protocol.TryReadNativeError(startError);
|
|
string? startResponseSha256 = startResponse is null || startResponse.Length == 0
|
|
? null
|
|
: Convert.ToHexString(SHA256.HashData(startResponse)).ToLowerInvariant();
|
|
string? startResponseBase64 = startResponse is null || startResponse.Length == 0
|
|
? null
|
|
: Convert.ToBase64String(startResponse);
|
|
|
|
if (startSuccess && startResponse is { Length: 8 })
|
|
{
|
|
HistorianTagQueryStartResponse parsed = HistorianTagQueryProtocol.ParseStartTagQueryResponse(startResponse);
|
|
parsedQueryHandle = parsed.QueryHandle;
|
|
parsedTagCount = parsed.TagCount;
|
|
queryIdForEnd = parsed.QueryHandle;
|
|
foreach ((string requestVariant, byte[] requestBuffer) in CreateQueryTagRequestBuffers(parsed.TagCount))
|
|
{
|
|
queryAttempts.Add(ProbeQueryTag(retrievalChannel, handle, parsed.QueryHandle, requestVariant, requestBuffer));
|
|
}
|
|
}
|
|
|
|
bool? endSuccess = null;
|
|
int? endErrorByteCount = null;
|
|
uint? endNativeErrorCode = null;
|
|
if (queryIdForEnd != 0)
|
|
{
|
|
try
|
|
{
|
|
endSuccess = retrievalChannel.EndTagQuery(handle, ref queryIdForEnd, out byte[]? endError);
|
|
endErrorByteCount = endError?.Length;
|
|
HistorianNativeError? endNativeError = endError is null ? null : HistorianOpen2Protocol.TryReadNativeError(endError);
|
|
endNativeErrorCode = endNativeError?.Code;
|
|
}
|
|
catch
|
|
{
|
|
endSuccess = null;
|
|
}
|
|
}
|
|
|
|
return new WcfTagQueryHandleResult(
|
|
startAttempt.Name,
|
|
startAttempt.RequestBuffer.Length,
|
|
startAttempt.RequestSha256,
|
|
handleVariant,
|
|
startSuccess,
|
|
startResponse?.Length,
|
|
startResponseSha256,
|
|
startResponseBase64,
|
|
startError?.Length,
|
|
startNativeError?.Code,
|
|
parsedQueryHandle,
|
|
parsedTagCount,
|
|
endSuccess,
|
|
endErrorByteCount,
|
|
endNativeErrorCode,
|
|
queryAttempts,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfTagQueryHandleResult(
|
|
startAttempt.Name,
|
|
startAttempt.RequestBuffer.Length,
|
|
startAttempt.RequestSha256,
|
|
handleVariant,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
queryAttempts,
|
|
FormatException(ex));
|
|
}
|
|
}
|
|
|
|
static IReadOnlyList<(string Variant, byte[] Buffer)> CreateQueryTagRequestBuffers(uint tagCount)
|
|
{
|
|
uint effectiveTagCount = tagCount == 0 ? 1 : tagCount;
|
|
uint boundedTagCount = Math.Min(effectiveTagCount, 5);
|
|
return
|
|
[
|
|
("u16-0-start0-count1", BuildQueryTagRequest(0, 0, 1)),
|
|
("u16-1-start0-count1", BuildQueryTagRequest(1, 0, 1)),
|
|
("u16-2-start0-count1", BuildQueryTagRequest(2, 0, 1)),
|
|
("u16-0-start0-count-start-count", BuildQueryTagRequest(0, 0, boundedTagCount)),
|
|
("u16-1-start0-count-start-count", BuildQueryTagRequest(1, 0, boundedTagCount)),
|
|
("u16-2-start0-count-start-count", BuildQueryTagRequest(2, 0, boundedTagCount))
|
|
];
|
|
}
|
|
|
|
static byte[] BuildQueryTagRequest(ushort selector, uint startIndex, uint tagCount)
|
|
{
|
|
using MemoryStream stream = new();
|
|
using BinaryWriter writer = new(stream, Encoding.Unicode, leaveOpen: true);
|
|
writer.Write(selector);
|
|
writer.Write(startIndex);
|
|
writer.Write(tagCount);
|
|
return stream.ToArray();
|
|
}
|
|
|
|
static WcfQueryTagResult ProbeQueryTag(
|
|
IRetrievalServiceContract3 retrievalChannel,
|
|
string handle,
|
|
uint startQueryId,
|
|
string requestVariant,
|
|
byte[] requestBuffer)
|
|
{
|
|
uint queryId = startQueryId;
|
|
try
|
|
{
|
|
bool querySuccess = retrievalChannel.QueryTag(
|
|
handle,
|
|
ref queryId,
|
|
requestBuffer,
|
|
out byte[]? responseBuffer,
|
|
out byte[]? errorBuffer);
|
|
HistorianNativeError? nativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
return new WcfQueryTagResult(
|
|
requestVariant,
|
|
requestBuffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(requestBuffer)).ToLowerInvariant(),
|
|
querySuccess,
|
|
queryId,
|
|
responseBuffer?.Length,
|
|
responseBuffer is null || responseBuffer.Length == 0 ? null : Convert.ToHexString(SHA256.HashData(responseBuffer)).ToLowerInvariant(),
|
|
responseBuffer is null || responseBuffer.Length == 0 ? null : Convert.ToBase64String(responseBuffer),
|
|
errorBuffer?.Length,
|
|
nativeError?.Code,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfQueryTagResult(
|
|
requestVariant,
|
|
requestBuffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(requestBuffer)).ToLowerInvariant(),
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
FormatException(ex));
|
|
}
|
|
}
|
|
|
|
static object ProbeWcfLikeTagBrowseAttempt(string host, int port, TimeSpan timeout, string filter)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract>? retrievalFactory = null;
|
|
IRetrievalServiceContract? retrievalChannel = null;
|
|
try
|
|
{
|
|
WcfOpen2Attempt openAttempt = new(
|
|
"legacy-v1-integrated-windows-v11-mode1026-hist-integrated",
|
|
host,
|
|
HistorianWcfServiceNames.HistoryIntegrated,
|
|
string.Empty,
|
|
[],
|
|
4,
|
|
11,
|
|
1026,
|
|
UseWindowsTransportSecurity: true);
|
|
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null)
|
|
{
|
|
return new
|
|
{
|
|
Filter = filter,
|
|
Success = false,
|
|
OpenErrorSize = openError?.Length,
|
|
OpenNativeErrorCode = openNativeError?.Code,
|
|
Error = "Open2 failed or returned an unrecognized output shape."
|
|
};
|
|
}
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
uint startReturnCode = retrievalChannel.StartLikeTagNameSearch(
|
|
openOutput.Handle,
|
|
filter,
|
|
(uint)InsqlTagType.All,
|
|
isNotLike: false);
|
|
|
|
List<object> batches = [];
|
|
bool isMore = false;
|
|
if (startReturnCode == 0)
|
|
{
|
|
for (int batch = 0; batch < 8; batch++)
|
|
{
|
|
uint getReturnCode = retrievalChannel.GetLikeTagnames(
|
|
openOutput.Handle,
|
|
out byte[]? tagNameBuffer,
|
|
out uint tagNameBufferSize,
|
|
out isMore);
|
|
byte[] bytes = tagNameBuffer ?? [];
|
|
batches.Add(new
|
|
{
|
|
Batch = batch,
|
|
ReturnCode = getReturnCode,
|
|
TagNameBufferSize = tagNameBufferSize,
|
|
ByteCount = bytes.Length,
|
|
Sha256 = bytes.Length == 0 ? null : Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant(),
|
|
Base64 = bytes.Length == 0 ? null : Convert.ToBase64String(bytes),
|
|
IsMore = isMore
|
|
});
|
|
|
|
if (getReturnCode != 0 || !isMore)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return new
|
|
{
|
|
Filter = filter,
|
|
Success = startReturnCode == 0,
|
|
StartReturnCode = startReturnCode,
|
|
BatchCount = batches.Count,
|
|
Batches = batches
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new
|
|
{
|
|
Filter = filter,
|
|
Success = false,
|
|
Error = FormatException(ex)
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static bool IsTruthyProperty(object value, string propertyName)
|
|
{
|
|
return value.GetType().GetProperty(propertyName)?.GetValue(value) is true;
|
|
}
|
|
|
|
static WcfTagInfoResult ProbeWcfTagInfoAttempt(string host, int port, TimeSpan timeout, string tagName)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract2>? retrievalFactory = null;
|
|
IRetrievalServiceContract2? retrievalChannel = null;
|
|
try
|
|
{
|
|
WcfOpen2Attempt openAttempt = new(
|
|
"legacy-v1-integrated-windows-v11-mode1026-hist-integrated",
|
|
host,
|
|
HistorianWcfServiceNames.HistoryIntegrated,
|
|
string.Empty,
|
|
[],
|
|
4,
|
|
11,
|
|
1026,
|
|
UseWindowsTransportSecurity: true);
|
|
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null)
|
|
{
|
|
return new WcfTagInfoResult(
|
|
tagName,
|
|
false,
|
|
openError is null ? null : checked((uint)openError.Length),
|
|
openNativeError?.Code,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
[],
|
|
[],
|
|
"Open2 failed or returned an unrecognized output shape.");
|
|
}
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
uint isOriginalAllowedReturnCode = retrievalChannel.IsOriginalAllowed(openOutput.Handle, out bool isOriginalAllowed);
|
|
uint tagTypeReturnCode = retrievalChannel.GetTagTypeFromName(openOutput.Handle, tagName, out uint tagType);
|
|
uint isManualReturnCode = retrievalChannel.IsManualTag(openOutput.Handle, tagName, out bool isManual);
|
|
uint tagInfoReturnCode = retrievalChannel.GetTagInfoFromName(openOutput.Handle, tagName, out uint tagMetadataByteCount, out byte[]? tagMetadata);
|
|
List<WcfTagInfoByNameBufferResult> nameBufferAttempts = [];
|
|
foreach ((string name, byte[] buffer) in CreateTagInfoByNameBuffers(tagName))
|
|
{
|
|
nameBufferAttempts.Add(ProbeTagInfosFromNameBuffer(retrievalChannel, openOutput.Handle, name, buffer));
|
|
}
|
|
List<WcfTagInfoByNameBufferResult> idBufferAttempts = [];
|
|
if (tagInfoReturnCode != 0)
|
|
{
|
|
foreach ((string name, byte[] buffer) in CreateTagInfoByIdBuffers(tagInfoReturnCode))
|
|
{
|
|
idBufferAttempts.Add(ProbeTagInfosFromIdBuffer(retrievalChannel, openOutput.Handle, name, buffer));
|
|
}
|
|
}
|
|
|
|
return new WcfTagInfoResult(
|
|
tagName,
|
|
isOriginalAllowedReturnCode == 0 && tagTypeReturnCode == 0 && isManualReturnCode == 0,
|
|
null,
|
|
null,
|
|
isOriginalAllowedReturnCode,
|
|
isOriginalAllowed,
|
|
tagTypeReturnCode,
|
|
tagType,
|
|
isManualReturnCode,
|
|
isManual,
|
|
null,
|
|
null,
|
|
tagInfoReturnCode,
|
|
tagMetadataByteCount,
|
|
tagMetadata is null ? null : tagMetadata.Length,
|
|
tagMetadata is null ? null : Convert.ToHexString(SHA256.HashData(tagMetadata)).ToLowerInvariant(),
|
|
tagMetadata is null ? null : Convert.ToBase64String(tagMetadata),
|
|
nameBufferAttempts,
|
|
idBufferAttempts,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfTagInfoResult(
|
|
tagName,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
[],
|
|
[],
|
|
FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static IReadOnlyList<(string Name, byte[] Buffer)> CreateTagInfoByIdBuffers(uint tagId)
|
|
{
|
|
return
|
|
[
|
|
("u32-count-u32-id", BuildTagIdBuffer(tagId, countSize: 4, includeCount: true, idSize: 4)),
|
|
("u16-count-u32-id", BuildTagIdBuffer(tagId, countSize: 2, includeCount: true, idSize: 4)),
|
|
("u32-id-only", BuildTagIdBuffer(tagId, countSize: 0, includeCount: false, idSize: 4)),
|
|
("u32-count-u64-id", BuildTagIdBuffer(tagId, countSize: 4, includeCount: true, idSize: 8)),
|
|
("u64-id-only", BuildTagIdBuffer(tagId, countSize: 0, includeCount: false, idSize: 8))
|
|
];
|
|
}
|
|
|
|
static byte[] BuildTagIdBuffer(uint tagId, int countSize, bool includeCount, int idSize)
|
|
{
|
|
using MemoryStream stream = new();
|
|
using BinaryWriter writer = new(stream, Encoding.Unicode, leaveOpen: true);
|
|
if (includeCount)
|
|
{
|
|
if (countSize == 2)
|
|
{
|
|
writer.Write((ushort)1);
|
|
}
|
|
else
|
|
{
|
|
writer.Write((uint)1);
|
|
}
|
|
}
|
|
|
|
if (idSize == 8)
|
|
{
|
|
writer.Write((ulong)tagId);
|
|
}
|
|
else
|
|
{
|
|
writer.Write(tagId);
|
|
}
|
|
|
|
return stream.ToArray();
|
|
}
|
|
|
|
static IReadOnlyList<(string Name, byte[] Buffer)> CreateTagInfoByNameBuffers(string tagName)
|
|
{
|
|
return
|
|
[
|
|
("u32-count-wide-string", BuildTagNameBuffer(tagName, countSize: 4, includeCount: true, historianString: true, nullTerminated: false)),
|
|
("u16-count-wide-string", BuildTagNameBuffer(tagName, countSize: 2, includeCount: true, historianString: true, nullTerminated: false)),
|
|
("wide-string-only", BuildTagNameBuffer(tagName, countSize: 0, includeCount: false, historianString: true, nullTerminated: false)),
|
|
("null-terminated-wide", BuildTagNameBuffer(tagName, countSize: 0, includeCount: false, historianString: false, nullTerminated: true)),
|
|
("raw-wide", BuildTagNameBuffer(tagName, countSize: 0, includeCount: false, historianString: false, nullTerminated: false))
|
|
];
|
|
}
|
|
|
|
static byte[] BuildTagNameBuffer(string tagName, int countSize, bool includeCount, bool historianString, bool nullTerminated)
|
|
{
|
|
using MemoryStream stream = new();
|
|
using BinaryWriter writer = new(stream, Encoding.Unicode, leaveOpen: true);
|
|
if (includeCount)
|
|
{
|
|
if (countSize == 2)
|
|
{
|
|
writer.Write((ushort)1);
|
|
}
|
|
else
|
|
{
|
|
writer.Write((uint)1);
|
|
}
|
|
}
|
|
|
|
if (historianString)
|
|
{
|
|
writer.Write((uint)tagName.Length);
|
|
}
|
|
|
|
writer.Write(Encoding.Unicode.GetBytes(tagName));
|
|
if (nullTerminated)
|
|
{
|
|
writer.Write((ushort)0);
|
|
}
|
|
|
|
return stream.ToArray();
|
|
}
|
|
|
|
static WcfTagInfoByNameBufferResult ProbeTagInfosFromIdBuffer(IRetrievalServiceContract2 retrievalChannel, uint handle, string attempt, byte[] buffer)
|
|
{
|
|
try
|
|
{
|
|
uint sequence = 0;
|
|
uint returnCode = retrievalChannel.GetTagInfosFromId(
|
|
handle,
|
|
checked((uint)buffer.Length),
|
|
buffer,
|
|
ref sequence,
|
|
out uint tagInfosSize,
|
|
out byte[]? tagInfos);
|
|
|
|
return new WcfTagInfoByNameBufferResult(
|
|
attempt,
|
|
buffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(buffer)).ToLowerInvariant(),
|
|
returnCode,
|
|
sequence,
|
|
tagInfosSize,
|
|
tagInfos?.Length,
|
|
tagInfos is null ? null : Convert.ToHexString(SHA256.HashData(tagInfos)).ToLowerInvariant(),
|
|
tagInfos is null ? null : Convert.ToBase64String(tagInfos),
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfTagInfoByNameBufferResult(
|
|
attempt,
|
|
buffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(buffer)).ToLowerInvariant(),
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
FormatException(ex));
|
|
}
|
|
}
|
|
|
|
static WcfTagInfoByNameBufferResult ProbeTagInfosFromNameBuffer(IRetrievalServiceContract2 retrievalChannel, uint handle, string attempt, byte[] buffer)
|
|
{
|
|
try
|
|
{
|
|
uint sequence = 0;
|
|
uint returnCode = retrievalChannel.GetTagInfosFromName(
|
|
handle,
|
|
checked((uint)buffer.Length),
|
|
buffer,
|
|
ref sequence,
|
|
out uint tagInfosSize,
|
|
out byte[]? tagInfos);
|
|
|
|
return new WcfTagInfoByNameBufferResult(
|
|
attempt,
|
|
buffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(buffer)).ToLowerInvariant(),
|
|
returnCode,
|
|
sequence,
|
|
tagInfosSize,
|
|
tagInfos?.Length,
|
|
tagInfos is null ? null : Convert.ToHexString(SHA256.HashData(tagInfos)).ToLowerInvariant(),
|
|
tagInfos is null ? null : Convert.ToBase64String(tagInfos),
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfTagInfoByNameBufferResult(
|
|
attempt,
|
|
buffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(buffer)).ToLowerInvariant(),
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
FormatException(ex));
|
|
}
|
|
}
|
|
|
|
static WcfStartQueryResult StartWcfQueryAttempt(string host, int port, TimeSpan timeout, HistorianDataQueryRequest request, bool useWindowsRetrievalTransport = false)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract2>? retrievalFactory = null;
|
|
IRetrievalServiceContract2? retrievalChannel = null;
|
|
try
|
|
{
|
|
WcfOpen2Attempt openAttempt = new(
|
|
"legacy-v1-integrated-windows-v11-mode1026-hist-integrated",
|
|
host,
|
|
HistorianWcfServiceNames.HistoryIntegrated,
|
|
string.Empty,
|
|
[],
|
|
4,
|
|
11,
|
|
1026,
|
|
UseWindowsTransportSecurity: true);
|
|
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null)
|
|
{
|
|
return new WcfStartQueryResult(
|
|
request.Option,
|
|
request.ColumnSelectorFlags,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
request.QueryType,
|
|
false,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
openError is null ? null : checked((uint)openError.Length),
|
|
openNativeError?.Code,
|
|
"Open2 failed or returned an unrecognized output shape.");
|
|
}
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract2>(
|
|
useWindowsRetrievalTransport
|
|
? HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout)
|
|
: HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
if (useWindowsRetrievalTransport)
|
|
{
|
|
retrievalFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? retrievalCredential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (retrievalCredential is not null)
|
|
{
|
|
retrievalFactory.Credentials.Windows.ClientCredential = retrievalCredential;
|
|
}
|
|
}
|
|
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
uint isOriginalAllowedReturnCode = retrievalChannel.IsOriginalAllowed(openOutput.Handle, out bool isOriginalAllowed);
|
|
byte[] requestBuffer = HistorianDataQueryProtocol.SerializeFullHistoryRequest(request);
|
|
uint queryHandle = 0;
|
|
bool startSuccess = retrievalChannel.StartQuery2(
|
|
openOutput.Handle,
|
|
HistorianDataQueryProtocol.QueryRequestTypeData,
|
|
checked((uint)requestBuffer.Length),
|
|
requestBuffer,
|
|
out uint responseSize,
|
|
out byte[]? responseBuffer,
|
|
ref queryHandle,
|
|
out uint errorSize,
|
|
out byte[]? errorBuffer);
|
|
HistorianNativeError? nativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
uint? legacyReturnCode = null;
|
|
uint? legacyQueryHandle = null;
|
|
uint? legacyResponseSize = null;
|
|
int? legacyResponseByteCount = null;
|
|
string? legacyResponseSha256 = null;
|
|
string? legacyError = null;
|
|
try
|
|
{
|
|
uint oldQueryHandle = 0;
|
|
legacyReturnCode = retrievalChannel.StartQuery(
|
|
openOutput.Handle,
|
|
HistorianDataQueryProtocol.QueryRequestTypeData,
|
|
checked((uint)requestBuffer.Length),
|
|
requestBuffer,
|
|
out uint oldResponseSize,
|
|
out byte[]? oldResponseBuffer,
|
|
ref oldQueryHandle);
|
|
legacyQueryHandle = oldQueryHandle;
|
|
legacyResponseSize = oldResponseSize;
|
|
legacyResponseByteCount = oldResponseBuffer?.Length;
|
|
legacyResponseSha256 = oldResponseBuffer is null
|
|
? null
|
|
: Convert.ToHexString(SHA256.HashData(oldResponseBuffer)).ToLowerInvariant();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
legacyError = FormatException(ex);
|
|
}
|
|
|
|
return new WcfStartQueryResult(
|
|
request.Option,
|
|
request.ColumnSelectorFlags,
|
|
requestBuffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(requestBuffer)).ToLowerInvariant(),
|
|
openOutput.Handle,
|
|
isOriginalAllowedReturnCode,
|
|
isOriginalAllowed,
|
|
request.QueryType,
|
|
true,
|
|
startSuccess,
|
|
queryHandle,
|
|
responseSize,
|
|
responseBuffer?.Length,
|
|
legacyReturnCode,
|
|
legacyQueryHandle,
|
|
legacyResponseSize,
|
|
legacyResponseByteCount,
|
|
legacyResponseSha256,
|
|
legacyError,
|
|
errorSize,
|
|
nativeError?.Code,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfStartQueryResult(request.Option, request.ColumnSelectorFlags, null, null, null, null, null, request.QueryType, false, false, null, null, null, null, null, null, null, null, null, null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static int StartWcfEventQuery(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
uint eventCount = args.Length > 3 && uint.TryParse(args[3], out uint parsedEventCount)
|
|
? parsedEventCount
|
|
: 3;
|
|
DateTime endUtc = DateTime.UtcNow;
|
|
DateTime startUtc = endUtc.AddDays(-1);
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
|
|
List<WcfStartEventQueryResult> results = [];
|
|
foreach (HistorianEventQueryAttempt attempt in HistorianEventQueryProtocol.CreateStartEventQueryAttempts(startUtc, endUtc, eventCount))
|
|
{
|
|
foreach (WcfOpen2Attempt openAttempt in CreateEventQueryOpen2Attempts(host))
|
|
{
|
|
results.Add(StartWcfEventQueryAttempt(host, port, timeout, attempt, openAttempt, "open-handle", null));
|
|
results.Add(StartWcfEventQueryAttempt(host, port, timeout, attempt, openAttempt, "open-handle", null, "StartQuery2"));
|
|
results.Add(StartWcfEventQueryAttempt(host, port, timeout, attempt, openAttempt, "client-app-event-slot-handle", 1));
|
|
results.Add(StartWcfEventQueryAttempt(host, port, timeout, attempt, openAttempt, "client-app-event-slot-handle", 1, "StartQuery2"));
|
|
results.Add(StartWcfEventQueryAttempt(host, port, timeout, attempt, openAttempt, "event-tag-handle", 10_000_000));
|
|
results.Add(StartWcfEventQueryAttempt(host, port, timeout, attempt, openAttempt, "event-tag-handle", 10_000_000, "StartQuery2"));
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.Success) ? 0 : 1;
|
|
}
|
|
|
|
static IReadOnlyList<WcfOpen2Attempt> CreateEventQueryOpen2Attempts(string host)
|
|
{
|
|
return
|
|
[
|
|
new(
|
|
"legacy-v1-process-integrated-windows-v11-mode1026-hist-integrated",
|
|
host,
|
|
HistorianWcfServiceNames.HistoryIntegrated,
|
|
string.Empty,
|
|
[],
|
|
4,
|
|
11,
|
|
1026,
|
|
UseWindowsTransportSecurity: true),
|
|
new(
|
|
"legacy-v1-event-integrated-windows-v11-mode1281-hist-integrated",
|
|
host,
|
|
HistorianWcfServiceNames.HistoryIntegrated,
|
|
string.Empty,
|
|
[],
|
|
4,
|
|
11,
|
|
1281,
|
|
UseWindowsTransportSecurity: true),
|
|
];
|
|
}
|
|
|
|
static WcfStartEventQueryResult StartWcfEventQueryAttempt(
|
|
string host,
|
|
int port,
|
|
TimeSpan timeout,
|
|
HistorianEventQueryAttempt eventAttempt,
|
|
WcfOpen2Attempt openAttempt,
|
|
string handleMode,
|
|
uint? clientHandleOverride,
|
|
string retrievalOperation = "StartEventQuery")
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract4>? retrievalFactory = null;
|
|
IRetrievalServiceContract4? retrievalChannel = null;
|
|
try
|
|
{
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null)
|
|
{
|
|
return new WcfStartEventQueryResult(
|
|
eventAttempt.Name,
|
|
openAttempt.Name,
|
|
handleMode,
|
|
retrievalOperation,
|
|
eventAttempt.Version,
|
|
eventAttempt.RequestBuffer.Length,
|
|
eventAttempt.RequestSha256,
|
|
null,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
openError is null ? null : checked((uint)openError.Length),
|
|
openNativeError?.Code,
|
|
"Open2 failed or returned an unrecognized output shape.");
|
|
}
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract4>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
uint queryHandle = 0;
|
|
uint clientHandle = clientHandleOverride ?? openOutput.Handle;
|
|
bool startSuccess;
|
|
uint responseSize;
|
|
byte[]? responseBuffer;
|
|
uint errorSize;
|
|
byte[]? errorBuffer;
|
|
if (retrievalOperation == "StartQuery2")
|
|
{
|
|
startSuccess = retrievalChannel.StartQuery2(
|
|
clientHandle,
|
|
HistorianEventQueryProtocol.QueryRequestTypeEvent,
|
|
checked((uint)eventAttempt.RequestBuffer.Length),
|
|
eventAttempt.RequestBuffer,
|
|
out responseSize,
|
|
out responseBuffer,
|
|
ref queryHandle,
|
|
out errorSize,
|
|
out errorBuffer);
|
|
}
|
|
else
|
|
{
|
|
startSuccess = retrievalChannel.StartEventQuery(
|
|
clientHandle,
|
|
HistorianEventQueryProtocol.QueryRequestTypeEvent,
|
|
checked((uint)eventAttempt.RequestBuffer.Length),
|
|
eventAttempt.RequestBuffer,
|
|
out responseSize,
|
|
out responseBuffer,
|
|
ref queryHandle,
|
|
out errorSize,
|
|
out errorBuffer);
|
|
}
|
|
HistorianNativeError? nativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
|
|
return new WcfStartEventQueryResult(
|
|
eventAttempt.Name,
|
|
openAttempt.Name,
|
|
handleMode,
|
|
retrievalOperation,
|
|
eventAttempt.Version,
|
|
eventAttempt.RequestBuffer.Length,
|
|
eventAttempt.RequestSha256,
|
|
clientHandle,
|
|
startSuccess,
|
|
queryHandle,
|
|
responseSize,
|
|
responseBuffer?.Length,
|
|
errorSize,
|
|
nativeError?.Code,
|
|
null,
|
|
null,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfStartEventQueryResult(
|
|
eventAttempt.Name,
|
|
openAttempt.Name,
|
|
handleMode,
|
|
retrievalOperation,
|
|
eventAttempt.Version,
|
|
eventAttempt.RequestBuffer.Length,
|
|
eventAttempt.RequestSha256,
|
|
null,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static int RegisterEventTagAndStartQuery(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
uint eventCount = args.Length > 3 && uint.TryParse(args[3], out uint parsedEventCount)
|
|
? parsedEventCount
|
|
: 3;
|
|
DateTime endUtc = DateTime.UtcNow;
|
|
DateTime startUtc = endUtc.AddDays(-1);
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
|
|
HistorianEventQueryAttempt eventAttempt = HistorianEventQueryProtocol.CreateStartEventQueryAttempts(startUtc, endUtc, eventCount)[0];
|
|
WcfOpen2Attempt openAttempt = CreateEventQueryOpen2Attempts(host)
|
|
.First(static attempt => attempt.ConnectionMode == 1281);
|
|
|
|
List<WcfRegisterEventTagResult> results = [];
|
|
foreach ((string registrationName, byte[] registrationBuffer) in CreateRegisterEventTagBuffers())
|
|
{
|
|
results.AddRange(RegisterEventTagAndStartQueryAttempt(host, port, timeout, openAttempt, eventAttempt, registrationName, registrationBuffer));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.StartSuccess) ? 0 : 1;
|
|
}
|
|
|
|
static IReadOnlyList<(string Name, byte[] Buffer)> CreateRegisterEventTagBuffers()
|
|
{
|
|
Guid eventTagHandleAsDotNetGuid = new("00989680-0000-0000-0000-000000000000");
|
|
Guid eventTagHandleAsRfcStyleGuid = new("80969800-0000-0000-0000-000000000000");
|
|
|
|
return
|
|
[
|
|
("empty-guid-vector", BuildGuidVector([])),
|
|
("zero-guid-vector", BuildGuidVector([Guid.Empty])),
|
|
("event-handle-10000000-dotnet-guid-vector", BuildGuidVector([eventTagHandleAsDotNetGuid])),
|
|
("event-handle-10000000-rfc-style-guid-vector", BuildGuidVector([eventTagHandleAsRfcStyleGuid]))
|
|
];
|
|
}
|
|
|
|
static byte[] BuildGuidVector(IReadOnlyList<Guid> values)
|
|
{
|
|
using MemoryStream stream = new();
|
|
using BinaryWriter writer = new(stream, Encoding.Unicode, leaveOpen: true);
|
|
writer.Write(checked((uint)values.Count));
|
|
foreach (Guid value in values)
|
|
{
|
|
writer.Write(value.ToByteArray());
|
|
}
|
|
|
|
return stream.ToArray();
|
|
}
|
|
|
|
static IReadOnlyList<WcfRegisterEventTagResult> RegisterEventTagAndStartQueryAttempt(
|
|
string host,
|
|
int port,
|
|
TimeSpan timeout,
|
|
WcfOpen2Attempt openAttempt,
|
|
HistorianEventQueryAttempt eventAttempt,
|
|
string registrationName,
|
|
byte[] registrationBuffer)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract4>? retrievalFactory = null;
|
|
IRetrievalServiceContract4? retrievalChannel = null;
|
|
try
|
|
{
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null)
|
|
{
|
|
return
|
|
[
|
|
new WcfRegisterEventTagResult(
|
|
registrationName,
|
|
registrationBuffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(registrationBuffer)).ToLowerInvariant(),
|
|
null,
|
|
null,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
eventAttempt.RequestSha256,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
openError is null ? null : checked((uint)openError.Length),
|
|
openNativeError?.Code,
|
|
"Open2 failed or returned an unrecognized output shape.")
|
|
];
|
|
}
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract4>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
List<WcfRegisterEventTagResult> handleResults = [];
|
|
foreach ((string handleVariant, string handle) in CreateOpen2GuidHandleCandidates(openOut!))
|
|
{
|
|
uint elementCount = BinaryPrimitives.ReadUInt32LittleEndian(registrationBuffer.AsSpan(0, 4));
|
|
bool registerSuccess = historyChannel.RegisterTags2(
|
|
handle,
|
|
elementCount,
|
|
registrationBuffer,
|
|
out byte[]? registerOutput,
|
|
out byte[]? registerError);
|
|
HistorianNativeError? registerNativeError = registerError is null ? null : HistorianOpen2Protocol.TryReadNativeError(registerError);
|
|
|
|
uint queryHandle = 0;
|
|
bool startSuccess = retrievalChannel.StartEventQuery(
|
|
openOutput.Handle,
|
|
HistorianEventQueryProtocol.QueryRequestTypeEvent,
|
|
checked((uint)eventAttempt.RequestBuffer.Length),
|
|
eventAttempt.RequestBuffer,
|
|
out uint responseSize,
|
|
out byte[]? responseBuffer,
|
|
ref queryHandle,
|
|
out uint errorSize,
|
|
out byte[]? errorBuffer);
|
|
HistorianNativeError? startNativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
|
|
handleResults.Add(new WcfRegisterEventTagResult(
|
|
registrationName,
|
|
registrationBuffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(registrationBuffer)).ToLowerInvariant(),
|
|
handleVariant,
|
|
openOutput.Handle,
|
|
registerSuccess,
|
|
registerOutput?.Length,
|
|
registerError?.Length,
|
|
registerNativeError?.Code,
|
|
registerOutput is null ? null : Convert.ToHexString(SHA256.HashData(registerOutput)).ToLowerInvariant(),
|
|
eventAttempt.RequestSha256,
|
|
startSuccess,
|
|
queryHandle,
|
|
responseSize,
|
|
responseBuffer?.Length,
|
|
errorSize,
|
|
startNativeError?.Code,
|
|
null,
|
|
null,
|
|
null));
|
|
}
|
|
|
|
return handleResults;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return
|
|
[
|
|
new WcfRegisterEventTagResult(
|
|
registrationName,
|
|
registrationBuffer.Length,
|
|
Convert.ToHexString(SHA256.HashData(registrationBuffer)).ToLowerInvariant(),
|
|
null,
|
|
null,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
eventAttempt.RequestSha256,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
FormatException(ex))
|
|
];
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static IReadOnlyList<(string Variant, string Handle)> CreateOpen2GuidHandleCandidates(byte[] openOutput)
|
|
{
|
|
List<(string Variant, string Handle)> candidates = [];
|
|
if (openOutput.Length >= 16)
|
|
{
|
|
candidates.Add(("open-output-first16-dotnet-guid", new Guid(openOutput.AsSpan(0, 16)).ToString("D")));
|
|
candidates.Add(("open-output-first16-rfc4122-guid", CreateRfc4122Guid(openOutput.AsSpan(0, 16)).ToString("D")));
|
|
}
|
|
|
|
if (openOutput.Length >= 20)
|
|
{
|
|
candidates.Add(("open-output-bytes4-19-dotnet-guid", new Guid(openOutput.AsSpan(4, 16)).ToString("D")));
|
|
candidates.Add(("open-output-bytes4-19-rfc4122-guid", CreateRfc4122Guid(openOutput.AsSpan(4, 16)).ToString("D")));
|
|
}
|
|
|
|
return candidates;
|
|
}
|
|
|
|
static int AddEventTagAndStartQuery(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
|
|
? parsedPort
|
|
: HistorianWcfBindingFactory.DefaultPort;
|
|
uint eventCount = args.Length > 3 && uint.TryParse(args[3], out uint parsedEventCount)
|
|
? parsedEventCount
|
|
: 3;
|
|
DateTime endUtc = DateTime.UtcNow;
|
|
DateTime startUtc = endUtc.AddDays(-1);
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
|
|
HistorianEventQueryAttempt eventAttempt = HistorianEventQueryProtocol.CreateStartEventQueryAttempts(startUtc, endUtc, eventCount)[0];
|
|
WcfOpen2Attempt openAttempt = CreateEventQueryOpen2Attempts(host)
|
|
.First(static attempt => attempt.ConnectionMode == 1281);
|
|
|
|
List<WcfAddEventTagResult> results = [];
|
|
foreach ((string payloadName, byte[] payload) in CreateAddEventTagPayloads())
|
|
{
|
|
results.Add(AddEventTagAndStartQueryAttempt(host, port, timeout, openAttempt, eventAttempt, payloadName, payload));
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(results, CreateJsonOptions()));
|
|
return results.Any(static result => result.StartSuccess) ? 0 : 1;
|
|
}
|
|
|
|
static IReadOnlyList<(string Name, byte[] Buffer)> CreateAddEventTagPayloads()
|
|
{
|
|
byte[] metadata = BuildCommonArchestraEventTagMetadata(DateTime.UtcNow);
|
|
using MemoryStream vectorStream = new();
|
|
using (BinaryWriter vectorWriter = new(vectorStream, Encoding.Unicode, leaveOpen: true))
|
|
{
|
|
vectorWriter.Write(1u);
|
|
vectorWriter.Write(metadata);
|
|
}
|
|
|
|
return
|
|
[
|
|
("ctagmetadata-only", metadata),
|
|
("vector-count-plus-ctagmetadata", vectorStream.ToArray())
|
|
];
|
|
}
|
|
|
|
static byte[] BuildCommonArchestraEventTagMetadata(DateTime createdUtc)
|
|
{
|
|
Guid eventTagId = new("353b8145-5df0-4d46-a253-871aef49b321");
|
|
Guid commonArchestraEventTypeId = new("5f59ae42-3bb6-4760-91a5-ab0be01f2f27");
|
|
|
|
using MemoryStream stream = new();
|
|
using BinaryWriter writer = new(stream, Encoding.Unicode, leaveOpen: true);
|
|
|
|
writer.Write((byte)3); // CTagMetadata serialization version for zero shard/source ids.
|
|
writer.Write((ushort)0x0086); // tag name, description, and creation timestamp.
|
|
writer.Write((byte)5); // CDataType struct/event discriminator from ConvertEventTagToTagMetadata.
|
|
writer.Write(eventTagId.ToByteArray());
|
|
WriteCompressedHistorianString(writer, "CM_EVENT");
|
|
WriteCompressedHistorianString(writer, "AnE Event");
|
|
writer.Write(createdUtc.ToFileTimeUtc());
|
|
writer.Write(new byte[] { 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00 });
|
|
writer.Write(0u); // Storage rate for storage type 2.
|
|
writer.Write(0u); // Time deadband for storage type 2.
|
|
writer.Write(commonArchestraEventTypeId.ToByteArray());
|
|
|
|
return stream.ToArray();
|
|
}
|
|
|
|
static void WriteCompressedHistorianString(BinaryWriter writer, string value)
|
|
{
|
|
if (value.Length == 0)
|
|
{
|
|
writer.Write((byte)0);
|
|
return;
|
|
}
|
|
|
|
if (value.Length > byte.MaxValue)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(value), "This probe only supports short synthetic metadata strings.");
|
|
}
|
|
|
|
writer.Write((byte)0x09); // one-byte length plus common high-byte compression.
|
|
writer.Write((byte)value.Length);
|
|
writer.Write((byte)0);
|
|
foreach (char character in value)
|
|
{
|
|
if (character > byte.MaxValue)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(value), "This probe only supports ASCII synthetic metadata strings.");
|
|
}
|
|
|
|
writer.Write((byte)character);
|
|
}
|
|
}
|
|
|
|
static WcfAddEventTagResult AddEventTagAndStartQueryAttempt(
|
|
string host,
|
|
int port,
|
|
TimeSpan timeout,
|
|
WcfOpen2Attempt openAttempt,
|
|
HistorianEventQueryAttempt eventAttempt,
|
|
string payloadName,
|
|
byte[] payload)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? historyFactory = null;
|
|
IHistoryServiceContract2? historyChannel = null;
|
|
ChannelFactory<IRetrievalServiceContract4>? retrievalFactory = null;
|
|
IRetrievalServiceContract4? retrievalChannel = null;
|
|
try
|
|
{
|
|
historyFactory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, openAttempt.ServiceName));
|
|
historyFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
historyFactory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
historyFactory.Open();
|
|
historyChannel = historyFactory.CreateChannel();
|
|
if (historyChannel is IClientChannel historyClientChannel)
|
|
{
|
|
historyClientChannel.Open();
|
|
}
|
|
|
|
byte[] openBuffer = BuildOpen2Buffer(openAttempt);
|
|
bool openSuccess = historyChannel.OpenConnection2(ref openBuffer, out byte[]? openOut, out byte[]? openError);
|
|
HistorianNativeError? openNativeError = openError is null ? null : HistorianOpen2Protocol.TryReadNativeError(openError);
|
|
HistorianLegacyOpen2Output? openOutput = openOut is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(openOut);
|
|
if (!openSuccess || openOutput is null)
|
|
{
|
|
return new WcfAddEventTagResult(
|
|
payloadName,
|
|
payload.Length,
|
|
Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(),
|
|
null,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
eventAttempt.RequestSha256,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
openError is null ? null : checked((uint)openError.Length),
|
|
openNativeError?.Code,
|
|
"Open2 failed or returned an unrecognized output shape.");
|
|
}
|
|
|
|
uint addReturnCode = historyChannel.AddTags(
|
|
openOutput.Handle,
|
|
1,
|
|
checked((uint)payload.Length),
|
|
payload,
|
|
out uint addOutputByteCount,
|
|
out byte[]? addOutput);
|
|
|
|
retrievalFactory = new ChannelFactory<IRetrievalServiceContract4>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
retrievalFactory.Open();
|
|
retrievalChannel = retrievalFactory.CreateChannel();
|
|
if (retrievalChannel is IClientChannel retrievalClientChannel)
|
|
{
|
|
retrievalClientChannel.Open();
|
|
}
|
|
|
|
uint queryHandle = 0;
|
|
bool startSuccess = retrievalChannel.StartEventQuery(
|
|
openOutput.Handle,
|
|
HistorianEventQueryProtocol.QueryRequestTypeEvent,
|
|
checked((uint)eventAttempt.RequestBuffer.Length),
|
|
eventAttempt.RequestBuffer,
|
|
out uint responseSize,
|
|
out byte[]? responseBuffer,
|
|
ref queryHandle,
|
|
out uint errorSize,
|
|
out byte[]? errorBuffer);
|
|
HistorianNativeError? startNativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
|
|
return new WcfAddEventTagResult(
|
|
payloadName,
|
|
payload.Length,
|
|
Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(),
|
|
openOutput.Handle,
|
|
true,
|
|
addReturnCode,
|
|
addOutputByteCount,
|
|
addOutput?.Length,
|
|
addOutput is null ? null : Convert.ToHexString(SHA256.HashData(addOutput)).ToLowerInvariant(),
|
|
eventAttempt.RequestSha256,
|
|
startSuccess,
|
|
queryHandle,
|
|
responseSize,
|
|
responseBuffer?.Length,
|
|
errorSize,
|
|
startNativeError?.Code,
|
|
null,
|
|
null,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfAddEventTagResult(
|
|
payloadName,
|
|
payload.Length,
|
|
Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(),
|
|
null,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
eventAttempt.RequestSha256,
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(retrievalChannel);
|
|
AbortOrClose(retrievalFactory);
|
|
AbortOrClose(historyChannel);
|
|
AbortOrClose(historyFactory);
|
|
}
|
|
}
|
|
|
|
static IReadOnlyList<WcfOpen2Attempt> CreateCredentialOpen2Attempts(string host)
|
|
{
|
|
string userName = Environment.GetEnvironmentVariable("HISTORIAN_USER") ?? string.Empty;
|
|
string password = Environment.GetEnvironmentVariable("HISTORIAN_PASSWORD") ?? string.Empty;
|
|
if (string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(password))
|
|
{
|
|
return [];
|
|
}
|
|
|
|
byte[] widePassword = HistorianOpen2Protocol.EncodeWidePassword(password);
|
|
byte[] nullTerminatedWidePassword = string.IsNullOrEmpty(password)
|
|
? [0, 0]
|
|
: [.. widePassword, 0, 0];
|
|
|
|
string[] userVariants = CreateUserNameVariants(userName);
|
|
ushort[] clientVersions = [0, 4, 11];
|
|
uint[] connectionModes = [2, 0];
|
|
(string Name, byte[] Bytes)[] passwordVariants =
|
|
[
|
|
("wide", widePassword),
|
|
("wide-null", nullTerminatedWidePassword)
|
|
];
|
|
|
|
List<WcfOpen2Attempt> attempts = [];
|
|
foreach (string variant in userVariants)
|
|
{
|
|
foreach ((string passwordVariantName, byte[] passwordBytes) in passwordVariants)
|
|
{
|
|
foreach (ushort clientVersion in clientVersions)
|
|
{
|
|
foreach (uint connectionMode in connectionModes)
|
|
{
|
|
attempts.Add(new WcfOpen2Attempt(
|
|
$"legacy-v1-env-{variant}-{passwordVariantName}-v{clientVersion}-mode{connectionMode}",
|
|
host,
|
|
HistorianWcfServiceNames.History,
|
|
ResolveUserNameVariant(userName, variant),
|
|
passwordBytes,
|
|
4,
|
|
clientVersion,
|
|
connectionMode,
|
|
UseWindowsTransportSecurity: false));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return attempts;
|
|
}
|
|
|
|
static string[] CreateUserNameVariants(string userName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(userName))
|
|
{
|
|
return ["empty-user"];
|
|
}
|
|
|
|
bool alreadyQualified = userName.Contains('\\', StringComparison.Ordinal) || userName.Contains('@', StringComparison.Ordinal);
|
|
return alreadyQualified
|
|
? ["as-entered"]
|
|
: ["as-entered", "dot-qualified", "machine-qualified"];
|
|
}
|
|
|
|
static string ResolveUserNameVariant(string userName, string variant)
|
|
{
|
|
return variant switch
|
|
{
|
|
"empty-user" => string.Empty,
|
|
"dot-qualified" => $".\\{userName}",
|
|
"machine-qualified" => $"{Environment.MachineName}\\{userName}",
|
|
_ => userName
|
|
};
|
|
}
|
|
|
|
static WcfOpen2Result OpenHistorySession2(int port, WcfOpen2Attempt attempt, TimeSpan timeout)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract2>? factory = null;
|
|
IHistoryServiceContract2? channel = null;
|
|
byte[] input = BuildOpen2Buffer(attempt);
|
|
try
|
|
{
|
|
factory = new ChannelFactory<IHistoryServiceContract2>(
|
|
CreateOpen2Binding(attempt, timeout),
|
|
CreateOpen2EndpointAddress(attempt, port));
|
|
if (attempt.UseWindowsTransportSecurity)
|
|
{
|
|
factory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
factory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
}
|
|
else if (attempt.UseCertificateTransportSecurity)
|
|
{
|
|
factory.Credentials.ServiceCertificate.SslCertificateAuthentication =
|
|
new X509ServiceCertificateAuthentication
|
|
{
|
|
CertificateValidationMode = X509CertificateValidationMode.None
|
|
};
|
|
}
|
|
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
byte[] inParameters = input;
|
|
bool success = channel.OpenConnection2(ref inParameters, out byte[]? outParameters, out byte[]? errorBuffer);
|
|
HistorianNativeError? nativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
HistorianLegacyOpen2Output? legacyOutput = outParameters is null ? null : HistorianOpen2Protocol.TryReadLegacyOpen2Output(outParameters);
|
|
HistorianNativeOpen3Output? nativeOpen3Output = outParameters is null ? null : HistorianOpen2Protocol.TryReadNativeOpen3Output(outParameters);
|
|
uint? returnedHandle = legacyOutput?.Handle ?? nativeOpen3Output?.Handle;
|
|
uint? closeHandleOneReturnCode = null;
|
|
uint? closeReturnedHandleReturnCode = null;
|
|
WcfClose2CandidateResult? close2CandidateResult = null;
|
|
WcfRetrievalHandleProbe? retrievalHandleOneProbe = null;
|
|
WcfRetrievalHandleProbe? retrievalReturnedHandleProbe = null;
|
|
if (success)
|
|
{
|
|
retrievalHandleOneProbe = ProbeRetrievalHandle(attempt.HostName, port, timeout, 1);
|
|
if (returnedHandle.HasValue)
|
|
{
|
|
retrievalReturnedHandleProbe = ProbeRetrievalHandle(attempt.HostName, port, timeout, returnedHandle.Value);
|
|
}
|
|
|
|
if (outParameters is { Length: >= 16 })
|
|
{
|
|
close2CandidateResult = TryClose2Candidates(channel, outParameters);
|
|
}
|
|
|
|
try
|
|
{
|
|
closeHandleOneReturnCode = channel.CloseConnection(1);
|
|
}
|
|
catch
|
|
{
|
|
closeHandleOneReturnCode = null;
|
|
}
|
|
|
|
if (returnedHandle.HasValue)
|
|
{
|
|
try
|
|
{
|
|
closeReturnedHandleReturnCode = channel.CloseConnection(returnedHandle.Value);
|
|
}
|
|
catch
|
|
{
|
|
closeReturnedHandleReturnCode = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return new WcfOpen2Result(
|
|
attempt.Name,
|
|
attempt.ServiceName,
|
|
success,
|
|
input.Length,
|
|
outParameters?.Length,
|
|
errorBuffer?.Length,
|
|
outParameters is null ? null : Convert.ToBase64String(outParameters),
|
|
errorBuffer is null ? null : Convert.ToBase64String(errorBuffer),
|
|
nativeError?.Type,
|
|
nativeError?.Code,
|
|
nativeError?.Name,
|
|
legacyOutput?.Handle,
|
|
legacyOutput?.ConnectTimeFileTimeUtc,
|
|
legacyOutput?.ServerStatus,
|
|
nativeOpen3Output?.ProtocolVersion,
|
|
nativeOpen3Output?.Handle,
|
|
nativeOpen3Output?.ConnectTimeFileTimeUtc,
|
|
nativeOpen3Output?.ServerTimeFileTimeUtc,
|
|
nativeOpen3Output?.TrailingBytes.Length,
|
|
closeHandleOneReturnCode,
|
|
closeReturnedHandleReturnCode,
|
|
close2CandidateResult?.SuccessfulVariant,
|
|
close2CandidateResult?.LastVariant,
|
|
close2CandidateResult?.LastErrorByteCount,
|
|
close2CandidateResult?.LastNativeError?.Type,
|
|
close2CandidateResult?.LastNativeError?.Code,
|
|
close2CandidateResult?.LastNativeError?.Name,
|
|
retrievalHandleOneProbe?.ReturnCode,
|
|
retrievalHandleOneProbe?.IsOriginalAllowed,
|
|
retrievalHandleOneProbe?.Error,
|
|
retrievalReturnedHandleProbe?.ReturnCode,
|
|
retrievalReturnedHandleProbe?.IsOriginalAllowed,
|
|
retrievalReturnedHandleProbe?.Error,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfOpen2Result(attempt.Name, attempt.ServiceName, false, input.Length, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
|
|
#pragma warning disable CA1416 // This reverse-engineering probe targets the Windows AVEVA Historian client stack.
|
|
static int ProbeWcfAuthContext(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort) ? parsedPort : HistorianWcfBindingFactory.DefaultPort;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
Guid clientKey = Guid.NewGuid();
|
|
string handle = clientKey.ToString("D");
|
|
string hostName = Environment.MachineName;
|
|
string processName = Path.GetFileNameWithoutExtension(Environment.ProcessPath) ?? "AVEVA.Historian.ReverseEngineering";
|
|
string userName = WindowsIdentity.GetCurrent().Name;
|
|
long connectTime = 0;
|
|
uint serverStatus = 0;
|
|
|
|
ChannelFactory<IHistoryServiceContract2>? factory = null;
|
|
IHistoryServiceContract2? channel = null;
|
|
try
|
|
{
|
|
factory = new ChannelFactory<IHistoryServiceContract2>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.HistoryIntegrated));
|
|
factory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
NetworkCredential? credential = TryCreateWindowsCredentialFromEnvironment();
|
|
if (credential is not null)
|
|
{
|
|
factory.Credentials.Windows.ClientCredential = credential;
|
|
}
|
|
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
bool validateSuccess = channel.ValidateClient2(
|
|
handle,
|
|
hostName,
|
|
processName,
|
|
checked((uint)Environment.ProcessId),
|
|
userName,
|
|
ref connectTime,
|
|
out serverStatus,
|
|
out byte[]? validateError);
|
|
HistorianNativeError? validateNativeError = validateError is null ? null : HistorianOpen2Protocol.TryReadNativeError(validateError);
|
|
|
|
bool? exchangeSuccess = null;
|
|
int? exchangeOutputLength = null;
|
|
string? exchangeOutputSha256 = null;
|
|
int? exchangeErrorLength = null;
|
|
HistorianNativeError? exchangeNativeError = null;
|
|
string? sharedSecretSha256 = null;
|
|
int? publicKeyLength = null;
|
|
string? publicKeySha256 = null;
|
|
if (validateSuccess)
|
|
{
|
|
using ECDiffieHellmanCng ecdh = new()
|
|
{
|
|
KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash,
|
|
HashAlgorithm = CngAlgorithm.Sha256,
|
|
KeySize = 256
|
|
};
|
|
#pragma warning disable SYSLIB0043 // Native wrapper uses ECDiffieHellmanCng public CNG blob bytes.
|
|
byte[] publicKey = ecdh.PublicKey.ToByteArray();
|
|
#pragma warning restore SYSLIB0043
|
|
publicKeyLength = publicKey.Length;
|
|
publicKeySha256 = Convert.ToHexString(SHA256.HashData(publicKey)).ToLowerInvariant();
|
|
exchangeSuccess = channel.ExchangeKey(handle, publicKey, out byte[]? exchangeOutput, out byte[]? exchangeError);
|
|
exchangeOutputLength = exchangeOutput?.Length;
|
|
exchangeErrorLength = exchangeError?.Length;
|
|
exchangeOutputSha256 = exchangeOutput is null || exchangeOutput.Length == 0
|
|
? null
|
|
: Convert.ToHexString(SHA256.HashData(exchangeOutput)).ToLowerInvariant();
|
|
exchangeNativeError = exchangeError is null ? null : HistorianOpen2Protocol.TryReadNativeError(exchangeError);
|
|
if (exchangeSuccess == true && exchangeOutput is { Length: > 0 })
|
|
{
|
|
using CngKey serverKey = CngKey.Import(exchangeOutput, CngKeyBlobFormat.EccPublicBlob);
|
|
byte[] sharedSecret = ecdh.DeriveKeyMaterial(serverKey);
|
|
sharedSecretSha256 = Convert.ToHexString(SHA256.HashData(sharedSecret)).ToLowerInvariant();
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Host = host,
|
|
Port = port,
|
|
Service = HistorianWcfServiceNames.HistoryIntegrated,
|
|
Binding = "MDAS Net.TCP Windows transport",
|
|
HandleSha256 = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(handle))).ToLowerInvariant(),
|
|
HostName = "<redacted>",
|
|
ProcessName = processName,
|
|
UserName = "<redacted>",
|
|
ValidateClient2 = new
|
|
{
|
|
Success = validateSuccess,
|
|
ConnectTimeFileTimeUtc = connectTime,
|
|
ServerStatus = serverStatus,
|
|
ErrorLength = validateError?.Length,
|
|
NativeError = validateNativeError is null ? null : new { validateNativeError.Type, validateNativeError.Code, validateNativeError.Name }
|
|
},
|
|
ExchangeKey = new
|
|
{
|
|
Success = exchangeSuccess,
|
|
PublicKeyLength = publicKeyLength,
|
|
PublicKeySha256 = publicKeySha256,
|
|
OutputLength = exchangeOutputLength,
|
|
OutputSha256 = exchangeOutputSha256,
|
|
ErrorLength = exchangeErrorLength,
|
|
NativeError = exchangeNativeError is null ? null : new { exchangeNativeError.Type, exchangeNativeError.Code, exchangeNativeError.Name },
|
|
SharedSecretSha256 = sharedSecretSha256
|
|
}
|
|
}, CreateJsonOptions()));
|
|
return 0;
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
#pragma warning restore CA1416
|
|
|
|
#pragma warning disable CA1416 // This reverse-engineering probe targets the Windows AVEVA Historian client stack.
|
|
static int ProbeWcfValidateClientCredential(string[] args)
|
|
{
|
|
string host = args.Length > 1 ? args[1] : "localhost";
|
|
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort) ? parsedPort : HistorianWcfBindingFactory.DefaultPort;
|
|
bool useCertificateTransport = HasOption(args, "--cert");
|
|
bool useNamedPipe = HasOption(args, "--pipe");
|
|
bool skipGetVersion = HasOption(args, "--skip-getv");
|
|
bool lazyOpen = HasOption(args, "--lazy-open");
|
|
bool runImpersonated = HasOption(args, "--run-impersonated");
|
|
bool lowerHandle = HasOption(args, "--lower-handle");
|
|
bool usePipeTransportSecurity = HasOption(args, "--pipe-transport-security");
|
|
bool useStaticChannelFactory = HasOption(args, "--static-channel-factory");
|
|
bool seedExchangeKey = HasOption(args, "--exchange-key");
|
|
bool impersonateChannelSetup = HasOption(args, "--impersonate-channel-setup");
|
|
if (useCertificateTransport && useNamedPipe)
|
|
{
|
|
throw new ArgumentException("--cert and --pipe are mutually exclusive.");
|
|
}
|
|
if (useStaticChannelFactory && !useNamedPipe)
|
|
{
|
|
throw new ArgumentException("--static-channel-factory currently applies only to --pipe.");
|
|
}
|
|
|
|
string targetName = args.Skip(3).FirstOrDefault(static arg => !arg.StartsWith("--", StringComparison.Ordinal)) ?? @"NT SERVICE\aahClientAccessPoint";
|
|
string serviceName = useNamedPipe
|
|
? HistorianWcfServiceNames.History
|
|
: useCertificateTransport ? HistorianWcfServiceNames.HistoryCertificate : HistorianWcfServiceNames.HistoryIntegrated;
|
|
TimeSpan timeout = TimeSpan.FromSeconds(10);
|
|
string handle = Guid.NewGuid().ToString("D");
|
|
handle = lowerHandle ? handle.ToLowerInvariant() : handle.ToUpperInvariant();
|
|
NetworkCredential? configuredCredential = TryCreateWindowsCredentialFromEnvironment();
|
|
List<object> rounds = [];
|
|
|
|
ChannelFactory<IHistoryServiceContract2>? factory = null;
|
|
IHistoryServiceContract2? channel = null;
|
|
try
|
|
{
|
|
Binding binding = useNamedPipe
|
|
? CreateMdasNetNamedPipeBinding(timeout, 66303, usePipeTransportSecurity)
|
|
: useCertificateTransport
|
|
? HistorianWcfBindingFactory.CreateMdasNetTcpCertificateBinding(timeout)
|
|
: HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout);
|
|
EndpointAddress endpointAddress = useNamedPipe
|
|
? new EndpointAddress($"net.pipe://localhost/{serviceName}")
|
|
: HistorianWcfBindingFactory.CreateEndpointAddress(host, port, serviceName);
|
|
if (useStaticChannelFactory)
|
|
{
|
|
factory = new ChannelFactory<IHistoryServiceContract2>(binding);
|
|
}
|
|
else
|
|
{
|
|
factory = new ChannelFactory<IHistoryServiceContract2>(
|
|
binding,
|
|
endpointAddress);
|
|
}
|
|
|
|
if (useCertificateTransport && factory is not null)
|
|
{
|
|
factory.Credentials.ServiceCertificate.SslCertificateAuthentication =
|
|
new X509ServiceCertificateAuthentication
|
|
{
|
|
CertificateValidationMode = X509CertificateValidationMode.None
|
|
};
|
|
}
|
|
else if (factory is not null)
|
|
{
|
|
factory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
|
if (configuredCredential is not null)
|
|
{
|
|
factory.Credentials.Windows.ClientCredential = configuredCredential;
|
|
}
|
|
}
|
|
|
|
void SetupChannel()
|
|
{
|
|
channel ??= useStaticChannelFactory
|
|
? factory!.CreateChannel(endpointAddress)
|
|
: factory!.CreateChannel();
|
|
if (channel is IContextChannel contextChannel)
|
|
{
|
|
contextChannel.OperationTimeout = TimeSpan.FromMinutes(10);
|
|
}
|
|
|
|
if (!lazyOpen)
|
|
{
|
|
if (!useNamedPipe && factory is not null)
|
|
{
|
|
factory.Open();
|
|
}
|
|
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (impersonateChannelSetup)
|
|
{
|
|
WindowsIdentity.RunImpersonated(WindowsIdentity.GetCurrent().AccessToken, SetupChannel);
|
|
}
|
|
else
|
|
{
|
|
SetupChannel();
|
|
}
|
|
|
|
IHistoryServiceContract2 activeChannel = channel
|
|
?? throw new InvalidOperationException("WCF channel was not created.");
|
|
uint? getVersionReturnCode = null;
|
|
uint? interfaceVersion = null;
|
|
if (!skipGetVersion)
|
|
{
|
|
uint returnCode;
|
|
uint version = 0;
|
|
if (runImpersonated)
|
|
{
|
|
returnCode = WindowsIdentity.RunImpersonated(WindowsIdentity.GetCurrent().AccessToken, () =>
|
|
activeChannel.GetInterfaceVersion(out version));
|
|
}
|
|
else
|
|
{
|
|
returnCode = activeChannel.GetInterfaceVersion(out version);
|
|
}
|
|
|
|
getVersionReturnCode = returnCode;
|
|
interfaceVersion = version;
|
|
}
|
|
|
|
bool? exchangeSuccess = null;
|
|
int? exchangePublicKeyLength = null;
|
|
string? exchangePublicKeySha256 = null;
|
|
int? exchangeOutputLength = null;
|
|
string? exchangeOutputSha256 = null;
|
|
int? exchangeErrorLength = null;
|
|
HistorianNativeError? exchangeNativeError = null;
|
|
string? exchangeSharedSecretSha256 = null;
|
|
if (seedExchangeKey)
|
|
{
|
|
using ECDiffieHellmanCng ecdh = new()
|
|
{
|
|
KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash,
|
|
HashAlgorithm = CngAlgorithm.Sha256,
|
|
KeySize = 256
|
|
};
|
|
#pragma warning disable SYSLIB0043 // Native wrapper uses ECDiffieHellmanCng public CNG blob bytes.
|
|
byte[] publicKey = ecdh.PublicKey.ToByteArray();
|
|
#pragma warning restore SYSLIB0043
|
|
exchangePublicKeyLength = publicKey.Length;
|
|
exchangePublicKeySha256 = HashBytesOrNull(publicKey);
|
|
exchangeSuccess = activeChannel.ExchangeKey(handle, publicKey, out byte[]? exchangeOutput, out byte[]? exchangeError);
|
|
exchangeOutput ??= [];
|
|
exchangeError ??= [];
|
|
exchangeOutputLength = exchangeOutput.Length;
|
|
exchangeOutputSha256 = HashBytesOrNull(exchangeOutput);
|
|
exchangeErrorLength = exchangeError.Length;
|
|
exchangeNativeError = HistorianOpen2Protocol.TryReadNativeError(exchangeError);
|
|
if (exchangeSuccess == true && exchangeOutput.Length > 0)
|
|
{
|
|
using CngKey serverKey = CngKey.Import(exchangeOutput, CngKeyBlobFormat.EccPublicBlob);
|
|
byte[] sharedSecret = ecdh.DeriveKeyMaterial(serverKey);
|
|
exchangeSharedSecretSha256 = HashBytesOrNull(sharedSecret);
|
|
}
|
|
}
|
|
|
|
using NegotiateAuthentication auth = new(new NegotiateAuthenticationClientOptions
|
|
{
|
|
Package = "Negotiate",
|
|
TargetName = targetName,
|
|
RequiredProtectionLevel = ProtectionLevel.EncryptAndSign,
|
|
Credential = configuredCredential ?? CredentialCache.DefaultNetworkCredentials
|
|
});
|
|
|
|
byte[] incoming = [];
|
|
bool? finalServerSuccess = null;
|
|
string? finalStatus = null;
|
|
int? finalServerOutputLength = null;
|
|
HistorianNativeError? finalNativeError = null;
|
|
for (int round = 0; round < 8; round++)
|
|
{
|
|
byte[]? outgoing = auth.GetOutgoingBlob(incoming, out NegotiateAuthenticationStatusCode statusCode);
|
|
outgoing ??= [];
|
|
HistorianWcfAuthenticationProtocol.TryApplyNativeNtlmNegotiateVersionFlag(outgoing);
|
|
byte[] wrappedOutgoing = HistorianWcfAuthenticationProtocol.WrapValidateClientCredentialToken(round == 0, outgoing);
|
|
|
|
bool serverSuccess;
|
|
byte[]? serverOutput;
|
|
byte[]? errorBuffer;
|
|
if (runImpersonated)
|
|
{
|
|
(serverSuccess, serverOutput, errorBuffer) = WindowsIdentity.RunImpersonated(
|
|
WindowsIdentity.GetCurrent().AccessToken,
|
|
() =>
|
|
{
|
|
bool success = activeChannel.ValidateClientCredential(
|
|
handle,
|
|
wrappedOutgoing,
|
|
out byte[]? output,
|
|
out byte[]? error);
|
|
return (success, output, error);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
serverSuccess = activeChannel.ValidateClientCredential(
|
|
handle,
|
|
wrappedOutgoing,
|
|
out serverOutput,
|
|
out errorBuffer);
|
|
}
|
|
|
|
serverOutput ??= [];
|
|
ValidateClientCredentialResponse? parsedServerOutput =
|
|
HistorianWcfAuthenticationProtocol.TryReadValidateClientCredentialResponse(serverOutput);
|
|
bool serverContinue = parsedServerOutput?.Continue is true;
|
|
byte[] serverToken = parsedServerOutput?.Token ?? [];
|
|
HistorianNativeError? nativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
rounds.Add(new
|
|
{
|
|
Round = round,
|
|
ClientStatus = statusCode.ToString(),
|
|
OutgoingLength = outgoing.Length,
|
|
OutgoingSha256 = HashBytesOrNull(outgoing),
|
|
OutgoingPrefixHex = ToPrefixHex(outgoing, 32),
|
|
WrappedOutgoingLength = wrappedOutgoing.Length,
|
|
WrappedOutgoingSha256 = HashBytesOrNull(wrappedOutgoing),
|
|
WrappedOutgoingPrefixHex = ToPrefixHex(wrappedOutgoing, 32),
|
|
ServerSuccess = serverSuccess,
|
|
ServerOutputLength = serverOutput.Length,
|
|
ServerOutputSha256 = HashBytesOrNull(serverOutput),
|
|
ServerOutputPrefixHex = ToPrefixHex(serverOutput, 32),
|
|
ServerContinue = serverContinue,
|
|
ServerTokenLength = serverToken.Length,
|
|
ServerTokenSha256 = HashBytesOrNull(serverToken),
|
|
ErrorLength = errorBuffer?.Length,
|
|
NativeError = nativeError is null ? null : new { nativeError.Type, nativeError.Code, nativeError.Name }
|
|
});
|
|
|
|
finalServerSuccess = serverSuccess;
|
|
finalStatus = statusCode.ToString();
|
|
finalServerOutputLength = serverOutput.Length;
|
|
finalNativeError = nativeError;
|
|
if (!serverSuccess)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (statusCode == NegotiateAuthenticationStatusCode.Completed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded)
|
|
{
|
|
break;
|
|
}
|
|
|
|
incoming = serverToken;
|
|
if (incoming.Length == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(JsonSerializer.Serialize(new
|
|
{
|
|
Host = host,
|
|
Port = port,
|
|
Service = serviceName,
|
|
Operation = "ValCl",
|
|
TransportSecurity = useNamedPipe ? "NamedPipeNone" : useCertificateTransport ? "Certificate" : "Windows",
|
|
InterfaceVersion = interfaceVersion,
|
|
GetVersionReturnCode = getVersionReturnCode,
|
|
SkippedGetInterfaceVersion = skipGetVersion,
|
|
LazyOpen = lazyOpen,
|
|
UsedStaticChannelFactory = useStaticChannelFactory,
|
|
SeededExchangeKey = seedExchangeKey,
|
|
ImpersonatedChannelSetup = impersonateChannelSetup,
|
|
UsedNamedPipe = useNamedPipe,
|
|
UsedPipeTransportSecurity = usePipeTransportSecurity,
|
|
RanImpersonated = runImpersonated,
|
|
LowerHandle = lowerHandle,
|
|
TargetName = targetName,
|
|
HandleSha256 = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(handle))).ToLowerInvariant(),
|
|
HandleLength = handle.Length,
|
|
HandleHyphenCount = handle.Count(static c => c == '-'),
|
|
UsedEnvironmentCredential = configuredCredential is not null,
|
|
IsAuthenticated = auth.IsAuthenticated,
|
|
IsEncrypted = auth.IsEncrypted,
|
|
IsSigned = auth.IsSigned,
|
|
Package = auth.Package,
|
|
ExchangeKey = new
|
|
{
|
|
Success = exchangeSuccess,
|
|
PublicKeyLength = exchangePublicKeyLength,
|
|
PublicKeySha256 = exchangePublicKeySha256,
|
|
OutputLength = exchangeOutputLength,
|
|
OutputSha256 = exchangeOutputSha256,
|
|
ErrorLength = exchangeErrorLength,
|
|
NativeError = exchangeNativeError is null ? null : new { exchangeNativeError.Type, exchangeNativeError.Code, exchangeNativeError.Name },
|
|
SharedSecretSha256 = exchangeSharedSecretSha256
|
|
},
|
|
FinalStatus = finalStatus,
|
|
FinalServerSuccess = finalServerSuccess,
|
|
FinalServerOutputLength = finalServerOutputLength,
|
|
FinalNativeError = finalNativeError is null ? null : new { finalNativeError.Type, finalNativeError.Code, finalNativeError.Name },
|
|
Rounds = rounds
|
|
}, CreateJsonOptions()));
|
|
return 0;
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
#pragma warning restore CA1416
|
|
|
|
static string? HashBytesOrNull(byte[] bytes)
|
|
{
|
|
return bytes.Length == 0
|
|
? null
|
|
: Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
|
}
|
|
|
|
static Binding CreateMdasNetNamedPipeBinding(TimeSpan timeout, int maxBufferSize, bool useTransportSecurity)
|
|
{
|
|
NetNamedPipeBinding nativeShape = new()
|
|
{
|
|
MaxBufferSize = maxBufferSize,
|
|
MaxReceivedMessageSize = maxBufferSize
|
|
};
|
|
nativeShape.Security.Mode = useTransportSecurity
|
|
? NetNamedPipeSecurityMode.Transport
|
|
: NetNamedPipeSecurityMode.None;
|
|
nativeShape.ReaderQuotas.MaxArrayLength = maxBufferSize;
|
|
|
|
BindingElementCollection elements = nativeShape.CreateBindingElements();
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
if (elements[i] is MessageEncodingBindingElement encoding)
|
|
{
|
|
elements[i] = new MdasMessageEncodingBindingElement(encoding);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new CustomBinding(elements)
|
|
{
|
|
CloseTimeout = timeout,
|
|
OpenTimeout = timeout,
|
|
ReceiveTimeout = timeout,
|
|
SendTimeout = timeout
|
|
};
|
|
}
|
|
|
|
static string ToPrefixHex(byte[] bytes, int maxBytes)
|
|
{
|
|
int count = Math.Min(bytes.Length, maxBytes);
|
|
StringBuilder builder = new(count * 2);
|
|
for (int index = 0; index < count; index++)
|
|
{
|
|
builder.Append(bytes[index].ToString("x2"));
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
static Binding CreateOpen2Binding(WcfOpen2Attempt attempt, TimeSpan timeout)
|
|
{
|
|
if (attempt.UseWindowsTransportSecurity)
|
|
{
|
|
return HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(timeout);
|
|
}
|
|
|
|
if (attempt.UseCertificateTransportSecurity)
|
|
{
|
|
return HistorianWcfBindingFactory.CreateMdasNetTcpCertificateBinding(timeout);
|
|
}
|
|
|
|
return HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout);
|
|
}
|
|
|
|
static EndpointAddress CreateOpen2EndpointAddress(WcfOpen2Attempt attempt, int port)
|
|
{
|
|
Uri endpoint = HistorianWcfBindingFactory.CreateEndpointAddress(attempt.HostName, port, attempt.ServiceName).Uri;
|
|
return attempt.UseCertificateTransportSecurity
|
|
? new EndpointAddress(endpoint, new DnsEndpointIdentity("localhost"))
|
|
: new EndpointAddress(endpoint);
|
|
}
|
|
|
|
static WcfClose2CandidateResult TryClose2Candidates(IHistoryServiceContract2 channel, byte[] output)
|
|
{
|
|
List<(string Variant, string Handle)> candidates =
|
|
[
|
|
("first16-guid-dotnet-d", new Guid(output.AsSpan(0, 16)).ToString("D")),
|
|
("first16-guid-rfc4122-d", CreateRfc4122Guid(output.AsSpan(0, 16)).ToString("D")),
|
|
("first16-hex", Convert.ToHexString(output.AsSpan(0, 16)).ToLowerInvariant()),
|
|
("first16-base64", Convert.ToBase64String(output.AsSpan(0, 16).ToArray())),
|
|
("full32-hex", Convert.ToHexString(output).ToLowerInvariant()),
|
|
("full32-base64", Convert.ToBase64String(output))
|
|
];
|
|
|
|
string? lastVariant = null;
|
|
int? lastErrorByteCount = null;
|
|
HistorianNativeError? lastNativeError = null;
|
|
foreach ((string variant, string handle) in candidates)
|
|
{
|
|
lastVariant = variant;
|
|
try
|
|
{
|
|
bool success = channel.CloseConnection2(handle, out byte[]? errorBuffer);
|
|
lastErrorByteCount = errorBuffer?.Length;
|
|
lastNativeError = errorBuffer is null ? null : HistorianOpen2Protocol.TryReadNativeError(errorBuffer);
|
|
if (success)
|
|
{
|
|
return new WcfClose2CandidateResult(variant, lastVariant, lastErrorByteCount, lastNativeError);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
lastErrorByteCount = null;
|
|
lastNativeError = null;
|
|
}
|
|
}
|
|
|
|
return new WcfClose2CandidateResult(null, lastVariant, lastErrorByteCount, lastNativeError);
|
|
}
|
|
|
|
static Guid CreateRfc4122Guid(ReadOnlySpan<byte> bytes)
|
|
{
|
|
Span<byte> guidBytes = stackalloc byte[16];
|
|
bytes[..16].CopyTo(guidBytes);
|
|
guidBytes[..4].Reverse();
|
|
guidBytes[4..6].Reverse();
|
|
guidBytes[6..8].Reverse();
|
|
return new Guid(guidBytes);
|
|
}
|
|
|
|
static WcfRetrievalHandleProbe ProbeRetrievalHandle(string host, int port, TimeSpan timeout, uint handle)
|
|
{
|
|
ChannelFactory<IRetrievalServiceContract>? factory = null;
|
|
IRetrievalServiceContract? channel = null;
|
|
try
|
|
{
|
|
factory = new ChannelFactory<IRetrievalServiceContract>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(host, port, HistorianWcfServiceNames.Retrieval));
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
uint returnCode = channel.IsOriginalAllowed(handle, out bool isAllowed);
|
|
return new WcfRetrievalHandleProbe(returnCode, isAllowed, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfRetrievalHandleProbe(null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
|
|
static byte[] BuildOpen2Buffer(WcfOpen2Attempt attempt)
|
|
{
|
|
string processName = Path.GetFileNameWithoutExtension(Environment.ProcessPath) ?? "AVEVA.Historian.ReverseEngineering";
|
|
HistorianOpen2Request request = new(
|
|
attempt.HostName,
|
|
processName,
|
|
(uint)Environment.ProcessId,
|
|
attempt.UserName,
|
|
attempt.Password,
|
|
attempt.ClientType,
|
|
attempt.ClientVersion,
|
|
attempt.ConnectionMode,
|
|
HistorianMetadataNamespace.Empty);
|
|
|
|
if (attempt.UseNativeVersion3Buffer)
|
|
{
|
|
return HistorianOpen2Protocol.SerializeNativeVersion3(request, new HistorianClientCommonInfo(
|
|
FormatVersion: 3,
|
|
ServerNodeName: attempt.HostName,
|
|
ClientNodeName: Environment.MachineName,
|
|
ProcessId: (uint)Environment.ProcessId,
|
|
HcalVersion: 17,
|
|
ProcessName: processName,
|
|
Proxy: string.Empty,
|
|
DataSourceId: string.Empty,
|
|
ShardId: Guid.Empty,
|
|
ClientVersion: attempt.ClientVersion,
|
|
ClientTimestamp: (ulong)DateTime.UtcNow.ToFileTimeUtc(),
|
|
ClientDllVersion: string.Empty));
|
|
}
|
|
|
|
if (attempt.UseNativeOpenConnection3Version6Buffer)
|
|
{
|
|
return HistorianOpen2Protocol.SerializeNativeOpenConnection3Version6(request, new HistorianClientCommonInfo(
|
|
FormatVersion: 4,
|
|
ServerNodeName: FitExactLength(Environment.MachineName, 15),
|
|
ClientNodeName: FitExactLength($"Server({attempt.HostName}), Client({Environment.MachineName})", 44),
|
|
ProcessId: (uint)Environment.ProcessId,
|
|
HcalVersion: 17,
|
|
ProcessName: string.Empty,
|
|
Proxy: string.Empty,
|
|
DataSourceId: FitExactLength("aahClient.dll()", 15),
|
|
ShardId: Guid.Empty,
|
|
ClientVersion: 999999,
|
|
ClientTimestamp: (ulong)DateTime.UtcNow.ToFileTimeUtc(),
|
|
ClientDllVersion: FitExactLength("aahClientManaged.dll(2020)", 25)),
|
|
Guid.NewGuid(),
|
|
new byte[1026]);
|
|
}
|
|
|
|
return HistorianOpen2Protocol.SerializeLegacyVersion1(request);
|
|
}
|
|
|
|
static string FitExactLength(string value, int length)
|
|
{
|
|
if (value.Length == length)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return value.Length > length
|
|
? value[..length]
|
|
: value.PadRight(length);
|
|
}
|
|
|
|
static NetworkCredential? TryCreateWindowsCredentialFromEnvironment()
|
|
{
|
|
string userName = Environment.GetEnvironmentVariable("HISTORIAN_USER") ?? string.Empty;
|
|
string password = Environment.GetEnvironmentVariable("HISTORIAN_PASSWORD") ?? string.Empty;
|
|
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrEmpty(password))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int slash = userName.IndexOf('\\');
|
|
if (slash > 0 && slash < userName.Length - 1)
|
|
{
|
|
return new NetworkCredential(userName[(slash + 1)..], password, userName[..slash]);
|
|
}
|
|
|
|
return new NetworkCredential(userName, password);
|
|
}
|
|
|
|
static byte[] CreateVersionedBuffer(int version, int width)
|
|
{
|
|
byte[] buffer = new byte[1026];
|
|
if (width == 2)
|
|
{
|
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer, checked((ushort)version));
|
|
}
|
|
else
|
|
{
|
|
BinaryPrimitives.WriteUInt32LittleEndian(buffer, checked((uint)version));
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static WcfOpenResult OpenHistorySession(int port, WcfOpenAttempt attempt, TimeSpan timeout)
|
|
{
|
|
ChannelFactory<IHistoryServiceContract>? factory = null;
|
|
IHistoryServiceContract? channel = null;
|
|
uint handle = 0;
|
|
try
|
|
{
|
|
factory = new ChannelFactory<IHistoryServiceContract>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
HistorianWcfBindingFactory.CreateEndpointAddress(attempt.HostName, port, HistorianWcfServiceNames.History));
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
string storageSessionId = attempt.StorageSessionId;
|
|
string processName = Path.GetFileNameWithoutExtension(Environment.ProcessPath) ?? "AVEVA.Historian.ReverseEngineering";
|
|
uint returnCode = channel.OpenConnection(
|
|
attempt.HostName,
|
|
processName,
|
|
(uint)Environment.ProcessId,
|
|
attempt.UserName,
|
|
attempt.Password,
|
|
attempt.PasswordLength,
|
|
clientType: attempt.ClientType,
|
|
attempt.ClientVersion,
|
|
connectionMode: 2,
|
|
connectionTimeout: 1000,
|
|
ref storageSessionId,
|
|
out handle,
|
|
out long connectTime,
|
|
out uint serverStatus);
|
|
|
|
if (handle != 0)
|
|
{
|
|
_ = channel.CloseConnection(handle);
|
|
}
|
|
|
|
return new WcfOpenResult(attempt.Name, attempt.ClientType, attempt.ClientVersion, returnCode == 0 && handle != 0, returnCode, handle, connectTime, serverStatus, storageSessionId, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfOpenResult(attempt.Name, attempt.ClientType, attempt.ClientVersion, false, null, handle == 0 ? null : handle, null, null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
|
|
static IEnumerable<string> GetCandidatePaths(string serviceName)
|
|
{
|
|
yield return serviceName;
|
|
yield return $"HCAP/{serviceName}";
|
|
yield return $"hcap/{serviceName}";
|
|
yield return $"aa/{serviceName}";
|
|
}
|
|
|
|
static WcfProbeResult ProbeService<TContract>(
|
|
string host,
|
|
int port,
|
|
string path,
|
|
Func<TContract, WcfProbeCallResult> call,
|
|
TimeSpan timeout)
|
|
where TContract : class
|
|
{
|
|
Uri endpoint = HistorianWcfBindingFactory.CreateEndpointAddress(host, port, path).Uri;
|
|
ChannelFactory<TContract>? factory = null;
|
|
TContract? channel = null;
|
|
try
|
|
{
|
|
factory = new ChannelFactory<TContract>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpBinding(timeout),
|
|
new EndpointAddress(endpoint));
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
WcfProbeCallResult callResult = call(channel);
|
|
|
|
return new WcfProbeResult(path, endpoint.ToString(), true, callResult.ReturnCode, callResult.InterfaceVersion, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfProbeResult(path, endpoint.ToString(), false, null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
|
|
static WcfProbeResult ProbeCertificateService<TContract>(
|
|
string host,
|
|
int port,
|
|
string path,
|
|
Func<TContract, WcfProbeCallResult> call,
|
|
TimeSpan timeout,
|
|
string? dnsIdentity)
|
|
where TContract : class
|
|
{
|
|
Uri endpoint = HistorianWcfBindingFactory.CreateEndpointAddress(host, port, path).Uri;
|
|
EndpointAddress endpointAddress = string.IsNullOrWhiteSpace(dnsIdentity)
|
|
? new EndpointAddress(endpoint)
|
|
: new EndpointAddress(endpoint, new DnsEndpointIdentity(dnsIdentity));
|
|
ChannelFactory<TContract>? factory = null;
|
|
TContract? channel = null;
|
|
try
|
|
{
|
|
factory = new ChannelFactory<TContract>(
|
|
HistorianWcfBindingFactory.CreateMdasNetTcpCertificateBinding(timeout),
|
|
endpointAddress);
|
|
factory.Credentials.ServiceCertificate.SslCertificateAuthentication =
|
|
new X509ServiceCertificateAuthentication
|
|
{
|
|
CertificateValidationMode = X509CertificateValidationMode.None
|
|
};
|
|
factory.Open();
|
|
channel = factory.CreateChannel();
|
|
if (channel is IClientChannel clientChannel)
|
|
{
|
|
clientChannel.Open();
|
|
}
|
|
|
|
WcfProbeCallResult callResult = call(channel);
|
|
|
|
return new WcfProbeResult(path, endpoint.ToString(), true, callResult.ReturnCode, callResult.InterfaceVersion, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new WcfProbeResult(path, endpoint.ToString(), false, null, null, FormatException(ex));
|
|
}
|
|
finally
|
|
{
|
|
AbortOrClose(channel);
|
|
AbortOrClose(factory);
|
|
}
|
|
}
|
|
|
|
static void AbortOrClose(object? communicationObject)
|
|
{
|
|
if (communicationObject is ICommunicationObject clientChannel)
|
|
{
|
|
try
|
|
{
|
|
if (clientChannel.State == CommunicationState.Faulted)
|
|
{
|
|
clientChannel.Abort();
|
|
}
|
|
else
|
|
{
|
|
clientChannel.Close();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
clientChannel.Abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
static string FormatException(Exception exception)
|
|
{
|
|
StringBuilder builder = new();
|
|
for (Exception? current = exception; current is not null; current = current.InnerException)
|
|
{
|
|
if (builder.Length > 0)
|
|
{
|
|
builder.Append(" -> ");
|
|
}
|
|
|
|
builder.Append(current.GetType().Name);
|
|
builder.Append(": ");
|
|
builder.Append(current.Message);
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
static int UnknownCommand(string command)
|
|
{
|
|
Console.Error.WriteLine($"Unknown command '{command}'.");
|
|
PrintHelp();
|
|
return 2;
|
|
}
|
|
|
|
static void PrintHelp()
|
|
{
|
|
Console.WriteLine("""
|
|
AVEVA.Historian.ReverseEngineering
|
|
|
|
Commands:
|
|
exports [dll-path] [--json] Print PE exports and SHA256 for a native DLL.
|
|
manifest [output-path] Write capture-manifest.json for planned scenarios.
|
|
methods [dll-path] [filter] Print MethodDef tokens and RVAs from a managed/mixed DLL.
|
|
dnlib-method [dll-path] [filter] Inspect method IL calls with dnlib; optional --write-copy <path>.
|
|
method-refs [dll-path] [token] List methods whose IL directly references a metadata token.
|
|
instrument-startdataquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs DataQueryRequest bytes and the native client-handle candidate.
|
|
instrument-getnextrow [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs query handle and DataQueryResultRow memory.
|
|
instrument-wcf-readquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs WCF StartQuery2/GetNextQueryResultBuffer2 buffers.
|
|
instrument-wcf-openconnection [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs WCF OpenConnection return/session scalars.
|
|
instrument-historianclient-openconnection [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs HistorianClient.OpenConnection success and handle.
|
|
instrument-cserverclient-gethandle [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs CServerClient.GetHandle return values.
|
|
instrument-retrieval-console-startquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs CRetrievalConsoleClient.StartQuery handle candidates.
|
|
instrument-retrieval-upper-startquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs CRetrieval/CSrv StartQuery handle candidates.
|
|
instrument-cclientcommon-startquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs CClientCommon.StartQuery WCF handle candidates.
|
|
instrument-cclientbase-openconnection [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs CClientBase.OpenConnection handle creation candidates.
|
|
instrument-starteventquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs EventQueryRequest bytes.
|
|
instrument-getnexteventrow [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs EventQueryResultRow memory.
|
|
instrument-openconnection2 [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs OpenConnection2 request/response bytes.
|
|
instrument-openconnection3 [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs OpenConnection3 request/response bytes.
|
|
instrument-cclientinfo-context [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs CClientInfo context/client-key GUID bytes.
|
|
instrument-wcf-auth-context [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs sanitized ValidateClient/ExchangeKey metadata.
|
|
instrument-starttagquery [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs StartTagQuery request bytes.
|
|
instrument-tagquery-gettaginfo [dll-path] [output-path]
|
|
Write a reverse-only wrapper copy that logs TagQuery CTagMetadata vectors.
|
|
mark <scenario-name> Emit a timestamp marker for Wireshark/API Monitor notes.
|
|
wcf-probe [host] [port] Probe Hist/Retr/Stat WCF GetV endpoints with MDAS encoding.
|
|
wcf-cert-probe [host] [port] [dns]
|
|
Probe HistCert GetV with MDAS over TLS transport security.
|
|
wcf-status [host] [port] [param] Probe Stat operations that may not require a session handle.
|
|
wcf-open [host] [port] Try read-only Hist.OpenConnection with managed WCF/MDAS.
|
|
wcf-open2 [host] [port] Try legacy byte-buffer Hist.Open2 with managed WCF/MDAS.
|
|
wcf-auth-context [host] [port] Probe Hist-Integrated ValidateClient2 plus ExchangeKey context setup.
|
|
wcf-auth-valcl [host] [port] [target] [--cert|--pipe] [--run-impersonated] [--lower-handle] [--pipe-transport-security] [--static-channel-factory] [--exchange-key] [--impersonate-channel-setup]
|
|
Probe ValCl with Negotiate SSPI client tokens.
|
|
wcf-tag-info [host] [port] [tag] Open Hist-Integrated and probe simple Retr tag metadata calls.
|
|
wcf-like-tag-browse [host] [port] [filter]
|
|
Open Hist-Integrated and probe Retr StartLikeTagNameSearch/GetLikeTagnames.
|
|
wcf-tag-query [host] [port] [filter]
|
|
Open Hist-Integrated and probe Retr StartTagQuery/QueryTag handle formats.
|
|
wcf-start-query [host] [port] [tag] [--max-attempts n] [--timeout-seconds n]
|
|
Open Hist-Integrated and probe Retr.StartQuery2.
|
|
wcf-start-event-query [host] [port] [event-count]
|
|
Open Hist-Integrated and probe Retr.StartEventQuery.
|
|
wcf-register-event-tag [host] [port] [event-count]
|
|
Probe Hist.RTag2 GUID-vector registration before event query.
|
|
wcf-add-event-tag [host] [port] [event-count]
|
|
Probe Hist.AddT with synthetic CM_EVENT metadata before event query.
|
|
""");
|
|
}
|
|
|
|
static JsonSerializerOptions CreateJsonOptions()
|
|
{
|
|
return new JsonSerializerOptions
|
|
{
|
|
WriteIndented = true
|
|
};
|
|
}
|
|
|
|
static string? GetOption(string[] args, string optionName)
|
|
{
|
|
for (int i = 0; i < args.Length - 1; i++)
|
|
{
|
|
if (args[i].Equals(optionName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return args[i + 1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static int? TryReadIntOption(string[] args, string optionName)
|
|
{
|
|
string? value = GetOption(args, optionName);
|
|
return int.TryParse(value, out int parsed) ? parsed : null;
|
|
}
|
|
|
|
static bool HasOption(string[] args, string optionName)
|
|
{
|
|
return args.Any(arg => arg.Equals(optionName, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
internal sealed record CaptureManifest(
|
|
DateTimeOffset GeneratedUtc,
|
|
IReadOnlyList<NativeBinaryInventory> Binaries,
|
|
IReadOnlyList<CaptureScenario> Scenarios);
|
|
|
|
internal sealed record CaptureMarker(string Scenario, DateTimeOffset TimestampUtc, int ProcessId);
|
|
|
|
internal sealed record WcfProbeCallResult(uint ReturnCode, uint InterfaceVersion);
|
|
|
|
internal sealed record WcfProbeResult(
|
|
string Service,
|
|
string Endpoint,
|
|
bool Success,
|
|
uint? ReturnCode,
|
|
uint? InterfaceVersion,
|
|
string? Error);
|
|
|
|
internal sealed record WcfOpenAttempt(
|
|
string Name,
|
|
string HostName,
|
|
string UserName,
|
|
byte[] Password,
|
|
ushort PasswordLength,
|
|
string StorageSessionId,
|
|
byte ClientType,
|
|
ushort ClientVersion);
|
|
|
|
internal sealed record WcfOpenResult(
|
|
string Attempt,
|
|
byte ClientType,
|
|
ushort ClientVersion,
|
|
bool Success,
|
|
uint? ReturnCode,
|
|
uint? Handle,
|
|
long? ConnectTime,
|
|
uint? ServerStatus,
|
|
string? StorageSessionId,
|
|
string? Error);
|
|
|
|
internal sealed record WcfStatusResult(
|
|
string Service,
|
|
string Endpoint,
|
|
bool Success,
|
|
uint? InterfaceReturnCode,
|
|
uint? InterfaceVersion,
|
|
uint? ServerTimeReturnCode,
|
|
uint? SystemTimeSize,
|
|
int? SystemTimeByteCount,
|
|
string? SystemTimeBase64,
|
|
string? ParsedServerTime,
|
|
string? ServerTimeError,
|
|
uint? TimeZoneNameReturnCode,
|
|
string? SystemTimeZoneName,
|
|
string? TimeZoneNameError,
|
|
uint? CaseSensitiveReturnCode,
|
|
bool? IsDbCaseSensitive,
|
|
string? CaseSensitiveError,
|
|
string? SystemParameterName,
|
|
bool? SystemParameterSuccess,
|
|
string? SystemParameterValue,
|
|
uint? SystemParameterErrorSize,
|
|
string? SystemParameterErrorBase64,
|
|
byte? SystemParameterNativeErrorType,
|
|
uint? SystemParameterNativeErrorCode,
|
|
string? SystemParameterNativeErrorName,
|
|
string? SystemParameterError,
|
|
string? Error);
|
|
|
|
internal sealed record WcfOpen2Attempt(
|
|
string Name,
|
|
string HostName,
|
|
string ServiceName,
|
|
string UserName,
|
|
byte[] Password,
|
|
byte ClientType,
|
|
ushort ClientVersion,
|
|
uint ConnectionMode,
|
|
bool UseWindowsTransportSecurity = false,
|
|
bool UseNativeVersion3Buffer = false,
|
|
bool UseNativeOpenConnection3Version6Buffer = false,
|
|
bool UseCertificateTransportSecurity = false);
|
|
|
|
internal sealed record WcfOpen2Result(
|
|
string Attempt,
|
|
string ServiceName,
|
|
bool Success,
|
|
int InputByteCount,
|
|
int? OutputByteCount,
|
|
int? ErrorByteCount,
|
|
string? OutputBase64,
|
|
string? ErrorBase64,
|
|
byte? NativeErrorType,
|
|
uint? NativeErrorCode,
|
|
string? NativeErrorName,
|
|
uint? LegacyHandle,
|
|
long? LegacyConnectTimeFileTimeUtc,
|
|
uint? LegacyServerStatus,
|
|
byte? NativeOpen3ProtocolVersion,
|
|
uint? NativeOpen3Handle,
|
|
long? NativeOpen3ConnectTimeFileTimeUtc,
|
|
long? NativeOpen3ServerTimeFileTimeUtc,
|
|
int? NativeOpen3TrailingByteCount,
|
|
uint? CloseHandleOneReturnCode,
|
|
uint? CloseReturnedHandleReturnCode,
|
|
string? Close2SuccessfulVariant,
|
|
string? Close2LastVariant,
|
|
int? Close2LastErrorByteCount,
|
|
byte? Close2LastNativeErrorType,
|
|
uint? Close2LastNativeErrorCode,
|
|
string? Close2LastNativeErrorName,
|
|
uint? RetrievalHandleOneIsOriginalAllowedReturnCode,
|
|
bool? RetrievalHandleOneIsOriginalAllowed,
|
|
string? RetrievalHandleOneError,
|
|
uint? RetrievalReturnedHandleIsOriginalAllowedReturnCode,
|
|
bool? RetrievalReturnedHandleIsOriginalAllowed,
|
|
string? RetrievalReturnedHandleError,
|
|
string? Error);
|
|
|
|
internal sealed record WcfStartQueryResult(
|
|
string Option,
|
|
ulong ColumnSelectorFlags,
|
|
int? RequestByteCount,
|
|
string? RequestSha256,
|
|
uint? ClientHandle,
|
|
uint? IsOriginalAllowedReturnCode,
|
|
bool? IsOriginalAllowed,
|
|
uint QueryType,
|
|
bool OpenSuccess,
|
|
bool Success,
|
|
uint? QueryHandle,
|
|
uint? ResponseSize,
|
|
int? ResponseByteCount,
|
|
uint? LegacyReturnCode,
|
|
uint? LegacyQueryHandle,
|
|
uint? LegacyResponseSize,
|
|
int? LegacyResponseByteCount,
|
|
string? LegacyResponseSha256,
|
|
string? LegacyError,
|
|
uint? ErrorSize,
|
|
uint? NativeErrorCode,
|
|
string? Error);
|
|
|
|
internal sealed record WcfTagInfoResult(
|
|
string TagName,
|
|
bool Success,
|
|
uint? OpenErrorSize,
|
|
uint? OpenNativeErrorCode,
|
|
uint? IsOriginalAllowedReturnCode,
|
|
bool? IsOriginalAllowed,
|
|
uint? TagTypeReturnCode,
|
|
uint? TagType,
|
|
uint? IsManualReturnCode,
|
|
bool? IsManual,
|
|
uint? IsValidReturnCode,
|
|
bool? IsValid,
|
|
uint? TagInfoReturnCode,
|
|
uint? TagMetadataByteCount,
|
|
int? TagMetadataByteArrayLength,
|
|
string? TagMetadataSha256,
|
|
string? TagMetadataBase64,
|
|
IReadOnlyList<WcfTagInfoByNameBufferResult> NameBufferAttempts,
|
|
IReadOnlyList<WcfTagInfoByNameBufferResult> IdBufferAttempts,
|
|
string? Error);
|
|
|
|
internal sealed record WcfTagInfoByNameBufferResult(
|
|
string Attempt,
|
|
int InputByteCount,
|
|
string InputSha256,
|
|
uint? ReturnCode,
|
|
uint? Sequence,
|
|
uint? TagInfosSize,
|
|
int? TagInfosByteCount,
|
|
string? TagInfosSha256,
|
|
string? TagInfosBase64,
|
|
string? Error);
|
|
|
|
internal sealed record WcfTagQueryResult(
|
|
string Filter,
|
|
int StartRequestByteCount,
|
|
string StartRequestSha256,
|
|
bool Success,
|
|
uint? OpenErrorSize,
|
|
uint? OpenNativeErrorCode,
|
|
IReadOnlyList<WcfTagQueryHandleResult> HandleAttempts,
|
|
string? Error);
|
|
|
|
internal sealed record WcfTagQueryHandleResult(
|
|
string StartAttempt,
|
|
int StartRequestByteCount,
|
|
string StartRequestSha256,
|
|
string HandleVariant,
|
|
bool StartSuccess,
|
|
int? StartResponseByteCount,
|
|
string? StartResponseSha256,
|
|
string? StartResponseBase64,
|
|
int? StartErrorByteCount,
|
|
uint? StartNativeErrorCode,
|
|
uint? ParsedQueryHandle,
|
|
uint? ParsedTagCount,
|
|
bool? EndSuccess,
|
|
int? EndErrorByteCount,
|
|
uint? EndNativeErrorCode,
|
|
IReadOnlyList<WcfQueryTagResult> QueryAttempts,
|
|
string? Error);
|
|
|
|
internal sealed record WcfQueryTagResult(
|
|
string RequestVariant,
|
|
int RequestByteCount,
|
|
string RequestSha256,
|
|
bool Success,
|
|
uint? QueryIdAfterCall,
|
|
int? ResponseByteCount,
|
|
string? ResponseSha256,
|
|
string? ResponseBase64,
|
|
int? ErrorByteCount,
|
|
uint? NativeErrorCode,
|
|
string? Error);
|
|
|
|
internal sealed record WcfStartEventQueryResult(
|
|
string Attempt,
|
|
string OpenAttempt,
|
|
string HandleMode,
|
|
string RetrievalOperation,
|
|
ushort Version,
|
|
int RequestByteCount,
|
|
string RequestSha256,
|
|
uint? ClientHandle,
|
|
bool Success,
|
|
uint? QueryHandle,
|
|
uint? ResponseSize,
|
|
int? ResponseByteCount,
|
|
uint? ErrorSize,
|
|
uint? NativeErrorCode,
|
|
uint? OpenErrorSize,
|
|
uint? OpenNativeErrorCode,
|
|
string? Error);
|
|
|
|
internal sealed record WcfRegisterEventTagResult(
|
|
string RegistrationAttempt,
|
|
int RegistrationByteCount,
|
|
string RegistrationSha256,
|
|
string? RegisterHandleVariant,
|
|
uint? ClientHandle,
|
|
bool RegisterSuccess,
|
|
int? RegisterOutputByteCount,
|
|
int? RegisterErrorByteCount,
|
|
uint? RegisterNativeErrorCode,
|
|
string? RegisterOutputSha256,
|
|
string EventRequestSha256,
|
|
bool StartSuccess,
|
|
uint? QueryHandle,
|
|
uint? ResponseSize,
|
|
int? ResponseByteCount,
|
|
uint? ErrorSize,
|
|
uint? NativeErrorCode,
|
|
uint? OpenErrorSize,
|
|
uint? OpenNativeErrorCode,
|
|
string? Error);
|
|
|
|
internal sealed record WcfAddEventTagResult(
|
|
string PayloadAttempt,
|
|
int PayloadByteCount,
|
|
string PayloadSha256,
|
|
uint? ClientHandle,
|
|
bool OpenSuccess,
|
|
uint? AddReturnCode,
|
|
uint? AddOutputByteCount,
|
|
int? AddOutputByteArrayLength,
|
|
string? AddOutputSha256,
|
|
string EventRequestSha256,
|
|
bool StartSuccess,
|
|
uint? QueryHandle,
|
|
uint? ResponseSize,
|
|
int? ResponseByteCount,
|
|
uint? ErrorSize,
|
|
uint? NativeErrorCode,
|
|
uint? OpenErrorSize,
|
|
uint? OpenNativeErrorCode,
|
|
string? Error);
|
|
|
|
internal sealed record WcfRetrievalHandleProbe(uint? ReturnCode, bool? IsOriginalAllowed, string? Error);
|
|
|
|
internal sealed record WcfClose2CandidateResult(string? SuccessfulVariant, string? LastVariant, int? LastErrorByteCount, HistorianNativeError? LastNativeError);
|
|
|
|
internal sealed record CaptureScenario(string Name, string NativeOperation, string ManagedApi, string EvidenceFile)
|
|
{
|
|
public static readonly IReadOnlyList<CaptureScenario> Defaults =
|
|
[
|
|
new("connect-process", "mdas_OpenConnection2", "ProbeAsync/GetConnectionStatusAsync", "fixtures/protocol/2020/connect-process.bin"),
|
|
new("history-raw", "mdas_StartDataRetrievalQuery + mdas_GetNextDataQueryResult", "ReadRawAsync", "fixtures/protocol/2020/history-raw.bin"),
|
|
new("history-aggregate", "mdas_StartDataRetrievalQuery + mdas_GetNextDataQueryResult", "ReadAggregateAsync", "fixtures/protocol/2020/history-aggregate.bin"),
|
|
new("history-at-time", "mdas_StartDataRetrievalQuery + mdas_GetNextDataQueryResult", "ReadAtTimeAsync", "fixtures/protocol/2020/history-at-time.bin"),
|
|
new("history-block", "mdas_StartBlockRetrievalQuery + mdas_GetNextBlockQueryResult", "ReadBlocksAsync", "fixtures/protocol/2020/history-block.bin"),
|
|
new("event-query", "mdas_StartEventDataRetrievalQuery + mdas_GetNextEventDataQueryResult", "ReadEventsAsync", "fixtures/protocol/2020/event-query.bin"),
|
|
new("tag-browse", "mdas_StartLikeTagNameSearch + mdas_GetLikeTagnames", "BrowseTagNamesAsync", "fixtures/protocol/2020/tag-browse.bin"),
|
|
new("tag-metadata", "mdas_GetTagInfoByName", "GetTagMetadataAsync", "fixtures/protocol/2020/tag-metadata.bin"),
|
|
new("status", "mdas_GetStorageStatus/mdas_GetSystemParameter", "GetConnectionStatusAsync/GetStoreForwardStatusAsync", "fixtures/protocol/2020/status.bin"),
|
|
new("write-streamed-value", "mdas_AddStreamValue", "WriteStreamedValueAsync", "fixtures/protocol/2020/write-streamed-value.bin"),
|
|
new("write-event", "mdas_AddStreamValue for HistorianEvent", "WriteEventAsync", "fixtures/protocol/2020/write-event.bin")
|
|
];
|
|
}
|
|
|
|
internal sealed record NativeBinaryInventory(string Path, string Sha256, IReadOnlyList<string> Exports)
|
|
{
|
|
public static NativeBinaryInventory Read(string path)
|
|
{
|
|
string fullPath = System.IO.Path.GetFullPath(path);
|
|
byte[] bytes = File.ReadAllBytes(fullPath);
|
|
string hash = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
|
IReadOnlyList<string> exports = PeExportReader.ReadExports(bytes).Order(StringComparer.Ordinal).ToArray();
|
|
return new NativeBinaryInventory(fullPath, hash, exports);
|
|
}
|
|
}
|
|
|
|
internal sealed record ManagedMethodInventory(string Token, string Rva, string DeclaringType, string Name)
|
|
{
|
|
public static IReadOnlyList<ManagedMethodInventory> Read(string path, string? filter)
|
|
{
|
|
using FileStream stream = File.OpenRead(System.IO.Path.GetFullPath(path));
|
|
using PEReader peReader = new(stream);
|
|
MetadataReader reader = peReader.GetMetadataReader();
|
|
List<ManagedMethodInventory> methods = [];
|
|
|
|
foreach (MethodDefinitionHandle handle in reader.MethodDefinitions)
|
|
{
|
|
MethodDefinition definition = reader.GetMethodDefinition(handle);
|
|
string name = reader.GetString(definition.Name);
|
|
TypeDefinitionHandle declaringTypeHandle = definition.GetDeclaringType();
|
|
string declaringType = declaringTypeHandle.IsNil
|
|
? "<Module>"
|
|
: FormatTypeName(reader, reader.GetTypeDefinition(declaringTypeHandle));
|
|
string qualifiedName = $"{declaringType}.{name}";
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter)
|
|
&& !qualifiedName.Contains(filter, StringComparison.OrdinalIgnoreCase)
|
|
&& !name.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int token = MetadataTokens.GetToken(handle);
|
|
methods.Add(new ManagedMethodInventory(
|
|
$"0x{token:X8}",
|
|
$"0x{definition.RelativeVirtualAddress:X}",
|
|
declaringType,
|
|
name));
|
|
}
|
|
|
|
return methods;
|
|
}
|
|
|
|
private static string FormatTypeName(MetadataReader reader, TypeDefinition type)
|
|
{
|
|
string name = reader.GetString(type.Name);
|
|
string ns = reader.GetString(type.Namespace);
|
|
return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}";
|
|
}
|
|
}
|
|
|
|
internal static class PeExportReader
|
|
{
|
|
public static IReadOnlyList<string> ReadExports(ReadOnlySpan<byte> bytes)
|
|
{
|
|
int peOffset = ReadInt32(bytes, 0x3c);
|
|
ushort sectionCount = ReadUInt16(bytes, peOffset + 6);
|
|
ushort optionalHeaderSize = ReadUInt16(bytes, peOffset + 20);
|
|
int optionalHeader = peOffset + 24;
|
|
ushort magic = ReadUInt16(bytes, optionalHeader);
|
|
int dataDirectory = magic == 0x20b ? optionalHeader + 112 : optionalHeader + 96;
|
|
uint exportRva = ReadUInt32(bytes, dataDirectory);
|
|
if (exportRva == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
SectionHeader[] sections = ReadSections(bytes, optionalHeader + optionalHeaderSize, sectionCount);
|
|
int exportOffset = RvaToOffset(exportRva, sections);
|
|
uint nameCount = ReadUInt32(bytes, exportOffset + 24);
|
|
uint namesRva = ReadUInt32(bytes, exportOffset + 32);
|
|
int namesOffset = RvaToOffset(namesRva, sections);
|
|
|
|
string[] exports = new string[nameCount];
|
|
for (int i = 0; i < exports.Length; i++)
|
|
{
|
|
uint nameRva = ReadUInt32(bytes, namesOffset + (i * 4));
|
|
exports[i] = ReadAsciiNullTerminated(bytes, RvaToOffset(nameRva, sections));
|
|
}
|
|
|
|
return exports;
|
|
}
|
|
|
|
private static SectionHeader[] ReadSections(ReadOnlySpan<byte> bytes, int offset, int count)
|
|
{
|
|
SectionHeader[] sections = new SectionHeader[count];
|
|
for (int i = 0; i < sections.Length; i++)
|
|
{
|
|
int sectionOffset = offset + (i * 40);
|
|
sections[i] = new SectionHeader(
|
|
VirtualSize: ReadUInt32(bytes, sectionOffset + 8),
|
|
VirtualAddress: ReadUInt32(bytes, sectionOffset + 12),
|
|
RawSize: ReadUInt32(bytes, sectionOffset + 16),
|
|
RawPointer: ReadUInt32(bytes, sectionOffset + 20));
|
|
}
|
|
|
|
return sections;
|
|
}
|
|
|
|
private static int RvaToOffset(uint rva, IReadOnlyList<SectionHeader> sections)
|
|
{
|
|
foreach (SectionHeader section in sections)
|
|
{
|
|
uint size = Math.Max(section.VirtualSize, section.RawSize);
|
|
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + size)
|
|
{
|
|
return checked((int)(section.RawPointer + (rva - section.VirtualAddress)));
|
|
}
|
|
}
|
|
|
|
return checked((int)rva);
|
|
}
|
|
|
|
private static string ReadAsciiNullTerminated(ReadOnlySpan<byte> bytes, int offset)
|
|
{
|
|
int end = offset;
|
|
while (end < bytes.Length && bytes[end] != 0)
|
|
{
|
|
end++;
|
|
}
|
|
|
|
return Encoding.ASCII.GetString(bytes[offset..end]);
|
|
}
|
|
|
|
private static ushort ReadUInt16(ReadOnlySpan<byte> bytes, int offset)
|
|
{
|
|
return BinaryPrimitives.ReadUInt16LittleEndian(bytes.Slice(offset, sizeof(ushort)));
|
|
}
|
|
|
|
private static uint ReadUInt32(ReadOnlySpan<byte> bytes, int offset)
|
|
{
|
|
return BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(offset, sizeof(uint)));
|
|
}
|
|
|
|
private static int ReadInt32(ReadOnlySpan<byte> bytes, int offset)
|
|
{
|
|
return BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(offset, sizeof(int)));
|
|
}
|
|
|
|
private readonly record struct SectionHeader(uint VirtualSize, uint VirtualAddress, uint RawSize, uint RawPointer);
|
|
}
|