RemoteNET 1.0.4.17

dotnet add package RemoteNET --version 1.0.4.17                
NuGet\Install-Package RemoteNET -Version 1.0.4.17                
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.4.17" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RemoteNET --version 1.0.4.17                
#r "nuget: RemoteNET, 1.0.4.17"                
#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.4.17

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

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/8
✅ .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. msbuild ALL_BUILD.vcxproj
  9. Open RemoteNET.sln in Visual Studio
  10. Compile the RemoteNET project

Minimal Working Example

This fun example dumps all private RSA keys (which are stored in RSACryptoServiceProviders) in a target app's memory:

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

// Finding every RSACryptoServiceProvider instance
RemoteApp remoteApp = RemoteAppFactory.Connect("MyDotNetTarget.exe", RuntimeType.Managed);
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));
}

For a more advanced example, See this "KeeFarce" remake.

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:

// 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 new 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.
See above example about dumping RSA keys.

✳️ 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/KeeFarce: Which was a major inspiration for this project.
Also, multiple parts of this project were adapted from ones in KeeFarce (DLL Injection, Bootstrap, IntPtr-to-Object converter).
TheLeftExit/Trickster: Which I used for the MSVC Diver (for C++ targets).
pardeike/Harmony: Which I used for hooking .NET methods.
microsoft/Detours: Which I used for hooking native methods.
citronneur/detours.net: Which I used as a wrapper for Detours.
uxmal/reko: Which I used to demangle C++ symbols.

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.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 324 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 373 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