
Eine Rust-Bibliothek, mit der Sie die CLR hosten und Dotnet-Binärdateien ausführen können.
ClrOxide ist eine Rust-Bibliothek, mit der du die CLR hosten und .NET-Binärdateien dynamisch ausführen kannst.
Ich wollte sie ohne besonderen Grund Kepler nennen, aber es gibt bereits ein Paket namens kepler in cargo. :(
Ich arbeite seit zwei Jahren immer wieder daran, die CLR mit Rust zu hosten, und vor zwei Wochen hat endlich etwas Klick gemacht!
Diese Bibliothek wäre ohne die folgenden Projekte nicht möglich:
winim/clr das Überschreiben des Ausgabepuffers für Console.Write ermöglicht und die Ausgabe erhält! Das Streben nach derselben Eleganz ist der einzige Grund, warum diese Bibliothek zwei Jahre gebraucht hat.
Wie soll ich Cas dazu bringen, sich mit Rust zu beschäftigen, wenn er das nicht nachbauen kann!? Meine Arbeit an einem Rust-Implant für NimPlant ist auch der Grund, warum ich überhaupt in diesen Kaninchenbau geraten bin.go-clr, das bei mir einfach alles Klick machen ließ!go-clr hat auch Kuroshs Projekt dinvoke_rs einige komplizierte Rust/Win32-Details klarer gemacht und es dem Projekt ermöglicht, voranzukommen.ClrOxide funktioniert nur, wenn es für x86_64-pc-windows-gnu oder x86_64-pc-windows-msvc kompiliert wird.
Die Kompilierung für i686-pc-windows-gnu schlägt aufgrund bekannter Probleme mit dem Panic-Unwinding von Rust fehl. Es könnte mit i686-pc-windows-msvc funktionieren, aber ich habe es selbst nicht ausprobiert.
Obwohl ich selbst nicht auf dieses Problem gestoßen bin, könnte es Fälle geben, in denen du deine Assembly gezielt als x64 statt als Any CPU kompilieren musst.
Die windows-Crate hatte bis vor ein paar Wochen keine Typdefinitionen für mscoree.dll. Es sieht so aus, als wären die Definitionen für mscoree.dll in Version 0.48.0 in die windows-Crate aufgenommen worden. Allerdings scheinen diese Definitionen nicht korrekt zu funktionieren. Nur ein Beispiel: Ein Vtable-Eintrag, der auf eine Funktion innerhalb des CLR-Threads zeigen sollte (sagen wir an Adresse 0x7ffef16821a0), liefert irgendwie eine Adresse, die weit außerhalb des Bereichs liegt (0x750003cac9053b48).
Die windows-Crate macht viele raffinierte Dinge mit Vtables, um Sicherheit zu gewährleisten, aber ironischerweise sind diese wahrscheinlich die Ursache für die obige Zugriffsverletzung. Oder es passiert etwas anderes ... Ich hatte vor, die offiziellen Definitionen für V2 zu verwenden, um den Wartungsaufwand auszulagern, aber das ist ein Ausschlusskriterium.
Weitere Beispiele findest du im Ordner examples/.
ClrOxide lädt die CLR in den aktuellen Prozess, löst mscorlib auf und leitet die Ausgabe von System.Console um. Schließlich wird deine ausführbare Datei geladen und ausgeführt und ihre Ausgabe als Zeichenkette zurückgegeben.
Das Streaming der Ausgabe wird derzeit nicht unterstützt, obwohl ich sicher bin, dass die Magie der CLR-Manipulation, die zum Umleiten der Ausgabe verwendet wird, ein guter Leitfaden für alle sein könnte, die es implementieren möchten.
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);
}
Du kannst den Kontext so anpassen, dass eine benutzerdefinierte Anwendungsdomäne verwendet wird. Das kann nützlich sein, wenn du DefaultDomain vermeiden möchtest. Weitere Details findest du in examples/custom_app_domain.rs.
...
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 verwendenWir müssen die Funktion CreateInterface aus mscoree.dll laden, um die CLR zu starten. Du kannst einen benutzerdefinierten Loader bereitstellen, indem du die Standard-Features deaktivierst.
Füge zunächst default-features = false zu deiner Abhängigkeitsdeklaration hinzu.
clroxide = { version = "1.0.6", default-features = false }
Und dann stelle eine Funktion mit der Signatur fn() -> Result<isize, String> bereit, die beim Erstellen der Clr-Instanz einen Zeiger auf die Funktion CreateInterface zurückgibt.
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 patchen, um das Beenden zu verhindern
Du kannst die von ClrOxide bereitgestellten Bausteine verwenden, um System.Environment.Exit zu patchen, wie in Massaging your CLR: Preventing Environment.Exit in In-Process .NET Assemblies von MDSec beschrieben.
Die Referenzimplementierung findest du in examples/patch_exit.rs. Da dies die Verwendung von VirtualProtect oder NtProtectVirtualMemory erfordert, habe ich nicht vor, dies als Feature zu ClrOxide hinzuzufügen.