未初期化変数

Tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

基本情報

ここでの核心は、未初期化変数は割り当てられたメモリに既に存在していた値を持つという挙動を理解することです。例:

  • Function 1: initializeVariable: 変数 x を宣言し、値(例: 0x1234)を割り当てます。この操作は、メモリ上の場所を確保して特定の値を格納するようなものです。
  • Function 2: useUninitializedVariable: ここでは別の変数 y を宣言しますが、値は割り当てません。Cでは未初期化変数が自動的にゼロに設定されることはありません。代わりに、そのメモリ位置に最後に格納されていた値を保持します。

これら二つの関数を順番に実行すると:

  1. initializeVariable 内で x に値(0x1234)が割り当てられ、特定のメモリアドレスを占有します。
  2. useUninitializedVariable では y を宣言しますが値は割り当てられないため、x の直後のメモリ位置を使用します。y を初期化しなかったため、最後にそこにあった値、つまり x が使っていた同じメモリ位置の値を「引き継ぐ」ことになります。

この挙動は低レベルプログラミングの重要な概念を示しています:メモリ管理は極めて重要であり、未初期化変数はメモリに残された機密データを意図せず保持する可能性があるため、予測不可能な挙動やセキュリティ脆弱性を引き起こします。

Uninitialized stack variables could pose several security risks like:

  • Data Leakage: 未初期化変数に保存されている場合、パスワード、暗号鍵、個人情報などの機密情報が露呈し、攻撃者がこれらのデータを読み取れる可能性があります。
  • Information Disclosure: 未初期化変数の内容はプログラムのメモリ配置や内部動作の詳細を明らかにし、攻撃者が標的型エクスプロイトを開発するのに役立つ可能性があります。
  • Crashes and Instability: 未初期化変数を扱う操作は未定義動作を引き起こし、プログラムのクラッシュや予測不能な結果を招く可能性があります。
  • Arbitrary Code Execution: 特定のシナリオでは、攻撃者がこれらの脆弱性を悪用してプログラムの実行フローを変更し、任意のコードを実行させることができる可能性があり、リモートコード実行の脅威を含む場合があります。

#include <stdio.h>

// Function to initialize and print a variable
void initializeAndPrint() {
int initializedVar = 100; // Initialize the variable
printf("Initialized Variable:\n");
printf("Address: %p, Value: %d\n\n", (void*)&initializedVar, initializedVar);
}

// Function to demonstrate the behavior of an uninitialized variable
void demonstrateUninitializedVar() {
int uninitializedVar; // Declare but do not initialize
printf("Uninitialized Variable:\n");
printf("Address: %p, Value: %d\n\n", (void*)&uninitializedVar, uninitializedVar);
}

int main() {
printf("Demonstrating Initialized vs. Uninitialized Variables in C\n\n");

// First, call the function that initializes its variable
initializeAndPrint();

// Then, call the function that has an uninitialized variable
demonstrateUninitializedVar();

return 0;
}

仕組み:

  • initializeAndPrint 関数: この関数は整数変数 initializedVar を宣言し、値 100 を代入してから、変数のメモリアドレスと値の両方を出力します。このステップは単純で、初期化済み変数がどのように振る舞うかを示します。
  • demonstrateUninitializedVar 関数: この関数では、初期化せずに整数変数 uninitializedVar を宣言します。その値を出力しようとすると、出力にランダムな値が表示されることがあります。この値は、そのメモリ位置に以前あったデータを表します。環境やコンパイラによって実際の出力は異なり、場合によっては安全のために一部のコンパイラが変数を自動的にゼロで初期化することがありますが、これに依存すべきではありません。
  • main 関数: main 関数は上記の両方の関数を順番に呼び出し、初期化済み変数と未初期化変数の対比を示します。

Practical exploitation patterns (2024–2025)

