Minimal framework to inject the .NET Runtime into a process in Rust

Overview

錆の核 sabinokaku

Minimal framework to inject the .NET Runtime into a process.

Supports Windows and Linux. macOS support is complicated due to SIP, and will not be covered here.

Building

Building sabinokaku requires the nightly toolchain. The workspace Cargo.toml is preconfigured to strip debug symbols, otherwise the Linux kaku.so binary will bloat from ~500KB to around 3MB large.

$ cargo build --release

The sabinokaku-common module contains platform independent logic such as configuration parsing and bootstrapping hostfxr and nethost. The sabinokaku-loader module contains the code for the actual injected assembly; this will be built as cdylib (.dll or .so), and is the actual assembly that has to be injected into the address space of the target process.

injector-example is a simple dummy program that injects a DLL into a running process, or simply runs Hello World and dumps environment variables for testing injection on Linux.

Usage

  1. Create a class library project for use as your entry point, and add True to the csproj to properly generate the runtime configuration.
  2. The entry point in .NET must always have the signature public static int Main(IntPtr args, int sizeBytes).
  3. Create a kaku.co file, see Configuration for syntax, and add it to your project.
  4. Add a prebuilt binary of kaku.dll on Windows, or libkaku.so on Linux.

Your csproj should have the following entries.

Always Always Always ">
<PropertyGroup>
  <GenerateRuntimeConfigurationFiles>TrueGenerateRuntimeConfigurationFiles>
PropertyGroup>
<ItemGroup>
  <None Update="kaku.co">
    <CopyToOutputDirectory>AlwaysCopyToOutputDirectory>
  None>
  <None Update="kaku.dll">
    <CopyToOutputDirectory>AlwaysCopyToOutputDirectory>
  None>
  
  
  <None Update="libkaku.so">
      <CopyToOutputDirectory>AlwaysCopyToOutputDirectory>
  None>
ItemGroup>
  1. On Windows, inject kaku.dll into a running process with a DLL injection tool such as Reloaded.Injector. On Linux, libkaku.so hooks __libc_start_main and can be injected with LD_PRELOAD. On load, the CLR will be bootstrapped on a separate thread and your entry point function will be called.

Note that the lifetime of the host process always outlives the lifetime of the .NET Runtime thread. If the host process does not live long enough for the .NET Runtime to bootstrap and finish execution, it will be killed along with the host process.

Waiting for the thread to join before the process quits is doable, but not implemented since this leads to some unexpected behaviour such as zombie processes. If there seems to be such a need, please file an issue; I will consider making it configurable via kaku.co.

Configuration

To determine the .NET bootstrap point, sabinokaku requires a kaku.co file either in the same directory as kaku.dll/libkaku.so, or in the host process directory. kaku.co contains the preamble necessary for sabinokaku to bootstrap the .NET runtime. There are 2 preamble formats that sabinokaku understands. The long format (kaku_l) allows for the most flexibility, for example if you store the .NET entry point assembly in a child folder. The short form may be preferred for its shorter syntax.

Long Format Preamble

The long format always begins with kaku_l, followed by the path to runtimeconfig.json, the path to the .NET assembly DLL, the qualified name of the entry-point class, and the name of the entry-point function. All paths are relative to the location of kaku.co.

kaku_l
TestInject.runtimeconfig.json
TestInject.dll
TestInject.EntryPoint, TestInject
Main

Short Format Preamble

The short format always begins with kaku_s. Information is then inferred with the syntax AssemblyName::QualifiedClassName$EntryFunction. The short format preamble requires that your assembly and runtime configuration file is in the same folder as kaku.co.

kaku_s
TestInject::TestInject.EntryPoint!Main

Advanced Configuration

Setting Environment Variables

After the preamble, you may provide optional environment variables to be set before hostfxr is invoked and the .NET runtime is bootstrapped. The format is env KEY=VAR, be sure there are no spaces between the equals symbol or it will be taken as part of the environment string.

