FFRIエンジニアブログ

株式会社FFRIセキュリティのエンジニアが執筆する技術者向けブログです

2021年度新人研修その3:Windows解析研修

はじめに

2021 年度に新卒入社した H です。
FFRIセキュリティでは、入社後から 4 ヶ月間に渡って新人研修が行われます。
本記事では、今回実施された研修の中から、5 日間に渡って行われた「Windows 解析研修」について紹介します。

Windows 解析研修

Windows 解析研修では、Windows に関する解析を中心とした問題がいくつか用意されています。
本年度ではその内の 1 問として、CreateFileWとその内部で呼ばれているNtCreateFileについて、実行した際に得られるHANDLEの値は同じであるかを検証する問題が出題されました。
ここでは、この問題にクローズアップして、具体的な検証方法や考慮するべきことについて紹介していきます。

調査

まず、各関数についてHANDLEとして比較する値の見当をつけるため、Microsoft Docs を参照して定義を確認します。

CreateFileWはファイルなどのオブジェクトを開くまたは作成する関数であり、Win32 API の 1 つとしてユーザーに提供されています。
CreateFileWは以下のように定義されており、戻り値としてHANDLEが返されます。
CreateFileW function (fileapi.h) - Win32 apps | Microsoft Docs

HANDLE CreateFileW(
    LPCWSTR               lpFileName,
    DWORD                 dwDesiredAccess,
    DWORD                 dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD                 dwCreationDisposition,
    DWORD                 dwFlagsAndAttributes,
    HANDLE                hTemplateFile
);

NtCreateFileもファイルなどのオブジェクトを開くまたは作成する関数であり、ユーザーモードからカーネルモードへのインターフェイスの役割を担います。
NtCreateFileは以下のように定義されており、第 1 引数FileHandleHANDLEを指すポインターが格納されます。
NtCreateFile function (winternl.h) - Win32 apps | Microsoft Docs

__kernel_entry NTSTATUS NtCreateFile(
    PHANDLE            FileHandle,
    ACCESS_MASK        DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PIO_STATUS_BLOCK   IoStatusBlock,
    PLARGE_INTEGER     AllocationSize,
    ULONG              FileAttributes,
    ULONG              ShareAccess,
    ULONG              CreateDisposition,
    ULONG              CreateOptions,
    PVOID              EaBuffer,
    ULONG              EaLength
);

したがって、CreateFileWの戻り値とNtCreateFileの第 1 引数のポインターが指す値を検証すれば良いということになります。

実行するごとにこの値は変化します。

検証

次に、CreateFileWNtCreateFileの呼び出しの様子の解析方法を検討します。
解析には動的解析と静的解析の 2 つがあり、それぞれの利点と欠点を以下に示します。

動的解析 静的解析
利点 解析にかかるコストが比較的低い 詳細なロジックを把握しやすい
欠点 詳細なロジックを把握しづらい 解析にかかるコストが比較的高い

本研修の進め方は受講者に任せられており、各課題の時間配分についても自身で考える必要がありました。
そこで、一先ず動的解析の結果を元にレポートを作成し、余りの時間で静的解析に取り組みました。

動的解析

動的解析では、API Monitor を用いて Windows API が呼び出される様子を確認します。

検証にあたって、以下のようなCreateFileWを呼び出すプログラムを作成します。

#include <Windows.h>

int main() {
    HANDLE hFile = CreateFileW(
        L"test.txt",
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        CREATE_NEW,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    if (hFile == INVALID_HANDLE_VALUE) {
        return 1;
    }
    CloseHandle(hFile);
    return 0;
}

作成したプログラムを実行し、CreateFileW及びNtCreateFileが呼び出される様子を API Monitor で確認します。

APIが呼び出される様子

CreateFileWの呼び出しで得られたHANDLEは、0x00000000000000e0であることが分かります。

CreateFileWの戻り値と引数

また、NtCreateFileの呼び出しで得られたHANDLEも、0x00000000000000e0であることが分かります。

NtCreateFileの戻り値と引数

この結果から、CreateFileWとその内部で呼ばれているNtCreateFileについて、実行した際に得られるHANDLEの値は同じであると言えそうです。
しかし、引数や環境などの条件を変えて呼び出した場合でも、同じ結果になるという確証はありません。
このような動的解析からは、検証時に実行されなかったコードの挙動は分からないことを考慮しておかなければなりません。
したがって、より精細な結果が求められる場合は静的解析をする必要があります。

静的解析

静的解析では、Ghidra を用いてデコンパイル結果を解析します。

API Monitor で確認できた Call Stack の内容から、NtCreateFileKERNELBASE.dllに定義されたCreateFileWから呼び出されていることが分かります。

Call Stackの内容

そこで、KERNELBASE.dllを解析し、CreateFileWの内部でNtCreateFileがどのように呼び出されているのかを確認していきます。

KERNELBASE.dllのパスは以下のコマンドで調べることが出来ます。

whereコマンドの実行結果

KERNELBASE.dllを Ghidra で開いたら、Data Type Manager からwindows_vs12_64を適用します。

Data Type Managerでwindows_vs12_64を適用

CreateFileWのデコンパイル結果は以下の通りです。
ここで呼び出されている関数をCreateFileInternalとします。
CreateFileWCreateFileInternalのラッパー関数となっていることが分かります。

CreateFileWのデコンパイル結果

余談ですが、CreateFileInternalCreateFileWだけでなくCreateFileAからも同様に呼び出されています。
つまり、W版であるCreateFileWとA版であるCreateFileAの内部実装は共通しているということになります。

CreateFileInternalの呼び出し元

CreateFileInternalの、NtCreateFileの呼び出し付近のデコンパイル結果は以下の通りです。
ここで第 1 引数FileHandleとなっている変数をhandleとします。

CreateFileInternalのNtCreateFileの呼び出し付近のデコンパイル結果

NtCreateFile の引数が適切に表示されていない場合、Edit Function Signature から手動で定義することができます。

NtCreateFileの定義を編集

CreateFileInternalの、return 付近のデコンパイル結果は以下の通りです。
戻り値はhandleであることが分かります。

CreateFileInternalの戻り値

return 付近のでコンパイル結果が崩れている場合、__security_check_cookieを Edit Function Signature からインライン展開することで修正できます。

SecurityCheckCookieをインライン展開

__security_check_cookieは、コンパイラが埋め込んだセキュリティチェック機構です。
コンパイラ セキュリティの徹底調査 | Microsoft Docs
Ghidra のデコンパイラはこの関数を呼び出すことで RAX の値が破壊されるという前提で解析していますが、実際には RAX の値は破壊されません。
インライン展開することでextraout_RAXとなっている戻り値を修正することができます。

SecurityCheckCookieのインライン展開前

この結果から、CreateFileWとその内部で呼ばれているNtCreateFileについて、実行した際に得られるHANDLEの値は同じと言えます。
しかし、このような静的解析には時間がかかり、研修中は余りの時間だけでは解析を終えることができませんでした。

このように、目的や状況に応じて解析方法を選択する必要があります。
今回の研修を通じて、柔軟な姿勢で解析に取り組む必要があることを実感しました。

おわりに

本記事では、Windows 解析研修の中から 1 問をピックアップして紹介いたしました。
実際の研修ではこのような問題の他に、CTF 形式の問題も出題されました。
どの問題も Windows への興味を強く引き立てるもので、初めて知ることも多くあり、非常に実のある研修でした。
本記事が、FFRIセキュリティの新人研修制度に興味や不安がある方の参考になれば幸いです。
FFRIセキュリティの新人研修にご興味のある方は、こちらの新人研修についての記事一覧もご覧ください。

FFRIセキュリティの新人研修について

FFRIセキュリティでは、毎年 4 月から 4 ヶ月間、新入社員を対象に研修しており、セキュリティに関して事前知識がなくても、研修中に一通りの知識を学べるようになっています。
採用に関しては、採用情報を参照ください。