Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
ツール/GitHubGitHub/kudaes/unwinder
IDS/IPS回避ポストエクスプロイトレッドチーミングペイロード開発敵対的攻撃
GitHubkudaes/unwinder

Unwinder

Rust用のコールスタックスプーフィング

リポジトリを見る
3843651年前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

目次

  • SilentMoonWalk
    • 説明
    • クレジット
    • 使い方
      • call_function!() マクロ
      • indirect_syscall!() マクロ
      • パラメータの受け渡し
    • 例
      • kernel32.dll!Sleep() を呼び出す
      • kernel32.dll!OpenProcess() を呼び出す
      • NtDelayExecution() を間接システムコールとして呼び出す
      • マクロ呼び出しの連結
    • 注意事項
      • 最初のフレーム
      • PoC
  • スタック置換
    • 技術の説明
    • 使用方法
    • 例
    • 注意事項

SilentMoonWalk

説明

Unwinder は SilentMoonWalk 技術の完全な兵器化を提供し、Rust で完全かつ安定したコールスタックスプーフィングを実現します。

この技術には以下の特徴があります:

  • 最大11個のパラメータを持つ任意の関数を実行可能。
  • 最大11個のパラメータを持つ間接システムコールを(追加のヒープ割り当てなしで)実行可能。
  • このクレートは、それを通じて呼び出された関数が返す値を取得できます。
  • スプーフィング処理は、コールスタックのサイズを増やすことなく、何度でも連結できます。
  • TLS はスプーフィング処理中の効率を高めるために使用されます。
  • このクレートが必要とする Windows API 呼び出しには dinvoke_rs が使用されます。

クレジット

SilentMoonWalk 技術の作者に感謝します:

  • KlezVirus
  • Waldo-IRC
  • Trickster0

そしてもちろん、このプロジェクト全体にインスピレーションを与えた Twitter スレッド を公開した namazso に多大な感謝を捧げます。

使い方

このクレートをプロジェクトにインポートするには、cargo.toml に次の行を追加し、release モードでコンパイルしてください:

root@kitploit:~
[dependencies]
unwinder = "=0.1.4"

このクレートの主な機能は2つのマクロにまとめられています:

  • call_function!() マクロは、クリーンなコールスタックで任意の関数を実行できます。
  • indirect_syscall!() マクロは、指定された(間接)システムコールをクリーンなコールスタックで実行します。

これらのマクロを使用するには、std::ffi::c_void データ型をインポートする必要があります。

どちらのマクロも *mut c_void を返し、実行された関数が返す値を取得するために使用できます。詳細は例のセクションを参照してください。

call_function!() マクロ

このマクロは、クリーンなコールスタックで任意の関数を呼び出すために使用します。 このマクロは以下のパラメータを期待します:

  • 1つ目のパラメータは、コールスタックをスプーフィングした後に呼び出すメモリアドレスです。このパラメータは usize、isize、またはポインタとして渡す必要があります。
  • 2つ目のパラメータは、開始関数のフレームを保持するかどうかを示す bool です。不明な場合は false に設定してください。常に良好なコールスタックが保証されます。
  • 以降のパラメータは、コールスタックがスプーフィングされた後に関数に渡す引数です。

indirect_syscall!() マクロ

このマクロは、クリーンなコールスタックで任意の間接システムコールを実行するために使用します。 このマクロは以下のパラメータを期待します:

  • 1つ目のパラメータは、システムコールを実行したい NT 関数の名前を含む文字列です。
  • 2つ目のパラメータは、開始関数のフレームを保持するかどうかを示す bool です。不明な場合は false に設定してください。常に良好なコールスタックが保証されます。
  • 以降のパラメータは、NT 関数に渡す引数です。

パラメータの受け渡し

