Devlooped.Injector 1.0.0-rc

Prefix Reserved
This is a prerelease version of Devlooped.Injector.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Devlooped.Injector --version 1.0.0-rc                
NuGet\Install-Package Devlooped.Injector -Version 1.0.0-rc                
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="Devlooped.Injector" Version="1.0.0-rc" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Devlooped.Injector --version 1.0.0-rc                
#r "nuget: Devlooped.Injector, 1.0.0-rc"                
#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 Devlooped.Injector as a Cake Addin
#addin nuget:?package=Devlooped.Injector&version=1.0.0-rc&prerelease

// Install Devlooped.Injector as a Cake Tool
#tool nuget:?package=Devlooped.Injector&version=1.0.0-rc&prerelease                

Allows injecting .NET code into any Windows process.

Heavily based on Cory Plott's Snoop.

The only requirement is that the injected code must be a public static method on a public static class, such as:

namespace Sample;

public static class Startup
{
    public static void Start(string arg1, int arg2, bool debug)
    {
        if (debug)
            Debugger.Launch();

        // do stuff with arg1, arg2, etc.
        // note args are typed :)
    }
}

NOTE: parameter type conversion is supported and happens via the TypeConverter associated with the parameter type.

Usage

There are two main usages for this package:

  • From AnyCPU code: your code is bitness-agnostic and can be injected into the target process whether it's x86 or x64.
  • From x86/x64 code: you are injecting into a target process that has the same bitness as the calling code.

AnyCPU code

This is likely the more common scenario. You have .NET code that is AnyCPU and can therefore be injected regardless of the target process bitness. When referencing this package, you will get two (content) folders containing a helper Injector.exe for each architecture:

Screenshot

These files are automatically copied to the output directory under Injector\[x86|x64]\Injector.exe (and are also included when doing dotnet publish). This allows you to run the relevant executable that matches the target process bitness.

Injector.exe usage:

> Injector.exe -?
Usage: Injector.exe <mainWindowHandle> <assemblyFile> <typeName> <methodName>

Arguments:
  <processMainWindowHandle>   IntPtr of the main window handle of the process to inject, i.e. Process.MainWindowHandle.
  <assemblyFile>              The full path to the .NET assembly to load in the remote process.
  <typeName>                  Full type name of the public static class to invoke in the remote process.
  <methodName>                Name of the static method in that class to invoke in the remote process. Must be a
                              static method, which can also receive arguments, such as 'Start:true:42'.

To detect the target process bitness, you can use the following bit of interop:

static class NativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool IsWow64Process([In] IntPtr process, [Out] out bool wow64Process);
}

And the following code would lookup the target process (in this case, we just get the first instance of notepad.exe, as an example), and invoke the right executable:

var targetProcess = System.Diagnostics.Process.GetProcessesByName("notepad.exe")[0];

NativeMethods.IsWow64Process(targetProcess.Handle, out var isWow);
var platform = isWow ? "x86" : "x64";

Process.Start(Path.Combine("Injector", platform, "Injector.exe"),
    // IntPtr of the main window handle of the process to inject
    targetProcess.MainWindowHandle + " " +
    // The full path to the .NET assembly to load in the remote process
    Assembly.GetExecutingAssembly().Location + " " +
    // Full type name of the public static class to invoke in the remote process
    typeof(Startup).FullName + " " +
    // Name of the static method in that class to invoke in the remote process, 
    // and any parameters.
    $"{nameof(Startup.Start)}:hello:42:true");

NOTE: we can pass typed arguments to the Startup.Start method (shown as an example at the beginning) and type conversion will be applied automatically.

Platform-specific code

When building platform-specific code, the project would typically have (for a console app, for example):

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net6.0</TargetFramework>
		<Platforms>x64;x86</Platforms>
	</PropertyGroup>

</Project>

You would then build for either platform via: dotnet build --arch x64 or dotnet build --arch x86.

In this case, the bitness of the calling code (that intends to inject itself into a remote process) must match the target process bitness too. Since the bitness of both is the same, you can use the automatically referenced assembly from your code, rather than invoking the helper Injector.exe as shown in the first case.

The code will otherwise look similar to the previous case:

var targetProcess = System.Diagnostics.Process.GetProcessesByName("notepad.exe")[0];

// NOTE: target process bitness must match our own assembly architecture for 
// this to succeed.
Devlooped.Injector.Launch(
    // IntPtr of the main window handle of the process to inject
    targetProcess.MainWindowHandle,
    // The full path to the .NET assembly to load in the remote process
    Assembly.GetExecutingAssembly().Location,
    // Full type name of the public static class to invoke in the remote process
    typeof(Startup).FullName + " " +
    // Name of the static method in that class to invoke in the remote process, 
    // and any parameters.
    $"{nameof(Startup.Start)}:hello:42:true");

NOTE: the Devlooped.Injector type will NOT be available on AnyCPU projects

See Program.cs for a complete example.

Sponsors

Clarius Org Christian Findlay C. Augusto Proiete Kirill Osenkov MFB Technologies, Inc. Amazon Web Services SandRock David Pallmann

Sponsor this project  

Learn more about GitHub Sponsors

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Devlooped.Injector:

Package Downloads
xunit.vsix

Allows creating reliable, flexible and fast VS SDK integration (VSIX) tests that run using any xUnit capable runner, such as Visual Studio built-in Test Explorer or TestDriven.NET.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.0 1,202 9/23/2022
1.0.1 456 9/9/2022
1.0.0-rc 257 9/9/2022
1.0.0-beta 276 9/9/2022