アンチウイルスエンジンの作り方
はじめに
技術者のフォーラムの周りをローミングするとき、私はしばしば、非常に適応されていない言語(bat、PHP、…)で、時には”ウイルス対策を作る方法”を求め、ウイルス対策が何であるか、どのように構築されるべきかについて間違った考えを持っている(そして多くの経験があまりない)人を見ることがあります。
私はまた、非常に少数のまだ学校の人々と数週間でコーディングの一日あたり約4時間で、キディによって作られた多くの”ウイルス対策ソフト”を見てきました。 私はキディが熟練していないと言っているわけではありませんが、ウイルス対策エンジンを構築するには、フルタイムの仕事を持つ熟練した人々の
だから、私はここでは、Windows用とC/C++での基本的なウイルス対策コーディングのガイドラインをカバーします。 一つは、ここでウイルス対策エンジンを設計するためのポインタを見つけたり、単にそれらのほとんどが構築されている方法を学ぶことができます。
保護
良い保護のために、ウイルス対策は、カーネル内のコードを実行し、全体的にカーネルApiにアクセスできるようにするために、少なくとも一つのドライバ Vista以降、Microsoftは、ウイルス対策業界がカーネルに入り、ファイルシステム、レジストリ、ネットワークなどの戦略的な場所でフィルタを有効にするためのキーが必 それはこのために設計されていなかったので、Vista前のシステムのためのウイルス対策を構築することは、本当の痛みをすることができます場合は唖然とすることはありません。
- しかし、Vista以前のシステムでは、ウイルス対策会社はルートキットのような機能を使用してドアを守り、システムを保護することができました。 彼らは私たちが”フック”(フィルタリング目的のためのAPI迂回)と呼ぶものを使用しました。
- Vista+では、microsoftはユーザーランド呼び出しとカーネルApiの間に低レベルドライバーを挿入するApiを提供しました。 そうすれば、ウイルス対策製品をカーネルに登録するのは簡単です。 さらに、そのような登録ベースのシステムは、私たちは異なる目的を持ついくつかの製品が同居することができ、層に私たちのシステムのセキュリテ 実装は完全に製品に依存していたので、これはフックの場合ではありませんでした。
ノート: なぜなら、インターネット上で見つけるのは簡単で、フックする方法、フックする場所などを説明する章全体が必要だからです…しかし、それはカーネルApiと同じ
ドライバのコーディングについて学ぶには、有用なリンクを確認することができます:
http://msdn.microsoft.com/en-us/library/windows/hardware/gg490655.aspx
http://www.codeproject.com/Articles/9504/Driver-Development-Part-1-Introduction-to-Drivers
フックについて学ぶために、あなたはその基本的な例をチェックすることができます:
http://www.unknowncheats.me/forum/c-and-c/59147-writing-drivers-perform-kernel-level-ssdt-hooking.html
プロセス
ユーザーを最初に保護するのは、悪意のあるプロセスの起動です。 これは基本的なことです。 ウイルス対策は、P S S E T C R Eateprocessnotifyroutineexコールバックを登録する必要があります。 これにより、各プロセスの作成時に、メインスレッドが実行を開始する(および悪意のあるものを引き起こす)前に、ウイルス対策コールバックが通知され、
プロセス名、ファイルオブジェクト、PIDなどを受け取ります。 プロセスが保留中であるため、ドライバは悪意のあるもののためにプロセスのメモリを分析するようにサービスに指示することができます。 それは何かを引き起こし、ドライバは単にCreationStatusをFALSEに設定して戻ります。
NTSTATUS PsSetCreateProcessNotifyRoutineEx( _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, _In_ BOOLEAN Remove);
VOID CreateProcessNotifyEx( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);
typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable :1; ULONG Reserved :31; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus;} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
スレッド
プロセスと同じ考え方では、スレッドは悪意のあるものが損害を与える方法になる可能性があります。 たとえば、合法的なプロセスにコードを注入し、プロセスのコンテキスト内でそのコードでリモートスレッドを開始することができます(従うのは簡単ですか? 🙂 ). そうすれば、合法的なプロセスは悪意のあることを行うことができます。
p S S E T C R Eatethreadnotifyroutineコールバックを使用して新しいスレッドをフィルタリングできます。 スレッドが作成されるたびに、ウイルス対策はTIDとPIDで通知されます。 したがって、スレッドの開始アドレスコードを調べ、それを分析し、スレッドを停止するか再開することができます。
NTSTATUS PsSetCreateThreadNotifyRoutine( _In_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
VOID(*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );
画像
第三の動的な脅威は、メモリにロードできる画像に関するものです。 イメージは、EXE、DLL、またはSYSファイルのいずれかのPEファイルです。 ロードされたイメージを通知するには、P S Setloadimagenotifyroutineを登録するだけです。 このコールバックを使用すると、イメージが仮想メモリにロードされたときに通知され、実行されない場合でも通知されます。 プロセスがDLLをロードしようとしたとき、ドライバをロードしようとしたとき、または新しいプロセスを起動しようとしたときを検出できます。
コールバックは、完全な画像パス(静的解析に便利)に関する情報を取得し、私の意見ではより重要なのは、画像ベースアドレス(メモリ内解析の場合)です。 画像が悪意のある場合、ウイルス対策は、メモリ内の画像を解析してentrypointに移動し、アセンブリオペコード”ret”を呼び出して無効にするなど、実行を回避するた
NTSTATUS PsSetLoadImageNotifyRoutine( _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo );
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; //code addressing mode ULONG SystemModeImage : 1; //system mode image ULONG ImageMappedToAllPids : 1; //mapped in all processes ULONG Reserved : 22; }; }; PVOID ImageBase; ULONG ImageSelector; ULONG ImageSize; ULONG ImageSectionNumber;} IMAGE_INFO, *PIMAGE_INFO;
Filesystem
すべての動的なものが保護されると、ウイルス対策は、彼らが起動しようとしているときだけでなく、オンザフライで悪意のあるもののためにユー ウイルス対策は、ユーザーがフォルダ、アーカイブを開いたとき、またはディスクにダウンロードしたときにファイルをスキャンできるはずです。 さらに、ウイルス対策は、そのファイルを削除するには、任意のプログラムを禁止することによって、自分自身を保護することができるはずです。
このすべてを行う方法は、ファイルシステムにドライバをインストールすることです。 ここでは、minifilterについて話します。
minifilterは特定の種類のドライバであり、ファイルシステム上で行われるすべての読み取り/書き込み操作(IRPメジャー関数)にコールバックを登録することがで IRP(Interrupt Request Paquet)は、ディスク上の読み取り/書き込み操作を記述するために使用されるオブジェクトであり、ドライバスタックとともに送信されます。 Minifilterは単にそのスタックに挿入され、そのIRPを受信してそれをどうするかを決定します(操作を許可/拒否します)。
minifilterの小さな例については、その有用なリンクまたはそのリンクを確認してください。 マイクロソフトのガイドラインはここにあります。
WDKドキュメントの2つの例もこことここにあります。
基本的なminifilterコールバックは次のようになります。 コールバックにはPre操作とPost操作の2種類があり、クエリの後の前にフィルタリングすることができます。 ここではpreOperation擬似コードです:
FLT_PREOP_CALLBACK_STATUS PreOperationCallback (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext){ ... if ( all_good ) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } else { // Access denied Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } }
レジストリ
レジストリは守るべき最も重要な場所の一つです。 レジストリに単一の(またはいくつかの)キー/値を登録することにより、システム上の永続的な手を維持するためのマルウェアのための多くの多くの方 最も知られている場所は、キーとサービスを実行します。 これは、単にそのドライバ/サービスキーを削除することによって、ウイルス対策が(ファイルシステムと一緒に)敗北することができる場所でもあります。
これは、再起動場所を保護するためのウイルス対策のための本当の必要性ではありません,それらのほとんどはありません.しかし、彼らは彼らのインストールレジストリキーを保護する必要があります,マルウェアによって容易に敗北されないように. これは、CmRegisterCallbackを登録することによって行うことができます。
コールバックは、完全なキー名、アクセスの種類(Create、Rename、Delete、…)、および呼び出し元のPIDを取得するのに十分な情報を提供します。 そうすれば、Post Operation callbackのStatusフィールドを設定することで、呼び出しへのアクセスを許可するかどうかを簡単に設定できます。
NTSTATUS CmRegisterCallbackEx(_In_ PEX_CALLBACK_FUNCTION Function,_In_ PCUNICODE_STRING Altitude,_In_ PVOID Driver,_In_opt_ PVOID Context,_Out_ PLARGE_INTEGER Cookie,_Reserved_ PVOID Reserved);
EX_CALLBACK_FUNCTION RegistryCallback;NTSTATUS RegistryCallback( _In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2){ ... }Argument1 = typedef enum _REG_NOTIFY_CLASS { RegNtDeleteKey, RegNtPreDeleteKey = RegNtDeleteKey,...Argument2 = typedef struct _REG_POST_OPERATION_INFORMATION { PVOID Object; NTSTATUS Status; PVOID PreInformation; NTSTATUS ReturnStatus; PVOID CallContext; PVOID ObjectContext; PVOID Reserved;} REG_POST_OPERATION_INFORMATION, *PREG_POST_OPERATION_INFORMATION;
Network(Firewall)
ユーザーランドで行われるコンテキスト切り替えによって減速されることなく、特定のシステム(サーバー、巨大な帯域幅のユーザー)で巨大になる可能性のあるインターネットトラフィック全体の扉を守るために、httpトラフィックに十分なwebブラウザフィルタを除いて、基礎となるドライバを持たないファイアウォールをインストールすることは完全に推奨されていませんが、マルウェアの通信を保護することはできません。
ファイアウォールを正しく実装するには、NDIS、TDI、または低レベルIPフィルタリングドライバ用の別の方法をコーディングする必要があります。 NDIS/TDIは少し難しいですが、多くの知識が必要です(私の意見では他のフィルタよりも多く)。
とにかく、このようなドライバのコーディングを開始するためのいくつかのポインタ、microsoftのガイドライン、古いcodeprojectチュートリアル(しかしまだ読んで良い)、NDISファ また、NDISファイアウォールバイパスのトリックについての良い執筆と、ネットワークドライバスタックについての少しの説明、
Userland protection
userland protectionは必要ではありませんが、トロイの木馬バンカー、より具体的にはプロセススパイに対する追加のモジュールになる可能性があります。 それらはいくつかの理由から、一般的にすべてのプロセスに注入されます。
まず、プロセスがマルウェアとして識別された場合、(要求に応じて)プロセスを強制終了することができます(AVsは起動する前に停止することになってい あなたがその文脈にいるときにプロセスを停止する方が常に簡単です。
第二に、彼らは、パスワード、銀行情報を収集し、マルウェアのサーバーにインターネットの流れをリダイレクトするためにAPI呼び出しを迂回し、フィルタリング また、マルウェアDLLのLoadLibrayを避けるためにフック自体を設定することもできるため、コードインジェクションの特定の方法を禁止することができます。
すべてのプロセスにprotector DLLを注入する簡単な方法は、AppInitDllレジストリキーを使用してprotector DLLを登録することです。 User32をリンクするとすぐに、システム上で開始されたすべてのプロセスにDLLをロードします。dllイメージ(それらのほとんどはそうです)。
解析エンジン
解析エンジンは最も重要な部分の一つであり、ドライバからのファイル/メモリサンプルの解析を担当します。 高速でなければならない場合(巨大なデータベースであっても)、ほとんどのファイルタイプ(自己抽出された実行可能ファイル、アーカイブ-RAR、ZIP、パックされた:
- Unpacker:そのモジュールは、既知のパッカー(UPX、Armadillo、ASPack、…)のほとんどを検出して解凍できる必要があります
- 署名エンジン:ウイルス対策のデータベースには何百万もの署名が含 したがって、非常に強力なアルゴリズムがその一部でなければなりません。 いくつかの例 : AhoCorasick、RabinKarp、文字列マッチングアルゴリズム。
- Sandbox:そのモジュールは必要ありませんが、システムに影響を与えずに限られたメモリでサンプルを実行できるようにするにはプラスになります。 これは、未知のパッカーでパックされたサンプルを解凍し、ヒューリスティックエンジン(後を参照)が疑わしいまたは悪意のあると考えられるAPIコールを検出するのに役立つ可能性があります。 ここにいくつかの良いサンドボックス。
- ヒューリスティック-エンジン:前述のように、ヒューリスティック-エンジンは署名を検索するのではなく、疑わしい動作を検索します(すなわち、ヒューリスティック-エンジンは、署名を検索するのではなく、疑わしい動作を検索します)。 これは、静的分析によって、またはサンドボックスを介して行うことができます。
署名構文
署名構文は、署名エンジンが理解する言語の”辞書”です。 それは、見つけるパターンが何であるか、それを検索する方法、そしてサンプルをどこで検索するかを公式化する方法です。 構文は、研究者が理解するのに十分な単純さ、すべてのユースケースを処理するのに十分な強力さ、およびより良いエンジン性能のための解析が容易でな
VirusTotalは、オープンソースである優れた構文とエンジン(Yaraプロジェクト)を開発しました。 これは、独自の構文を作成するか、単にそれを使用するのに適したポインタでなければなりません。 ここではまた、ウイルス対策のための署名を作成する方法についての良い記事のブログです。
署名の例:
rule silent_banker : banker{ meta: description = "This is just an example" thread_level = 3 in_the_wild = true strings: $a = {6A 40 68 00 30 00 00 6A 14 8D 91} $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" condition: $a or $b or $c}
自己保護
自己保護は、マルウェアに敗北しないようにし、ユーザーを保護し続けるために、ウイルス対策にとって非常に重要です。 これが、ウイルス対策が独自のインストールを保護し、再起動時に永続性を維持できる必要がある理由です。
ファイル、レジストリキー、プロセス/スレッド、メモリなど、保護する場所がいくつかあります。
- ファイル保護は、アンチウイルスのファイルに関する特定のルール(削除、名前の変更、移動、書き込みのアクセスなし)で、minifilterに実装されています。
- レジストリ保護は、ドライバとサービスのレジストリキーのアクセスが拒否され、レジストリフィルタになります。
- システムをクラッシュさせずにカーネルモジュールをアンロードすることは非常に不可能であるため、ドライバスレッドは保護されています
- ユーザーランドプロセスであるサービスを保護できるようにするには、2つの解決策があります:
- 最も簡単なのは、サービスマネージャで障害のルールを追加し、すべての障害ルールを「サービスの再起動」に設定することです。 このようにして、サービスがservice managerによって停止されないと、サービスが再起動します。 もちろん、システムが再起動(または停止)されないまで、サービスはコマンドを受け入れることができないはずです。
- より一般的な第二の方法は、ObRegisterCallbacksを使用してプロセスハンドルにコールバックを設定することです。
ObjectTypeをPsProcessTypeに、操作をOB_OPERATION_HANDLE_CREATEに設定すると、前と後の操作コールバックを受け取り、照会されたプロセスハンドルにプロセス終了権(またはプロセス書き込み権、またはクラ
もちろん、重複したハンドルとPsThreadTypeをガードして、プロセスまたはスレッドのハンドルを取得する必要がある終了メソッドを避ける必要があります。 ここでは、そのコールバックの使用例を少し示します。
NTSTATUS ObRegisterCallbacks( _In_ POB_CALLBACK_REGISTRATION CallBackRegistration, _Out_ PVOID *RegistrationHandle);
typedef struct _OB_CALLBACK_REGISTRATION { USHORT Version; USHORT OperationRegistrationCount; UNICODE_STRING Altitude; PVOID RegistrationContext; OB_OPERATION_REGISTRATION *OperationRegistration;} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
typedef struct _OB_OPERATION_REGISTRATION { POBJECT_TYPE *ObjectType; OB_OPERATION Operations; POB_PRE_OPERATION_CALLBACK PreOperation; POB_POST_OPERATION_CALLBACK PostOperation;} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
VOID ObjectPostCallback( _In_ PVOID RegistrationContext, _In_ POB_POST_OPERATION_INFORMATION OperationInformation);
typedef struct _OB_POST_OPERATION_INFORMATION { OB_OPERATION Operation; union { ULONG Flags; struct { ULONG KernelHandle :1; ULONG Reserved :31; }; }; PVOID Object; POBJECT_TYPE ObjectType; PVOID CallContext; NTSTATUS ReturnStatus; POB_POST_OPERATION_PARAMETERS Parameters;} OB_POST_OPERATION_INFORMATION, *POB_POST_OPERATION_INFORMATION;
typedef union _OB_POST_OPERATION_PARAMETERS { OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation; OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;} OB_POST_OPERATION_PARAMETERS, *POB_POST_OPERATION_PARAMETERS;
typedef struct _OB_POST_CREATE_HANDLE_INFORMATION { ACCESS_MASK GrantedAccess;} OB_POST_CREATE_HANDLE_INFORMATION, *POB_POST_CREATE_HANDLE_INFORMATION;
GUI(グラフィカルユーザーインターフェイス)
これは氷山の目に見える部分です。 私の意見では、あなたの製品を販売したい場合は、最も重要な部分の1つ(多分)です。 ユーザーは、美しい、使いやすい、直感的なものが大好きです。 たとえそれが100%効率的ではないとしても。 GUIはセクシーでなければなりません。
GUIは空のシェルであり、グラフィカルな処理のみを行い、コア(サービス)にコマンドを送信/受信します。 また、進行状況バー、分析されているもの、設定を提供するものなども表示されます…ここにAvast UIがあります。 セクシーだろ? 🙂
Architecture
グローバルアーキテクチャは次のようなものになる可能性があります:
- GUI:管理者権限なし、WEAK
- Guard DLL:webブラウザ保護、MEDIUM
- Service:管理者権限。 カーネルコードへのゲートウェイとして機能し、いくつかのデータベースと一緒に決定を行います。STRONG
- Driver(s):Kernel filters,STRONG
GUIは管理権限を必要とせず、ユーザーアクションを取り、サービスに送信するだけです。 また、製品の状態も表示されます。 これ以上の何も、これはその目的ではありません。 GUIが強制終了された場合、サービスはGUIを再起動できる必要があるため、これは問題ではありません。
guard Dll(もしあれば)は、すべてのプロセスに大量に注入され、IATフックや悪意のあるスレッドを探すことができるはずです。 彼らはアンロードまたは敗北するのは非常に難しいはずです。 彼らは重要ではありませんが重要です。
サービスは製品の中核です。 それは不可能でなければならない、または少なくともkillで自己再起動できるはずです。 このサービスは、製品のすべてのモジュール間の通信を担当し、ドライバにコマンドを送信し、ユーザーからコマンドを取得し、サンプル分析のためにデータベース これは脳です。
カーネルドライバも重要です。 彼らは、システム上で起こるすべての情報を収集し、決定のためにサービスに送信する触手です。 彼らはまた、サービスの決定に基づいて、守られた場所へのアクセスを拒否することができます。
結論
強力で信頼性が高く安定したウイルス対策エンジンを作ることは複雑な作業であり、windowsカーネルプログラミング、windowsアプリケーションプログラミング、GUI設計、ソフトウェアアーキテクチャ、マルウェア解析、…
安定したドライバを構築することも複雑な作業です。 それはテスト、テスト、およびテストの多くを必要とします。 あなたのエンジンが完了し、作業されたら、あなただけのマルウェアを分析し、あなたのデータベースに署名を追加するために研究者を雇う必要があり 行け! ▲
リンク集
- http://www.symantec.com/connect/articles/building-anti-virus-engine