これら2つのマクロにさまざまな型の引数を渡すには、以下の点に注意する必要があります:

  • usize に変換できる任意の基本データ型(u8-u64、i8-i64、bool など)は、マクロに直接渡すことができます。
  • サイズが8、16、32、64 ビットの構造体と共用体は、同じサイズの整数として渡されます。
  • サイズが64 ビットを超える構造体と共用体は、ポインタとして渡す必要があります。
  • 文字列(&str と String)は、ポインタとして渡す必要があります。
  • NULL ポインタ(ptr::null()、ptr::null_mut() など)は 0 として渡されます(u8、u16、i32 など、どの型でも同じです)。
  • 浮動小数点および倍精度のパラメータは現在サポートされていません。
  • その他のデータ型はすべてポインタとして渡す必要があります。

例

kernel32.dll!Sleep() を呼び出す

root@kitploit:~
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep() 
let miliseconds = 1000i32;
unwinder::call_function!(sleep, false, miliseconds);

kernel32.dll!OpenProcess() を呼び出す

root@kitploit:~
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 
let open_process: isize = dinvoke_rs::dinvoke::get_function_address(k32, "Openprocess");
let desired_access: u32 = 0x1000;
let inherit = 0i32;
let pid = 20628i32;
let handle = unwinder::call_function!(open_process, false, desired_access, inherit, pid); // returns *mut c_void
let handle: HANDLE = std::mem::transmute(handle);
println!("Handle id: {:x}", handle.0);

このマクロは *mut c_void を返しますが、これは両方のデータ型のサイズが同じであるため、直接 HANDLE に変換できます。これにより、OpenProcess が返す値(対象プロセスへの新しいハンドル)にアクセスできます。

NtDelayExecution() を間接システムコールとして呼び出す

root@kitploit:~
let large = 0x8000000000000000 as u64; // Sleep indefinitely
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); // returns *mut c_void
println!("ntstatus: {:x}", ntstatus as i32);

このマクロは *mut c_void を返し、NtDelayExecution が返す NTSTATUS を取得するために使用できます。

マクロ呼び出しの連結

スプーフィング処理は、コールスタックサイズが異常に増加することなく、何度でも連結できます。実行フローも維持されます。以下はその例です:

root@kitploit:~
fn main()
{
	function_a();
}

fn function_a()
{
	unsafe
	{
		let func_b = function_b as usize;
		call_function!(func_b, false);
		println!("function_a done.");
	}
}

fn function_b()
{
	unsafe
	{
		let func_c = function_c as usize;
		call_function!(func_c, false);
		println!("function_b done.")
	}
}

fn function_c()
{
	unsafe
	{
		let large = 0x0000000000000000 as u64; // Don't sleep so we return to function_b, allowing to check the execution flow preservation.
		let large: *mut i64 = std::mem::transmute(&large);
		let alertable = false;
		let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large);
		println!("ntstatus: {:x}", (ntstatus as usize) as i32); //NTSTATUS is a i32, although that second casting is not really required in this case.
	}
}

注意事項

最初のフレーム

2つ目のパラメータを true に設定すると(両マクロとも)、スプーフィング処理はコールスタック内にスレッド開始アドレスのフレームを保持して、正当性を高めようとします。

メインモジュールを保持したスプーフィング済みコールスタック。

スレッドの開始関数が後続の関数への call を実行しない場合(代わりに jmp 命令が実行される場合など)、スタックにリターンアドレスがプッシュされないことになります。そのようなシナリオでは(2つ目のパラメータを false に設定した場合も)、スプーフィングされたコールスタックは BaseThreadInitThunk のフレームから始まります。

メインモジュールなしのスプーフィング済みコールスタック。

PoC

この技術の実装をテストするために、PE-sieve を /threads フラグ付きで使用しました。テスト結果は、このクレートの機能を使用した場合、コールスタックの検査によってペイロードの存在が明らかにならないことを示しています。2枚目の画像でわかるように、unwinder を使用しない場合はペイロードが検出されます。

unwinder を使用した場合の PE-sieve の結果。 unwinder を使用しない場合の PE-sieve の結果。

スタック置換

技術の説明

