オルトプラスエンジニアの日常をお伝えします!

APIフックに負けない強い体づくり

この記事は Altplus Advent Calendar 2017 の19日目のエントリです。

※Qiitaに書いた記事を転載しています。

こんにちわ。id:takuya-kikuchiです。

前回notepad.exeにAPIフックをしかけてイタズラする手法を紹介しました。 今回は、そういったAPIフックによる攻撃からいかに身を守るか、という方法を紹介したいと思います。方法は色々とありますが、APIフックの脅威からはAPIフックで身を守ることにしましょう。俺はこの力を正しい方向に使うんだ。notepadは俺が守る。

とりあえずDLLインジェクション

APIフックしたいので、とりあえずDLLをインジェクションさせてもらいましょう。正義のためだからいいよね。手法は前回と同様、SetWindowsHookExを使います。

また、今回は守る側が先にDLLインジェクションを仕掛けることとします。一般的に、この種の戦いは先手を取られるとかなり不利です。

さて、では具体的にどのような方法でnotepad.exeを守るか考えましょう。

特定のAPIがフックされていることを検知する

実際に危害を加えてきた場合にそれを検知するパターンです。今回、敵はCreateFileWをフックして悪事に及ぶことはわかっているので、こちらもCreateFileWをフックして対処しましょう。

dllの初期化処理はこちらです。 フックと、後々行うStackWalk64のための初期化処理(SymInitialize)を行なっています。

void ShieldNotepad() {
    TCHAR fileNameBuf[MAX_PATH];

    // notepad.exeのみを対象にする
    if (GetModuleFileName(GetModuleHandle(NULL), fileNameBuf, MAX_PATH) > 0) {
        if (_tcsstr(fileNameBuf, TEXT("notepad.exe")) != NULL) {
            // StackWalk用の初期化処理
            SymInitialize(GetCurrentProcess(), NULL, TRUE);
            // フックする。戻り値は本来のCreateFileWのアドレス
            createFileWPtr = (fpCreateFileW)RewriteFunction("kernel32.dll", "CreateFileW", ShieldedCreateFileW);
        }
    }
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        ShieldNotepad();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

フックした関数はこちら。フックされてたら死ぬ覚悟です。

HANDLE
WINAPI
ShieldedCreateFileW(
    _In_ LPCWSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
)
{
    TerminateIfHooked();
    return createFileWPtr(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}

さて、TerminateIfHookedの実装はこちらです。

void TerminateIfHooked() {
    CONTEXT context;
    STACKFRAME64 stackFrame;

    // StackWalkのためのコンテキストの準備。
    RtlCaptureContext(&context);

    // StackWalkのために、現在のスタックフレームの状態を設定。
    ZeroMemory(&stackFrame, sizeof(STACKFRAME64));
    stackFrame.AddrPC.Offset = context.Rip;
    stackFrame.AddrPC.Mode = AddrModeFlat;
    stackFrame.AddrFrame.Offset = context.Rsp;
    stackFrame.AddrFrame.Mode = AddrModeFlat;
    stackFrame.AddrStack.Offset = context.Rsp;
    stackFrame.AddrStack.Mode = AddrModeFlat;

    auto frameCount = 0;

    // 深さ10くらいまで探索。特に意味はない
    while (frameCount < 10)
    {
        // 1個上のスタックフレームを取得
        if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &stackFrame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
            break;
        }

        // ただしく取得できていたら、モジュール情報をチェック
        if (stackFrame.AddrPC.Offset != 0)
        {
            HMODULE mod;
            // アドレスから、属するモジュールベースを取得してくれるAPI
            if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)stackFrame.AddrPC.Offset, &mod)) {
                TCHAR fileNameBuf[MAX_PATH];
                // モジュール名を取得
                if (GetModuleFileNameW(mod, fileNameBuf, sizeof(fileNameBuf)) > 0) {
                    if (wcsstr(fileNameBuf, TEXT("WaruiDll.dll")) != NULL) {
                        // アラートを表示して
                        MessageBoxW(NULL, TEXT("API Hooking Detected!"), TEXT("Good bye."), MB_OK);
                        // さようなら
                        TerminateProcess(GetCurrentProcess(), 0);
                    }
                }
            }
            frameCount++;
        }
        else {
            break;
        }
    }
    return;
}

