← All Articles

WinAFLでDLLをFuzzingする

Posted on

この記事では、WinAFLによるDLLのファジングを行うために必要となるハーネス作成やバイナリパッチ当て等の流れを説明していきます。

Harness

Harnessとは


DLLをFuzzingする場合には、DLLをロードするEXEであるHarnessを書いてあげる必要があります。HarnessからDllMain()やExport関数を呼び出し、Fuzzを入力するということです。


Target DLL


2016年12月にウクライナを停電させたサイバー攻撃に使用されたマルウェアであるCrashoverrideのモジュール (DLL)をターゲットとします。

CrashoverrideはIndustroyerとも呼ばれるICSマルウェアの1つです。

攻撃者は、LauncherにDLLとConfigファイルのパスを指定することで、モジュール内に実装された機能を実行します。

今回のターゲットは、そのモジュールの中でも電力網で利用される制御プロトコルであるIEC 60870-5-104 (aka IEC 104)を送受信するものです。

動作の概要を理解するのにはESETのレポートを読むと良いです。

SHA256

  • Module: 7907dd95c1d36cf3dc842a1bd804f0db511a0f68f4b3d382c23a3c974a383cad
  • Launcher: 21c1fdd6cfd8ec3ffe3e922f944424b543643dbdab99fa731556f8805b0d5561

Targetの解析


Fuzzingを行うためには、Target内のどの部分を対象とするかを決める必要があります。

インプットをパースする関数には例外処理の甘さから脆弱性となり得る部分が多く含まれるため、パースの役割を持つ部分を探していきます。

今回のTargetはConfigファイルを読み込んで動作を変更するため、Configファイルのパース部分を探していきます。

ESETのレポートより、TargetはLauncherバイナリから下記コマンドで実行されるとあります。

$ %LAUNCHER%.exe %WORKING_DIRECTORY% %PAYLOAD%.dll %CONFIGURATION%.ini

LauncherにはConfigのパースには関係のない処理が含まれている可能性が高いです。LauncherをReverse engineeringしてDLL呼び出し部分の処理を確認していきます。


Launcher

defragsvcという名称で、DLL内のIEC 104送受信用の機能Wiper機能がサービスとして登録されます。

サービスの中身

((void (__cdecl *)(LPWSTR))lpCrash)(argv_cpy[3]);より、DLLのExport関数はCrash((wchar_t) ConfigPath);で呼び出せることが分かります。

続いて、Moduleの方をReverse engineeringしていきます。


Module

まずLoadLibrary()でModuleをロードした際に走る処理を確認しておきます。

DllMain()には処理が含まれていないため、Export関数のみに注目すれば良いことが分かります。

DllMain


Crash()を見てみると、ConfigファイルをTokenizeするLexerを実行したのち、Tokenを解析してIEC 104を送受信するParserが無限回数実行されることが分かります。

Crash

パケット送受信等を伴うParserはFuzzingを実行するまでに必要な動作の解析、バイナリのパッチ当てが多く必要になりそうであるため、まずはLexer単体でFuzzingを開始することにします。

Crash()を呼び出した際にLexerだけが実行されるようにバイナリにパッチを当てます。


Patch当て


call aa_lexer内部にはfopen()'とfclose()‘が両方含まれているため、call aa_lexer直後をret 0に書き換えることにします。

※Closeせずに実行終了される場合には、Fuzzingの実行がプロセスの開始からのループとなってしまい、exec speedが大幅に減速してしまいます。Target内部ではOpenしたファイルを必ずCloseしましょう。 Crashのアセンブリ


バイナリのパッチ当てにはIDA pluginのKeypatchidadif.pyを使いました。

Keypatchを使う場合には、call aa_lexer直下を選択しCtrl+KでPatcherを開きます。

Assemblyのボックスに変更後のインストラクションを記載し、Patchを押下します。

Patcher

call aa_lexer直下がretn 0に書き換わりました。Keypatchを使うと余ったバイトをNOPで埋めてくれるため便利です。

パッチ後のアセンブリ

この状態ではidbファイル上でパッチが当たっているだけで、バイナリ自体は編集されていないため、idadif.pyを実行してバイナリにパッチを当てます。

バイナリへのパッチの当て方はStackoverflowのAnswerに簡潔にまとまっています。

次に、Moduleを呼び出すためのHarnessを書いていきます。


Harness作成


以下に今回書いたHarnessのソースコードと、その重要なポイントについて記載します。

harness_crash.c
#include <windows.h>
#include <stdio.h>

extern __declspec(dllexport) void fuzz_crash(wchar_t* ConfigPath);

typedef int (*INSPECT)(wchar_t *file_path);
INSPECT Crash;

wchar_t* charToWChar(const char* text) {
    size_t size = strlen(text) + 1;
    wchar_t* wa = (wchar_t*)malloc(sizeof(wchar_t) * size);
    mbstowcs(wa, text, size);
    return wa;
}

void fuzz_crash(wchar_t *ConfigPath) {
    Crash(ConfigPath);
    return;
}

int main(int argc, char *argv[]) {
    char FuncName[] = "Crash";
    char DllName[] = "Crash104.dll";

    HMODULE hModule = LoadLibraryA(DllName);
    if (hModule == NULL) {
        printf("Failed to load library %s", DllName);
        return -1;
    }
    Crash = (INSPECT)GetProcAddress(hModule, FuncName);
    if (Crash == NULL) {
        printf("%s: GetProcAddress failed.", FuncName);
        FreeLibrary(hModule);
        return -1;
    }
    fuzz_crash(charToWChar(argv[1]));
    return 0;
}

ポイントは以下3点です。

ポイント1. char DllName[] = "Crash104.dll";

  • コンパイル時に指定したファイル名から名前を変更しないこと

DLLファイル名をコンパイル時から変更してしまっているとWinAFLによるFuzzing実行時に下記のエラーが発生します。

[-] PROGRAM ABORT : Test case 'id_000000' results in a timeout
         Location : perform_dry_run(), C:\winafl\afl-fuzz.c:2972

恐らく理由は、WinAFLは.edataセクションのName RVAに記載されたDLL名によってDLLをロードしているためだと予測しています。

ポイント2. fuzz_crash()

  • Target関数を呼び出すために関数を定義すること

ポイント3. extern __declspec(dllexport)

  • ポイント2で定義した関数を.edataセクションに格納することでExport関数とすること

ファジング開始


afl-fuzz.exe実行時に-target_method fuzz_crashの様に、Fuzzingループ対象の関数として指定できるようになります。

$ afl-fuzz.exe -i testcases -o results -D c:\dynamorio\build\bin32 -t 10000 -- -coverage_module Crash104.dll -fuzz_iterations 5000 -target_module harness_crash.exe -target_method fuzz_crash -nargs 1 -- .\harness_crash.exe @@

2日ほどFuzzingを走らせた後のcmdプロンプトのStatus画面を以下に示します。 uniq crashes: 79 の部分にて、Crashが発生するinputファイルが79個見つかっていることを示しています。

Status


-oで指定したフォルダであるresults内のcrashesにCrashを発生させるinputファイルが保存されています。

Crashes


BugIdを使えばCrashの種類を確認することもできます。

BugId report


reversingfuzzingwinaflicsmalware