これは SilentMoonWalk に代わるコールスタックスプーフィング手法で、プログラムの実行中にクリーンなコールスタックを維持できます。この技術の背後にある主なアイデアは、モジュール内で呼び出される各関数が、以前にプッシュされたリターンアドレスを処理し、スプーフィング対象のリターンアドレスと同じフレームサイズを持つ正当な関数を実行時に見つけるというものです。同じフレームサイズを持つ正当な関数が見つかると、その関数内のオフセットが計算され、最終的なアドレスを使用して最後のリターンアドレスを置換し、コールスタック内の異常なエントリを隠して、アンワインド可能な状態を維持します。元のリターンアドレスは unwinder によって保存され、return 命令が実行される前にスタック上の正しい位置に戻されるため、プログラムの通常のフローを継続できます。

スタック置換

これは実験的な機能であり、完全に機能するものの、まだ開発と研究が進行中です。この技術をコードに統合する場合は、必ずテストしてください。

使用方法

スタック置換機能を使用するには、cargo.toml に次の行を追加し、release モードでコンパイルしてください:

root@kitploit:~
[dependencies]
unwinder = {version = "0.1.4", features = ["Experimental"]}

この機能の主な機能は次のマクロにまとめられています:

  • start_stack_replacement!()/end_replacement!() のマクロペアは、unwinder にスタック置換プロセスの開始/終了を指示します。これらの2つのマクロは、コードのエントリポイント(例: DLL のエクスポート関数)で呼び出す必要があります。
  • replace_and_continue!()/restore!() のマクロペアは、最後のリターンアドレスの置換/復元を実行します。
  • 最後に、replace_and_call!()/replace_and_syscall!() のマクロペアは、現在のモジュール外の関数を呼び出すときにスタック置換を実行するために使用されます(例: Windows API を使用する場合、または他の DLL のコードを呼び出す場合)。これらのマクロはどちらも、この方法で呼び出された関数が返す値を含む *mut c_void を返します(つまり、SilentMoonWalk を実行するために使用される call_function および indirect_syscall マクロについて説明したのと同じ方法で動作します)。

これらのマクロを使用するには、std::ffi::c_void データ型をインポートする必要があります。 これらのマクロを使用するすべての関数には、最適化プロセス中に Rust コンパイラがインライン化しないように、#[no_mangle] または #[inline(never)] 属性を付ける必要があります。

これらすべての使用方法を示す実践的な例に入る前に、replace_and_call/replace_and_syscall のマクロペアと、期待される引数を渡す方法を簡単に確認しましょう。

replace_and_call

このマクロは、スタック置換を使用しながら、現在のモジュール外の任意の関数をクリーンなコールスタックで呼び出すために使用します。 このマクロは以下のパラメータを期待します:

  • 1つ目のパラメータは、呼び出す関数のメモリアドレスです。このパラメータは usize、isize、またはポインタとして渡す必要があります。
  • 以降のパラメータは、指定された関数に送信する引数です。パラメータの受け渡し セクションで指定された規則に従います。

replace_and_syscall

このマクロは、スタック置換を使用しながら、クリーンなコールスタックで任意の間接システムコールを実行するために使用します。 このマクロは以下のパラメータを期待します:

  • 1つ目のパラメータは、システムコールを実行したい NT 関数の名前を含む文字列です。
  • 以降のパラメータは、NT 関数に送信する引数です。パラメータの受け渡し セクションで指定された規則に従います。

例

これらのマクロの使用方法を示す最良の方法は、実践的な例だと思います。メモリにリフレクティブ注入される DLL を作成していると仮定しましょう。この DLL は ExportA と ExportB の2つの関数をエクスポートするため、これらの2つの関数をモジュールのエントリポイントと見なします。どちらの関数も、先頭で start_stack_replacement マクロを呼び出し、戻る前に逆の end_replacement マクロを呼び出す必要があります。start_stack_replacement マクロは引数としてモジュールのベースアドレスを期待します。実行時にそのアドレスが分からない場合は 0 を渡してもかまいません。マクロが自分でそれを見つけようとします。

root@kitploit:~
#[no_mangle]
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    ...
    unwinder::end_replacement!();

    true
}

#[no_mangle]
fn ExportedB() -> bool
{
    unwinder::start_replacement!(0);
    ...
    unwinder::end_replacement!();

    true
}

