Преобразование | OC | TC | MC | MC/TC | CL | SL | DL | D |
MM(queens) | 4 | 29.02 | 110.68 | 3.81 | 5 | 2 | 7 | 3 |
CM(queens) | ? | 170.24 | 1754.14 | 10.30 | 5 | 2 | 7 | ? |
Таблица 5. Сравнение методов маскировки для функции queens
В таблице 4 в столбце queens приведены базовые метрики сложности кода для исходной процедуры queens; в столбце MM(queens) - для процедуры, замаскированной с помощью предложенного метода маскировки; в столбце CM(queens) - для процедуры, замаскированной с помощью коммерческого маскировщика рассмотренного выше.
В таблице 5 приведены метрики цены применения маскирующего преобразования, усложнения программы требуемой глубины анализа для предложенного метода маскировки MM и для коммерческого маскировщика CM. Для коммерческого маскировщика сложность алгоритма маскировки неизвестна, поэтому в соответствующих ячейках таблицы стоит знак "?". Из таблицы видно, что новый метод маскировки MM существенно дешевле, чем реализованный в коммерческом маскировщике.
4. Автоматическое выявление уязвимостей защиты программ
Бурное развитие современных телекоммуникационных технологий позволило решить задачу доступа к информационным и вычислительным ресурсам вне зависимости от географического расположения поставщика и потребителя ресурсов. Сеть Интернет связывает миллионы компьютеров по всей планете. С другой стороны, именно общедоступность информационных ресурсов подняла на новый уровень требования к безопасности программного обеспечения. Необходимым условием обеспечения безопасности ПО является его корректная работа на всех возможных входных данных и всех других видах внешних по отношению к программе воздействий.
Следует заметить, что данное требование сильнее, чем требование отсутствия в программе ошибок, если под ошибками понимать несоответствие действи-тельного поведения программы специфицированному на указанном в спецификации множестве входных данных программы. Спецификация может определять поведение программы лишь на подмножестве множества всех возможных входных данных. Например, для программ, получающих данные от пользователя или из других неконтролируемых программой внешних источников реальное множество входных данных представляет собой просто множество всех возможных битовых строк вне зависимости от спецификации входных данных программы. Если программа является частью многопро-цессной системы и взаимодействует с другими процессами и окружением, реальное множество входных данных зависит и от всех возможных темпоральных вариантов взаимодействия процессов, а не только от специфицированных.
Когда требование корректной работы программы на всех возможных входных данных нарушается становится возможным появление так называемых уязвимостей защиты (security vulnerability). Уязвимости защиты могут приводить к тому, что одна программа может использоваться для преодоления ограничений защиты всей системы, частью которой является данная программа, в целом. В особенности это относится к программам, обслуживающим различные общедоступные сервисы сети Интернет и к программам, работающим в привилегированном режиме.
Рассмотрим, например, последний случай "взлома" Интернет-сервера проекта Debian Linux. Программа-сервер синхронизации файлов по сети rsync содержала уязвимость в защите, которая позволяла, подключившись к серверу rsync и подав на ему на вход специально подготовленные входные данные, принудить процессор исполнить не исполняемый код программы rsync, а исполняемый код, переданный в этих входных данных. Сама по себе программа rsync не является привилегированной, но таким образом был получен доступ к компьютеру с возможностью запускать произвольные программы (доступ shell account). Естественно, такой способ получения дос-тупа к компьютеру обходит все нормальные средства аутентификации, такие как ввод регистрационного имени и пароля, ввод однократного ключа и т. д.
Получив возможность выполнения произвольных программ на сервере, злоумышленник использовал другую уязвимость в защите, теперь непосред-ственно ядра Linux, которая была связана с недостаточной проверкой параметра, передаваемого системному вызову sbrk. Передавая этому систем-ному вызову отрицательные значения можно было добиться открытия доступа к критически важным страницам памяти, после чего можно было добиться выполнения произвольной программы уже с правами суперпользователя (root). Обычно такая программа - это интерпретатор командной строки /bin/sh. Таким образом, неизвестный злоумышленник в два этапа получил полный контроль над машиной, на которой он раньше даже не имел shell account.
Уязвимости в защите, которые могут быть использованы просто подключением к уязвимой программе без какой-либо авторизации называются удалённо-эксплуатируемыми (remotely exploitable). Уязвимости в защите, которые требуют наличия локального доступа типа shell account обычно называются локально-эксплуатируемыми (locally-exploitable). Наиболее опасен первый тип уязвимостей, так как он позволяет вообще произвольному (неизвестному) лицу получить возможность запуска произвольных программ.
Следует заметить, что данный пример отнюдь не единичен. Уязвимости разной степени опасности обнаруживаются в программах систематически несколько раз в месяц. В связи со столь неблагоприятной ситуацией, в США была разработана процедура сертификации программного обеспечения (Common Criteria). ПО, не прошедшее сертификацию по этой процедуре не может работать на критически важных серверах государственного значения.
Это показывает, почему ведущие производители телекоммуникационного оборудования и программного обеспечения привлекают большие ресурсы для аудита существующего массива программного обеспечения для выявления и устранения в них уязвимостей защиты. К сожалению, в настоящий момент процесс аудита программного обеспечения с целью выявления уязвимостей защиты совершенно неудовлетворительно поддерживается инструмен-тальными средствами. Как будет показано далее, основная проблема сущест-вующих инструментальных средств - высокий процент ложных срабатываний, когда фрагмент программы, не содержащий ошибок, приводящих к уязвимостям защиты, отмечается как опасный. Высокий процент ложных срабатываний требует большого количества ручного труда для отсеивания ложных сообщений от сообщений, действительно выявляющих ошибки.
В настоящее время в рамках контракта с Nortel Networks в отделе компи-ляторных технологий ведётся разработка инструментального средства для автоматического выявления уязвимостей защиты некоторых типов. Дальнейшие разделы настоящей работы посвящены описанию разрабаты-ваемого прототипа инструментального средства.
В настоящее время сложилась некоторая классификация уязвимостей защиты в зависимости от типа программных ошибок, которые могут приводить к появлению уязвимости в программе. В рамках данной работы мы рассмотрим лишь некоторые виды уязвимостей.
Переполнение буфера (buffer overflow). Данная уязвимость возникает как следствие отсутствия контроля или недостаточного контроля за выходом за пределы массива в памяти. Языки Си/Си++, чаще всего используемые для разработки программного обеспечения системного уровня, не реализуют авто-матического контроля выхода за пределы массива во время выполнения программы. Это самый старый из известных типов уязвимостей (знаменитый червь Морриса использовал, среди прочих, уязвимости переполнения буфера в программах sendmail и fingerd), уязвимости такого типа наиболее просто использовать.
По месту расположения буфера в памяти процесса различают переполнения буфера в стеке (stack buffer overflow), куче (heap buffer overflow) и области статических данных (bss buffer overflow). Все три вида переполнения буфера могут с успехом быть использованы для выполнения произвольного кода уязвимым процессом. Так, упомянутая выше программа rsync содержала уязвимость буфера в куче. Рассмотрим для примера более детально уязвимость переполнения буфера в стеке как наиболее простую на примере следующей простой программы:
#include < stdio.h>
int main(int argc, char **argv)
{
char buf[80];
gets(buf);
printf("%s", buf);
return 0;
}
Предположим, что стек процесса растёт в направлении уменьшения адресов памяти. В таком случае непосредственно перед выполнением функции gets стек будет иметь следующую структуру:
SP+96 | Аргументы командной строки, переменные окружения и т. д. |
SP+88 | Аргументы функции main (argc, argv) |
SP+84 | Адрес возврата из main в инициализационный код |
SP+80 | Адрес предыдущего стекового фрейма |
SP+80 | Сохранённые регистры (если есть), локальные переменные (если есть) |
SP | Буфер (char buf[80]) |
Как известно, функция gets не позволяет ограничивать длину вводимой со стандартного потока ввода строки. Вся введённая строка до символа '\n', кроме него самого, будет записана в память по адресам, начинающимся с адреса массива buf. При этом, если длина введённой строки превысит 80 символов, то первые 80 символов строки будут размещены в памяти, отведённой под массив buf, а последующие символы будут записаны в ячейки памяти, непосред-ственно следующие за buf. То есть, таким образом будут испорчены сначала сохранённые регистры и локальные переменные, затем адрес предыдущего стекового фрейма, затем адрес возврата из функции main и т. д. В момент, когда функция main будет завершаться с помощью оператора return, процессор выполнит переход по адресу, хранящемуся в стеке, но этот адрес испорчен в результате выполнения функции gets, поэтому переход произойдёт совсем в другое место, чем стандартный код завершения процесса.