Добавлен: 29.10.2018
Просмотров: 48134
Скачиваний: 190
706
Глава 9. Безопасность
что кошки таким способом могут идентифицировать друг друга. Предположим, что
кто-то изобретет небольшое устройство, способное проводить экспресс-анализ мочи,
предоставляя, таким образом, абсолютно надежный способ идентификации. Одним из
таких устройств может быть оборудован каждый компьютер, снабженный скромной
табличкой: «Пожалуйста, для входа в систему поместите пробу в устройство». Возмож-
но, такая система была бы абсолютно стойкой, но вряд ли нашла своих сторонников.
Когда в более ранние издания этой книги был включен предыдущий абзац, он носил
шуточный оттенок, не более того. Но этот пример в очередной раз показал, что иногда
выдумка становится действительностью. В настоящее время исследователями уже раз-
работаны системы распознавания запаха, применимые в биометрии (Rodriguez-Lujan
et al., 2013). Чего ждать дальше, визуализации запаха?
Потенциально задачу можно решить также с помощью укалывания большого пальца
и небольшого спектрографа. Пользователю нужно будет нажать большим пальцем на
выступающую острую часть специальной площадки, чтобы по капельке крови можно
было провести спектрографический анализ. Никто еще ничего подобного не публи-
ковал, но уже есть работа, где в качестве биометрического средства используется про-
бирка с кровью (Fuksis et al., 2011).
Наша точка зрения состоит в том, что любая схема аутентификации должна быть
физиологически приемлема для пользовательского сообщества. Измерения длины
пальцев не вызовет никаких проблем, но даже что-либо не требующее внедрения в тело
человека, например хранение в сети отпечатков пальцев, для многих может быть не-
приемлемым из-за ассоциации отпечатков пальцев с чем-то криминальным. И тем не
менее такая технология введена компанией Apple на iPhone 5S.
9.7. Взлом программного обеспечения
Один из основных способов взлома пользовательского компьютера основан на ис-
пользовании уязвимостей в программном обеспечении, запущенных в систему чтобы
сделать что-либо отличное от предназначения, заложенного программистом. Например,
довольно часто проводится атака, нацеленная на инфицирование пользовательского
браузера путем попутной, скрытой загрузки — drive-by-download. Проводя эту атаку,
киберпреступник инфицирует пользовательский браузер, помещая вредоносное со-
держимое в веб-сервер. Как только пользователь посещает веб-сайт, браузер инфици-
руется. Иногда веб-серверы полностью работают на взломщиков, и они должны найти
способ заманить людей на свой веб-сайт (возможно, рассылая спам с обещаниями
бесплатных программ или фильмов). Но возможно также, что взломщики поместят
вредоносное содержимое на легальный веб-сайт (например, в рекламе или на форуме).
Не так давно таким способом был скомпрометирован веб-сайт футбольной команды
Miami Dolphins, буквально за день до этого Dolphins завоевали суперкубок, что стало
одним из самых ожидаемых событий года. За несколько дней до этого события веб-сайт
пользовался большой популярностью, и машины многих пользователей, посетивших
его, были инфицированы. После исходного инфицирования с помощью drive-by-
download код взломщика запускался в браузере, загружающем настоящую зомби-про-
грамму (вредоносный код), выполнял эту программу и обеспечивал ее постоянный
запуск при каждой загрузке системы.
Поскольку книга посвящена операционным системам, наше внимание концентриру-
ется на способах нанесения вреда операционной системе. Здесь не рассматриваются
9.7. Взлом программного обеспечения
707
многочисленные способы использования уязвимостей в программном обеспечении
для взлома веб-сайтов и баз данных. Типовым сценарием будет обнаружение кем-либо
уязвимости в операционной системе с последующим поиском способа, позволяющего
воспользоваться ею для несанкционированного доступа к компьютеру, на котором за-
пущен код с дефектом. Атака drive-by-downloads не является частью общей картины,
поскольку мы увидим, что многие уязвимости и вставка вредоносного кода в пользо-
вательские приложения применимы и к ядру.
В знаменитой книге Льюиса Кэрролла «Алиса в Зазеркалье» Черная королева за-
ставляла Алису бежать сломя голову. Они бежали со всех ног, но всегда оставались
на одном и том же месте. Алиса подумала, что это странно, и сказала об этом. А коро-
лева ответила: «В нашей стране в другом месте можно оказаться, только если бежать
очень быстро и очень долго, что мы, собственно, и делали. Какая-то медлительная
страна! И здесь, знаешь ли, приходится бежать со всех ног, чтобы только остаться на
месте. Если же нужно попасть в другое место, следует бежать по меньшей мере вдвое
быстрее!». Эффект Черной королевы характерен для эволюционной борьбы видов.
На протяжении миллионов лет предки и зебр и львов эволюционировали. Зебры на-
чинали бегать быстрее, у них обострялись зрение, слух и обоняние, что позволяло им
убегать от львов. Но со временем львы также начинали бегать быстрее, они становились
крупнее, незаметнее и приобретали камуфляжную окраску, что позволяло им успешнее
охотиться на зебр. Но по мере «совершенствования» и львов и зебр никто из них не
становился удачливее во взаимном противостоянии, и те и другие продолжали суще-
ствовать в дикой природе. Следовательно, в эволюционной борьбе видов львов и зебр
произошло замыкание. Они бегут, чтобы оставаться на месте. Эффект Черной королевы
применим и к использованию программ. Взломщики становятся все изощреннее, чтобы
справиться с постоянно совершенствуемыми мерами безопасности.
Хотя каждый вредоносный код использует вполне определенную уязвимость в кон-
кретной программе, существует ряд общих категорий постоянно повторяющихся
уязвимостей, которые стоит изучить, чтобы понять механизмы взломов. В следующих
разделах будут рассмотрены не только несколько таких механизмов, но и препят-
ствующие их применению контрмеры для обхода этих механизмов, и даже некоторые
контрконтрмеры против таких приемов и т. д. Вы получите достаточное представление
о соревновании средств защиты и нападения и о том, что все это похоже на бег на месте
с Черной королевой.
Начнем с почтенного переполнения буфера, одной из наиболее известных технологий
в истории компьютерной безопасности. Она уже использовалась в самом первом сете-
вом черве, написанном Робертом Моррисом-младшим в 1988 году, и все еще широко
используется в наши дни. Несмотря на все контрмеры, исследователи предсказывают,
что переполнение буфера еще некоторое время с нами останется (Van der Veen, 2012).
Переполнения буферов идеально подходят для представления трех наиболее важных
механизмов защиты, доступных в большинстве современных систем: стекового индика-
тора, или «канарейки» (stack canaries), защиты от выполнения данных (data execution
protection) и рандомизации распределения адресного пространства (address-space
layout randomization). После этого будут рассмотрены другие технологии внедрения
вредоносного кода, такие как использование форматирующей строки (format string
attacks), переполнение целочисленных значений (integer overflows) и указателеи на
несуществующие объекты (dangling pointer exploits). Итак, приготовьтесь и наденьте
свою черную шляпу!
708
Глава 9. Безопасность
9.7.1. Атаки, использующие переполнение буфера
В основе множества атак лежит тот факт, что практически все операционные системы
написаны на языке программирования C или С++ (так как программисты любят эти
языки, а также потому, что программы на них компилируются в очень эффективный
объектный код). К сожалению, ни один компилятор языка C или С++ не выполняет
проверки границ массива. Например, библиотечная функция языка С gets, которая
считывает строку (неизвестного размера) в буфер фиксированного размера, но без
проверки на переполнение, является печально известной жертвой подобной атаки (не-
которые компиляторы даже предупреждают о наличии функции gets в коде). Соответ-
ственно следующий фрагмент программного кода также не проверяется компилятором:
01. void A() {
02. char B[128]; /* резервирование в стеке буфера объемом 128 байт */
03. printf ("Type log message:");
04. gets (B); /* чтение сообщения со стандартного ввода в буфер */
05. writeLog (B); /* вывод строки в файл журнала */
06. }
Функция A представляет собой процедуру регистрации происходящего в упрощенном
виде. При каждом ее выполнении пользователь получает приглашение на ввод реги-
страционного сообщения, а затем осуществляется чтение набранного пользователем
текста в буфере B с использованием функции gets из библиотеки языка C. И наконец,
вызывается доморощенная функция writeLog, которая, как предполагается, выводит
регистрационную запись в привлекательном формате (возможно, с добавлением даты
и времени к регистрационному сообщению, чтобы запись было легче искать в дальней-
шем). Предположим, что функция A является частью привилегированного процесса,
например программы, у которой идентификатор пользователя установлен с правами
самого высокого уровня (SETUID root). Взломщик, способный получить контроль над
таким процессом, по существу сам имеет привилегии root.
В показанном выше коде имеется несколько дефектов, хотя распознать их можно не
сразу. Проблема вызвана тем фактом, что функция gets считывает символы из стан-
дартного ввода до тех пор, пока не встретит символ новой строки. Она совершенно
не в курсе, что буфер B вмещает только 128 байт. Предположим, что пользователь на-
брал строку из 256 символов. Что будет с остальными 128 байтами? Поскольку gets не
проверяет факт нарушения границ буфера, остальные байты также будут сохранены
в стеке, как будто бы буфер имел длину 256 байтов. Все, что хранилось в этих местах
памяти изначально, будет просто переписано. Последствия обычно носят катастрофи-
ческий характер.
На рис. 9.19, а показана работающая основная программа, хранящая свои локальные
переменные в стеке. В какой-то момент времени она, как показано на рис. 9.19, б, вы-
зывает процедуру A. Стандартная последовательность вызова начинается с помещения
в стек адреса возврата (указывающего на инструкцию, следующую за инструкцией
вызова). Затем управление передается процедуре A, которая уменьшает указатель
стека на 128, чтобы выделить хранилище для своей локальной переменной (буфер B).
Итак, что же произойдет, если пользователь введет более 128 символов? Эта ситуа-
ция показана на рис. 9.19, в. Как уже упоминалось, функция gets копирует все байты
в буфер и выше, возможно, переписывая при этом многое из того, что содержится
в стеке, но, в частности, переписывая адрес возврата, помещенный в стек накануне.
9.7. Взлом программного обеспечения
709
Рис. 9.19. Складывающиеся ситуации: а — при работе основной программы;
б — после вызова процедуры А; в — при переполнении буфера, показанного серым цветом
Иными словами, часть регистрационной записи теперь заполняет место в памяти,
в котором, по предположению системы, содержится адрес инструкции, куда должен
осуществляться безусловный переход при возвращении из функции. Поскольку поль-
зователь набрал обычное регистрационное сообщение, его символы вряд ли будут
воспроизводить правильный адрес кода. И как только управление возвращается из
функции A, программа будет пытаться осуществить безусловный переход на неверное
место, а системе это придется совсем не по нраву. В большинстве случаев программа
тут же перейдет в аварийное состояние.
Теперь предположим, что это не простой пользователь, ошибочно набравший слишком
длинное сообщение, а взломщик, который послал сообщение с «хвостом», конкретно
нацеленное на нарушение хода выполнения программы. Скажем, им предоставлен
ввод, который тщательно выверен с тем, чтобы вместо адреса возврата был вписан
адрес начала буфера B. В результате этого при возвращении из функции A программа
осуществит безусловный переход на начало буфера B и выполнит байты в буфере как
код. Поскольку взломщик контролирует содержимое буфера, он может заполнить
его инструкциями машины с целью выполнения кода взлома в контексте исходной
программы. Получается, что взломщик переписал память своим собственным кодом
и добился его выполнения. Теперь программа полностью под контролем взломщика,
и он может заставить ее делать все, что захочет. Код взломщика часто используется
для запуска оболочки (например, посредством системного вызова exec), предоставляя
ему удобный доступ к машине. Поэтому такой код часто называют кодом запуска
оболочки,или шелл-кодом (shellcode), даже если он не запускает копию оболочки.
Этот прием срабатывает не только в отношении программ, использующих функ-
цию gets (применения которой следует избегать), но и при наличии любого кода,
который копирует предоставляемые пользователем данные в буфер без проверки
нарушения его границ. Эти пользовательские данные могут содержать параметры ко-
мандной строки, строковые данные среды окружения, данные, отправленные по сети,
710
Глава 9. Безопасность
или данные, считанные из пользовательского файла. Существует множество функций,
копирующих или перемещающих такие данные: strcpy, memcpy, strcat и многие другие.
Разумеется, уязвимым может также оказаться и старый цикл, написанный вами и пере-
мещающий байты в буфер. А что, если взломщик не знает точного адреса возврата?
Зачастую он может догадаться, где приблизительно, но не точно находится шелл-код.
В таком случае обычным решением становится размещение перед ним так называе-
мых nop-направляющих (nop sled) — последовательности однобайтовых инструкций
NO OPERATION (операция отсутствует), которые вообще ничего не делают. Когда
взломщику удается «приземлиться» на nop-направляющих, выполнение кода в конеч-
ном счете дойдет до настоящего шелл-кода. Nop-направляющие работают в стеке, но
они работают и в куче, где взломщики часто пытаются повысить свои шансы, размещая
по всей куче свои nop-направляющие и шелл-код. Например, в браузере вредоносный
код JavaScript может пытаться распределить как можно больше памяти и заполнить ее
длинной nop-направляющей и небольшим по объему шелл-кодом. Затем, если взлом-
щик сумел переключить ход выполнения программы и направить его на произвольный
адрес в куче, то шанс его попадания в nop-направляющие будет весьма высок. Такая
технология называется распылением в кучу (heap spraying).
Стековый индикатор («канарейка»)
Один часто используемый способ защиты от взлома, о котором уже кратко упоми-
налось, заключается в использовании стекового индикатора, или «канарейки» (stack
canaries). Это название происходит из шахтерской практики. Работа в шахте полна
опасностей. Может случиться выброс токсичных газов вроде угарного газа, который
убьет шахтеров. Ко всему прочему угарный газ не имеет запаха, поэтому шахтеры могут
даже его не заметить. В прошлом шахтеры, опасаясь такого выброса, в качестве системы
раннего предупреждения брали с собой в шахту канареек. Любой выброс токсичных
газов убьет канарейку раньше, чем нанесет вред ее хозяину. Если птичка сдохла, значит,
пора подниматься на поверхность.
Современные компьютерные системы также используют в качестве систем раннего
предупреждения канареек, правда, уже цифровых. Идея проста. В том месте, где про-
грамма осуществляет вызов функции, компилятор вставляет код для сохранения
в стеке произвольного значения «канарейки» непосредственно перед адресом возврата.
Перед возвращением из функции компилятор вставляет код для проверки значения
«канарейки». Если значение изменилось, значит, что-то пошло не так, как надо. В таком
случае лучше нажать тревожную кнопку и аварийное завершение, чем продолжать
выполнение программы.
Обход стековых «канареек»
«Канарейки» неплохо справляются с атаками, подобными ранее рассмотренной, но
возможности переполнения буфера все же сохраняются. Рассмотрим, к примеру,
следующий фрагмент кода. В нем используются две новые функции: функция strcpy
из состава библиотеки языка C, предназначенная для копирования строки в буфер,
и функция strlen, определяющая длину строки.
Обход стековой «канарейки» осуществляется путем изменения длины len и последу-
ющего непосредственного изменения адреса возврата: