RemoteNET 1.0.1.5

There is a newer version of this package available.
See the version list below for details.
dotnet add package RemoteNET --version 1.0.1.5                
NuGet\Install-Package RemoteNET -Version 1.0.1.5                
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.1.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RemoteNET --version 1.0.1.5                
#r "nuget: RemoteNET, 1.0.1.5"                
#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.1.5

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

icon

RemoteNET NuGet

This library lets you examine, create and interact with remote objects in other .NET processes.
It's like System.Runtime.Remoting except the other app doesn't need to be compiled (or consent) to support it.

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

Tested versions:

  • .NET 5/6
  • .NET Core 3.0/3.1
  • .NET Framework 4.5/4.6/4.7/4.8 (incl. subversions)

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.

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
Process keePassProc =  Process.GetProcessesByName("KeePass").Single();
RemoteApp remoteApp = RemoteApp.Connect(keePassProc);
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:

Process target =  Process.GetProcessesByName("OtherDotNetAppName").Single();
RemoteApp remoteApp = RemoteApp.Connect(target);

✳️ 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 altough 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. Generics/Arrays aren't completely supported
  2. Static members
  3. Support injecting to self with local diver
  4. Document "Reflection API" (RemoteType, RemoteMethodInfo, ... )
  5. Support other .NET framework CLR versions (Before .NET 4). Currently supports v4.0.30319
  6. Document Harmony (prefix hooks)
  7. 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 net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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.4.17 80 11/2/2024
1.0.4.8 68 11/2/2024
1.0.4.7 66 11/2/2024
1.0.4.6 163 9/14/2024
1.0.4.2 127 6/8/2024
1.0.4.1 114 6/7/2024
1.0.3.1 173 2/17/2024
1.0.2.27 147 2/10/2024
1.0.2.15 137 1/29/2024
1.0.2.14 270 12/1/2023
1.0.2.4 213 9/1/2023
1.0.2.3 188 8/31/2023
1.0.2.2 235 8/4/2023
1.0.2 204 5/26/2023
1.0.1.28 236 4/12/2023
1.0.1.27 227 4/12/2023
1.0.1.26 303 2/3/2023
1.0.1.25 322 12/29/2022
1.0.1.24 306 12/24/2022
1.0.1.23 352 12/9/2022
1.0.1.22 333 11/18/2022
1.0.1.21 373 11/12/2022
1.0.1.20 333 11/9/2022
1.0.1.19 390 11/5/2022
1.0.1.18 373 11/5/2022
1.0.1.17 370 11/1/2022
1.0.1.16 372 11/1/2022
1.0.1.15 399 10/9/2022
1.0.1.14 387 10/5/2022
1.0.1.13 445 9/27/2022
1.0.1.11 475 3/5/2022
1.0.1.10 421 3/5/2022
1.0.1.9 454 2/21/2022
1.0.1.8 435 2/20/2022
1.0.1.7 442 2/20/2022
1.0.1.6 455 2/11/2022
1.0.1.5 450 1/24/2022
1.0.1.4 315 12/31/2021