
ClrOxide は、CLR をホストし、dotnet バイナリを動的に実行できる Rust ライブラリです。
特に理由はないのですが Kepler と名付けたかったのですが、cargo には既に kepler という名前のパッケージがあるんです。:(
私は2年間、Rust で CLR をホストする作業を断続的に続けてきましたが、2週間前についに何かが噛み合いました!
このライブラリは、以下のプロジェクトなしでは実現できませんでした:
winim/clr が Console.Write の出力バッファを上書きして出力を取得するそのエレガントさ! 同じエレガントさを目指したからこそ、このライブラリは2年かかりました。
Cas がこれを再現できなければ、どうやって Rust に興味を持たせられるでしょうか!? 私が NimPlant 用の Rust インプラントを作る作業も、そもそもこの深みにはまったきっかけです。go-clr の中のあるものがすべてを噛み合わせてくれたおかげで、3日間の作業の結果です!go-clr と同様に、Kurosh の dinvoke_rs プロジェクトも Rust/win32 の複雑な部分を明確にし、プロジェクトを前進させてくれました。ClrOxide は、x86_64-pc-windows-gnu または x86_64-pc-windows-msvc 向けにコンパイルされた場合にのみ動作します。
i686-pc-windows-gnu 向けのコンパイルは、Rust のパニック巻き戻しに関する既知の問題により失敗します。i686-pc-windows-msvc であれば動作するかもしれませんが、自分では試していません。
この問題にはまだ遭遇していませんが、アセンブリを Any CPU ではなく x64 として明示的にコンパイルする必要がある場合があります。
windows crate には、数週間前まで mscoree.dll の型定義がありませんでした。mscoree.dll の定義は windows crate のバージョン 0.48.0 で追加されたようです。ただし、これらの定義は正しく動作しないようです。例を挙げると、CLR スレッド内の関数を指すはずの vtable エントリ(例えばアドレス 0x7ffef16821a0)が、なぜか範囲を大きく外れたアドレス(0x750003cac9053b48)を返します。
windows crate は安全性のために 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 用のカスタムローダーを使用するCLR を起動するには、mscoree.dll から CreateInterface 関数をロードする必要があります。デフォルト機能を無効にすることで、カスタムローダーを提供できます。
まず、依存関係の宣言に default-features = false を追加します。
clroxide = { version = "1.0.6", default-features = false }
次に、Clr インスタンスを作成するときに、CreateInterface 関数へのポインタを返す fn() -> Result<isize, String> シグネチャの関数を提供します。
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 をパッチしてプロセスを終了させないようにする
MDSec による Massaging your CLR: Preventing Environment.Exit in In-Process .NET Assemblies で説明されているように、ClrOxide が提供する構成要素を使用して System.Environment.Exit をパッチできます。
リファレンス実装は examples/patch_exit.rs で確認できます。これには VirtualProtect または NtProtectVirtualMemory の使用が必要なため、これを ClrOxide の機能として追加するつもりはありません。