スタック置換プロセスの開始には、end_replacement マクロが呼び出されるまで使用される新しいスタックを手作業で構築することが含まれます。次の図は、内部で何が行われているかを示しています:

スタック置換

理論的にはゼロから新しいスタックを開始する必要はありませんが、安定性を確保して何も壊れないようにするために、この方法でプロセスを実装することにしました。

次に、ExportedA 関数が別の2つの内部関数を複数回呼び出すと仮定しましょう。これらの2つの内部関数は、ExportedA 内のどこかを指す元のリターンアドレスの置換/復元を担当します。これに対処しないとコールスタックが壊れます。この置換プロセスでは、内部関数のコードを replace_and_continue マクロと restore マクロで囲みます:

root@kitploit:~
#[no_mangle]
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    let ret_a = internal_a();
    let ret_b = internal_b(ret_a);
    unwinder::end_replacement!();

    ret_b
}

#[inline(never)] // This attribute is mandatory
fn internal_a() -> bool
{
    unwinder::replace_and_continue();
    ...
    unwinder::restore();
    
    some_value
} 

#[inline(never)] // This attribute is mandatory
fn internal_b(value: bool) -> bool
{
    unwinder::replace_and_continue();
    ...
    unwinder::restore();
    
    some_value
} 

最後に、internal_a と internal_b の両方の関数は、いくつかの Windows API 機能を利用します。アンワインド可能なコールスタックを維持するには、これらの呼び出しを replace_and_call(通常の呼び出し)または replace_and_syscall(間接システムコール)マクロを通じて実行する必要があります。

root@kitploit:~
#[no_mangle] // This attribute is mandatory
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    let ret_a = internal_a();
    let ret_b = internal_b(ret_a);
    unwinder::end_replacement!();

    ret_b
}

#[inline(never)] // This attribute is mandatory
fn internal_a() -> bool
{
    unwinder::replace_and_continue();
    ...
    let module_name = "advapi32.dll";
    let module_name = CString::new(module_name.to_string()).expect("");
    let module_name_ptr: *mut u8 = std::mem::transmute(module_name.as_ptr());
    let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
    let load_library = dinvoke_rs::dinvoke::get_function_address(k32, "LoadLibraryA");
    let ret = unwinder::replace_and_call!(load_library, module_name_ptr); // Load a dll with an unwindable call stack
    println!("advapi.dll base address: 0x{:x}", ret as usize);
    ...
    unwinder::restore();
    
    some_value
} 

#[inline(never)] // This attribute is mandatory
fn internal_b(value: bool) -> bool
{
    unwinder::replace_and_continue();
    ...
    let large = 0xFFFFFFFFFF676980 as u64; // Sleep one second
    let large: *mut i64 = std::mem::transmute(&large);
    let alertable = false;
    let ntstatus = unwinder::replace_and_syscall!("NtDelayExecution", alertable, large);
    println!("ntstatus: {:x}", ntstatus as usize);
    ...
    unwinder::restore();
    
    some_value
} 

注意事項

これは開発中の機能であるため、いくつか注意すべき点があります:

  • ロードプロセス中に PE のヘッダーを削除している場合は、start_stack_replace マクロにモジュールのベースアドレスを渡す必要があります。現時点では、マクロはそれを自分で見つけることができません(次のアップデートで解決予定です)。
  • 気になるかもしれませんが、スタック置換は SilentMoonWalk 技術と同じ jmp rbx + concealment フレームの組み合わせを使用します。これは replace_and_call および replace_and_syscall マクロを使用する場合にのみ発生し、次のアップデートで変更される予定です。
  • replace_and_call および replace_and_syscall マクロはどちらも *mut c_void を返し、それを通じて実行された関数が返す値を取得するために使用できます。これは call_function および indirect_syscall マクロについて説明したのと同じ動作です。
  • replace_and_call および replace_and_syscall マクロは最大11個の引数を許可します。

この機能の使用中に発生したバグがあれば報告してください。

ツールをダウンロード