KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программы » Крис Касперский - ТЕХНИКА СЕТЕВЫХ АТАК

Крис Касперский - ТЕХНИКА СЕТЕВЫХ АТАК

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн "Крис Касперский - ТЕХНИКА СЕТЕВЫХ АТАК". Жанр: Программы издательство неизвестно, год неизвестен.
Перейти на страницу:

Рисунок 002.txt (показаны локальные таблицы HANDLE для двух Windows-процессов, и их отображение в глобальную таблицу дескрипторов UNIX-эмулятора)

Намного более существенны различия реализаций процессов в UNIX и Windows. Поддержка многозадачности в UNIX реализована крайне убого (да не обидятся ее поклонники на это высказывание). Системный вызов exec, запуская новый процесс, «подминает» текущий, поэтому, до запуска exec обычно используется функция fork, расщепляющая один процесс на два - родительский и дочерний. Процесс-сын наследует все открытые файлы отца, сегмент данных и продолжает выполнение с той же самой точки, в которой завершился вызов fork. Отличие между ними заключается лишь в возвращаемом функцией fork значении. Родительский процесс получает идентификатор дочернего, а сам дочерний всегда ноль. Поэтому, код, поражающий новый процесс, в UNIX приложениях обычно выглядит так: “if (fork()-0) exec(“/bin/vi”,”/etc/passwd”,0);”.

В Windows все намного естественнее и элегантнее. Вызов CreateProcess действительно порождает новый процесс, не затирая текущий. При этом сохраняется возможность наследования всех обработчиков установкой флага bInheritHandles. Поэтому, функция CreateProcess практически эквивалентна последовательным вызовам fork + exec. Но аналога fork в Windows нет, как нет возможности расщепления процессов! По большому счету это просто не нужно современным программистам, но часто использовалось разработчиками UNIX-приложений.

Любой эмулятор UNIX должен уметь имитировать вызов fork, благо гибкая архитектура Windows это позволяет. Чаще всего создается приостановленный (suspend) процесс с той же самой стартовой информацией, что и текущий. До выполнения функции main() из родительского процесса в дочерний копируется сегмент данных, стек и дублируются все обработчики. В последнюю очередь модифицируется контекст процесса, хранящий значения регистров, в том числе и регистра указателя очередной выполняемой команды (в 386+ серии микропроцессоров он носит название EIP).

Гораздо проще имитировать exec, - достаточно вызвать CreateProcess и “прибить” текущий процесс, имитируя его замещение новым. Остается всего лишь переустановить идентификатор, сохраняя генеалогическую линию. Если этого не сделать, вновь порожденный процесс станет потомком процесса, вызвавшего exec, а в оригинальной системе UNIX функция.exec не создает дочернего процесса и сохраняет идентификатор текущего. Но идентификатор процесса выдается операционной системой и не может быть изменен по желанию приложения! Другими словами, если “Процесс 0” породил “Процесс 1” и запомнил его идентификатор, а “Процесс 1”, имитируя вызов exec, обратился к функции CreateProcess и вызвал exit для завершения своей работы, то “Процесс 0” будет по-прежнему ссылаться на уже несуществующий “Процесс 1”, ничего не зная о порожденном “Процессе 2”, обладающего иным идентификатором.

Ситуация разрешается созданием собственной таблицы идентификаторов эмулятором UNIX. Родительский процесс в качестве идентификатора получает индекс ячейки такой таблицы, содержащей настоящий идентификатор дочернего процесса. В результате появляется возможность «подменить» идентификатор «Процесса 1» на «Процесс 2» незаметно для родительского процесса. (Смотри рисунок 003.txt)


Имитация эмулятором системного вызова fork

В отличие от Windows, UNIX поддерживает сигналы - удобное средство межпроцессорного взаимодействия, своеобразный аналог программно-аппаратных прерываний. Механизм же сообщений, реализуемый Windows, требует постоянного опроса очереди сообщений на предмет обнаружения новых поступлений. Так, например, при закрытии окна приложению посылается сообщение WM_CLOSE, но если оно в этот момент не читает очередь, а занято чем-то другим, скажем, форматированием очередного трека дискеты или сортировкой данных, то проигнорирует попытку закрытия, и продолжит работу вплоть до следующей проверки очереди. Поэтому, практически в каждом Windows-приложении присутствует следующий код:

