A rust library that allows you to host the CLR and execute dotnet binaries.

Overview

ClrOxide

ClrOxide is a rust library that allows you to host the CLR and dynamically execute dotnet binaries.

I wanted to call it Kepler for no particular reason, but there's already a package named kepler in cargo. :(

I have been working on hosting CLR with rust on and off for 2 years now, and finally something clicked two weeks ago!

This library wouldn't be possible without the following projects:

  • NimPlant and its execute assembly implementation
    • The elegance with which winim/clr allows overwriting the output buffer for Console.Write and gets the output! Striving for the same elegance is the only reason this library took two years. How can I convince Cas to dabble with rust if he can't replicate this!? My work for a rust implant for NimPlant is also how I got into this rabbit hole in the first place.
  • go-clr by ropnop
    • A very special thank you to ropnop here! This whole library is the result of 3 days of work thanks to something in go-clr that just made everything click for me!
  • dinvoke_rs by Kudaes
    • Similar to go-clr, Kurosh's dinvoke_rs project also made some rust/win32 intricacies clearer and allowed the project to move forward.
  • Various CLR-related rust libraries

Architecture Constraints

ClrOxide

ClrOxide only works if compiled for x86_64-pc-windows-gnu or x86_64-pc-windows-msvc.

Compiling for i686-pc-windows-gnu fails due to known issues with rust panic unwinding. It might work with i686-pc-windows-msvc, but I haven't tried it myself.

Assembly

Although I haven't run into this issue myself, there might be cases where you need to specifically compile your assembly as x64 instead of Any CPU.

Design Constraints

windows crate had no type definitions for mscoree.dll until a few weeks ago. It looks like the definitions for mscoree.dll have made their way into the windows crate in version 0.48.0. However, these definitions don't appear to be working correctly. Just as an example; a vtable entry that should point to a function within the CLR thread (let's say at address 0x7ffef16821a0), somehow returns an address widely out of range (0x750003cac9053b48).

The windows crate does a lot of fancy stuff with vtables for safety, but ironically, these are likely causing the access violation above. Or something else is happening... I intended to use the official definitions for V2 to offload the maintenance burden, but this is a dealbreaker.

Usage

You can find more examples in the examples/ folder.

Run an assembly and capture its output

assembly_arch

ClrOxide will load the CLR in the current process, resolve mscorlib and redirect the output for System.Console, finally loading and running your executable and returning its output as a string.

Streaming the output is not currently supported, although I'm sure the CLR wrangling magic used for redirecting the output could be a good guide for anyone willing to implement it.

use clroxide::clr::Clr;
use std::{env, fs, process::exit};

fn main() -> Result<(), String> {
    let (path, args) = prepare_args();

    let contents = fs::read(path).expect("Unable to read file");
    let mut clr = Clr::new(contents, args)?;

    let results = clr.run()?;

    println!("[*] Results:\n\n{}", results);

    Ok(())
}

fn prepare_args() -> (String, Vec<String>) {
    let mut args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        println!("Please provide a path to a dotnet executable");

        exit(1)
    }

    let mut command_args: Vec<String> = vec![];

    if args.len() > 2 {
        command_args = args.split_off(2)
    }

    let path = args[1].clone();

    println!("[+] Running `{}` with given args: {:?}", path, command_args);

    return (path, command_args);
}

Use a custom app domain

assembly_arch

You can update the context to use a custom app domain. This can be useful if you want to avoid DefaultDomain. Check out examples/custom_app_domain.rs for more details.

...

  let app_domain = clr.using_runtime_host(|host| {
      let app_domain = unsafe { (*host).create_domain("CustomDomain")? };

      Ok(app_domain)
  })?;

  clr.use_app_domain(app_domain)?;

...

Use a custom loader for mscoree.dll

We need to load the CreateInterface function from mscoree.dll to kickstart the CLR. You can provide a custom loader by disabling default features.

First, add default-features = false to your dependency declaration.

clroxide = { version = "1.0.6", default-features = false }

And then provide a function with the signature fn() -> Result<isize, String> that returns a pointer to the CreateInterface function when creating the Clr instance.

litcrypt::use_litcrypt!();

fn load_function() -> Result<isize, String> {
  let library = custom_load_library_a(lc!("mscoree.dll\0"));

  if library == 0 {
    return Err("Failed".into());
  }
  
  let function = custom_get_process_address(library, lc!("CreateInterface\0"));
  
  if function == 0 {
    return Err("Failed".into());
  }
  
  Ok(function)
}

fn main() -> Result<(), String> {
 
  // ...

  let mut context = Clr::new(contents, args, load_function)?;

  // ...
  
}

Patch System.Environment.Exit to not exit

assembly_arch

You can use the building blocks provided by ClrOxide to patch System.Environment.Exit as described in Massaging your CLR: Preventing Environment.Exit in In-Process .NET Assemblies by MDSec.

You can check the reference implementation at examples/patch_exit.rs. Since this requires using VirtualProtect or NtProtectVirtualMemory, I don't intend to add this as a feature to ClrOxide.

You might also like...
A very simple third-party cargo subcommand to execute a custom command