少しわかりづらいのですが、やりたいことは、スタックトレースを1個ずつ上っていき、モジュール名を取得。気にくわない名前が見つかったら自害する、というかんじです。

もちろん、きちんと対策するのであれば、StackWalk64がフックされている可能性も考えなければなりません。

他にも方法はある

悪意あるDLLのロード自体を妨害することも有効でしょうし、わざわざCreateFileWが呼ばれなくとも、主要なAPIがフックされてる時点で自害するという手もあります。もうちょっと色々試したかったのですが、今回はここまで。

今回のコードはこちらにアップしてあります。くれぐれもご利用は慎重に、自己責任でよろしくお願いします。

おわりに

今回は、APIフックに対してAPIフックで応える形で、notepad.exeを守る方法について検討しました。

なお今回は守る側が先にフックを仕掛けている前提でしたが、逆の場合もあるでしょう。そうなると、また方針も変わってきます。DLLのロード順序の先取り合戦も、熾烈な争いが繰り広げられていることでしょう。さらには、CreateRemoteThread等による、DLL Injectionを伴わないような攻撃も当然考えられます。

また、今回はユーザ空間のみについて考えましたが、カーネルドライバが混ざってくるとより戦いは激化することになります。悪意あるハッカーにカーネルドライバを混入させられたら負けだ。気をつけましょう。

ともあれ、攻撃も防御も完璧などありえません。この戦いに終わりもありません。セキュリティいたちごっこは今日も続きます

あなたのアプリも狙われているかもしれませんよ。 それではまた。

Franz5でさくっとアプリをつくる

こんにちわ。 id:xshimadaです。

オルトプラスでエンジニアの身の回りの世話をしています。

ところで、いろんなプロジェクトやコミュニティに関わっているとたくさんのSlackやChatowrkなどにアクセスしなきゃいけない時があってたいへんですよね。

わたしはいろんなメッセンジャーをまとめるためにFranz を利用しています。

meetfranz.com

FranzはWindows、Mac、Linuxに対応しており、Slack、Chatwork、Telegramなど、さまざまなサービスが利用できる便利アプリです。 またGithubにも公開されています。

FranzではChatwork サービスもサポートしているのですが、オルトプラスで使っているKDDI ChatworkではURLが異なるせいかセッションが切れやすい気がします。

今日はサンプルとして、KDDI Chatworkに対応させてみましょう。

まず、Franz 5からは追加したサービスは以下のフォルダに格納されます。

Windows:
C:\Users\[ユーザ名]\AppData\Roaming\Franz\recipes

Mac
/Users/[ユーザ名]/Library/Application Support/Franz/recipes

recipesの下にdevというフォルダを作ってください。

そうすると、FranzのSettingsのAvailable ServicesにDevelopmentというカテゴリが追加されます。

f:id:xshimada:20171218100309p:plain

Settingsからサービスとしてchatworkを登録するとrecipesの下のchatworkフォルダができます。 今回はさくっとおわらずために、recipesの下のchatworkフォルダをdev配下にkddichatworkとしてコピーしてきます。

フォルダの構成としては、

  • Configuration (package.json)
  • Frontend API (webview.js)
  • Backend API (index.js)
  • Icons (icon.svg、icon.png)

となっています。

試しにpackage.jsonを開いてみます。

package.json

{
  "id": "chatwork",
  "name": "chatwork",
  "version": "1.0.1",
  "description": "chatwork",
  "main": "index.js",
  "author": "Koma",
  "license": "MIT",
  "config": {
    "serviceURL": "https://www.chatwork.com"
  }
}

idは「kddichatwork」などユニークな名前にして、nameは表示名なので、「KDDI Chatwork」とします。 serviceURLは、「kcw.kddi.ne.jp」などご契約のURLに変更してください。

あとは、Franzをリロードするとアプリが使えるようになります。

f:id:xshimada:20171218101005p:plain

今回は試しにさくっとしましたが、Franz serviceの詳しい作り方は、こちらにドキュメントがありますので、日本のサービスのrecipeをいろいろ作ってみると楽しいと思います。

FranzのCommunityもありますので興味のある方は参加するのもよいでしょう。

Franzで社内のポータル、メール、メッセンジャーなどもrecipeにしておいて、入社時に一括導入してあげると便利かもしれませんね!!