古典的な ‘read-before-write’ バグは依然として重要であり、現代の緩和策(ASLR、canaries)はしばしば秘匿性に依存します。典型的な攻撃面:

  • Partially initialized structs copied to userland: Kernel や drivers はしばしば長さフィールドだけを memset し、その後 copy_to_user(&u, &local_struct, sizeof(local_struct)) を行います。パディングや未使用フィールドは stack canary halves、saved frame pointers、または kernel pointers を leak します。構造体に function pointer が含まれている場合、未初期化のままにしておくと、後で再利用されたときに controlled overwrite を可能にすることもあります。
  • Uninitialized stack buffers reused as indexes/lengths: 未初期化の size_t len;read(fd, buf, len) の上限に使うと、攻撃者に out-of-bounds reads/writes を許したり、スタックスロットが以前の呼び出しの大きな値を保持している場合にサイズチェックをバイパスさせることができます。
  • Compiler-added padding: 個々のメンバが初期化されていても、その間の暗黙のパディングバイトは初期化されません。構造体全体を userland にコピーすると、過去のスタック内容(canaries、pointers)を含むことが多い padding が leak します。
  • ROP/Canary disclosure: デバッグのために関数がローカル構造体を stdout にコピーすると、未初期化のパディングが stack canary を reveal し、その結果 subsequent stack overflow exploitation を brute-force なしで可能にすることがあります。

Minimal PoC pattern to detect such issues during review:

struct msg {
char data[0x20];
uint32_t len;
};

ssize_t handler(int fd) {
struct msg m;              // never fully initialized
m.len = read(fd, m.data, sizeof(m.data));
// later debug helper
write(1, &m, sizeof(m));   // leaks padding + stale stack
return m.len;
}

緩和策 & コンパイラオプション(バイパス時の注意点)

  • Clang/GCC auto-init: 最近のツールチェーンは -ftrivial-auto-var-init=zero または -ftrivial-auto-var-init=pattern を提供し、関数入口ですべての自動(stack)変数をゼロまたは毒パターン(0xAA / 0xFE)で埋めます。これによりほとんどの uninitialized-stack info leak が防がれ、secrets を既知の値に変換することで exploitation が難しくなります。
  • Linux kernel hardening: CONFIG_INIT_STACK_ALL または新しい CONFIG_INIT_STACK_ALL_PATTERN でビルドされたカーネルは、関数入口で全ての stack スロットをゼロ/パターン初期化し、そうでなければ leak する canaries/pointers を消去します。これらのオプションが有効な Clang-built カーネルを配送するディストリビューション(6.8+ の hardening 設定で一般的)を探してください。
  • Opt-out attributes: Clang は特定のローカル/struct に __attribute__((uninitialized)) を許可するようになり、グローバルな auto-init が有効な場合でもパフォーマンスクリティカルな領域を未初期化のままにできます。そのような注釈は side channels のための意図的な攻撃面を示すことが多いため、注意深くレビューしてください。

攻撃者の観点では、バイナリがこれらのフラグでビルドされているかどうかを把握することで、stack-leak primitives が使えるか、それとも heap/data-section の公開に切り替える必要があるかが決まります。

Finding uninitialized-stack bugs quickly

  • Compiler diagnostics: Build with -Wall -Wextra -Wuninitialized (GCC/Clang). For C++ code, clang-tidy -checks=cppcoreguidelines-init-variables will auto-fix many cases to zero-init and is handy to spot missed locals during audit.
  • Dynamic tools: -fsanitize=memory (MSan) in Clang or Valgrind’s --track-origins=yes reliably flag reads of uninitialized stack bytes during fuzzing. Instrument test harnesses with these to surface subtle padding leaks.
  • Grepping patterns: In reviews, search for copy_to_user / write calls of whole structs, or memcpy/send of stack data where only part of the struct is set. Pay special attention to error paths where initialization is skipped.

ARM64 Example

This doesn’t change at all in ARM64 as local variables are also managed in the stack, you can check this example were this is shown.

References

Tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする