Переповнення стекового буфераВ програмах, переповнення буфера стеку трапляється, коли програма пише за адресами в програмному стеку виклику поза призначеною структурою даних; це зазвичай буфер фіксованої довжини.[1][2] Баг переповнення буфера стеку трапляється, коли програма пише більше даних в буфер розміщений в стеку, ніж було фактично виділено місця для буфера. Це майже завжди призводить до псування прилеглих даних в стеку, і в разі якщо переповнення було зроблено помилково, часто призводить до краху програми або некоректної роботи. Цей тип переповнення є одним з випадків загальнішого класу багів програмування, знаних як переповнення буфера.[1] Якщо атакована програма виконується з спеціальними привілеями, або приймає дані з недовірених хостів мережі (напр. вебсерверів), тоді баг є потенційною вразливістю безпеки. Якщо стековий буфер залитий даними, які надійшли від недовіреного користувача, тоді цей користувач може пошкодити стек в такий спосіб, що в стеку опиняється виконуваний код, інжектований ним, відтак він отримує управління процесом. Це один з найстаріших і найнадійніших методів для зловмисників отримати неавторизований доступ до комп'ютера.[3][4][5] Використання переповнень стекових буферівКанонічний метод експлуатації переповнення стекового буфера є затирання адреси повернення в функцію, яка викликала (caller) поточну функцію (callee) вказівником на контрольовані хакером дані (зазвичай на той же стек).[3][6] Це ілюструється в прикладі нижче:
#include<string.h>
void foo(char* bar){
char c[12];
strcpy(c, bar); // перевірки межі масиву немає...
}
int main(int argc, char** argv){
foo(argv[1]);
return 0;
}
Цей код бере аргумент з командного рядка і копіює його в локальну стекову змінну c. Все працює добре для аргументів командного рядка, меншими за 12 символів (як ви можете бачити з малюнка Б нижче). Будь-які аргументи більші за 11 символів в довжину спричинять пошкодження стеку. (Максимальне число символів, що будуть безпечними є на один менше ніж розмір буфера, бо рядки в мові C обмежуються нульовим байтом, який теж треба враховувати. Рядок з дванадцяти однобайтних символів насправді вимагатиме тринадцятибайтного вмістилища. Нульовий байт тоді потре один байт ділянки пам'яті поза кінцем буфера.)
Примітка. На малюнку вказівник На малюнку 'В' вище, коли аргумент взятий з командного рядка - більший за 11 байтів, Хакер також може модифікувати внутрішні змінні для експлуатації деяких багів. Той же приклад: #include<stdio.h>
#include<string.h>
void foo(char* bar){
float MyFloat = 10.5f; // Адреса = 0x0023FF4C
char c[12]; // Адреса = 0x0023FF30
// Друкуватиме 10.500000
printf("My float value = %f\n",MyFloat);
/* ---------------------------------------
Карта пам'яти:
@: пам'ять виділена c
#: пам'ять виділена MyFloat
-: інша пам'ять
*c *MyFloat
0x0023FF30 0x0023FF4C
| |
@@@@@@@@@@@@----------------####
foo("My string is too long !!!!! XXXX");
memcpy потре послідовністю байтів 0x10 0x10 0xC0 0x42
значення змінної MyFloat. Як плаваючий тип, ця послідовність
є числом 96.0313720703125 (байт 0x42 - старший!)
------------------------------------------ */
memcpy(c,bar,strlen(bar)); // не перевіряється межа масиву...
// надрукує 96.031372
printf("My Float value = %f\n", MyFloat);
}
int main(int argc, char** argv){
foo("my string is too long !!!!! \x10\x10\xC0\x42");
return 0;
}
Платформозалежні відмінностіРізні платформи мають тонкі відмінності в своїй реалізації стеку виклику, що може завадити експлуатації переповнення стекового буфера. Деякі машинні архітектури зберігають верхньорівневу адресу повернення стеку виклику в регістрі. Це означає, що будь-яка потерта адреса повернення не використовуватиметься до пізнішого розгортання стеку викликів. Інший приклад машинноспецифічної деталі, яка може завадити використати цю техніку - це факт, що більшість RISC архітектур не дозволяють невирівняний (unaligned) доступ до пам'яті. Тобто, адреси інструкцій мають бути завжди числами, кратними до якогось числа (наприклад - 4).[7] В поєднанні з фіксованою довжиною машинної команди, це машинне обмеження може зробити техніку Стеки, які ростуть вгоруВ темі переповнень стекових буферів, є часто обговорювана архітектура яка рідко зустрічається на практиці, в якій стек росте в протилежному напрямку. Тобто, верх стеку рухається в бік старших адрес, коли стек заповнюється, а не навпаки, як в звичайному стеку. Ця зміна архітектури часто пропонується як рішення проблеми переповнення стекового буфера, бо будь-яке переповнення стекового буфера, яке трапиться всередині того самого стекового кадру, не зможе перетерти вказівник повернення. Подальший розгляд цього заявленого захисту, виявляє що це не відповідає дійсності. Будь-яке переповнення, що трапляється в буфері з попереднього стекового кадру все одно тертиме адресу повернення і дозволятиме шкідливу експлуатацію бага.[10] Наприклад, в коді вгорі, вказівник повернення з Схеми захистуЗа роки було розроблено чимало схем для перешкоджання експлуатації переповнення стекового буфера. Вони зазвичай мають одну з двох форм. Перший метод - це детектувати переповнення буфера і запобігти передаванню керування на шкідливий код. Другий метод намагається запобігти виконанню шкідливого коду зі стека без прямої детекції переповнення стекового буфера.[11] Стекові канаркиСтекові канарки - названі так через те, що вони діють як канарки в вугільній шахті - використовуються для детекції переповнення стекового буфера перед тим, як виконання шкідливого коду може трапитися. Цей метод полягає в розміщенні невеликого цілого - величини, яка довільно вибирається на старті програми - в пам'яті, прямо перед стековим вказівником повернення. Найбільше буферних переповнень затирають пам'ять від молодших до старших адрес, тож, під час затирання адреси повернення (і отримання відтак контролю в процесі), величина "канарки" також зітреться. Ця величина перевіряється перед тим як підпроцедура використає адресу повернення на стеку, щоб переконатися, що її не змінено.[2] Ця техніка має ускладнити експлуатування переповнення стекового буфера, бо змушує хакера отримувати контроль над вказівником команд якимись нетрадиційнішими способами, такими як псування інших важливих змінних на стеку.[2] Невиконуваний стекІнший підхід до запобігання переповнення стекового буфера полягає в забороні виконання даних зі стекової області пам'яті. Це означає, що для виконання шелкоду зі стеку хакер має або знайти шлях скасувати захист від виконання з цієї області пам'яті, або знайти шлях перенести корисне навантаження свого шелкоду в незахищену (від виконання) область пам'яті. Цей метод стає популярніший зараз, через те, що апаратна підтримка флажка "не виконувати" (no-execute) доступна на більшості десктопних процесорів. Хоча цей метод став фактично традиційним в протидії експлуатації переповнення стекового буфера, хоча й має певні недоліки. По-перше нескладно знайти спосіб зберегти шелкод в незахищених областях пам'яті, таких як купа (heap).[12] Навіть якщо це буде не так, є інші шляхи. Найбільш вбивчий - це так званий метод "return to libc" для створення шелкоду. В цій атаці шкідливе навантаження заноситиметься в стек не шелкодом, а правильним стеком викликів, тож виконання буде рознесене по ланцюжку викликів функцій стандартної бібліотеки, зазвичай з ефектом зняття захисту від виконання і дозволом виконання шелкоду як нормального.[13] Це працює того що, в цій атаці, код не заноситься власне в стек. Замість розміщення коду в стеці і затирання адреси повернення в функцію, адресою верха стеку, адреса повернення затирається адресою (точкою входу) якої-небудь потрібної для атаки бібліотечної функції (напр. Відомі приклади
Див. такожПосилання
|