Трюки от Крыса Крис Касперски Хакер, номер #094, стр. 094-134-3 l.flag = get_config(is_debug_output_enabled); zoo(&l); bar(&l); } В данном случае преимущество не столь очевидно, но в больших проектах оно дает о себе знать! А что насчет эффективности?! Не снижается ли она за счет постоянных операций типа «l->a»? Отнюдь! Современные компиляторы легко определяют эффективный адрес элементов структур без промежуточных вычислений. А вот засылка множества аргументов в стек изрядно тормозит. Кроме того, предложенный метод позволяет безболезненно менять прототипы функций (в том числе и публичных). Скажем, захотелось нам добавить к функции, выводящей изображение спрайта на экран, новый аргумент — коэффициент прозрачности. В классическом случае мы ничего не можем сделать, поскольку это потребует изменений во всем проекте, и коллеги из соседних отделов нас тут же изжарят на медленном огне. А в случае со структурами можно добавлять сколько угодно аргументов. Старый код их просто «не замечает», зато новый - да! Структуры в борьбе с переполняющимися буферами Существует множество защитных механизмов типа Stack-Guard или Stack-Shield, но все это — детские игрушки, не способные остановить атакующего. Протектор Pro-Police, зародившийся в недрах японского отделения IBM (http://www.research.ibm.com/trl/projects/security/ssp/), — это самый сложный и совершенный механизм, реализующий модель безопасного стека (Safe Stack Usage Model), главной инновацией которого является переупорядочивание локальных переменных. Pro-police разбивает переменные на две группы: массивы и все остальные. На вершину карда стека попадают обычные (скалярные) переменные. Массивы идут за ними. Переполняющиеся буферы могут воздействовать друг на друга, но до указателей им уже не достать, во всяком случае, не таким простым путем. К сожалению, Pro-police работает только с компилятором GCC, а всем остальным остается только сосать лапу и в остервенении грызть свой хвост, или… воспользоваться структурами. Дело в том, что размещение локальных переменных в памяти может и не совпадать с порядком их объявления в программе, поэтому у нас нет никаких гарантий, что переменные p и s окажутся расположенными выше локальных буферов: foo() { int a; int b; int *p; char *s char buf1[669]; char buf2[996]; } А это значит, что при переполнении одного из буферов атакующий может воздействовать на указатели p и s со всеми вытекающими отсюда последствиями. Напротив, в структурах размещение элементов в памяти всегда совпадет с порядком их объявления! struct L { int a; int b; int *p; char *s char buf1[669]; int canary_1; char buf2[996]; int canary_2; }; Здесь canary_1 и canary_2 – магические переменные, инициализируемые случайным образом при входе в функцию и проверяемые перед выходом из нее. Если же они вдруг оказались искажены, значит, один из буферов был переполнен, и адрес возврата, возможно, смотрит на вредоносный shell-код. Поэтому вместо возврата мы завершаем программу в аварийном режиме (самое простое, что можно сделать) или передаем управление на специальную функцию, сохраняющую несохраненные данные. В общем, структуры — это сила! |