For example, to set DOTNET_MULTILEVEL_LOOKUP=0, an example kaku.co may be

kaku_s
TestInject::TestInject.EntryPoint!Main
env DOTNET_MULTILEVEL_LOOKUP=0

Multiple environment variables can be set.

kaku_s
TestInject::TestInject.EntryPoint!Main
env DOTNET_MULTILEVEL_LOOKUP=0
env COMPLUS_ForceENC=1

The order of environment variables set is not guaranteed.

⚠️ Warning ⚠️

Any set variables will also take effect against the hosting process after initialization, and sabinokaku will not restore the prior values. See Platform Differences for more details.

Providing your own hostfxr.dll

After the preamble, you may optionally provide the path to your own hostfxr.dll, which is resolved relative to kaku.co.

kaku_s
TestInject::TestInject.EntryPoint!Main
hostfxr runtime/host/hostfxr.dll
env DOTNET_MULTILEVEL_LOOKUP=0

sabinokaku will then try to load using your custom hostfxr.dll. If it does not exist, the runtime will fail to bootstrap. If you specify hostfxr multiple times, only the first entry is taken.

Specifying a runtime

After the preamble, you may optionally provide the path to a dotnet root folder containing a dotnet.exe and a .NET runtime, relative to kaku.co. Be sure to set DOTNET_MULTILEVEL_LOOKUP=0 as well.

kaku_s
TestInject::TestInject.EntryPoint!Main
dotnetroot runtime
env DOTNET_MULTILEVEL_LOOKUP=0

sabinokaku will then try to load the runtime specified.

If you specify dotnetroot multiple times, only the first entry is taken.

Platform Differences

Particularly when using the environment variables feature, note the differences in load order between Windows and Linux.

Windows uses DllMain and thus the host process is already executing when the runtime is injected. Changed environment variables will thus be visible to the host process only if they are read after initialization.

Linux hooks __libc_start_main and bootstraps the .NET runtime before main, thus any environment variables set may be visible to the target process main.

On both platforms, .NET initialization and execution happens on a separate thread. However, if an uncaught exception occurs it is not allowed to cross the FFI boundary. To avoid undefined behaviour, the host application will be aborted.

You might also like...
A Lean Secure Runtime for JavaScript
A Lean Secure Runtime for JavaScript

Tera tera is a lean secure capability-based runtime for JavaScript. It is primarily designed for multi-tenant serverless environment but has uses in o

Zinnia is a runtime for Filecoin Station modules. It provides a sandboxed environment to execute untrusted code on consumer-grade computers.
Zinnia is a runtime for Filecoin Station modules. It provides a sandboxed environment to execute untrusted code on consumer-grade computers.

🌼 Zinnia Zinnia is a runtime for Filecoin Station modules. It provides a sandboxed environment to execute untrusted code on consumer-grade computers.

Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

A weekly dive into commonly used modules in the Rust ecosystem, with story flavor!

Rust Module of the Week A weekly dive into commonly used modules in the Rust ecosystem, with story flavor! Build status Release Draft The goal The goa

Turns running Rust code into a serializable data structure.

WasmBox WasmBox turns running Rust code into a serializable data structure. It does this by compiling it to WebAssembly and running it in a sandbox. T

📦 Pack hundreds of Garry's Mod Lua files into just a handful
📦 Pack hundreds of Garry's Mod Lua files into just a handful

📦 gluapack gluapack is a program that can pack hundreds of Garry's Mod Lua files into just a handful. Features Quick, easy and portable - perfect for

Inline CSS into style attributes

css-inline A crate for inlining CSS into HTML documents. It is built with Mozilla's Servo project components. When you send HTML emails, you need to u

A small application to convert a 3D model in .obj format into HTML and CSS

obj-to-html is a small application that converts a 3D model in .obj format into HTML and CSS that will display that model in a web browser, spinning a

A super-lightweight Lua microservice (toy) framework.

Hive A super-lightweight microservice (toy) framework written in Rust. It uses Lua as interface to provide simple, fun developing experience and fast

Comments
  • Readme Specifies Non-Optimal CsProj property for Dynamic Loading

    Readme Specifies Non-Optimal CsProj property for Dynamic Loading

    In short:

    In the usage section of your library, you specify the usage of the GenerateRuntimeConfigurationFiles to ensure that loadable assemblies are created. While this works, the advised property to set for end users is instead EnableDynamicLoading property.

    vivaldi_zy6XQyuCOA

    Or to quote Mr Vitek Karas, one of the .NET Runtime developers.

    In the readme for this project there's a section about troubleshooting - specifically the case of a classlib which doesn't have .runtimeconfig.json by default. Starting with recent .NET Core 3.0 previews (8 for sure, probably earlier) the right way to mark a classlib as a component which will be dynamically loaded is to add <EnableDynamicLoading>true</EnableDynamicLoading> to the project.

    Under the hood it does currently set the GenerateRuntimeConfigurationFiles but this may change in the future.

    (original text)

    A few years ago I made the same mistake too, albeit all of the new properties weren't yet fully documented then.

    opened by Sewer56 1
Owner
Snowflake Emulator Frontend
The most over-engineered emulator frontend ever created.
Snowflake Emulator Frontend
An serverless framework based on wasm runtime.

wasm_serverless An distributed serverless framework based on wasm runtime. Feishu doc https://fvd360f8oos.feishu.cn/docx/XSxcdONk2oVJD5xtZuicxftqn3f?f

null 3 Nov 23, 2023
A minimal library for building compiled Node.js add-ons in Rust via Node-API

A minimal library for building compiled Node.js add-ons in Rust via Node-API

Node-API (N-API) for Rust 3.1k Dec 29, 2022
A parser, compiler, and virtual machine evaluator for a minimal subset of Lua; written from scratch in Rust.

lust: Lua in Rust This project implements a parser, compiler, and virtual machine evaluator for a minimal subset of Lua. It is written from scratch in

Phil Eaton 146 Dec 16, 2022
Lagoon is a dynamic, weakly-typed and minimal scripting language. 🏞

Lagoon is a dynamic, weakly-typed and minimal scripting language. It draws inspiration from a handful of modern languages including JavaScript, Rust and PHP.

Ryan Chandler 25 Jul 5, 2022
Objective-C Runtime bindings and wrapper for Rust.

Objective-C Runtime bindings and wrapper for Rust. Documentation: http://ssheldon.github.io/rust-objc/objc/ Crate: https://crates.io/crates/objc Messa

Steven Sheldon 336 Jan 2, 2023
A JavaScript Runtime built with Mozilla's SpiderMonkey Engine and Rust

Spiderfire Spiderfire is a javascript runtime built with Mozilla's SpiderMonkey engine and Rust. Spiderfire aims to disrupt the server-side javascript

Redfire 122 Dec 15, 2022
Modern JavaScript runtime for Sony PSP, based on rust-psp and QuickJS.

PSP.js Modern JavaScript runtime for Sony PSP, based on rust-psp and QuickJS. ⚠️ Currently in PoC state, unusable for developing JavaScript apps yet.

Yifeng Wang 15 Nov 28, 2022
Livny is a modern JavaScript and TypeScript runtime built on top of Rust

Livny is a modern JavaScript and TypeScript runtime built on top of Rust, Golang and the GraalVM Polyglot infrastructure that can run all of Deno and Node.jS applications. It is fine-tuned for user satisfaction, performance and security.

LivnyJS 1 Mar 2, 2022
The JavaScript runtime that aims for productivity and ease

Byte Byte is a easy and productive runtime for Javascript . It makes making complex programs simple and easy-to-scale with its large and fast Rust API

Byte 32 Jun 16, 2021
Diamond is a minimalistic, powerful, and modern Javascript runtime that uses Deno_Core.

Diamond Diamond is a minimalistic, powerful, and modern Javascript runtime that uses Deno_Core. Installation Diamond is currently in beta(not even Alp

Catermelon 4 Aug 30, 2021