
Uma biblioteca Rust que permite hospedar o CLR e executar binários dotnet.
ClrOxide é uma biblioteca rust que permite hospedar o CLR e executar dinamicamente binários dotnet.
Eu queria chamá-la de Kepler sem nenhum motivo específico, mas já existe um pacote chamado kepler no cargo. :(
Venho trabalhando em hospedar o CLR com rust, de forma intermitente, há 2 anos, e finalmente algo clicou há duas semanas!
Esta biblioteca não seria possível sem os seguintes projetos:
winim/clr permite sobrescrever o buffer de saída para Console.Write e obter a saída! Buscar a mesma elegância é a única razão pela qual esta biblioteca levou dois anos. Como posso convencer o Cas a se aventurar com rust se ele não consegue replicar isso!? Meu trabalho em um implant rust para NimPlant também é como entrei nessa toca de coelho em primeiro lugar.go-clr que simplesmente fez tudo se encaixar para mim!go-clr, o projeto dinvoke_rs do Kurosh também esclareceu algumas complexidades de rust/win32 e permitiu que o projeto avançasse.ClrOxide só funciona se compilado para x86_64-pc-windows-gnu ou x86_64-pc-windows-msvc.
Compilar para i686-pc-windows-gnu falha devido a problemas conhecidos com o unwinding de pânico do rust. Pode funcionar com i686-pc-windows-msvc, mas eu mesmo não tentei.
Embora eu não tenha encontrado esse problema pessoalmente, pode haver casos em que você precise compilar seu assembly especificamente como x64 em vez de Any CPU.
A crate windows não tinha definições de tipo para mscoree.dll até algumas semanas atrás. Parece que as definições para mscoree.dll chegaram à crate windows na versão 0.48.0. No entanto, essas definições não parecem estar funcionando corretamente. Apenas como exemplo; uma entrada de vtable que deveria apontar para uma função dentro do thread do CLR (digamos, no endereço 0x7ffef16821a0), de alguma forma retorna um endereço totalmente fora do intervalo (0x750003cac9053b48).
A crate windows faz muitas coisas sofisticadas com vtables por segurança, mas, ironicamente, essas coisas provavelmente estão causando a violação de acesso acima. Ou algo mais está acontecendo... Eu pretendia usar as definições oficiais para a V2 para aliviar o fardo da manutenção, mas isso é um impeditivo.
Você pode encontrar mais exemplos na pasta examples/.
ClrOxide carregará o CLR no processo atual, resolverá mscorlib e redirecionará a saída de System.Console, finalmente carregando e executando seu executável e retornando sua saída como uma string.
O streaming da saída não é suportado atualmente, embora eu tenha certeza de que a mágica de manipulação do CLR usada para redirecionar a saída pode ser um bom guia para qualquer pessoa disposta a implementá-lo.
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);
}
Você pode atualizar o contexto para usar um domínio de aplicativo personalizado. Isso pode ser útil se você quiser evitar o DefaultDomain. Consulte examples/custom_app_domain.rs para mais detalhes.
...
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.dllPrecisamos carregar a função CreateInterface de mscoree.dll para iniciar o CLR. Você pode fornecer um carregador personalizado desabilitando os recursos padrão.
Primeiro, adicione default-features = false à sua declaração de dependência.
clroxide = { version = "1.0.6", default-features = false }
E então forneça uma função com a assinatura fn() -> Result<isize, String> que retorne um ponteiro para a função CreateInterface ao criar a instância do 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 to not exit
Você pode usar os blocos de construção fornecidos pelo ClrOxide para aplicar patch em System.Environment.Exit conforme descrito em Massaging your CLR: Preventing Environment.Exit in In-Process .NET Assemblies por MDSec.
Você pode verificar a implementação de referência em examples/patch_exit.rs. Como isso requer o uso de VirtualProtect ou NtProtectVirtualMemory, não pretendo adicionar isso como um recurso ao ClrOxide.