Crates.io | clroxide |
lib.rs | clroxide |
version | 1.1.1 |
source | src |
created_at | 2023-03-17 10:05:36.035439 |
updated_at | 2023-03-31 02:40:12.945111 |
description | A library that allows you to host the CLR and execute dotnet binaries. |
homepage | https://github.com/yamakadi/clroxide |
repository | https://github.com/yamakadi/clroxide |
max_upload_size | |
id | 812594 |
size | 474,048 |
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:
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
that just made everything click for me!go-clr
, Kurosh's dinvoke_rs
project also made some rust/win32 intricacies clearer and allowed the project to move forward.and likely a few more...
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.
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
.
You can find more examples in the examples/
folder.
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);
}
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)?;
...
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)?;
// ...
}
System.Environment.Exit
to not exitYou 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
.