
Windows向けパッカーの書き方チュートリアル!

難しそうに思えますか? このREADMEを要約したプレゼンテーション版を試してみてください。YouTube動画も近日公開予定です!
パッカーは、自身のアドレス空間(あるいは別プロセスのアドレス空間)内で別のプログラムを展開し起動するプログラムです。デバッガや仮想サンドボックスなどの解析環境を攻撃するベクターとして知られることもあります。主に以下のような用途で使われます:
基本的に、パッカーにはほんのいくつかの基本ステップしかありません:
同様にシンプルに、パッカーはいくつかの構成要素だけで成り立っています:
パッカーの構築は厄介な場合があります。なぜなら、スタブをビルドし、何らかの方法でパッカー実行可能ファイルに組み込む必要があるからです。Windowsでは、単純なコンパイルを超えたVisual Studioのビルドシステムを学ぶのは骨の折れる作業になり得ます。幸い、CMakeはVisual Studioをサポートするシンプルでクロスプラットフォームなビルドシステムを提供し、高度にカスタマイズ可能です!
このチュートリアルでは、以下を教えることを目的としています:
すでにC++とCMakeに精通している方は、パッキングのセクションに直接進んでください。そうでない場合は、読み進めてください!
まず、このビルドシステムが機能し、適切にパックされた実行可能ファイルを作成することを何らかの方法で確認しましょう。CMakeとVisual Studioをインストールしたら、お好みのターミナルでこのリポジトリのルートディレクトリに移動し、次のコマンドを実行してください:``` $ mkdir build $ cd build $ cmake ../
これにより、packerチュートリアルコードをビルドするために必要なプロジェクトファイルが作成されます。次に実行します:```
$ cmake --build ./ --config Release
This will build the packer project in Release mode. Then, you can run the following test:
これを実行すると、Release モードで packer プロジェクトがビルドされます。次に、次のテストを実行できます:```
$ ctest -C Release ./
すべてがうまくいけば、test\_pack と test_unpack は成功するはずです。パッキングの結果を自分で確認したい場合は、次のように表示されるはずです:```
$ ./packed.exe
I'm just a little guy!
これを実現したすべての構成要素について説明しましょう。
パッカーの主要コンポーネントが何であるかはわかりましたが、開発サイクルの一環としてパッカーをすぐにテストするにはどうすればよいでしょうか? バイナリのパックおよびアンパック処理を適切にテストするには、プロジェクト全体に3つ目のバイナリを追加する必要があります。つまり、全体として次の3つのプロジェクトが必要です:
また、バイナリがスタブ実行ファイルに圧縮されるようにするための圧縮ライブラリも必要です。zlib がこれに最適です。
最初の段階でのプロジェクト階層は、次のようになるはずです:``` packer/ +---+ CMakeLists.txt + dummy/ | | | +---+ CMakeLists.txt | + src/ | | | +---+ main.cpp | + stub/ | | | +---+ CMakeLists.txt | + src/ | | | +---+ main.cpp | + src/ | | | +---+ main.cpp | + zlib-1.2.13/ | +---+ CMakeLists.txt + ...
各フォルダのmain.cppは、今のところ次のようにシンプルにできます:```cpp
#include <iostream>
int main(int argc, char *argv[]) {
std::cout << "I'm just a little guy!" << std::endl;
return 0;
}
すでに、対処すべき単純な依存関係チェーンがあることを確認しておきましょう。これは、CMake がいくつかの設定を追加することでうまく解決してくれます。
では、CMake の設定として、ルートプロジェクト、つまり packer から始めましょう。
CMake を扱うには、通常、最低バージョンの指定が必要です。CMake は長い間存在しており、以前のバージョンの長期的な使用をサポートしているためです。その後、packer プロジェクトを C++ プロジェクトとして宣言できます。```cmake
cmake_minimum_required(VERSION 3.24)
project(packer CXX)
また、パッカーを「MultiThreaded」(すなわち /MT)として宣言し、「MultiThreadedDLL」(すなわち /MD)の代わりにすることで、ランタイムDLLの依存関係を気にする必要がなくなります。```cmake
# this line will mark our packer as MultiThreaded, instead of MultiThreadedDLL
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
変数設定文の山かっこ内の文はジェネレータ式と呼ばれ、必要な場所で構成時のデータを解決するのに役立ちます。このファイル全体で頻繁に目にすることになるでしょう。このジェネレータ式は、構成がDebugであることを検出すると文字列Debugを出力し、それ以外の場合は何も出力しません。これにより、Debugコンパイルプロファイルが選択された場合はMultiThreadedDebugのランタイムライブラリが提供され、Releaseなどの非デバッグコンパイルプロファイルが選択された場合はMultiThreadedが提供されます。そこで何が起こっているのかをよく理解するには、条件付きジェネレータ式を参照してください。CMAKE_MSVC_RUNTIME_LIBRARY変数の詳細については、CMakeドキュメントを参照してください。
CMakeでは、Visual StudioがUIでプロジェクトを作成するときに自動的に行うように、ソースコードを階層に整理できます。また、フォルダー内を再帰的に検索して、一致するファイル名を見つけます。設定ファイルでは、ヘッダー(.hpp)、コード(.cpp)、リソーススクリプト(.rc)にグローバル再帰を設定しています。この例では、main.cppだけが必要ですが、これは大規模なプロジェクトにとって知っておくと便利です。```cmake
file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/src/.cpp) file(GLOB_RECURSE HDR_FILES ${PROJECT_SOURCE_DIR}/src/.hpp) file(GLOB_RECURSE RC_FILES ${PROJECT_SOURCE_DIR}/src/*.rc)
source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Header Files" FILES ${HDR_FILES}) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Source Files" FILES ${SRC_FILES}) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Resource Files" FILES ${RC_FILES})
ここで、CMakeはより正確には**メイクシステム**と表現すべきであることを説明しておきます。CMakeは、指定されたコンパイラに基づいて、与えられた環境に適した正しい「メイクシステム」を作成します。Linuxでは、これは検出された(または指定された)コンパイラ用のmakefileになります。Windows上の私たちにとっては、これは私たちのVisual Studioのバージョンと互換性のあるVisual Studioプロジェクトを生成します。つまり、MSVCメイクシステムを作成したら、必要に応じてVisual Studioだけをすべてに使用できるということです! 結局のところ、あなたはCMakeを使ってVisual Studioを構成しているのです。つまり、ここで行っていることは、プロジェクトをGUIで作成するときにVisual Studioが自動的に行う、プロジェクト内のファイルのツリーをVisual Studioで作成することに他なりません。
次に、依存プロジェクトを追加する必要があります。```cmake
# this will add zlib as a build target
add_subdirectory(${PROJECT_SOURCE_DIR}/zlib-1.2.13)
# this will add our stub project
add_subdirectory(${PROJECT_SOURCE_DIR}/stub)