Skip to content

Commit

Permalink
Update Readme Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Mbucari committed Nov 10, 2023
1 parent f3c6b5b commit 8521816
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 119 deletions.
159 changes: 40 additions & 119 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Inject a .NET Core dll into a native Win32 or Win64 process. InjectDotnet is a library, not a standalone application. This allows developers/hackers to pass any argument to the injected dll, not just a string. There are two complementary libraries:

- **InjectDotnet**: Injects a managed dll into a native process.
- **InjectDotnet.NativeHelper**: Referenced by the injected dll and provides methods for hooking native functions
- **InjectDotnet.NativeHelper**: Referenced by the injected dll and provides methods for hooking native functions.

Add [InjectDotnet](https://www.nuget.org/packages/InjectDotnet) to your injector, and add [InjectDotnet.NativeHelper](https://www.nuget.org/packages/InjectDotnet.NativeHelper) to your injected dll.

Expand All @@ -13,139 +13,60 @@ Add [InjectDotnet](https://www.nuget.org/packages/InjectDotnet) to your injector

Unlike other dotnet dll injectors, this one does not rely on a native dll to load the runtime in the target process. Loading and executing the injected dll is accomplished by hand-written assembly instructions that are written directly into the target process' memory space and executed.

## Hooking Imported Functions
Hooking imports is accomplished by replacing the target function's address in a module's import address table with a pointer to the hook delegate. The hook delegate can be either an `[UnmanagedCallersOnly]` method or a managed delegate with the same signature as the imported function. The original function pointer is stored in `ImportHook.OriginalFunction` and can be called by creating a delegate for it.
## Inject Into Running Processes

In the sample, all of notepad.exe's calls to `WriteFile` will call `WriteFile_hook`, and `WriteFile_hook` modifies the parameters before calling `Kernel32.WriteFile`.
**InjectDotnet** Supports injecting managed Dlls into running processes using the traditional `CreateRemoteThread()` method.

It's as simple as the following example.

```C#
static INativeHook? WriteFileHook;
static WriteFileDelegate? WriteFile_original;

delegate bool WriteFileDelegate(
IntPtr hFile,
byte* lpBuffer,
int nNumberOfBytesToWrite,
ref int lpNumberOfBytesWritten,
IntPtr lpOverlapped);

public static int Bootstrap(IntPtr argument, int size)
{
//Hook kernel32.WriteFile in the main module's import table
WriteFileHook
= Process
.GetCurrentProcess()
.MainModule
?.GetImportByName("kernel32", "WriteFile")
?.Hook(WriteFile_hook);

if (WriteFileHook is not null)
WriteFile_original = Marshal
.GetDelegateForFunctionPointer<WriteFileDelegate>(WriteFileHook.OriginalFunction);
}

static bool WriteFile_hook(
IntPtr hFile,
byte* lpBuffer,
int nNumberOfBytesToWrite,
ref int NumberOfBytesWritten,
IntPtr lpOverlapped)
{
return WriteFile_original!(hFile, lpBuffer, nNumberOfBytesToWrite, ref NumberOfBytesWritten, lpOverlapped);
}
var target = Process.GetProcessesByName("target");

target.Inject(
"InjectedDll.runtimeconfig.json",
"InjectedDll.dll",
"InjectedDll.HookDemo, InjectedDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Bootstrap"
"this is an argument passed to the injected dll's Bootstrap() method");
```
## Hooking Exported Functions

Hooking exports is accomplished by overwriting the original function's entry point instructions with a jump to a block of memory allocated nearby. NativeHelper will attempt to create a trampoline (using a C# port of [minhook](https://github.com/TsudaKageyu/minhook)). If successdul, the original function may be called without removing the hook. If trampoline creation failed, the hook must be removed via `RemoveHook()` before calling the original function. See the `HasTrampoline` property. In both cases, calls to the original function will jump to the hook delegate when the hook is installed. The delegate can be either an `[UnmanagedCallersOnly]` method or a managed delegate with the same signature as the exported function.
You may optionally wait for bootstrap method to return to receive it's return code, and the injector supports passing structs with additional data to the injected dll (see the samples prokjects).

In the sample, all calls to `ReadFile` within notepad.exe's process will call `ReadFile_hook`. `ReadFile_hook` peeks at the parameters, calls `Kernelbase.ReadFile`, and then returns the file handle.
## Inject Into a New Process at Startup

```C#
using BOOL = System.Int32;

static INativeHook? ReadFileHook;
static delegate* unmanaged[Stdcall]<IntPtr, byte*, int, int*, IntPtr, BOOL> ReadFile_original;

public static int Bootstrap(IntPtr argument, int size)
{
delegate* unmanaged[Stdcall]<IntPtr, byte*, int, int*, IntPtr, BOOL> hook2 = &ReadFile_hook;
ReadFileHook
= currentProc
.GetModulesByName("kernel32")
.FirstOrDefault()
?.GetExportByName("ReadFile")
?.Hook(hook2);

if (ReadFileHook is not null)
ReadFile_original =
(delegate* unmanaged[Stdcall]<IntPtr, byte*, int, int*, IntPtr, BOOL>)
ReadFileHook.OriginalFunction;
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
static BOOL ReadFile_hook(IntPtr hFile, byte* lpBuffer, int nNumberOfBytesToWrite, int* lpNumberOfBytesWritten, IntPtr lpOverlapped)
{
var result = ReadFile_original(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
return result;
}
```
## Hooking Arbitrary Addresses
**InjectDotnet** Supports injecting managed Dlls at the entry point of a process using its built-in debugger.

You may install a hook at any address using `JumpHook.Create()`.
It's as simple as the following example.

## Breakpoint Hooks
```C#
var debugger = new Debugger("target.exe", arguments: null);

You may perform hooking using i386 hardware breakpoints. `BreakpointHook` is thread-specific because it relies on the CPU's debug registers being set in the thread's context. You're also limited to four breakpoints per thread. Additionally, you will almost certainly not be able to debug the hook function because the hardware breakpoint will notify the debugger of a stop and it will deadlock.
debugger.InjectStartup(
"InjectedDll.runtimeconfig.json",
"InjectedDll.dll",
"InjectedDll.HookDemo, InjectedDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Bootstrap",
"this is an argument passed to the injected dll's Bootstrap() method");

```C#
static INativeHook? CreateFileWHook;
static delegate* unmanaged[Stdcall]<IntPtr, uint, uint, IntPtr, uint, uint, IntPtr, IntPtr> CreateFileW_original;

public static int Bootstrap(IntPtr argument, int size)
{
var firstThread = currentProc.Threads.Cast<ProcessThread>().MinBy(t => t.StartTime);

delegate* unmanaged[Stdcall]<IntPtr, uint, uint, IntPtr, uint, uint, IntPtr, IntPtr> hook3 = &CreateFileW_hook;
CreateFileWHook
= currentProc
.GetModulesByName("kernel32")
.FirstOrDefault()
?.GetExportByName("CreateFileW")
?.Hook(hook3, firstThread, installAfterCreate: false);

if (CreateFileWHook?.OriginalFunction is not null or 0)
{
CreateFileW_original =
(delegate* unmanaged[Stdcall]<IntPtr, uint, uint, IntPtr, uint, uint, IntPtr, IntPtr>)
CreateFileWHook.OriginalFunction;

CreateFileWHook.InstallHook();
}
}

[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
static IntPtr CreateFileW_hook(
IntPtr lpFileName, uint dwDesiredAccess, uint dwShareMode,
IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile)
{
var result
= CreateFileW_original(
lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);

return result;
}
await debugger.ResumeProcessAsync();
```
The debugger supports all [win32 debug events](https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-debug_event#members), and you may use them to, for example, receive data from the injected dll via [OutputDebugString](https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw).

## Hooking Native Functions

**InjectDotnet.NativeHelper** supports three different hooking methods:

|Hook Type|Description|
|-|-|
|[ImportHook](docs/HookingImports.md)|Replaces the hooked function's import address table entry in the target module with a pointer to the hooking function.|
|[JumpHook](docs/HookingExports.md)|Overwrite's the first instruction(s) of the hooked function with a jump to a Trampoline.|
|[BreakpointHook](docs/BreakpointHooks.md)|Sets a hardware breakpoint at the hooked function's first instruction and uses a vectored exception handler to intercept execution.|

## See the samples for useage.
There are two sample projects:
- **SampleInjected** - A .NET 6.0 dll to be injected into a native process and uses `InjectDotnet.NativeHelper` to hook native functions.
- **SampleInjector** - The program that uses `InjectDotnet` to inject `SampleInjected` into Windows notepad.exe and pass it two strings and a png image as arguments. Executes `SampleInjected.Program.Bootstrap` after injection.
- **InjectedDll** - A .NET 7.0 dll to be injected into a native process and uses `InjectDotnet.NativeHelper` to hook native functions.
- **InjectIntoRunning** - The program that uses `InjectDotnet` to inject `InjectedDll` into HxD.exe and pass it two strings and a png image as arguments. Executes `InjectedDll.HookDemo.Bootstrap` after injection.
- **InjectAtStartup** - The program that uses `InjectDotnet` to debug HxD.exe and inject `InjectedDll` at its entry point. Executes `InjectedDll.HookDemo.Bootstrap` after injection.

`SampleInjected.Program.Bootstrap` loads the two strings and the png image from native memory, frees the native memory, and then opens a `System.Windows.Forms.Form` to display the strings and image.

Expand Down
52 changes: 52 additions & 0 deletions docs/BreakpointHooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Breakpoint Hooks

You may perform hooking using i386 hardware breakpoints. `BreakpointHook` is thread-specific because it relies on the CPU's debug registers being set in the thread's context. You're also limited to four breakpoints per thread.

Additionally, you will not be able to debug the hooking function. The attached debugger will see the hardware breakpoint as a Single Step debug event and will either handle it (so `BreakpointHook` cannot handle it), or it will wait indefinitely for user input before continuing (resulting in deadlock). For this reason, `BreakpointHook` will throw an exception if the process is being debugged.

```C#
static INativeHook? CreateFileWHook;
static delegate* unmanaged[Stdcall]<IntPtr, uint, uint, IntPtr, uint, uint, IntPtr, IntPtr> CreateFileW_original;

public static int Bootstrap(IntPtr argument, int size)
{
using var currentProc = Process.GetCurrentProcess();
var firstThread = currentProc.Threads.Cast<ProcessThread>().MinBy(t => t.StartTime);

delegate* unmanaged[Stdcall]<IntPtr, uint, uint, IntPtr, uint, uint, IntPtr, IntPtr> hook3 = &CreateFileW_hook;
CreateFileWHook
= currentProc
.GetModulesByName("kernel32")
.FirstOrDefault()
?.GetExportByName("CreateFileW")
?.Hook(hook3, firstThread, installAfterCreate: false);

if (CreateFileWHook?.OriginalFunction is not null or 0)
{
CreateFileW_original =
(delegate* unmanaged[Stdcall]<IntPtr, uint, uint, IntPtr, uint, uint, IntPtr, IntPtr>)
CreateFileWHook.OriginalFunction;

CreateFileWHook.InstallHook();
}
}

[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
static IntPtr CreateFileW_hook(
IntPtr lpFileName, uint dwDesiredAccess, uint dwShareMode,
IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile)
{
var result
= CreateFileW_original(
lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);

return result;
}
```
41 changes: 41 additions & 0 deletions docs/HookingImports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Hooking Imported Functions
Hooking imports is accomplished by replacing the target function's address in a module's import address table with a pointer to the hook delegate. The hook delegate can be either an `[UnmanagedCallersOnly]` method or a managed delegate with the same signature as the imported function. The original function pointer is stored in `ImportHook.OriginalFunction` and can be called by creating a delegate for it.

In the sample, all of notepad.exe's calls to `WriteFile` will call `WriteFile_hook`, and `WriteFile_hook` modifies the parameters before calling `Kernel32.WriteFile`.

```C#
static INativeHook? WriteFileHook;
static WriteFileDelegate? WriteFile_original;

delegate bool WriteFileDelegate(
IntPtr hFile,
byte* lpBuffer,
int nNumberOfBytesToWrite,
ref int lpNumberOfBytesWritten,
IntPtr lpOverlapped);

public static int Bootstrap(IntPtr argument, int size)
{
//Hook kernel32.WriteFile in the main module's import table
WriteFileHook
= Process
.GetCurrentProcess()
.MainModule
?.GetImportByName("kernel32", "WriteFile")
?.Hook(WriteFile_hook);

if (WriteFileHook is not null)
WriteFile_original = Marshal
.GetDelegateForFunctionPointer<WriteFileDelegate>(WriteFileHook.OriginalFunction);
}

static bool WriteFile_hook(
IntPtr hFile,
byte* lpBuffer,
int nNumberOfBytesToWrite,
ref int NumberOfBytesWritten,
IntPtr lpOverlapped)
{
return WriteFile_original!(hFile, lpBuffer, nNumberOfBytesToWrite, ref NumberOfBytesWritten, lpOverlapped);
}
```
36 changes: 36 additions & 0 deletions docs/JumpHooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Hooking Exported Functions (Jump Hooks)

Hooking exports is accomplished by overwriting the original function's entry point instructions with a jump to a block of memory allocated nearby. NativeHelper will attempt to create a trampoline (using a C# port of [minhook](https://github.com/TsudaKageyu/minhook)). If successdul, the original function may be called without removing the hook. If trampoline creation failed, the hook must be removed via `RemoveHook()` before calling the original function. See the `HasTrampoline` property. In both cases, calls to the original function will jump to the hook delegate when the hook is installed. The delegate can be either an `[UnmanagedCallersOnly]` method or a managed delegate with the same signature as the exported function.

In the sample, all calls to `ReadFile` within notepad.exe's process will call `ReadFile_hook`. `ReadFile_hook` peeks at the parameters, calls `Kernelbase.ReadFile`, and then returns the file handle.

You may create a jump hook for an arbitrary instruction using `JumpHook.Create()`.

```C#
using BOOL = System.Int32;

static INativeHook? ReadFileHook;
static delegate* unmanaged[Stdcall]<IntPtr, byte*, int, int*, IntPtr, BOOL> ReadFile_original;

public static int Bootstrap(IntPtr argument, int size)
{
delegate* unmanaged[Stdcall]<IntPtr, byte*, int, int*, IntPtr, BOOL> hook2 = &ReadFile_hook;
ReadFileHook
= currentProc
.GetModulesByName("kernel32")
.FirstOrDefault()
?.GetExportByName("ReadFile")
?.Hook(hook2);

if (ReadFileHook is not null)
ReadFile_original =
(delegate* unmanaged[Stdcall]<IntPtr, byte*, int, int*, IntPtr, BOOL>)
ReadFileHook.OriginalFunction;
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
static BOOL ReadFile_hook(IntPtr hFile, byte* lpBuffer, int nNumberOfBytesToWrite, int* lpNumberOfBytesWritten, IntPtr lpOverlapped)
{
var result = ReadFile_original(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
return result;
}
```

0 comments on commit 8521816

Please sign in to comment.