
Une bibliothèque Rust qui vous permet d'héberger le CLR et d'exécuter des binaires dotnet.
ClrOxide est une bibliothèque Rust qui permet d'héberger le CLR et d'exécuter dynamiquement des binaires dotnet.
Je voulais l'appeler Kepler sans raison particulière, mais il existe déjà un package nommé kepler sur cargo. :(
Je travaille sur l'hébergement du CLR avec Rust par intermittence depuis 2 ans, et quelque chose a enfin cliqué il y a deux semaines !
Cette bibliothèque n'aurait pas été possible sans les projets suivants :
winim/clr permet de remplacer le tampon de sortie pour Console.Write et d'obtenir la sortie ! La quête de cette même élégance est la seule raison pour laquelle cette bibliothèque a pris deux ans.
Comment puis-je convaincre Cas de se mettre à Rust s'il ne peut pas reproduire cela !? Mon travail pour un implant Rust pour NimPlant est aussi ce qui m'a fait tomber dans ce terrier de lapin à l'origine.go-clr qui a tout déclenché pour moi !go-clr, le projet dinvoke_rs de Kurosh a également clarifié certaines subtilités rust/win32 et a permis au projet d'avancer.ClrOxide ne fonctionne que s'il est compilé pour x86_64-pc-windows-gnu ou x86_64-pc-windows-msvc.
La compilation pour i686-pc-windows-gnu échoue en raison de problèmes connus avec le déroulement de pile en cas de panique de Rust. Cela pourrait fonctionner avec i686-pc-windows-msvc, mais je ne l'ai pas essayé moi-même.
Bien que je n'aie pas rencontré ce problème moi-même, il peut y avoir des cas où vous devez compiler votre assembly spécifiquement en x64 au lieu d'Any CPU.
La crate windows ne disposait d'aucune définition de types pour mscoree.dll jusqu'à il y a quelques semaines. Il semble que les définitions pour mscoree.dll aient fait leur chemin dans la crate windows dans la version 0.48.0. Cependant, ces définitions ne semblent pas fonctionner correctement. À titre d'exemple, une entrée de vtable qui devrait pointer vers une fonction dans le thread CLR (disons à l'adresse 0x7ffef16821a0), renvoie bizarrement une adresse largement hors limites (0x750003cac9053b48).
La crate windows fait beaucoup de choses sophistiquées avec les vtables pour des raisons de sécurité, mais ironiquement, ce sont probablement elles qui causent la violation d'accès ci-dessus. Ou alors il se passe autre chose... J'avais l'intention d'utiliser les définitions officielles pour V2 afin d'alléger la charge de maintenance, mais c'est rédhibitoire.
Vous trouverez d'autres exemples dans le dossier examples/.
ClrOxide chargera le CLR dans le processus courant, résoudra mscorlib et redirigera la sortie de System.Console, puis chargera et exécutera votre exécutable et retournera sa sortie sous forme de chaîne de caractères.
Le streaming de la sortie n'est actuellement pas pris en charge, même si je suis sûr que la magie de manipulation du CLR utilisée pour rediriger la sortie pourrait être un bon guide pour quiconque voudrait l'implémenter.
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);
}
Vous pouvez mettre à jour le contexte pour utiliser un domaine d'application personnalisé. Cela peut être utile si vous voulez éviter DefaultDomain. Consultez examples/custom_app_domain.rs pour plus de détails.
...
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.dllNous devons charger la fonction CreateInterface depuis mscoree.dll pour lancer le CLR. Vous pouvez fournir un chargeur personnalisé en désactivant les fonctionnalités par défaut.
Tout d'abord, ajoutez default-features = false à votre déclaration de dépendance.
clroxide = { version = "1.0.6", default-features = false }
Ensuite, fournissez une fonction avec la signature fn() -> Result<isize, String> qui retourne un pointeur vers la fonction CreateInterface lors de la création de l'instance 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 pour ne pas quitter
Vous pouvez utiliser les briques fournies par ClrOxide pour patcher System.Environment.Exit comme décrit dans Massaging your CLR: Preventing Environment.Exit in In-Process .NET Assemblies par MDSec.
Vous pouvez consulter l'implémentation de référence dans examples/patch_exit.rs. Comme cela nécessite l'utilisation de VirtualProtect ou NtProtectVirtualMemory, je n'ai pas l'intention d'ajouter cela comme fonctionnalité à ClrOxide.