![]() |
VOOZH | about |
dotnet add package RemoteNET --version 1.0.4.22
NuGet\Install-Package RemoteNET -Version 1.0.4.22
<PackageReference Include="RemoteNET" Version="1.0.4.22" />
<PackageVersion Include="RemoteNET" Version="1.0.4.22" />Directory.Packages.props
<PackageReference Include="RemoteNET" />Project file
paket add RemoteNET --version 1.0.4.22
#r "nuget: RemoteNET, 1.0.4.22"
#:package RemoteNET@1.0.4.22
#addin nuget:?package=RemoteNET&version=1.0.4.22Install as a Cake Addin
#tool nuget:?package=RemoteNET&version=1.0.4.22Install as a Cake Tool
This library lets you examine, create and interact with remote objects in other .NET/Visual C++ 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 😃
✅ .NET 5/6/7/8/9
✅ .NET Core 3.0/3.1
✅ .NET Framework 4.5/4.6/4.7/4.8 (incl. subversions)
✅ MSVC-compiled C++ (experimental)
There are 2 ways to get the library:
RemoteNET.dll and ScubaDiver.API.dll in your project.detours.net)cd <<your RemoteNET repo path>>\srcmkdir detours_buildcd detours_buildcmake ..\detours.netmsbuild /t:restore,build ALL_BUILD.vcxprojRemoteNET.sln in Visual StudioThis 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, .
This section documents most parts of the library's API which you'll likely need.
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).
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());
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);
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);
}
Just like accessing fields, invoking methods can be done on the dynamic objects.
See above example about dumping RSA keys.
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:
dynamicsAction<...>.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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net7.0 net7.0 is compatible. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.4.22 | 402 | 1/4/2025 |
| 1.0.4.21 | 353 | 1/4/2025 |
| 1.0.4.17 | 362 | 11/2/2024 |
| 1.0.4.8 | 340 | 11/2/2024 |
| 1.0.4.7 | 339 | 11/2/2024 |
| 1.0.4.6 | 375 | 9/14/2024 |
| 1.0.4.2 | 326 | 6/8/2024 |
| 1.0.4.1 | 336 | 6/7/2024 |
| 1.0.3.1 | 384 | 2/17/2024 |
| 1.0.2.27 | 359 | 2/10/2024 |
| 1.0.2.15 | 325 | 1/29/2024 |
| 1.0.2.14 | 386 | 12/1/2023 |
| 1.0.2.4 | 337 | 9/1/2023 |
| 1.0.2.3 | 324 | 8/31/2023 |
| 1.0.2.2 | 364 | 8/4/2023 |
| 1.0.2 | 368 | 5/26/2023 |
| 1.0.1.28 | 403 | 4/12/2023 |
| 1.0.1.27 | 388 | 4/12/2023 |
| 1.0.1.26 | 481 | 2/3/2023 |
| 1.0.1.25 | 494 | 12/29/2022 |