
Библиотека на Rust, которая позволяет размещать CLR и выполнять dotnet-бинарники.
ClrOxide — это библиотека на Rust, которая позволяет размещать CLR и динамически выполнять dotnet-бинарники.
Я хотел назвать её Kepler без особой причины, но в cargo уже есть пакет с именем kepler. :(
Я работал над размещением CLR с помощью Rust с перерывами в течение двух лет, и наконец две недели назад что-то щёлкнуло!
Эта библиотека была бы невозможна без следующих проектов:
winim/clr позволяет перезаписывать буфер вывода для Console.Write и получать результат! Стремление к той же элегантности — единственная причина, по которой библиотека создавалась два года.
Как я могу убедить Cas заняться Rust, если он не сможет повторить это!? Моя работа над rust-имплантом для NimPlant — это также то, с чего я вообще попал в эту кроличью нору.go-clr наконец всё для меня встало на свои места!go-clr, проект dinvoke_rs от Kurosh также прояснил некоторые тонкости взаимодействия Rust и win32 и позволил проекту двигаться дальше.ClrOxide работает только при компиляции для x86_64-pc-windows-gnu или x86_64-pc-windows-msvc.
Компиляция для i686-pc-windows-gnu не удаётся из-за известных проблем с размоткой паники (panic unwinding) в Rust. Возможно, она заработает с i686-pc-windows-msvc, но я сам этого не пробовал.
Хотя я сам не сталкивался с этой проблемой, могут быть случаи, когда вам нужно компилировать вашу сборку именно как x64, а не Any CPU.
В крейте windows не было определений типов для mscoree.dll до нескольких недель назад. Похоже, определения для mscoree.dll попали в крейт windows в версии 0.48.0. Однако эти определения, судя по всему, работают некорректно. Например, запись в vtable, которая должна указывать на функцию внутри потока CLR (скажем, по адресу 0x7ffef16821a0), каким-то образом возвращает адрес, совершенно выходящий за пределы диапазона (0x750003cac9053b48).
Крейт windows делает много хитрых вещей с vtable ради безопасности, но, по иронии судьбы, именно они, скорее всего, вызывают указанное выше нарушение доступа. Или происходит что-то ещё... Я намеревался использовать официальные определения для V2, чтобы снять с себя бремя поддержки, но это неприемлемо.
Больше примеров можно найти в папке examples/.
ClrOxide загрузит CLR в текущий процесс, разрешит mscorlib и перенаправит вывод для System.Console, затем загрузит и запустит ваш исполняемый файл и вернёт его вывод в виде строки.
Потоковый вывод в настоящее время не поддерживается, хотя я уверен, что магия возни с CLR, используемая для перенаправления вывода, может стать хорошим ориентиром для тех, кто захочет это реализовать.
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);
}
Вы можете изменить контекст, чтобы использовать пользовательский домен приложения. Это может быть полезно, если вы хотите избежать DefaultDomain. Подробности смотрите в 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Нам нужно загрузить функцию CreateInterface из mscoree.dll, чтобы запустить CLR. Вы можете предоставить собственный загрузчик, отключив стандартные возможности.
Сначала добавьте default-features = false в объявление зависимости.
clroxide = { version = "1.0.6", default-features = false }
Затем предоставьте функцию с сигнатурой fn() -> Result<isize, String>, которая возвращает указатель на функцию CreateInterface при создании экземпляра 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, чтобы он не завершал процесс
Вы можете использовать строительные блоки, предоставляемые ClrOxide, для патча System.Environment.Exit, как описано в «Massaging your CLR: предотвращение Environment.Exit во встроенных .NET-сборках» от MDSec.
Вы можете посмотреть эталонную реализацию в examples/patch_exit.rs. Поскольку для этого требуется использование VirtualProtect или NtProtectVirtualMemory, я не собираюсь добавлять это в качестве функции в ClrOxide.