Неініціалізовані змінні
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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Основна інформація
Головна ідея тут — зрозуміти, що відбувається з неініціалізованими змінними, оскільки вони матимуть значення, яке вже знаходилося в призначеній їм ділянці пам’яті. Приклад:
- Функція 1:
initializeVariable: Ми оголошуємо зміннуxі присвоюємо їй значення, скажімо0x1234. Ця дія подібна до резервування місця в пам’яті та запису туди конкретного значення. - Функція 2:
useUninitializedVariable: Тут ми оголошуємо іншу зміннуy, але не присвоюємо їй значення. У C непроініціалізовані змінні автоматично не встановлюються в нуль. Натомість вони зберігають те значення, яке останнім було записане в цю ділянку пам’яті.
Коли ми запускаємо ці дві функції послідовно:
- У
initializeVariablexотримує значення (0x1234), яке займає конкретну адресу в пам’яті. - У
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
- CONFIG_INIT_STACK_ALL_PATTERN documentation
- GHSL-2024-197: GStreamer uninitialized stack variable leading to function pointer overwrite
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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