cargo-x A very simple third-party cargo subcommand to execute a custom command Usage install cargo-x cargo install cargo-x or upgrade cargo install -

👁️ A tool to execute a certain command when a target file is modified.

Ojo Ojo is a simple utility that allows you to execute a specific command each time a certain file is being saved. Usage Let's say you are sick the fo

Abuse the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code.
Abuse the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code.

jscythe abuses the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code, even if t

Execute KDL files!

kdl-script A Compiler for KDLScript, the KDL-based programming language! KDLScript is a "fake" scripting language that actually just exists to declare

A filesystem driver that allows you to view your Blackboard course contents as if they were normal files and folders on your system!
A filesystem driver that allows you to view your Blackboard course contents as if they were normal files and folders on your system!

BlackboardFS Blackboard: noun A website so bad that it might as well be a network drive. BlackboardFS is a filesystem driver that allows you to view y

A tool that allows you to modify, edit, and recompile the AST script of Artemis engine.

Artemis AST Script Processor This utility offers a set of Rust functions to parse and manipulate Artemis AST-based scripts. Features Tokenization: Con

Dead simple, memoized cargo subcommand to hoist cargo-built binaries into the current working directory, written in Rust.
Dead simple, memoized cargo subcommand to hoist cargo-built binaries into the current working directory, written in Rust.

cargo-hoist Dead simple cargo subcommand to hoist cargo-built binaries into scope. stable Install | User Docs | Crate Docs | Reference | Contributing

Rust crate that allows you to display status & progress information in a terminal

status-line This crate allows you to display status & progress information in a terminal This crate handles the problem of displaying a small amount o

💫 This program allows you to do requests in Rust without reqwest !

Zapros 💫 This program allows you to do requests in Rust without reqwest ! Usage : Get use crate::http_client::HttpClient; use crate::http_client::Htt

Comments
  • Easier method for specifying runtime version?

    Easier method for specifying runtime version?

    Hello! Great work on this project.

    In my testing, I found that old .NET versions would preempt newer ones in the get_first_available_runtime() method used by Clr::new(). For that reason, I think it might be useful to expose a with_runtime_version() or somesuch method that easily allows the instantiation of a runtime of a specific version. As it stands now, it's a little clunky to do that in place.

    opened by mttaggart 2
  • Fails if the assembly is compiled for Any CPU

    Fails if the assembly is compiled for Any CPU

    This wasn't the case with the assemblies I tested, but there are likely combinations of architecture between the rust binary and dotnet assembly where the execution fails.

    I'll investigate further when I have more time.

    opened by yamakadi 1
Owner
YK
YK
This rust compiler backend emmits valid CLR IR, enambling you to use Rust in .NET projects

What is rustc_codegen_clr? NOTE: this project is a very early proof-of-concept This is a compiler backend for rustc which targets the .NET platform an

null 252 Sep 7, 2023
Web-based tool that allows browsing and comparing symbol and type information of Microsoft Windows binaries across different versions of the OS.

WinDiff About WinDiff is an open-source web-based tool that allows browsing and comparing symbol and type information of Microsoft Windows binaries ac

Erwan Grelet 208 Jun 15, 2023
REC2 (Rusty External Command and Control) is client and server tool allowing auditor to execute command from VirusTotal and Mastodon APIs written in Rust. 🦀

Information: REC2 is an old personal project (early 2023) that I didn't continue development on. It's part of a list of projects that helped me to lea

Quentin Texier (g0h4n) 104 Oct 7, 2023
Windlass, a Rust Klipper host side protocol implementation

Windlass Windlass is an implementation of the host side of the Klipper protocol. The project is currently to be considered alpha quality code. Licensi

Annex-Engineering 8 May 1, 2023
Execute Javascript code in the Dioxus, and get the return value ( for Dioxus )

Golde Dioxus Execute Javascript code in the Dioxus, and get the return value. This demo can help use Javascript to calc the + operator formula. use di

YuKun Liu 15 Dec 27, 2022
Workflows make it easy to browse, search, execute and share commands (or a series of commands)--without needing to leave your terminal.

Workflows The repo for all public Workflows that appear within Warp and within commands.dev. To learn how to create local or repository workflows, see

Warp 369 Jan 2, 2023
Workflows make it easy to browse, search, execute and share commands (or a series of commands)--without needing to leave your terminal.

Workflows The repo for all public Workflows that appear within Warp and within commands.dev. To learn how to create local or repository workflows, see

Warp 227 Jun 1, 2022
scan markdown files and execute `console` blocks

exec-commands − scan markdown files and execute console blocks exec-commands is a utility to update command-line-tool examples embedded in markdown fi

Hajime Suzuki 3 Nov 27, 2022
tmplt is a command-line interface tool that allows you to quickly and easily set up project templates for various programming languages and frameworks

tmplt A User Friendly CLI Tool For Creating New Projects With Templates About tmplt is a command-line tool that lets users quickly create new projects

Humble Penguin 35 Apr 8, 2023
Execute Rust code carefully, with extra checking along the way

cargo-careful cargo careful is a tool to run your Rust code extra carefully -- opting into a bunch of nightly-only extra checks that help detect Undef

Ralf Jung 240 Dec 28, 2022