From 55f9641fff5ee6e02513a21c4de752f0b049f359 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:12:32 +0000 Subject: [PATCH 01/12] Initial plan From 04718c9b590baeb87f063a9034de34b04d8bdd71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:23:13 +0000 Subject: [PATCH 02/12] Add custom function registration API and infrastructure Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- src/RemoteNET/UnmanagedRemoteApp.cs | 49 ++++++++++++++ src/ScubaDiver.API/DiverCommunicator.cs | 16 +++++ .../RegisterCustomFunctionRequest.cs | 57 ++++++++++++++++ .../RegisterCustomFunctionResponse.cs | 18 +++++ src/ScubaDiver/DiverBase.cs | 3 + src/ScubaDiver/DotNetDiver.cs | 12 ++++ src/ScubaDiver/MsvcDiver.cs | 50 ++++++++++++++ .../CustomUndecoratedFunction.cs | 35 ++++++++++ .../MsvcPrimitives/MsvcTypesManager.cs | 67 ++++++++++++++++++- 9 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 src/ScubaDiver.API/Interactions/RegisterCustomFunctionRequest.cs create mode 100644 src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs create mode 100644 src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs diff --git a/src/RemoteNET/UnmanagedRemoteApp.cs b/src/RemoteNET/UnmanagedRemoteApp.cs index d5020a6..8cea0fc 100644 --- a/src/RemoteNET/UnmanagedRemoteApp.cs +++ b/src/RemoteNET/UnmanagedRemoteApp.cs @@ -215,6 +215,55 @@ public override bool InjectDll(string path) return res; } + // + // Custom Functions + // + + /// + /// Registers a custom function on a remote type for unmanaged targets + /// + /// The type to add the function to + /// Name of the function + /// Module name where the function is located (e.g., "MyModule.dll") + /// Offset within the module where the function is located + /// Return type of the function + /// Parameter types of the function + /// True if registration was successful, false otherwise + public bool RegisterCustomFunction( + Type parentType, + string functionName, + string moduleName, + ulong offset, + Type returnType, + params Type[] parameterTypes) + { + if (parentType == null) + throw new ArgumentNullException(nameof(parentType)); + if (string.IsNullOrEmpty(functionName)) + throw new ArgumentException("Function name cannot be null or empty", nameof(functionName)); + if (string.IsNullOrEmpty(moduleName)) + throw new ArgumentException("Module name cannot be null or empty", nameof(moduleName)); + + var request = new RegisterCustomFunctionRequest + { + ParentTypeFullName = parentType.FullName, + ParentAssembly = parentType.Assembly?.GetName()?.Name, + FunctionName = functionName, + ModuleName = moduleName, + Offset = offset, + ReturnTypeFullName = returnType?.FullName ?? "void", + ReturnTypeAssembly = returnType?.Assembly?.GetName()?.Name, + Parameters = parameterTypes?.Select((pt, idx) => new RegisterCustomFunctionRequest.ParameterTypeInfo + { + Name = $"param{idx}", + TypeFullName = pt.FullName, + Assembly = pt.Assembly?.GetName()?.Name + }).ToList() ?? new List() + }; + + return _unmanagedCommunicator.RegisterCustomFunction(request); + } + // // IDisposable // diff --git a/src/ScubaDiver.API/DiverCommunicator.cs b/src/ScubaDiver.API/DiverCommunicator.cs index 070773e..a92146e 100644 --- a/src/ScubaDiver.API/DiverCommunicator.cs +++ b/src/ScubaDiver.API/DiverCommunicator.cs @@ -537,6 +537,22 @@ public void UnhookMethod(LocalHookCallback callback) public delegate (bool voidReturnType, ObjectOrRemoteAddress res) LocalEventCallback(ObjectOrRemoteAddress[] args, ObjectOrRemoteAddress retVal); + public bool RegisterCustomFunction(RegisterCustomFunctionRequest request) + { + var requestJsonBody = JsonConvert.SerializeObject(request); + var resJson = SendRequest("register_custom_function", null, requestJsonBody); + + try + { + RegisterCustomFunctionResponse response = JsonConvert.DeserializeObject(resJson, _withErrors); + return response?.Success ?? false; + } + catch (Exception ex) + { + throw new Exception($"Failed to register custom function. Error: {ex.Message}", ex); + } + } + public void Dispose() { if (_httpClient != null) diff --git a/src/ScubaDiver.API/Interactions/RegisterCustomFunctionRequest.cs b/src/ScubaDiver.API/Interactions/RegisterCustomFunctionRequest.cs new file mode 100644 index 0000000..d3d9d8d --- /dev/null +++ b/src/ScubaDiver.API/Interactions/RegisterCustomFunctionRequest.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace ScubaDiver.API.Interactions +{ + /// + /// Request to register a custom function on a type (primarily for unmanaged targets) + /// + public class RegisterCustomFunctionRequest + { + /// + /// Full type name of the parent type that this function belongs to + /// + public string ParentTypeFullName { get; set; } + + /// + /// Assembly name of the parent type + /// + public string ParentAssembly { get; set; } + + /// + /// Name of the function to register + /// + public string FunctionName { get; set; } + + /// + /// Module name where the function is located (e.g., "MyModule.dll") + /// + public string ModuleName { get; set; } + + /// + /// Offset within the module where the function is located + /// + public ulong Offset { get; set; } + + /// + /// Full type name of the return type + /// + public string ReturnTypeFullName { get; set; } + + /// + /// Assembly of the return type + /// + public string ReturnTypeAssembly { get; set; } + + /// + /// List of parameter types (full type names) + /// + public List Parameters { get; set; } + + public class ParameterTypeInfo + { + public string Name { get; set; } + public string TypeFullName { get; set; } + public string Assembly { get; set; } + } + } +} diff --git a/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs b/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs new file mode 100644 index 0000000..6fa0bc0 --- /dev/null +++ b/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs @@ -0,0 +1,18 @@ +namespace ScubaDiver.API.Interactions +{ + /// + /// Response for a custom function registration request + /// + public class RegisterCustomFunctionResponse + { + /// + /// Whether the registration was successful + /// + public bool Success { get; set; } + + /// + /// Error message if registration failed + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/ScubaDiver/DiverBase.cs b/src/ScubaDiver/DiverBase.cs index c49d465..4c908e9 100644 --- a/src/ScubaDiver/DiverBase.cs +++ b/src/ScubaDiver/DiverBase.cs @@ -62,6 +62,8 @@ public DiverBase(IRequestsListener listener) // Hooking {"/hook_method", MakeHookMethodResponse}, {"/unhook_method", MakeUnhookMethodResponse}, + // Custom Functions + {"/register_custom_function", MakeRegisterCustomFunctionResponse}, }; _remoteHooks = new ConcurrentDictionary(); } @@ -370,6 +372,7 @@ private string MakeLaunchDebuggerResponse(ScubaDiverMessage arg) protected abstract string MakeSetFieldResponse(ScubaDiverMessage arg); protected abstract string MakeArrayItemResponse(ScubaDiverMessage arg); protected abstract string MakeUnpinResponse(ScubaDiverMessage arg); + protected abstract string MakeRegisterCustomFunctionResponse(ScubaDiverMessage arg); private string MakeDieResponse(ScubaDiverMessage req) { diff --git a/src/ScubaDiver/DotNetDiver.cs b/src/ScubaDiver/DotNetDiver.cs index eb5842a..3eeda4f 100644 --- a/src/ScubaDiver/DotNetDiver.cs +++ b/src/ScubaDiver/DotNetDiver.cs @@ -1531,6 +1531,18 @@ protected override string MakeUnpinResponse(ScubaDiverMessage arg) } } + protected override string MakeRegisterCustomFunctionResponse(ScubaDiverMessage arg) + { + // Custom function registration is not supported for managed (.NET) targets + // This feature is only available for unmanaged (native C++) targets + RegisterCustomFunctionResponse response = new RegisterCustomFunctionResponse + { + Success = false, + ErrorMessage = "Custom function registration is not supported for managed (.NET) targets" + }; + return JsonConvert.SerializeObject(response); + } + // IDisposable public override void Dispose() { diff --git a/src/ScubaDiver/MsvcDiver.cs b/src/ScubaDiver/MsvcDiver.cs index 0f02308..227f561 100644 --- a/src/ScubaDiver/MsvcDiver.cs +++ b/src/ScubaDiver/MsvcDiver.cs @@ -865,6 +865,56 @@ protected override string MakeUnpinResponse(ScubaDiverMessage arg) return QuickError("Not Implemented"); } + protected override string MakeRegisterCustomFunctionResponse(ScubaDiverMessage arg) + { + string body = arg.Body; + if (string.IsNullOrEmpty(body)) + { + return QuickError("Missing body"); + } + + var request = JsonConvert.DeserializeObject(body); + if (request == null) + { + return QuickError("Failed to deserialize body"); + } + + Logger.Debug($"[MsvcDiver][MakeRegisterCustomFunctionResponse] Registering custom function: {request.FunctionName} on type {request.ParentTypeFullName}"); + + try + { + // Extract parameter type names from the request + string[] argTypeFullNames = request.Parameters?.Select(p => p.TypeFullName).ToArray() ?? new string[0]; + + bool success = _typesManager.RegisterCustomFunction( + request.ParentTypeFullName, + request.ParentAssembly, + request.FunctionName, + request.ModuleName, + request.Offset, + request.ReturnTypeFullName, + argTypeFullNames); + + RegisterCustomFunctionResponse response = new RegisterCustomFunctionResponse + { + Success = success, + ErrorMessage = success ? null : "Failed to register custom function" + }; + + return JsonConvert.SerializeObject(response); + } + catch (Exception ex) + { + Logger.Debug($"[MsvcDiver][MakeRegisterCustomFunctionResponse] Exception: {ex}"); + RegisterCustomFunctionResponse response = new RegisterCustomFunctionResponse + { + Success = false, + ErrorMessage = ex.Message + }; + return JsonConvert.SerializeObject(response); + } + } + public override void Dispose() { } diff --git a/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs b/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs new file mode 100644 index 0000000..95e49e2 --- /dev/null +++ b/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs @@ -0,0 +1,35 @@ +using ScubaDiver.Rtti; + +namespace ScubaDiver +{ + /// + /// Represents a custom user-defined function that can be registered on a type + /// + public class CustomUndecoratedFunction : UndecoratedFunction + { + private readonly ModuleInfo _module; + private readonly nuint _address; + private readonly string _retType; + private readonly string[] _argTypes; + + public CustomUndecoratedFunction( + string moduleName, + nuint baseAddress, + ulong offset, + string functionName, + string returnType, + string[] argTypes) + : base(functionName, functionName, functionName, argTypes?.Length) + { + _module = new ModuleInfo { Name = moduleName, BaseAddress = baseAddress }; + _address = baseAddress + offset; + _retType = returnType; + _argTypes = argTypes ?? new string[0]; + } + + public override ModuleInfo Module => _module; + public override nuint Address => _address; + public override string RetType => _retType; + public override string[] ArgTypes => _argTypes; + } +} diff --git a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs index 0b4d22b..fd57305 100644 --- a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs +++ b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs @@ -135,6 +135,7 @@ public class MsvcType : Type private MsvcModule _module; private MsvcMethod[] _methods; private VftableInfo[] _vftables; + private List _customMethods = new List(); public Rtti.TypeInfo TypeInfo { get; set; } public MsvcType(MsvcModule module, Rtti.TypeInfo typeInfo) @@ -152,6 +153,11 @@ public void SetVftables(VftableInfo[] vftables) _vftables = vftables; } + public void AddCustomMethod(MsvcMethod method) + { + _customMethods.Add(method); + } + public override MsvcModule Module => _module; public override string Name => TypeInfo.Name; @@ -159,7 +165,12 @@ public void SetVftables(VftableInfo[] vftables) public override string FullName => TypeInfo.FullTypeName; - public override MsvcMethod[] GetMethods(BindingFlags bindingAttr) => _methods; + public override MsvcMethod[] GetMethods(BindingFlags bindingAttr) + { + if (_methods == null) + return _customMethods.ToArray(); + return _methods.Concat(_customMethods).ToArray(); + } public new MsvcMethod[] GetMethods() => GetMethods(0); public VftableInfo[] GetVftables() => _vftables; public override MemberInfo[] GetMembers(BindingFlags bindingAttr) => [.. GetVftables(), .. GetMethods()]; @@ -467,6 +478,60 @@ bool IsNotExport(ulong addr) return res; } } + + /// + /// Registers a custom function on a type + /// + public bool RegisterCustomFunction( + string parentTypeFullName, + string parentAssembly, + string functionName, + string moduleName, + ulong offset, + string returnTypeFullName, + string[] argTypeFullNames) + { + try + { + // Get the parent type + Predicate moduleFilter = Filter.CreatePredicate(parentAssembly); + Predicate typeFilter = Filter.CreatePredicate(parentTypeFullName); + MsvcTypeStub typeStub = GetType(moduleFilter, typeFilter); + if (typeStub == null) + return false; + + MsvcType parentType = typeStub.Upgrade(); + if (parentType == null) + return false; + + // Find the module base address + List modules = GetUndecoratedModules(Filter.CreatePredicate(moduleName)); + if (modules.Count == 0) + return false; + + UndecoratedModule targetModule = modules.First(); + nuint moduleBaseAddress = targetModule.ModuleInfo.BaseAddress; + + // Create a custom undecorated function + CustomUndecoratedFunction customFunc = new CustomUndecoratedFunction( + moduleName, + moduleBaseAddress, + offset, + functionName, + returnTypeFullName, + argTypeFullNames); + + // Create an MsvcMethod from the custom function and add it to the type + MsvcMethod customMethod = new MsvcMethod(parentType, customFunc); + parentType.AddCustomMethod(customMethod); + + return true; + } + catch + { + return false; + } + } } public class MsvcModuleExports From ccbbf459202e9433304253558575400f0d9e691a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:25:28 +0000 Subject: [PATCH 03/12] Add tests and documentation for custom function registration Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- README.md | 32 ++++++++ .../RegisterCustomFunctionTests.cs | 82 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs diff --git a/README.md b/README.md index d663817..9df8852 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,38 @@ The limitations: 2. The callback must define the exact number of parameters for that event 3. Lambda expression are not allowed. The callback must be cast to an `Action<...>`. +### ✳️ Registering Custom Functions (Unmanaged/C++ Targets Only) +For unmanaged (MSVC C++) targets, you can register custom functions that aren't automatically discovered by the RTTI scanner. +This is useful when you know the address of a function in the target process and want to call it through RemoteNET. + +```C# +// Connect to an unmanaged target +UnmanagedRemoteApp unmanagedApp = (UnmanagedRemoteApp)RemoteAppFactory.Connect("MyNativeTarget.exe", RuntimeType.Unmanaged); + +// Get a remote type +Type remoteType = unmanagedApp.GetRemoteType("MyNamespace::MyClass", "MyModule.dll"); + +// Register a custom function on the type +bool success = unmanagedApp.RegisterCustomFunction( + parentType: remoteType, + functionName: "MyCustomFunction", + moduleName: "MyModule.dll", + offset: 0x1234, // Offset from module base address + returnType: typeof(int), + parameterTypes: new[] { typeof(int), typeof(float) } +); + +// After registration, the function can be invoked like any other remote method +dynamic dynamicObj = remoteObject.Dynamify(); +int result = dynamicObj.MyCustomFunction(42, 3.14f); +``` + +**Notes:** +- This feature is only available for unmanaged (C++) targets +- You need to know the module name and offset where the function is located +- The function will be added to the type's method list and can be invoked normally +- Parameter and return types should be specified as .NET types + ## TODOs 1. Static members 2. Document "Reflection API" (RemoteType, RemoteMethodInfo, ... ) diff --git a/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs b/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs new file mode 100644 index 0000000..010baea --- /dev/null +++ b/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs @@ -0,0 +1,82 @@ +using ScubaDiver.API.Interactions; + +namespace ScubaDiver.API.Tests +{ + [TestFixture] + public class RegisterCustomFunctionTests + { + [Test] + public void RegisterCustomFunctionRequest_ValidData_SetsPropertiesCorrectly() + { + // Arrange + var request = new RegisterCustomFunctionRequest + { + ParentTypeFullName = "MyNamespace::MyClass", + ParentAssembly = "MyModule.dll", + FunctionName = "MyCustomFunction", + ModuleName = "MyModule.dll", + Offset = 0x1234, + ReturnTypeFullName = "int", + ReturnTypeAssembly = "System.Private.CoreLib", + Parameters = new List + { + new RegisterCustomFunctionRequest.ParameterTypeInfo + { + Name = "param1", + TypeFullName = "int", + Assembly = "System.Private.CoreLib" + }, + new RegisterCustomFunctionRequest.ParameterTypeInfo + { + Name = "param2", + TypeFullName = "float", + Assembly = "System.Private.CoreLib" + } + } + }; + + // Assert + Assert.That(request.ParentTypeFullName, Is.EqualTo("MyNamespace::MyClass")); + Assert.That(request.ParentAssembly, Is.EqualTo("MyModule.dll")); + Assert.That(request.FunctionName, Is.EqualTo("MyCustomFunction")); + Assert.That(request.ModuleName, Is.EqualTo("MyModule.dll")); + Assert.That(request.Offset, Is.EqualTo(0x1234)); + Assert.That(request.ReturnTypeFullName, Is.EqualTo("int")); + Assert.That(request.Parameters.Count, Is.EqualTo(2)); + Assert.That(request.Parameters[0].Name, Is.EqualTo("param1")); + Assert.That(request.Parameters[0].TypeFullName, Is.EqualTo("int")); + Assert.That(request.Parameters[1].Name, Is.EqualTo("param2")); + Assert.That(request.Parameters[1].TypeFullName, Is.EqualTo("float")); + } + + [Test] + public void RegisterCustomFunctionResponse_Success_SetsPropertiesCorrectly() + { + // Arrange + var response = new RegisterCustomFunctionResponse + { + Success = true, + ErrorMessage = null + }; + + // Assert + Assert.That(response.Success, Is.True); + Assert.That(response.ErrorMessage, Is.Null); + } + + [Test] + public void RegisterCustomFunctionResponse_Failure_SetsPropertiesCorrectly() + { + // Arrange + var response = new RegisterCustomFunctionResponse + { + Success = false, + ErrorMessage = "Failed to register custom function" + }; + + // Assert + Assert.That(response.Success, Is.False); + Assert.That(response.ErrorMessage, Is.EqualTo("Failed to register custom function")); + } + } +} From 444720c278668de50852b0cb76b047fdda8c8415 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:26:36 +0000 Subject: [PATCH 04/12] Add missing using statement for Interactions namespace --- src/RemoteNET/UnmanagedRemoteApp.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RemoteNET/UnmanagedRemoteApp.cs b/src/RemoteNET/UnmanagedRemoteApp.cs index 8cea0fc..64e2061 100644 --- a/src/RemoteNET/UnmanagedRemoteApp.cs +++ b/src/RemoteNET/UnmanagedRemoteApp.cs @@ -6,6 +6,7 @@ using RemoteNET.Internal; using RemoteNET.RttiReflection; using ScubaDiver.API; +using ScubaDiver.API.Interactions; using ScubaDiver.API.Interactions.Dumps; using ScubaDiver.API.Utils; From 437a0ad755ce8057a2add745379cda37c5afbe4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:27:03 +0000 Subject: [PATCH 05/12] Update gitignore to exclude build artifacts Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index de1d236..2828314 100644 --- a/.gitignore +++ b/.gitignore @@ -340,3 +340,4 @@ src/RemoteNET/publish.ps1 src/detours_build/ src/ConsoleApp/ +src/RemoteNET/Resources/ From a78fbbdc86b223595e405e8d9770a4302e176457 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:31:06 +0000 Subject: [PATCH 06/12] Address code review feedback - fix exception handling, overflow check, and missing using Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- .../RegisterCustomFunctionTests.cs | 1 + .../MsvcPrimitives/CustomUndecoratedFunction.cs | 10 ++++++++-- src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs | 9 +++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs b/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs index 010baea..a16a116 100644 --- a/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs +++ b/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs @@ -1,4 +1,5 @@ using ScubaDiver.API.Interactions; +using System.Collections.Generic; namespace ScubaDiver.API.Tests { diff --git a/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs b/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs index 95e49e2..22ab954 100644 --- a/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs +++ b/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs @@ -22,9 +22,15 @@ public CustomUndecoratedFunction( : base(functionName, functionName, functionName, argTypes?.Length) { _module = new ModuleInfo { Name = moduleName, BaseAddress = baseAddress }; - _address = baseAddress + offset; + + // Check for potential overflow when adding baseAddress and offset + checked + { + _address = baseAddress + offset; + } + _retType = returnType; - _argTypes = argTypes ?? new string[0]; + _argTypes = argTypes ?? Array.Empty(); } public override ModuleInfo Module => _module; diff --git a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs index fd57305..dcbebee 100644 --- a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs +++ b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs @@ -509,7 +509,10 @@ public bool RegisterCustomFunction( if (modules.Count == 0) return false; - UndecoratedModule targetModule = modules.First(); + UndecoratedModule targetModule = modules.FirstOrDefault(); + if (targetModule == null) + return false; + nuint moduleBaseAddress = targetModule.ModuleInfo.BaseAddress; // Create a custom undecorated function @@ -527,8 +530,10 @@ public bool RegisterCustomFunction( return true; } - catch + catch (Exception ex) { + // Log the exception for debugging purposes + Logger.Debug($"[MsvcTypesManager][RegisterCustomFunction] Failed to register custom function. Exception: {ex}"); return false; } } From 44745b7140b95a843c0a5e4990c1166931956b27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:33:04 +0000 Subject: [PATCH 07/12] Final code review fixes - use Array.Empty and direct indexing Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- src/ScubaDiver/MsvcDiver.cs | 2 +- src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ScubaDiver/MsvcDiver.cs b/src/ScubaDiver/MsvcDiver.cs index 227f561..9b83c09 100644 --- a/src/ScubaDiver/MsvcDiver.cs +++ b/src/ScubaDiver/MsvcDiver.cs @@ -884,7 +884,7 @@ protected override string MakeRegisterCustomFunctionResponse(ScubaDiverMessage a try { // Extract parameter type names from the request - string[] argTypeFullNames = request.Parameters?.Select(p => p.TypeFullName).ToArray() ?? new string[0]; + string[] argTypeFullNames = request.Parameters?.Select(p => p.TypeFullName).ToArray() ?? Array.Empty(); bool success = _typesManager.RegisterCustomFunction( request.ParentTypeFullName, diff --git a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs index dcbebee..68038f7 100644 --- a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs +++ b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs @@ -509,10 +509,8 @@ public bool RegisterCustomFunction( if (modules.Count == 0) return false; - UndecoratedModule targetModule = modules.FirstOrDefault(); - if (targetModule == null) - return false; - + // Since we've verified the list is not empty, we can safely access the first element + UndecoratedModule targetModule = modules[0]; nuint moduleBaseAddress = targetModule.ModuleInfo.BaseAddress; // Create a custom undecorated function From cd3cef289259d6d244c2b13f509d01e98ba6711c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:43:54 +0000 Subject: [PATCH 08/12] Update custom function registration to modify existing RemoteRttiType instances - Modified response to return TypeDump.TypeMethod instead of just success/failure - Updated UnmanagedRemoteApp to verify Type is RemoteRttiType and add method to it - Use RttiTypesFactory.AddFunctionImpl to ensure consistent method creation - Updated DiverCommunicator with overload to return method dump - Updated tests to validate RegisteredMethod property - Updated documentation to clarify that existing type instances are updated Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- README.md | 8 +++-- src/RemoteNET/UnmanagedRemoteApp.cs | 29 ++++++++++++--- .../RegisterCustomFunctionTests.cs | 15 ++++++-- src/ScubaDiver.API/DiverCommunicator.cs | 10 ++++++ .../RegisterCustomFunctionResponse.cs | 7 ++++ src/ScubaDiver/MsvcDiver.cs | 35 +++++++++++++++++-- 6 files changed, 94 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9df8852..ecaf67d 100644 --- a/README.md +++ b/README.md @@ -147,11 +147,13 @@ The limitations: For unmanaged (MSVC C++) targets, you can register custom functions that aren't automatically discovered by the RTTI scanner. This is useful when you know the address of a function in the target process and want to call it through RemoteNET. +**Important:** The type must be a `RemoteRttiType` instance that was fetched from the remote process. Any existing instances of that type will be automatically updated with the new method. + ```C# // Connect to an unmanaged target UnmanagedRemoteApp unmanagedApp = (UnmanagedRemoteApp)RemoteAppFactory.Connect("MyNativeTarget.exe", RuntimeType.Unmanaged); -// Get a remote type +// Get a remote type (this creates a RemoteRttiType) Type remoteType = unmanagedApp.GetRemoteType("MyNamespace::MyClass", "MyModule.dll"); // Register a custom function on the type @@ -165,14 +167,16 @@ bool success = unmanagedApp.RegisterCustomFunction( ); // After registration, the function can be invoked like any other remote method +// All existing instances of this type will have the new method available dynamic dynamicObj = remoteObject.Dynamify(); int result = dynamicObj.MyCustomFunction(42, 3.14f); ``` **Notes:** - This feature is only available for unmanaged (C++) targets +- The parent type must be a RemoteRttiType (obtained via `UnmanagedRemoteApp.GetRemoteType()`) - You need to know the module name and offset where the function is located -- The function will be added to the type's method list and can be invoked normally +- The function will be added to the type's method list and available on all instances - Parameter and return types should be specified as .NET types ## TODOs diff --git a/src/RemoteNET/UnmanagedRemoteApp.cs b/src/RemoteNET/UnmanagedRemoteApp.cs index 64e2061..eea78e1 100644 --- a/src/RemoteNET/UnmanagedRemoteApp.cs +++ b/src/RemoteNET/UnmanagedRemoteApp.cs @@ -223,7 +223,7 @@ public override bool InjectDll(string path) /// /// Registers a custom function on a remote type for unmanaged targets /// - /// The type to add the function to + /// The type to add the function to (must be a RemoteRttiType) /// Name of the function /// Module name where the function is located (e.g., "MyModule.dll") /// Offset within the module where the function is located @@ -245,10 +245,16 @@ public bool RegisterCustomFunction( if (string.IsNullOrEmpty(moduleName)) throw new ArgumentException("Module name cannot be null or empty", nameof(moduleName)); + // Verify the type is a RemoteRttiType + if (!(parentType is RemoteRttiType rttiType)) + { + throw new ArgumentException("The parent type must be a RemoteRttiType. Only remote RTTI types created by UnmanagedRemoteApp can have custom functions registered.", nameof(parentType)); + } + var request = new RegisterCustomFunctionRequest { - ParentTypeFullName = parentType.FullName, - ParentAssembly = parentType.Assembly?.GetName()?.Name, + ParentTypeFullName = rttiType.Namespace + "::" + rttiType.Name, // Use namespace::name format for RTTI types + ParentAssembly = rttiType.Assembly?.GetName()?.Name, FunctionName = functionName, ModuleName = moduleName, Offset = offset, @@ -262,7 +268,22 @@ public bool RegisterCustomFunction( }).ToList() ?? new List() }; - return _unmanagedCommunicator.RegisterCustomFunction(request); + bool success = _unmanagedCommunicator.RegisterCustomFunction(request, out var methodDump); + + if (success && methodDump != null) + { + // Create a TypeDump for the parent type to pass to AddFunctionImpl + TypeDump parentTypeDump = new TypeDump + { + Assembly = rttiType.Assembly?.GetName()?.Name, + FullTypeName = rttiType.Namespace + "::" + rttiType.Name + }; + + // Use the existing factory method to add the function to the RemoteRttiType + RttiTypesFactory.AddFunctionImpl(this, parentTypeDump, methodDump, rttiType, areConstructors: false); + } + + return success; } // diff --git a/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs b/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs index a16a116..47a6803 100644 --- a/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs +++ b/src/ScubaDiver.API.Tests/RegisterCustomFunctionTests.cs @@ -1,4 +1,5 @@ using ScubaDiver.API.Interactions; +using ScubaDiver.API.Interactions.Dumps; using System.Collections.Generic; namespace ScubaDiver.API.Tests @@ -54,15 +55,23 @@ public void RegisterCustomFunctionRequest_ValidData_SetsPropertiesCorrectly() public void RegisterCustomFunctionResponse_Success_SetsPropertiesCorrectly() { // Arrange + var methodDump = new TypeDump.TypeMethod + { + Name = "TestMethod", + ReturnTypeFullName = "int" + }; var response = new RegisterCustomFunctionResponse { Success = true, - ErrorMessage = null + ErrorMessage = null, + RegisteredMethod = methodDump }; // Assert Assert.That(response.Success, Is.True); Assert.That(response.ErrorMessage, Is.Null); + Assert.That(response.RegisteredMethod, Is.Not.Null); + Assert.That(response.RegisteredMethod.Name, Is.EqualTo("TestMethod")); } [Test] @@ -72,12 +81,14 @@ public void RegisterCustomFunctionResponse_Failure_SetsPropertiesCorrectly() var response = new RegisterCustomFunctionResponse { Success = false, - ErrorMessage = "Failed to register custom function" + ErrorMessage = "Failed to register custom function", + RegisteredMethod = null }; // Assert Assert.That(response.Success, Is.False); Assert.That(response.ErrorMessage, Is.EqualTo("Failed to register custom function")); + Assert.That(response.RegisteredMethod, Is.Null); } } } diff --git a/src/ScubaDiver.API/DiverCommunicator.cs b/src/ScubaDiver.API/DiverCommunicator.cs index a92146e..df244c8 100644 --- a/src/ScubaDiver.API/DiverCommunicator.cs +++ b/src/ScubaDiver.API/DiverCommunicator.cs @@ -539,12 +539,22 @@ public void UnhookMethod(LocalHookCallback callback) public bool RegisterCustomFunction(RegisterCustomFunctionRequest request) { + return RegisterCustomFunction(request, out _); + } + + public bool RegisterCustomFunction(RegisterCustomFunctionRequest request, out TypeDump.TypeMethod methodDump) + { + methodDump = null; var requestJsonBody = JsonConvert.SerializeObject(request); var resJson = SendRequest("register_custom_function", null, requestJsonBody); try { RegisterCustomFunctionResponse response = JsonConvert.DeserializeObject(resJson, _withErrors); + if (response?.Success == true) + { + methodDump = response.RegisteredMethod; + } return response?.Success ?? false; } catch (Exception ex) diff --git a/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs b/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs index 6fa0bc0..2c7ebca 100644 --- a/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs +++ b/src/ScubaDiver.API/Interactions/RegisterCustomFunctionResponse.cs @@ -1,3 +1,5 @@ +using ScubaDiver.API.Interactions.Dumps; + namespace ScubaDiver.API.Interactions { /// @@ -14,5 +16,10 @@ public class RegisterCustomFunctionResponse /// Error message if registration failed /// public string ErrorMessage { get; set; } + + /// + /// The method dump for the registered function (when successful) + /// + public TypeDump.TypeMethod RegisteredMethod { get; set; } } } diff --git a/src/ScubaDiver/MsvcDiver.cs b/src/ScubaDiver/MsvcDiver.cs index 9b83c09..f94f71c 100644 --- a/src/ScubaDiver/MsvcDiver.cs +++ b/src/ScubaDiver/MsvcDiver.cs @@ -895,10 +895,41 @@ protected override string MakeRegisterCustomFunctionResponse(ScubaDiverMessage a request.ReturnTypeFullName, argTypeFullNames); + if (!success) + { + RegisterCustomFunctionResponse failResponse = new RegisterCustomFunctionResponse + { + Success = false, + ErrorMessage = "Failed to register custom function" + }; + return JsonConvert.SerializeObject(failResponse); + } + + // Create a TypeMethod dump from the registered function + TypeDump.TypeMethod methodDump = new TypeDump.TypeMethod + { + Name = request.FunctionName, + DecoratedName = request.FunctionName, + UndecoratedFullName = request.FunctionName, + ReturnTypeFullName = request.ReturnTypeFullName, + ReturnTypeAssembly = request.ReturnTypeAssembly, + ReturnTypeName = request.ReturnTypeFullName, + Visibility = "Public", + Attributes = 0, + Parameters = request.Parameters?.Select((p, idx) => new TypeDump.TypeMethod.MethodParameter + { + Name = p.Name ?? $"a{idx}", + FullTypeName = p.TypeFullName, + TypeName = p.TypeFullName, + Assembly = p.Assembly + }).ToList() ?? new List() + }; + RegisterCustomFunctionResponse response = new RegisterCustomFunctionResponse { - Success = success, - ErrorMessage = success ? null : "Failed to register custom function" + Success = true, + ErrorMessage = null, + RegisteredMethod = methodDump }; return JsonConvert.SerializeObject(response); From 6f0bcf3f02eb5c04246de0d869e65a43a3e0c512 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 18:29:59 +0000 Subject: [PATCH 09/12] Change RegisterCustomFunction to return MethodInfo and fix compilation errors - Changed return type from bool to MethodInfo (returns null on error) - Added using ScubaDiver.API.Utils to MsvcTypesManager for Filter class - Added using System.Reflection to UnmanagedRemoteApp - Method now returns the newly registered MethodInfo as a convenience - Fixed compilation errors: Filter and CustomUndecoratedFunction now accessible - Updated README with new return type and null-check example Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- README.md | 16 ++++++---- src/RemoteNET/UnmanagedRemoteApp.cs | 32 +++++++++++-------- .../MsvcPrimitives/MsvcTypesManager.cs | 1 + 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index ecaf67d..9d1d83f 100644 --- a/README.md +++ b/README.md @@ -156,8 +156,8 @@ UnmanagedRemoteApp unmanagedApp = (UnmanagedRemoteApp)RemoteAppFactory.Connect(" // Get a remote type (this creates a RemoteRttiType) Type remoteType = unmanagedApp.GetRemoteType("MyNamespace::MyClass", "MyModule.dll"); -// Register a custom function on the type -bool success = unmanagedApp.RegisterCustomFunction( +// Register a custom function on the type - returns the MethodInfo or null on error +MethodInfo customMethod = unmanagedApp.RegisterCustomFunction( parentType: remoteType, functionName: "MyCustomFunction", moduleName: "MyModule.dll", @@ -166,10 +166,13 @@ bool success = unmanagedApp.RegisterCustomFunction( parameterTypes: new[] { typeof(int), typeof(float) } ); -// After registration, the function can be invoked like any other remote method -// All existing instances of this type will have the new method available -dynamic dynamicObj = remoteObject.Dynamify(); -int result = dynamicObj.MyCustomFunction(42, 3.14f); +if (customMethod != null) +{ + // After registration, the function can be invoked like any other remote method + // All existing instances of this type will have the new method available + dynamic dynamicObj = remoteObject.Dynamify(); + int result = dynamicObj.MyCustomFunction(42, 3.14f); +} ``` **Notes:** @@ -178,6 +181,7 @@ int result = dynamicObj.MyCustomFunction(42, 3.14f); - You need to know the module name and offset where the function is located - The function will be added to the type's method list and available on all instances - Parameter and return types should be specified as .NET types +- Returns the `MethodInfo` for the registered method, or `null` if registration fails ## TODOs 1. Static members diff --git a/src/RemoteNET/UnmanagedRemoteApp.cs b/src/RemoteNET/UnmanagedRemoteApp.cs index eea78e1..3bdf717 100644 --- a/src/RemoteNET/UnmanagedRemoteApp.cs +++ b/src/RemoteNET/UnmanagedRemoteApp.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; using RemoteNET.Common; using RemoteNET.Internal; using RemoteNET.RttiReflection; @@ -229,8 +230,8 @@ public override bool InjectDll(string path) /// Offset within the module where the function is located /// Return type of the function /// Parameter types of the function - /// True if registration was successful, false otherwise - public bool RegisterCustomFunction( + /// The MethodInfo for the registered function, or null if registration failed + public MethodInfo RegisterCustomFunction( Type parentType, string functionName, string moduleName, @@ -270,20 +271,25 @@ public bool RegisterCustomFunction( bool success = _unmanagedCommunicator.RegisterCustomFunction(request, out var methodDump); - if (success && methodDump != null) + if (!success || methodDump == null) { - // Create a TypeDump for the parent type to pass to AddFunctionImpl - TypeDump parentTypeDump = new TypeDump - { - Assembly = rttiType.Assembly?.GetName()?.Name, - FullTypeName = rttiType.Namespace + "::" + rttiType.Name - }; - - // Use the existing factory method to add the function to the RemoteRttiType - RttiTypesFactory.AddFunctionImpl(this, parentTypeDump, methodDump, rttiType, areConstructors: false); + return null; } - return success; + // Create a TypeDump for the parent type to pass to AddFunctionImpl + TypeDump parentTypeDump = new TypeDump + { + Assembly = rttiType.Assembly?.GetName()?.Name, + FullTypeName = rttiType.Namespace + "::" + rttiType.Name + }; + + // Use the existing factory method to add the function to the RemoteRttiType + // AddFunctionImpl will add the method to rttiType's internal methods list + RttiTypesFactory.AddFunctionImpl(this, parentTypeDump, methodDump, rttiType, areConstructors: false); + + // Find and return the newly added method + var methods = rttiType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + return methods.FirstOrDefault(m => m.Name == functionName); } // diff --git a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs index 68038f7..9d7296f 100644 --- a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs +++ b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs @@ -1,4 +1,5 @@ using NtApiDotNet.Win32; +using ScubaDiver.API.Utils; using ScubaDiver.Rtti; using System; using System.Collections.Generic; From caa5782d55bf5faf0e70f8a434222ac15e3b060a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 18:53:39 +0000 Subject: [PATCH 10/12] Make AddFunctionImpl return MethodInfo to avoid O(n) search - Changed AddFunctionImpl signature to return MethodInfo instead of void - Returns the newly created RemoteRttiMethodInfo or RemoteRttiConstructorInfo - UnmanagedRemoteApp now directly returns the MethodInfo from AddFunctionImpl - Removed inefficient O(n) search through all methods by name - More efficient and safer since it returns the exact method that was created Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- .../Internal/Reflection/Rtti/RttiTypesFactory.cs | 4 +++- src/RemoteNET/UnmanagedRemoteApp.cs | 8 ++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs b/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs index 72abf21..6ad3458 100644 --- a/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs +++ b/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs @@ -168,7 +168,7 @@ private static void AddGroupOfFunctions(RemoteApp app, TypeDump typeDump, List CreateTypeFactory(string namespaceAndTypeName, string moduleName) diff --git a/src/RemoteNET/UnmanagedRemoteApp.cs b/src/RemoteNET/UnmanagedRemoteApp.cs index 3bdf717..b67c3da 100644 --- a/src/RemoteNET/UnmanagedRemoteApp.cs +++ b/src/RemoteNET/UnmanagedRemoteApp.cs @@ -284,12 +284,8 @@ public MethodInfo RegisterCustomFunction( }; // Use the existing factory method to add the function to the RemoteRttiType - // AddFunctionImpl will add the method to rttiType's internal methods list - RttiTypesFactory.AddFunctionImpl(this, parentTypeDump, methodDump, rttiType, areConstructors: false); - - // Find and return the newly added method - var methods = rttiType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - return methods.FirstOrDefault(m => m.Name == functionName); + // AddFunctionImpl returns the newly created MethodInfo + return RttiTypesFactory.AddFunctionImpl(this, parentTypeDump, methodDump, rttiType, areConstructors: false); } // From 4aa39db8e69a3c27eadbaaab0781a6a1759d64ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 19:07:27 +0000 Subject: [PATCH 11/12] Add CustomUndecoratedFunction.cs reference to ScubaDiver project files - Added Compile reference to project_net6_x64/ScubaDiver_Net6_x64.csproj - Added Compile reference to project_net6_x86/ScubaDiver_Net6_x86.csproj - Fixes build error: CustomUndecoratedFunction type not found - Projects use explicit file references, so new files must be added manually Co-authored-by: theXappy <10898152+theXappy@users.noreply.github.com> --- src/ScubaDiver/project_net6_x64/ScubaDiver_Net6_x64.csproj | 1 + src/ScubaDiver/project_net6_x86/ScubaDiver_Net6_x86.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ScubaDiver/project_net6_x64/ScubaDiver_Net6_x64.csproj b/src/ScubaDiver/project_net6_x64/ScubaDiver_Net6_x64.csproj index cb1445b..877fc3b 100644 --- a/src/ScubaDiver/project_net6_x64/ScubaDiver_Net6_x64.csproj +++ b/src/ScubaDiver/project_net6_x64/ScubaDiver_Net6_x64.csproj @@ -49,6 +49,7 @@ + diff --git a/src/ScubaDiver/project_net6_x86/ScubaDiver_Net6_x86.csproj b/src/ScubaDiver/project_net6_x86/ScubaDiver_Net6_x86.csproj index c99e5cd..fc1a424 100644 --- a/src/ScubaDiver/project_net6_x86/ScubaDiver_Net6_x86.csproj +++ b/src/ScubaDiver/project_net6_x86/ScubaDiver_Net6_x86.csproj @@ -49,6 +49,7 @@ + From 2832563169f3a75b2a0ab5e3657633e928452fe4 Mon Sep 17 00:00:00 2001 From: RemoteNet Date: Fri, 28 Nov 2025 17:32:18 +0200 Subject: [PATCH 12/12] Changes to MSVC/RTTI Type system --- src/RemoteNET.Tests/RttiTypesFactoryTests.cs | 6 +-- .../Reflection/Rtti/RttiTypesFactory.cs | 10 ++-- src/RemoteNET/UnmanagedRemoteApp.cs | 49 +++++++++++++++---- src/ScubaDiver/DiverBase.cs | 7 +++ src/ScubaDiver/MsvcDiver.cs | 6 +-- .../CustomUndecoratedFunction.cs | 9 ++-- .../MsvcPrimitives/MsvcTypesManager.cs | 10 ++-- 7 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/RemoteNET.Tests/RttiTypesFactoryTests.cs b/src/RemoteNET.Tests/RttiTypesFactoryTests.cs index a2bce320..26b3527 100644 --- a/src/RemoteNET.Tests/RttiTypesFactoryTests.cs +++ b/src/RemoteNET.Tests/RttiTypesFactoryTests.cs @@ -260,7 +260,7 @@ public void AddFunctionImpl_DifferentDeclaringClassOnFunc_DifferentDeclaringType RemoteApp? fakeApp = new FakeRemoteApp(); // Act - RttiTypesFactory.AddFunctionImpl(fakeApp, typeDump, func, childType, false); + RttiTypesFactory.AddFunctionImpl(fakeApp, typeDump.Assembly, func, childType, false); // Assert MethodInfo? method = childType.GetMethods().Single(); @@ -291,7 +291,7 @@ public void AddFunctionImpl_SameDeclaringClassOnFunc_SameDeclaringType() RemoteApp? fakeApp = new FakeRemoteApp(); // Act - RttiTypesFactory.AddFunctionImpl(fakeApp, typeDump, func, childType, false); + RttiTypesFactory.AddFunctionImpl(fakeApp, typeDump.Assembly, func, childType, false); // Assert MethodInfo? method = childType.GetMethods().Single(); @@ -345,7 +345,7 @@ public void UndecoratingConstRef_ParseType_NoMethod() RemoteApp? fakeApp = new FakeRemoteApp(); // Act - RttiTypesFactory.AddFunctionImpl(fakeApp, typeDump, func, childType, false); + RttiTypesFactory.AddFunctionImpl(fakeApp, typeDump.Assembly, func, childType, false); // Assert // Expecting `AddFunctionImpl` to NOT add that function (not supported yet) diff --git a/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs b/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs index 6ad3458..d383e5c 100644 --- a/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs +++ b/src/RemoteNET/Internal/Reflection/Rtti/RttiTypesFactory.cs @@ -164,18 +164,16 @@ private static void AddGroupOfFunctions(RemoteApp app, TypeDump typeDump, List parameters = new List(func.Parameters.Count); int i = 1; foreach (TypeDump.TypeMethod.MethodParameter restarizedParameter in func.Parameters) @@ -255,7 +253,7 @@ Lazy CreateTypeFactory(string namespaceAndTypeName, string moduleName) if (_shittyCache.TryGetValue(namespaceAndTypeName, out resultType)) return resultType; - resultType = RttiTypesResolver.Instance.Resolve(typeDump.Assembly, $"{typeDump.Assembly}!{namespaceAndTypeName}"); + resultType = RttiTypesResolver.Instance.Resolve(moduleName, $"{moduleName}!{namespaceAndTypeName}"); if (resultType != null) return resultType; @@ -284,7 +282,7 @@ Lazy CreateTypeFactory(string namespaceAndTypeName, string moduleName) // Prefer any matches in the existing assembly var paramTypeInSameAssembly = - possibleParamTypes.Where(t => t.Assembly == typeDump.Assembly).ToArray(); + possibleParamTypes.Where(t => t.Assembly == moduleName).ToArray(); if (paramTypeInSameAssembly.Length > 0) { if (paramTypeInSameAssembly.Length > 1) diff --git a/src/RemoteNET/UnmanagedRemoteApp.cs b/src/RemoteNET/UnmanagedRemoteApp.cs index b67c3da..f9c2296 100644 --- a/src/RemoteNET/UnmanagedRemoteApp.cs +++ b/src/RemoteNET/UnmanagedRemoteApp.cs @@ -252,6 +252,42 @@ public MethodInfo RegisterCustomFunction( throw new ArgumentException("The parent type must be a RemoteRttiType. Only remote RTTI types created by UnmanagedRemoteApp can have custom functions registered.", nameof(parentType)); } + static RegisterCustomFunctionRequest.ParameterTypeInfo CreateParamInfo(Type pt, int idx) + { + var typeName = pt?.FullName ?? "void"; + var assembly = pt.Assembly?.GetName()?.Name; + + if (pt.IsPrimitive) + { + typeName = pt.Name; + assembly = "mscorlib"; + + if (pt == typeof(ulong)) + { + typeName = "ulong"; + } + } + + return new RegisterCustomFunctionRequest.ParameterTypeInfo + { + Name = $"param{idx}", + TypeFullName = typeName, + Assembly = assembly + }; + } + + var retTypeName = returnType?.FullName ?? "void"; + var retTypeAssembly = returnType?.Assembly?.GetName()?.Name; + if (returnType != null && returnType.IsPrimitive) + { + retTypeName = returnType.Name; + retTypeAssembly = "mscorlib"; + if (returnType == typeof(ulong)) + { + retTypeName = "ulong"; + } + } + var request = new RegisterCustomFunctionRequest { ParentTypeFullName = rttiType.Namespace + "::" + rttiType.Name, // Use namespace::name format for RTTI types @@ -259,14 +295,9 @@ public MethodInfo RegisterCustomFunction( FunctionName = functionName, ModuleName = moduleName, Offset = offset, - ReturnTypeFullName = returnType?.FullName ?? "void", - ReturnTypeAssembly = returnType?.Assembly?.GetName()?.Name, - Parameters = parameterTypes?.Select((pt, idx) => new RegisterCustomFunctionRequest.ParameterTypeInfo - { - Name = $"param{idx}", - TypeFullName = pt.FullName, - Assembly = pt.Assembly?.GetName()?.Name - }).ToList() ?? new List() + ReturnTypeFullName = retTypeName, + ReturnTypeAssembly = retTypeAssembly, + Parameters = parameterTypes?.Select(CreateParamInfo).ToList() ?? new List() }; bool success = _unmanagedCommunicator.RegisterCustomFunction(request, out var methodDump); @@ -285,7 +316,7 @@ public MethodInfo RegisterCustomFunction( // Use the existing factory method to add the function to the RemoteRttiType // AddFunctionImpl returns the newly created MethodInfo - return RttiTypesFactory.AddFunctionImpl(this, parentTypeDump, methodDump, rttiType, areConstructors: false); + return RttiTypesFactory.AddFunctionImpl(this, parentTypeDump.Assembly, methodDump, rttiType, areConstructors: false) as MethodInfo; } // diff --git a/src/ScubaDiver/DiverBase.cs b/src/ScubaDiver/DiverBase.cs index 4c908e9..6bc0b1e 100644 --- a/src/ScubaDiver/DiverBase.cs +++ b/src/ScubaDiver/DiverBase.cs @@ -130,6 +130,13 @@ public string QuickError(string error, string stackTrace = null) private void HandleDispatchedRequest(object obj, ScubaDiverMessage request) { + // Check if the "debug" query parameter is set. If so, launch the debugger + if (request.QueryString.Get("debug") == "1") + { + Logger.Debug("[DiverBase] Debugging enabled"); + Debugger.Launch(); + } + Stopwatch sw = Stopwatch.StartNew(); string body; if (_responseBodyCreators.TryGetValue(request.UrlAbsolutePath, out var respBodyGenerator)) diff --git a/src/ScubaDiver/MsvcDiver.cs b/src/ScubaDiver/MsvcDiver.cs index f94f71c..f39325f 100644 --- a/src/ScubaDiver/MsvcDiver.cs +++ b/src/ScubaDiver/MsvcDiver.cs @@ -212,7 +212,6 @@ protected override ObjectOrRemoteAddress InvokeEventCallback(IPEndPoint callback protected override HookResponse InvokeHookCallback(IPEndPoint callbacksEndpoint, int token, string stackTrace, object retValue, params object[] parameters) { - Logger.Debug($"[{nameof(MsvcDiver)}] InvokeHookCallback Entered. EndPoint: {callbacksEndpoint} Token: {token}"); ReverseCommunicator reverseCommunicator = new(callbacksEndpoint); ObjectOrRemoteAddress[] remoteParams = new ObjectOrRemoteAddress[parameters.Length]; @@ -296,7 +295,7 @@ protected override string MakeTypesResponse(ScubaDiverMessage req) string typeFilter = req.QueryString.Get("type_filter"); if (string.IsNullOrWhiteSpace(typeFilter)) - return QuickError("Missing parameter 'type_filter'"); + return QuickError("Missing parameter 'type_filter'. Try this: /types?type_filter=*"); ParseFullTypeName(typeFilter, out var assemblyFilter, out typeFilter); Predicate typeFilterPredicate = Filter.CreatePredicate(typeFilter); @@ -339,7 +338,6 @@ protected override string MakeTypeResponse(ScubaDiverMessage req) { return QuickError("Failed to deserialize body"); } - Logger.Debug($"[MsvcDiver][MakeTypeResponse] Resolving type Name: {request.Assembly} {request.TypeFullName} vftable: 0x{request.MethodTableAddress:x16}"); TypeDump dump; if (request.MethodTableAddress != 0) @@ -499,7 +497,6 @@ protected override string MakeObjectResponse(ScubaDiverMessage arg) // Check if the object is already frozen if (_freezer.IsFrozen(objAddr)) { - Logger.Debug($"[MsvcDiver][MakeObjectResponse] Object at 0x{objAddr:X16} is already frozen."); ObjectDump alreadyFrozenObjDump = new ObjectDump() { Type = fullTypeName, @@ -556,7 +553,6 @@ protected override string MakeCreateObjectResponse(ScubaDiverMessage arg) protected override string MakeInvokeResponse(ScubaDiverMessage arg) { - Console.WriteLine($"[{nameof(MsvcDiver)}] MakeInvokeResponse Entered (!)"); if (string.IsNullOrEmpty(arg.Body)) return QuickError("Missing body"); diff --git a/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs b/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs index 22ab954..00edad8 100644 --- a/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs +++ b/src/ScubaDiver/MsvcPrimitives/CustomUndecoratedFunction.cs @@ -1,4 +1,6 @@ +using Microsoft.Diagnostics.Runtime.AbstractDac; using ScubaDiver.Rtti; +using System; namespace ScubaDiver { @@ -13,20 +15,19 @@ public class CustomUndecoratedFunction : UndecoratedFunction private readonly string[] _argTypes; public CustomUndecoratedFunction( - string moduleName, - nuint baseAddress, + ModuleInfo module, ulong offset, string functionName, string returnType, string[] argTypes) : base(functionName, functionName, functionName, argTypes?.Length) { - _module = new ModuleInfo { Name = moduleName, BaseAddress = baseAddress }; + _module = module; // Check for potential overflow when adding baseAddress and offset checked { - _address = baseAddress + offset; + _address = module.BaseAddress + (nuint)offset; } _retType = returnType; diff --git a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs index 9d7296f..8a27c02 100644 --- a/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs +++ b/src/ScubaDiver/MsvcPrimitives/MsvcTypesManager.cs @@ -508,16 +508,18 @@ public bool RegisterCustomFunction( // Find the module base address List modules = GetUndecoratedModules(Filter.CreatePredicate(moduleName)); if (modules.Count == 0) - return false; + { + modules = GetUndecoratedModules(Filter.CreatePredicate(moduleName + ".dll")); + if (modules.Count == 0) + return false; + } // Since we've verified the list is not empty, we can safely access the first element UndecoratedModule targetModule = modules[0]; - nuint moduleBaseAddress = targetModule.ModuleInfo.BaseAddress; // Create a custom undecorated function CustomUndecoratedFunction customFunc = new CustomUndecoratedFunction( - moduleName, - moduleBaseAddress, + targetModule.ModuleInfo, offset, functionName, returnTypeFullName,