· while (GetMessage ( amp;msg, NULL, 0, 0))
· {
· TranslateMessage ( amp;msg);
· DispatchMessage ( amp;msg);
·}

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

В UNIX все гораздо проще - операционная система автоматически прерывает работу процесса-получателя и передают управление на подпрограмму обработки сигнала, а после ее завершения возобновляет выполнение прерванного процесса с того же самого места.

К счастью, в Windows 9x/Winwos NT существует многопоточность и множество механизмов межпроцессорных взаимодействий, поэтому имитировать поддержку сигналов вполне реально. Для этого в каждом эмулируемом приложении необходимо выделить отдельный поток, ожидающий ну, например, события (Event) и передающий управление на соответствующую подпрограмму при его наступлении. (Смотри рисунок 004.txt) События выгодно отличаются возможностью «заморозить» ожидающий поток, не теряя впустую драгоценного процессорного времени.

Следует отметить, функция “kill” вовсе не убивает процесс, как это следует из ее названия, а отправляет ему сигнал, который тот может либо проигнорировать, либо обработать по своему усмотрению.


Имитация эмулятором механизма сообщений

Другое существенное отличие заключается в поведении кучи (heap) при изменении размеров выделенного блока. Куча - это динамически распределяемая область памяти. Не вникая в технические тонкости той или иной реализации, достаточно отметить три важнейшие операции, совершаемые над кучами - выделение блока памяти, освобождение и изменение его размеров. Первые две никаких проблем не вызывают, но вот динамическое изменение размера - штука интересная. Легко уменьшить размер блока, но намного чаще программистам требуется его увеличить (для того кучи и задуманы, чтобы сначала запросить немножечко памяти, а по мере потребности требовать ее все больше и больше). А как поступить, если к концу одного блока вплотную примыкает следующий? UNIX использует трансляцию адресов, то есть определенным образом отображает физические адреса на логические, создавая видимость непрерывности всего блока памяти, хотя на самом деле он состоит из множества беспорядочно разбросанных фрагментов. А вот Windows находит подходящий по размеру свободный блок памяти, проецирует в его начало содержимое увеличиваемого блока и возвращает новый указатель начала блока. Программы Windows, рассчитанные на такое поведение, выполняются успешно, но вот, попробуй-ка, научи UNIX-приложения этим фокусам! Поэтому, эмулятор UNIX должен иметь свой собственный менеджер куч, работающий с памятью Windows на низком уровне функциями VirtualAlloc и VirtualFree.

Описанные выше различия относятся к тонкостям реализации, и скрыты от простого пользователя, отличающего Windows от UNIX по наклону черты-разделителя. Совершенно непонятно, почему разработчики MS-DOS вопреки всем соглашениям де-факто, решили проявить оригинальность, применив не прямой, а обратный слеш, зарезервированный в Си для управляющих символов. Очевидно, перенос программы из UNIX в MS-DOS (Windows) потребовал бы переделок значительной части кода и существенных усилий. К счастью, эмуляторы позволяют этого избежать, автоматически переформатировав строку (в простейшем случае) или установив новый драйвер файловой системы (для обеспечения полной имитации). Не следует забывать, файловая система в UNIX монтируемая, т.е. объединяет в одну логическую структуру файлы и каталоги, физически расположенные не только на разных носителях, но и компьютерах.

Гораздо больше неудобств доставляет «двойной» перенос строки в MS-DOS (Windows), задаваемый символами “rn”, тогда как UNIX ожидает лишь одиночного символа “n”. Поэтому, в большинстве случаев UNIX-приложения не могут корректно обрабатывать тексты, созданные редакторами MS-DOS (Windows) и наоборот. Так, текст программы, набранный в редакторе edit, вызовет протест со стороны UNIX-версии интерпретатора Perl.

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

Точно так, в MS-DOS и UNIX не совпадают наименования устройств. Например, для подавления вывода сообщений на экран в MS-DOS используется конструкция “»nul” (“echo Это сообщение никогда не появится на экране»nul”), а в UNIX - “»/dev/null” (“echo Это сообщение никогда не появится на экране» /dev/null”). Другие примеры различий показаны в таблице 1

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*