アンチフック-理論

ごく最近、アプリケーションをシステムAPIのインターセプトから保護することに戸惑い、私が来たものを共有して議論することにしました。 インターセプトシステムAPIは、元の関数を適切な場所にリダイレクトすることになることをご存じでしょう。これにより、関数のパラメーターを変更したり、元とは異なる結果を返したり、元の呼び出しをパラメーターとともに保存したりできます。 これは理論的な部分であるため、記事の例には疑似コードが添付されます。



以下のインターセプトメソッドが一般的に使用されます。

  1. 関数の位置に無条件ジャンプを挿入します(x86-5バイトjmp addr、x64-12バイト、mov rax addr、jmp rax)
  2. 元の関数呼び出しの場所への呼び出しステートメントの挿入(hooked_function_entry:call my_function)
  3. アプリケーションインポートテーブルの変更、プロキシDLLの実装。
  4. ハードウェアブレークポイントを使用してコードを変更せずに傍受する。
  5. Zw / Nt関数のカーネルドライバーを介した傍受。


ユーザーモードからのインターセプトを検出するための次のメソッドをリストできます。

1.呼び出す前の関数の先頭と機械語命令のオペコードとの比較:



//0x90 - nop //0xE9 - jmp //0xE8 - call if (*mainFunc == 0xE9 || *mainFunc == 0x90 || *mainFunc == 0xE8 ...) printf("Hook detected");
      
      





このメソッドは静的であり、よりインテリジェントな遷移を簡単に省くことができます。

2.上記のインターセプトの方法はメモリの修正であり、crcチェックサムチェックによって検出できますが、メモリを読み取るためのapiもインターセプトでき、誤った結果が返されます。

3.重要なAPIを復元して、制御されたプロセスを生成します(それ自体のアクティブなデバッグを使用)。

4.システムライブラリのエクスポートテーブルを使用して、重要なAPIを列挙し、逆アセンブルされた関数の長さと元の関数の長さを比較します。

5.ライブラリからバイトをコピーして変更されたAPIを復元するためのスキームの例は次のとおりです。

-特定のモジュールのベースを取得する

-イテレーションのエクスポート

-必要な機能のRVAを取得します。

-変換RVA-> FileOffset。

-オリジナルの読み取りとメモリへの書き込み。

-場合によっては、再ロックを忘れないでください。

6.ライブラリインポートテーブルを表示し、別のシステムライブラリからインポートされた関数ごとに、コンプライアンスチェックを行い、一致しない場合はディスクから読み取り、メモリに書き込みます。

7. syscalls、int2e in(windowsの古いバージョン)を使用したNt / Zwのシステム機能のシミュレーション、すべてのシステムのsyscallテーブルはGoogleで簡単に見つかります。



Nt / Zw関数もインターセプトできるため、7番目の方法について説明します。この方法も完全にはほど遠いですが、私の意見では、上記のすべての方法が最善です。

Windows 7 SP1でのNtCreateFile関数の実装を見てみましょう



画像



次のことが起こります。

-eaxはsyscall-aの番号です。

-ecxをリセットします(wow64インデックス?)。

-edxには、パラメーターへのポインターが含まれています。

-さらに呼び出し、スタックの調整とリターン。



win7およびwin8でのx64呼び出し

画像



次のx86擬似コードを使用して、「ディスク上にファイルを作成できます。」

 //  NtCreateFile typedef NTSTATUS (NTAPI * NTCREATEFILE) (OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PLARGE_INTEGER AllocationSize OPTIONAL, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG CreateDisposition, IN ULONG CreateOptions, IN PVOID EaBuffer OPTIONAL, IN ULONG EaLength); #define InitializeObjectAttributes( p, n, a, r, s ) { \ (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ (p)->RootDirectory = r; \ (p)->Attributes = a; \ (p)->ObjectName = n; \ (p)->SecurityDescriptor = s; \ (p)->SecurityQualityOfService = NULL; \ } typedef VOID (NTAPI * RTLINITUNICODESTRING)(IN OUT PUNICODE_STRING, IN PCWSTR); unsigned char dNtCreateFile[] = {0xb8,0x52,0x00,0x00,0x00,0x33,0xc9,0x8d,0x54,0x24,0x04,0x64, 0xff,0x15,0xc0,0x00,0x00,0x00,0x83,0xc4,0x04,0xc2,0x2c,0x00}; ... RtlInitUnicodeString InitializeObjectAttributes ... DWORD oldp; VirtualProtect(&dNtCreateFile, sizeof(dNtCreateFile), PAGE_EXECUTE_READ, &oldp); auto func = (NTCREATEFILE) ((void*)dNtCreateFile); Ntstatus = (func)(&fileHandle, DesiredAccess, ObjectAttritubes, ioStatusBlock, 0, FileAttributes, ShareAccess, CreateDisposition ,CreateOptions, Optional_Buffer, 0);
      
      





この擬似コードはWindows 7 sp1でのみ機能しますが、ポイント5からの方法を使用してdNtCreateFileを「動的に」生成でき、その後、winxp以降のすべてのシステムで軽微な変更を加えたコードが機能します。 コメント内の考えは歓迎します。カーネルモードは極端なケースです。



All Articles