Неініціалізовані змінні

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Основна інформація

Головна ідея тут — зрозуміти, що відбувається з неініціалізованими змінними, оскільки вони матимуть значення, яке вже знаходилося в призначеній їм ділянці пам’яті. Приклад:

  • Функція 1: initializeVariable: Ми оголошуємо змінну x і присвоюємо їй значення, скажімо 0x1234. Ця дія подібна до резервування місця в пам’яті та запису туди конкретного значення.
  • Функція 2: useUninitializedVariable: Тут ми оголошуємо іншу змінну y, але не присвоюємо їй значення. У C непроініціалізовані змінні автоматично не встановлюються в нуль. Натомість вони зберігають те значення, яке останнім було записане в цю ділянку пам’яті.

Коли ми запускаємо ці дві функції послідовно:

  1. У initializeVariable x отримує значення (0x1234), яке займає конкретну адресу в пам’яті.
  2. У useUninitializedVariable оголошується y, але йому не присвоюється значення, тож він займає місце в пам’яті відразу після x. Через відсутність ініціалізації y в результаті “успадковує” значення з тієї самої ділянки пам’яті, що й x, оскільки це останнє значення, яке там було.

Ця поведінка ілюструє ключову концепцію низькорівневого програмування: керування пам’яттю критично важливе, і неініціалізовані змінні можуть призводити до непередбачуваної поведінки або вразливостей безпеки, оскільки вони ненавмисно можуть містити чутливі дані, що залишилися в пам’яті.

Неініціалізовані стекові змінні можуть становити кілька ризиків безпеки, таких як:

  • Витік даних: Чутлива інформація, наприклад паролі, ключі шифрування або персональні дані, може бути виявлена, якщо зберігатися в неініціалізованих змінних, що дозволяє атакам потенційно її прочитати.
  • Розкриття інформації: Вміст неініціалізованих змінних може розкрити деталі розташування пам’яті програми або її внутрішніх операцій, що допомагає зловмисникам розробляти цілеспрямовані експлойти.
  • Збої та нестабільність: Операції з неініціалізованими змінними можуть призводити до невизначеної поведінки, викликати крахи програм або непередбачувані результати.
  • Виконання довільного коду: У певних сценаріях зловмисники можуть використати ці вразливості для зміни потоку виконання програми, що дозволяє їм виконувати довільний код, включно з віддаленим виконанням коду.

Приклад

#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 викликає обидві наведені функції послідовно, демонструючи контраст між ініціалізованою змінною та неініціалізованою.

Практичні патерни експлуатації (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)). Padding та невикористані поля leak половини stack canary, збережені frame pointers або kernel pointers. Якщо struct містить 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: Навіть якщо окремі члени ініціалізовані, implicit padding bytes між ними — ні. Копіювання всього struct в userland leak padding, яке часто містить попередній вміст стека (canaries, pointers).
  • ROP/Canary disclosure: Якщо функція копіює локальний struct на stdout для відладки, неініціалізований padding може виявити stack canary, що дозволяє подальшу експлуатацію stack overflow без brute-force.

Мінімальний PoC патерн для виявлення таких проблем під час рев’ю:

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 leaks і ускладнює експлуатацію, перетворюючи секрети на відомі значення.
  • Linux kernel hardening: Ядра, зібрані з CONFIG_INIT_STACK_ALL або новішим CONFIG_INIT_STACK_ALL_PATTERN, ініціалізують кожен slot у stack нулями/патерном на вході в функцію, стираючи canaries/pointers, які в іншому випадку б leak. Шукайте дистрибутиви, що постачають Clang-зібрані ядра з увімкненими цими опціями (поширено в 6.8+ конфігураціях hardening).
  • Opt-out attributes: Clang тепер дозволяє __attribute__((uninitialized)) для конкретних locals/structs, щоб зберегти performance-critical області uninitialized навіть коли глобальний auto-init увімкнений. Уважно переглядайте такі анотації — вони часто позначають свідому attack surface для side channels.

З позиції атакуючого, знання того, чи був бінар зібраний з цими флагами, визначає, чи є застосовними stack-leak primitives або чи потрібно переходити до heap/data-section disclosures.

Як швидко знаходити uninitialized-stack баги

  • Compiler diagnostics: Збирайте з -Wall -Wextra -Wuninitialized (GCC/Clang). Для C++ коду clang-tidy -checks=cppcoreguidelines-init-variables автоматично виправляє багато випадків до zero-init і зручний для виявлення пропущених locals під час аудиту.
  • Dynamic tools: -fsanitize=memory (MSan) у Clang або Valgrind з --track-origins=yes надійно позначають читання невініціалізованих байтів зі stack під час fuzzing. Інструментуйте тестові harness з цими засобами, щоб виявити тонкі padding leaks.
  • Grepping patterns: Під час рев’ю шукайте виклики copy_to_user / write цілих struct-ів, або memcpy/send даних зі stack, коли лише частина struct-у ініціалізована. Особливу увагу звертайте на error paths, де ініціалізацію пропускають.

ARM64 Example

Це зовсім не змінюється на ARM64, оскільки локальні змінні також керуються в stack; ви можете check this example де це показано.

References

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks