
Una librería de Rust que te permite alojar el CLR y ejecutar binarios de dotnet.
ClrOxide es una biblioteca de Rust que te permite alojar el CLR y ejecutar dinámicamente binarios de dotnet.
Quería llamarlo Kepler sin ninguna razón en particular, pero ya existe un paquete llamado kepler en cargo. :(
He estado trabajando en alojar el CLR con Rust de forma intermitente durante 2 años, y finalmente algo hizo clic hace dos semanas.
Esta biblioteca no sería posible sin los siguientes proyectos:
winim/clr permite sobrescribir el búfer de salida para Console.Write y obtener la salida! Luchar por alcanzar esa misma elegancia es la única razón por la que esta biblioteca tardó dos años.
¿Cómo puedo convencer a Cas de que incursione en Rust si no puede replicar esto!? Mi trabajo en un implant en Rust para NimPlant es también cómo me metí en esta madriguera de conejo en primer lugar.go-clr que hizo que todo encajara para mí.go-clr, el proyecto dinvoke_rs de Kurosh también aclaró algunas complejidades de rust/win32 y permitió que el proyecto avanzara.ClrOxide solo funciona si se compila para x86_64-pc-windows-gnu o x86_64-pc-windows-msvc.
La compilación para i686-pc-windows-gnu falla debido a problemas conocidos con el unwinding de pánico de Rust. Podría funcionar con i686-pc-windows-msvc, pero no lo he probado yo mismo.
Aunque no me he encontrado con este problema personalmente, puede haber casos en los que necesites compilar tu ensamblado específicamente como x64 en lugar de Any CPU.
La crate windows no tenía definiciones de tipos para mscoree.dll hasta hace unas semanas. Parece que las definiciones de mscoree.dll han llegado a la crate windows en la versión 0.48.0. Sin embargo, estas definiciones no parecen funcionar correctamente. Solo como ejemplo; una entrada de vtable que debería apuntar a una función dentro del hilo del CLR (digamos en la dirección 0x7ffef16821a0), de alguna manera devuelve una dirección muy fuera de rango (0x750003cac9053b48).
La crate windows hace muchas cosas sofisticadas con las vtables por seguridad, pero, irónicamente, es probable que estén causando la violación de acceso anterior. O está sucediendo algo más... Tenía la intención de usar las definiciones oficiales para V2 para aliviar la carga de mantenimiento, pero esto es un obstáculo insalvable.
Puedes encontrar más ejemplos en la carpeta examples/.

ClrOxide cargará el CLR en el proceso actual, resolverá mscorlib y redirigirá la salida de System.Console, para finalmente cargar y ejecutar tu ejecutable y devolver su salida como una cadena.
La salida en streaming no está soportada actualmente, aunque estoy seguro de que la magia de manipulación del CLR utilizada para redirigir la salida podría ser una buena guía para cualquiera que quiera implementarla.
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);
}

Puedes actualizar el contexto para usar un dominio de aplicación personalizado. Esto puede ser útil si quieres evitar DefaultDomain. Consulta examples/custom_app_domain.rs para más detalles.
...
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.dllNecesitamos cargar la función CreateInterface de mscoree.dll para poner en marcha el CLR. Puedes proporcionar un cargador personalizado deshabilitando las características predeterminadas.
Primero, añade default-features = false a tu declaración de dependencias.
clroxide = { version = "1.0.6", default-features = false }
Y luego proporciona una función con la firma fn() -> Result<isize, String> que devuelva un puntero a la función CreateInterface al crear la instancia de Clr.
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 para evitar la salida
Puedes usar los componentes básicos proporcionados por ClrOxide para parchear System.Environment.Exit como se describe en Massaging your CLR: Preventing Environment.Exit in In-Process .NET Assemblies de MDSec.
Puedes consultar la implementación de referencia en examples/patch_exit.rs. Dado que esto requiere usar VirtualProtect o NtProtectVirtualMemory, no tengo intención de añadir esto como una funcionalidad a ClrOxide.