WinAFLでDLLをFuzzingする
Posted on
この記事では、WinAFLによるDLLのファジングを行うために必要となるハーネス作成やバイナリパッチ当て等の流れを説明していきます。
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関数のみに注目すれば良いことが分かります。
Crash()
を見てみると、ConfigファイルをTokenizeするLexerを実行したのち、Tokenを解析してIEC 104を送受信するParserが無限回数実行されることが分かります。
パケット送受信等を伴うParserはFuzzingを実行するまでに必要な動作の解析、バイナリのパッチ当てが多く必要になりそうであるため、まずはLexer単体でFuzzingを開始することにします。
Crash()
を呼び出した際にLexerだけが実行されるようにバイナリにパッチを当てます。
Patch当て
call aa_lexer
内部にはfopen()'と
fclose()‘が両方含まれているため、call aa_lexer
直後をret 0
に書き換えることにします。
※Closeせずに実行終了される場合には、Fuzzingの実行がプロセスの開始からのループとなってしまい、exec speedが大幅に減速してしまいます。Target内部ではOpenしたファイルを必ずCloseしましょう。
バイナリのパッチ当てにはIDA pluginのKeypatchとidadif.pyを使いました。
Keypatchを使う場合には、call aa_lexer
直下を選択しCtrl+K
でPatcherを開きます。
Assemblyのボックスに変更後のインストラクションを記載し、Patchを押下します。
call aa_lexer
直下がretn 0
に書き換わりました。Keypatchを使うと余ったバイトをNOPで埋めてくれるため便利です。
この状態ではidbファイル上でパッチが当たっているだけで、バイナリ自体は編集されていないため、idadif.pyを実行してバイナリにパッチを当てます。
バイナリへのパッチの当て方はStackoverflowのAnswerに簡潔にまとまっています。
次に、Moduleを呼び出すためのHarnessを書いていきます。
Harness作成
以下に今回書いたHarnessのソースコードと、その重要なポイントについて記載します。
#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個見つかっていることを示しています。
-o
で指定したフォルダであるresults内のcrashesにCrashを発生させるinputファイルが保存されています。
BugIdを使えばCrashの種類を確認することもできます。