theXappy/RemoteNET
GitHub: theXappy/RemoteNET
一个允许在不修改目标程序的前提下,以编程方式检查、创建并操作其他 .NET/C++ 进程中远程对象的进程交互库。
Stars: 46 | Forks: 3

# RemoteNET [][nuget-link]
这个库允许你检查、创建并与其他 .NET/Visual C++ 进程中的远程对象进行交互。
目标应用程序不需要专门编译(或同意)来支持它。
### 👉 立即尝试:下载 [RemoteNET Spy](https://github.com/theXappy/rnet-kit) 应用程序,查看此库的实际运行效果!👈
## **支持的目标**
✅ .NET 5/6/7/8/9
✅ .NET Core 3.0/3.1
✅ .NET Framework 4.5/4.6/4.7/4.8(包含子版本)
✅ MSVC 编译的 C++(实验性)
## 在项目中包含此库
有两种方法可以获取该库:
1. 从 [NuGet][nuget-link] 获取
-或者-
2. 克隆此仓库,编译后在你的项目中引用 `RemoteNET.dll` 和 `ScubaDiver.API.dll`。
## 编译
1. Clone
2. 初始化 git 模块(针对 `detours.net`)
3. 启动 "x64 Native Tools Command Prompt for VS 2022"
4. `cd <<你的 RemoteNET 仓库路径>>\src`
5. `mkdir detours_build`
6. `cd detours_build`
7. `cmake ..\detours.net`
8. `msbuild /t:restore,build ALL_BUILD.vcxproj`
9. 在 Visual Studio 中打开 `RemoteNET.sln`
10. 编译 RemoteNET 项目
## 最小可行示例
这个有趣的示例转储了目标应用程序内存中所有的私有 RSA 密钥(存储在 `RSACryptoServiceProvider` 中):
```
Func 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));
}
```
有关更高级的示例,[请参阅这个“KeeFarce”重制版](./KeeFarce_lookalike.md)。
## 使用指南
本节记录了你可能需要的库 API 的大部分内容。
### ✳️ 设置
要开始操作远程进程,你需要像这样创建一个 `RemoteApp` 对象:
```
// For .NET targets
RemoteApp remoteApp = RemoteAppFactory.Connect("MyDotNetTarget.exe", RuntimeType.Managed);
// For MSVC C++ target
RemoteApp remoteApp = RemoteAppFactory.Connect("MyNativeTarget.exe", RuntimeType.Unmanaged);
```
如果你有多个同名进程在运行,
你可以使用重载方法 `Connect(System.Diagnostics.Process p, RuntimeType r)`。
### ✳️ 获取现有的远程对象
RemoteNET 首要的功能是允许你在远程应用程序中查找现有对象。
为此,你需要搜索远程堆。
使用 `RemoteApp.QueryInstances()` 查找所需对象的可能候选者,并使用 `RemoteApp.GetRemoteObject()` 获取候选者的句柄。
```
IEnumerable candidates = remoteApp.QueryInstances("MyApp.PasswordContainer");
RemoteObject passwordContainer = remoteApp.GetRemoteObject(candidates.Single());
```
### ✳️ 创建新的远程对象
有时,远程应用程序中的现有对象不足以满足你的需求。
因此,你也可以远程创建新对象。
为此请使用类似 `Activator` 的接口:
```
// 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);
```
注意我们在第二次 `CreateInstance` 调用中是如何使用构造函数参数的。这些参数也可以是其他的 `RemoteObject`:
```
// 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);
```
### ✳️ 读取远程字段/属性
为了提供流畅的编码体验,RemoteNET 使用了一种特殊的动态对象,任何 `RemoteObject` 都可以转换为它。
该对象可以用来访问字段/属性,就像它们是本地对象的字段/属性一样:
```
// 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)
```
一个更有趣的示例是检索每个 `SqlConnection` 实例的 `ConnectionString`:
```
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);
}
```
### ✳️ 调用远程方法
就像访问字段一样,可以在动态对象上调用方法。
请参阅上面关于转储 RSA 密钥的示例。
### ✳️ 远程事件
你也可以订阅/取消订阅远程事件。语法类似于“标准 C#”,尽管不完全相同:
```
CandidateObject cand = remoteApp.QueryInstances("System.IO.FileSystemWatcher").Single();
RemoteObject remoteFileSysWatcher = remoteApp.GetRemoteObject(cand);
dynamic dynFileSysWatcher = remoteFileSysWatcher.Dynamify();
Action callback = (dynamic o, dynamic e) => Console.WriteLine("Event Invoked!");
dynFileSysWatcher.Changed += callback;
/* ... Somewhere further ... */
dynFileSysWatcher.Changed -= callback;
```
限制:
1. 回调的参数必须是 `dynamic`
2. 回调必须为该事件定义确切数量的参数
3. 不允许使用 Lambda 表达式。回调必须转换为 `Action<...>`。
### ✳️ 注册自定义函数(仅限非托管/C++ 目标)
对于非托管(MSVC C++)目标,你可以注册未被 RTTI 扫描器自动发现的自定义函数。
当你知道目标进程中某个函数的地址并希望通过 RemoteNET 调用它时,这非常有用。
**重要:** 类型必须是从远程进程获取的 `RemoteRttiType` 实例。该类型的任何现有实例都将自动更新为新方法。
```
// Connect to an unmanaged target
UnmanagedRemoteApp unmanagedApp = (UnmanagedRemoteApp)RemoteAppFactory.Connect("MyNativeTarget.exe", RuntimeType.Unmanaged);
// Get a remote type (this creates a RemoteRttiType)
Type remoteType = unmanagedApp.GetRemoteType("MyNamespace::MyClass", "MyModule.dll");
// 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",
offset: 0x1234, // Offset from module base address
returnType: typeof(int),
parameterTypes: new[] { typeof(int), typeof(float) }
);
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);
}
```
**注意:**
- 此功能仅适用于非托管(C++)目标
- 父类型必须是 RemoteRttiType(通过 `UnmanagedRemoteApp.GetRemoteType()` 获取)
- 你需要知道函数所在的模块名称和偏移量
- 该函数将被添加到类型的方法列表中,并在所有实例上可用
- 参数和返回类型应指定为 .NET 类型
- 返回已注册方法的 `MethodInfo`,如果注册失败则返回 `null`
## 待办事项
1. 静态成员
2. 记录“Reflection API”(RemoteType, RemoteMethodInfo, ... )
3. 支持其他 .NET framework CLR 版本(.NET 4 之前)。目前支持 v4.0.30319
4. 记录 Harmony(prefix/postfix/finalizer 钩子)
5. 支持更多 Harmony 功能
## 致谢
[denandz/KeeFarce](https://github.com/denandz/KeeFarce):这是本项目的主要灵感来源。
此外,本项目的多个部分改编自 KeeFarce(DLL 注入、Bootstrap、IntPtr-to-Object 转换器)。
[TheLeftExit/Trickster](https://github.com/TheLeftExit/Trickster):我用它作为 MSVC Diver 的基础(针对 C++ 目标)。主要是堆中的 C++ 对象搜索。
[pardeike/Harmony](https://github.com/pardeike/Harmony):我用它来钩子 .NET 方法。
[microsoft/Detours](https://github.com/microsoft/Detours):我用它来钩子原生方法。
[citronneur/detours.net](https://github.com/citronneur/detours.net):我用它作为 Detours 的包装器。
[uxmal/reko](https://github.com/uxmal/reko):我用它来还原(demangle)C++ 符号。
**icons8** 提供的“Puppet”图标
**Raymond Chen** 在 [2010 年的这篇博文](https://devblogs.microsoft.com/oldnewthing/20100812-00/?p=13163)中声明这个项目不应该被实现。
我非常喜欢帖子中的这句话:
标签:DLL注入, Hook, MSVC, Obj hunt, RSA密钥提取, SSH蜜罐, 云资产清单, 内存操作, 反射, 多人体追踪, 恶意分析, 流量审计, 足迹探测, 运行时操作, 进程注入, 远程交互, 逆向工程