それでは、楽しいFranz生活を!

Polymorphic Allocator in C++17

こんにちは、この記事はAltplus Advent Calnendar 2017の17日目のエントリです。

仕事ではAkka Streamの綺麗さに感動しつつ、プライベートのプロジェクトではC++17でコーディング中の竹田です。

Polymorphic Allocator

先日発行されたC++17ではPolymorphic Allocatorと呼ばれるAllocatorが導入されました。この導入を受けてか、Cppcon 2017ではAllocator関連の発表が多くありました。

Allocatorの問題点

そこにあるのに誰も気付かず、ないと生きていられない、まるで空気のような存在のAllocatorですが、みんな大好きstd::vectorテンプレートの第2型パラメータの彼です。

template<
    class T,
    class Allocator = std::allocator<T> // 今回の主役。
> class vector;

Allocatorは型に対してメモリを提供することが役割で、STL(Standard Template Library)をコンピュータのメモリ・モデルから切り離すことを目的として導入されました。Allocator (C++)@Wikipedia)従来のAllocatorは幾つかの問題点を抱えており、C++11以降、少しづつ改良されてきました。

問題点の1つは実装の詳細であるはずのAllocatorが型の一部としてあることです。STLではテンプレートのパラメータとしてAllocatorが渡されています。これはAllocatorが型の一部になるということです。STLのコンテナはValue Semanticsを念頭に設計されており、数学のコンセプトを表現していますが、Allocatorという実装の詳細が型に表われると、この対応がくずれてしまいます。例えば、std::vectorは、列(sequence)を表現しており、数列{1, 2, 3}は、std::vector<int>{1, 2, 3}で表現できます。Allocatorの異なる、std::vector<int, MyAllocator>{1, 2, 3}も、std::vector<int, YourAllocator>{1, 2, 3}も同じ数列{1, 2, 3}の表現ですが、型が違うため比較することはできません。

その他にもAllocatorが型の一部であるためカスタムAllocatorを使用したいクライアントのコードまでテンプレート化する必要があるなどの問題もあります。

これらの問題点を解決するために導入されたのがstd::pmr::polymorphic_allocatorstd::pmr名前空間したにあるコンテナです。

std::pmr

まずはstd::pmr::vectorテンプレートの定義を見てみます。std::vector@cppreference単純に、std::vectorのAllocatorにstd::prm::polymporphic_alloctorを指定したalias templateです。

namespace pmr {
    template <class T>
    using vector = std::vector<T, std::pmr::polymorphic_allocator<T>>;
}

std::pmr::polymorphic_allocatorはコンストラクタでstd::pmr::memory_resourceオブジェクトへのポインタを受け取ります。std::pmr::polymorphic_allocatorの振舞いstd::pmr::memory_resourceの振舞いで定義されます。ラムダがstd::functionで型消去されるのと同様に、Allocatorはstd::pmr::polymorphic_allocatorによって型を消去されます。異なるAllocatorを型消去で単一の型として扱うことで前述のAllocatorが型に表われる問題を解決します。

カスタムAllocator

std::prm::memory_resourceから派生したクラスを実装することでカスタムAllocatorを定義することができます。Howard HinnantのStack Allocatorを、std::memory_resourceを使用して実装してみます。Stack Allocatorは、要素数の少いコンテナ用のAllocatorで、ヒープではなくスタックにメモリを確保することで動的メモリ確保のコストを避けるAllocatorです。以下のコードではSTLのC++17対応状況の関係、std::experimental名前空間を使用していますが、C++17では、experimentalは不要です。

カスタムAllocatorを実装するには、std::pmr::memory_resourceから派生し、以下の3つのメソッドをオーバーライドする必要があります。

  • do_allocate
  • do_deallocate
  • do_is_equal
#include <experimental/memory_resource>
#include <array>
#include <vector>
#include <cassert>

