2026/1/12
「Hello World」を表示するだけのC++プログラムをコンパイルして、生成されたEXEファイルのサイズを見て、思わずため息をついた経験がある人も多いと思います。 普通にコンパイルすると、たった数行のコードに対し、数百KB〜数MBもの余計なものが勝手についてきます。
マルウェア開発において、ファイルサイズは小さければ小さいほど有利です。転送時間を短縮でき、パッカーやクリプターを通した際のオーバーヘッドも減らせるからです。
今回は、MinGW (GCC) を前提に、標準ライブラリ(CRT)を完全に排除し、数KBレベルまでEXEを小さくする手法を紹介します。
C++の標準機能(iostream や string)は使いません。これらをインクルードした瞬間、巨大なライブラリがリンクされるからです。 代わりに、Windows APIを直接叩きます。
#include <Windows.h>
// CRTのprintfなどは使えないため、WinAPIで代用
void PrintfW(const wchar_t* format, ...) {
wchar_t buffer[1024];
DWORD bytesWritten;
va_list args;
va_start(args, format);
wvsprintfW(buffer, format, args);
va_end(args);
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleW(hStdOut, buffer, lstrlenW(buffer), &bytesWritten, NULL);
}
// 独自のエントリーポイントを定義
extern "C" void EntryPoint() {
PrintfW(L"Hello, World!\n");
ExitProcess(0);
}
ポイントはコードだけでなく、コンパイルオプションにもあります。以下のコマンドでコンパイルします。
g++ main.cpp -nostdlib -fno-threadsafe-statics -fno-exceptions -fno-rtti -Wl,-e,EntryPoint -lkernel32 -luser32 -s -Os -Qn -Wall
なぜこれで小さくなるのか? 各オプションの「意味」を理解することが重要です。
-nostdlibこれが最も強力なオプションです。通常、GCCは main() 実行前の準備を行うスタートアップコードや、便利な機能が詰まった標準ライブラリを自動的にリンクします。このオプションはそれらをすべて無効化し、CRTや標準ライブラリの依存を完全に排除します。
C++の便利な機能は、裏で大量のコードを生成します。これらをすべてOFFにします。
-fno-exceptions: try-catch 例外処理の無効化。例外処理テーブルは非常に巨大です。-fno-rtti: 実行時型情報(dynamic_castなどが使う情報)の無効化。-fno-threadsafe-statics: 静的変数のスレッドセーフな初期化コード(Mutexロックなど)を削除。-Wl,-e,EntryPointリンカ(Linker)に対し、「プログラムの開始地点は main ではなく EntryPoint だ」と伝えます。CRTを排除した以上、標準の main は使えません。
-s (strip): デバッグシンボル情報を削除します。これだけでサイズがガクッと落ちます。-Os: サイズ優先の最適化(Optimize for Size)。処理速度よりも小ささを優先します。-Qn: バイナリ内の .comment セクション(GCCのバージョン情報など)を削除します。OpSec(身元隠蔽)の観点からも重要です。この手法を使うことで、通常は数百KB以上になる「Hello World」を、数KB〜十数KB程度まで縮小できます(ビルド環境によって差はあります)。