template<std::size_t N>
class stack_memory_resource : public std::experimental::pmr::memory_resource {
    std::array<std::byte, N> buf;
    std::byte* pos;
public:
    stack_memory_resource() noexcept : pos(buf.begin()){}
    stack_memory_resource(stack_memory_resource const&) = delete;
    stack_memory_resource& operator=(stack_memory_resource const&) = delete;

protected:
     // メモリの確保。実装の簡素化の為、alignmentは無視。
    void* do_allocate(std::size_t bytes, std::size_t /*alignment*/) override {
       if(bytes > remaining()){
        throw std::bad_alloc();
       }
       auto memory = pos;
       pos += bytes;
       return memory;
    }
     // メモリの解放。実装の簡素化の為、alignmentは無視。
    void do_deallocate(void* p, std::size_t bytes, std::size_t /*alignment*/) override {
        if(pointer_in_buffer(p)){
            if(auto pb = static_cast<std::byte*>(p); (pb + bytes) == pos){
                pos = pb;
            }
        }
    }
    // オブジェクトの比較。
    bool do_is_equal(std::experimental::pmr::memory_resource const & other) const noexcept override {
        return this == &other;
    }
private:
    std::size_t remaining() const {
        return buf.end() - pos;
    }

    bool pointer_in_buffer(void* p) const {
        return buf.begin() <= p && p < buf.end();
    }
};

// 標準ライブラリが対応していないため自分でalias templateを定義。
namespace std::pmr {
    template <typename T>
    using vector = std::vector<T, std::experimental::pmr::polymorphic_allocator<T>>;
}

int main(){
    stack_memory_resource<32> resourceA;
    stack_memory_resource<64> resourceB;
    // vectorのコンストラクタはpolymorphic_allocatorを期待しているが、
    // *memory_resourceからpolymorphic_allocatorへは暗黙の変換
    std::pmr::vector<int32_t> vecA({1, 2, 3, 4}, &resourceA);
    std::pmr::vector<int32_t> vecB({1, 2, 3, 4}, &resourceB);
    assert(vecA == vecB);// 異なるリソースからメモリを確保しても比較可能。
    return 0;
}

main関数で実装したカスタムAllocatorを使用しています。std::pmr::vectorに定義したstack_memory_resourceへのポインタを渡しています。異るリソース上に構築したvector同士の比較も可能で、値のセマンティックスに従った動作をします。

ベンチマーク

実装したstack_memory_resourceが、どれくらいの効果があるかquick-bench.comで計測した結果が以下の通りです。ループ中で要素数4のvectorの生成と破棄を繰り返した性能の比較です(gcc-7.2 & c++17 & -O3)。結果を見ると、stack_memory_resourceの方が約7倍ほど速いようです。

static void VectorCreationOnHeap(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    std::vector<int32_t> v{1,2,3,4}; 
    // Make sure the variable is not optimized away by compiler
    benchmark::DoNotOptimize(v);
  }
}
// Register the function as a benchmark
BENCHMARK(VectorCreationOnHeap);

static void VectorCreationOnStack(benchmark::State& state) {
  // Code before the loop is not measured
  for (auto _ : state) {
    stack_memory_resource<64> resource;
    std::pmr::vector<int32_t> v({1, 2, 3, 4}, &resource);
    benchmark::DoNotOptimize(v);
  }
}
BENCHMARK(VectorCreationOnStack);

kLrJPtoYF3-P-cmeQmw_LDiXwug.png

quick-bench.com計測結果

STLでは、スレッド・アンセーフでグローバル・ロックをとらないリソース(unsynchronized_pool_resource)や、今回のstack_memory_resourceのようにリソースの破棄のタイミングでのみメモリを解放するmonotonic_buffer_resourceなどいくつかのmemory_resourceを提供しています。

最後に

今回はC++17で導入されたpolymporhic_allocatorの仕組みを使ってカスタムAllocatorを実装する方法を見てきました。

Allocatorは様々な用途で広く利用されています。

  • GPUなどCPU以外のデバイスのメモリを使用する。Boost.Compute pinned_allocator
  • BitCoinソフトウェアのセキュリティ向上。破棄後にメモリをゼロ・クリア。メモリのスワップによるハード・ディスク上への情報の残りを防ぐ。Bitcoin Core allocators

Allocatorを使用することで、STLのコンテナという高い抽象度のIFを使用しつつ、さまざまな機能を追加することが可能です。皆さんも目的にそったAllocatorが無いか探してみるのはいかがでしょうか。From Security to performance to GPU programmingの、スライドに多くのAllocatorが紹介されています。

CppCon 2017 アロケータ関連の発表