Автор: (c)Крис Касперски ака мыщъх
Антивирусы (даже со всеми апдейтами) далеко не всегда распознают малварь и опытные хакеры доверяют только своим собственному хвосту, отладчику soft-ice и другому низкоуровневому инструментарию, позволяющему пробурить нору до самого ядра и разоблачить зловредные программы, где бы они ни скрывались!
Во времена MS-DOS ручная чистка компьютера была обычном делом. Количество исполняемых файлов измерялось десятками и существовало не так уж и много мест, пригодных для внедрения малвари (под малварью - от английского malware - здесь и далее по тексту подразумевается вредоносное программное обеспечение - вирусы, черви, шпионы и т.д.). С приходом Windows все изменилось. Из крохотного поселка операционная система превратилась в огромный, стремительно разрастающийся мегаполис, среди сотен тысяч файлов которого может спрятаться и слонопотам.
Обнаружить качественно спроектированную и грамотно заложенную закладку усилиями одного человека за разумное время - навряд ли возможно. Намного проще (да и быстрее) переустановить Windows с нуля. К счастью, качественная малварь - огромная редкость, практически не встречающая в живой природе и в основном приходится сталкиваться с пионерскими поделками, оставляющими после себя кучу следов и легко различимыми с помощью soft-ice и сопутствующих ему утилит.
Весь вопрос в том - как правильно ими пользоваться. Ну, установили мы soft-ice, нажали <CTRL-D>, увидели черный экран... Дальше-то что?! А вот дальше на сцене появился мыщъх и, плюхнувшись в кресло (предварительно подтолкнув под себя хвост), начинает делиться хакерскими секретами.
Рисунок 1. Windows - это целый мегаполис.
Чаще всего малварь копирует свою тушу в новый файл со случайным или фиксированным названием, реже - внедряется в уже существующие (что требует не только знания устройства PE-формата, но и определенных привилегий, в частности, из-под пользовательского аккаунта системные файлы просто так не заразишь). При этом подавляющее большинство malware-писателей забывают скорректировать дату/время создания файла, выдавая себя с головой.
Допустим, мы запустили файл сомнительного происхождения и хотим узнать - не натворил ли он чего в системе? Пуск -> Найти -> Файлы и Паки -> Параметры Поиска -> Файлы созданные за xxx последних дней (в нашем случае - за один). Все изменения, произошедшие за последние сутки в системе становятся видны, как на ладони! Как вариант: в FAR'е устанавливаем режим сортировки по дате создания (<CTRL-F8>) и заходим во все "злачные" каталоги типа WINNT, System32 и т.д. Файлы, созданные последними, будут на самом верху. Прием простой, как паровой котел, но чрезвычайно эффективный!
Рисунок 2. Поиск файлов, созданных за последнее время, с помощью штатных средств Windows - в данном случае мы видим файл hldrrr.exe, "окопавшийся" в каталоге C:\WINNT\system32.
Конечно, чем позже мы спохватимся, тем сложнее отличить "легальные" файлы от "нелегальных", особенно если на компьютер ставится большое количество самого разнообразного программного обеспечения. Но все файлы, устанавливаемые инсталлятором (где бы он их не размещал - в Program Files, WINNT или System32), имеют одну и ту же дату создания с небольшим разбросом по времени (ведь файлы создаются не параллельно, а последовательно), поэтому их стразу можно исключить из списка подозреваемых. А оставшиеся - подвергнуть тщательному допросу (см. "Признаки вредоносных файлов").
Рисунок 3. Исследование даты создания файлов при помощи FAR'а.
Естественно, дата создания файла элементарно изменяется средствами win32-API и малвари при желании ничего не стоит замаскироваться. Однако на NTFS-разделах каждый файл обладает множеством "невидимых" атрибутов, до которых нельзя дотянуться через API. В частности, атрибут 30h ($FILE_NAME) помимо стандартных времен создания/модификации/последнего обращения, хранит время последней модификации данной записи MFT (Master File Table - специального мастерфайла, содержащего информацию обо всех остальных объектах файловой системы). У "честных" файлов время создания и время последней модификации MFT всегда совпадает, а если это не так - мы имеем дело с подделкой. Еще существует атрибут 10h ($STANDARD_INFORMATION), также хранящий информацию о времени создания/модификации/последнего доступа файла и времени последней модификации MFT, однако в отличии от атрибута 30h здесь время последней модификации MFT автоматически обновляется всякий раз, когда файлу выделяется новая порция кластеров, а потому со временем его создания оно может и не совпадать.
Существует не так уж и много утилит, отображающих содержимое MFT в удобочитаемом виде. Одна из них - NtExplorer от Runtime Software. Грубо говоря, это Norton Disk Editor, но только под NTFS. К сожалению, NtExplorer не поддерживает ни плагинов, ни скриптов, поэтому быстро вывести список файлов с поддельными датами создания не получается и каждый из них приходится перебирать "руками", что очень сильно напрягает, но... NTFS совсем несложная (по нынешним меркам) файловая система, а все ее основные структуры давным-давно реконструированы, документированы и выложены в Сеть: http://linux-ntfs.sourceforge.net. Создание программы, выполняющий автоматизированный поиск "поддельных" файлов у нас не займет много времени, тем более, что подробное описание NTFS можно найти в моей книге "la technique de la restitution des données". В общем, дорогу осилит идущий!
Рисунок 4. Обнаружение файла с поддельным временем создания при помощи Runtime NtExplorer - FAR утверждает, что файл создан 07.05.2004, в то время как соответствующая ему запись в MFT модифицировалась 18.07.2006.
Обычно малварь создает свой собственный процесс (реже - внедряется в чужие), при этом у нее возникает вполне естественное желание скрыть этот процесс, убрав его из "Диспетчера Задач" и прочих системных утилит. Как это она делает? Для предоставления информации о процессах NT поддерживает два механизма: набор документированных процедур TOOLHELP32 (доставшийся в "наследство" от 9x), реализованных в KERNEL32.DLL, и недокументированная функция NtQuerySystemInformation, экспортируемая NTDLL.DLL, и представляющую собой тонкую "обертку" вокруг системного сервиса 97h, реализованного в NTOSKRNL.EXE. На самом деле, главная функция TOOLHELP32 - CreateToolhelp32Snapshot - полностью опирается на NtQuerySystemInformation, так что фактически механизм у нас один, только интерфейсы разные.
Малварь может легко перехватить процедуры Process32First/Process32Next из TOOLHELP32, только это ей ничего не даст, поскольку практически все утилиты ("Диспетчер Задач", FAR и даже примитивный tlist.exe из SDK) работают исключительно через NtQuerySystemInformation (что легко подтверждается установкой точки останова в soft-ice). Однако перехватить NtQuerySystemInformation с прикладного уровня ничуть не сложнее, чем процедуры из набора TOOLHELP32! Существует множество путей, как это сделать:
При наличии прав администратора малварь может проникнуть в NTOSKRNL.EXE и подменить сервис 97h своим собственным обработчиком. Тогда с прикладного уровня обнаружить зловредный процесс уже не удастся и придется спускаться на уровень ядра, подробно рассмотренное в разделе "Восстановление SST".
Soft-Ice - единственная из всех известных мыщъх'у программ, которая не использует NtQuerySystemInformation и для отображения списка процессов самостоятельно разбирает базовые структуры операционной системы, а потому легко выявляет скрытые процессы.
Рисунок 5. Soft-ice показывает процесс sysrtl, отсутствующий в "Диспетчере Задач".
Теоретически, малварь может внедриться в soft-ice и перехватить любую из его команд (например, команду "PROC"), действуя по той же схеме, что и IceExt/IceDump (благо, что обе утилиты распространяются в исходных текстах), но в живой природе такие "монстры" пока что не встречались. Можно надеяться, что IceExt, скрывающий soft-ice от большинства защит, скроет его и от малвари, однако при этом остается угроза сигнатурного поиска отладчика в памяти; к тому же на хакерских форумах не первый год обсуждается гипотетический алгоритм скрытия, перехватывающий функции переключения контекста и "вытирающий" себя в промежутках между ними. Однако реализация такого проекта упирается в непреодолимые практические трудности. Формат процессорных структур непостоянен и меняется от одной версии системы к другой, к тому же с ними взаимодействуют множество недокументированных функций, вызываемых в разное время из различных мест и малварь, пытающаяся замаскироваться, постоянно обрушивает систему в BSOD, чем сразу себя и разоблачает.
Таким образом, будем считать, что связки из soft-ice + IceExt для просмотра всех процессов (включая скрытые) вполне достаточно.
В последнее время все чаще и чаще малварь не создает для себя отдельный процесс (который очень легко заметить), а предпочитает внедряться в один из уже существующих. Для этого используются два механизма. В первом малварь выделяет в целевом процессе блок памяти функцией VirtualAllocEx, копирует себя через WriteProcessMemory и создает удаленный поток посредством CreateRemoteThread. Второй механизм начинается так же, как и первый, только вместо создания удаленного потока, малварь останавливает текущий поток процесса и изменяет регистр EIP функцией SetThreadContext (естественно, предварительно сохранив его оригинальное значение через GetThreadContext), передавая управление своей собственной процедуре, вызывающей CreateThread, восстанавливающей EIP и "размораживающей" ранее остановленный поток. Первый механизм работает только на NT, второй - на всех 32-разядных системах семейства Windows.
Как обнаружить такой метод вторжения? Естественно, количество потоков атакуемого процесса увеличивается на единицу, однако это еще не показатель. Никто и никогда не может сказать точно, сколько у приложения должно быть потоков. Даже его непосредственный разработчик! Проведем простой эксперимент. Запустим "Блокнот" и, переключившись на Диспетчер Задач, увидим один-единственный поток. Теперь зайдем в меню "файл" и скажем "открыть". Количество потоков внезапно подскакивает аж по пяти! Закрываем окно открытия файла - один поток исчезает, остаются четыре. Что это за ерунда такая?! Оказывается, все дело в динамических библиотеках SHLWAPI.DLL, RPCRT4.DLL и OLE32.DLL, "обслуживающих" окно и порождающих свои собственные, дочерние, потоки. Некоторые драйвера также могут порождать потоки в чужих приложениях (как правило, с целью вызова прикладных API). Нам необходимо как-то научиться отличать "легальные" потоки от "нелегальных", иначе наша борьба с малварью обречена.
Вот идея, простая как 3,5-дюймовая дискета: стартовый адрес легального потока лежит в пределах страничного имиджа (в секции .code и .text), а нелегального - в куче, т.е. области динамической памяти, выделенной функцией VirtualAllocEx. Чтобы разоблачить нелегалов, нам прежде всего понадобится карта адресного пространства. Soft-ice отображает ее не в самом наглядном виде и лучше воспользоваться OllyDbg или PE-TOOLS.
В OllyDbg в меню "file" выбираем "attach" и указываем процесс, чьи потоки мы будем исследовать. После успешного присоединения к процессу говорим "view" -> "memory" или давим <ALT-M>. Получаем карту следующего вида:
Рисунок 6. Карта памяти "Блокнота", отображенная отладчиком OllyDbg.
Регионы, помеченные как "Priv" (сокращение от "private") принадлежат блокам динамической памяти, "map" (сокращение от "mapping") - проекциям файлов, созданных функциями CreateFileMapping/MapViewOfFile), "Imag" (сокращение от "imaging") - страничным имиджам исполняемых файлов или динамических библиотек.
В PE-TOOLS для той же цели необходимо выделить процесс и в контекстном меню выбрать "dump region", при этом на экране появится диалоговое окно с картой памяти - не такое подробное, как у OllyDbg, но для нашей задачи вполне удовлетворительное.
Рисунок 7. Карта памяти "Блокнота", отображенная утилитой PE-TOOLS.
Для дальнейший экспериментов нам понадобится программа, создающая пару потоков - "честным" и "нечестным" путем. Исходный код (с опущенной обработкой ошибок и других исключительных ситуаций) может выглядеть так:
Листинг 1. Исходный код демонстрационной программы va_thread.c, компилируемой с настройками по умолчанию.
Компилируем, запускаем, заходим в soft-ice, даем команду "THREAD -x" (вывод детальной информации о потоках) и смотрим полученный результат:
:THREAD -x Extended Thread Info for thread 374 KTEB 873CFDA0 TID: 374 Process: va_thread(11C) Start EIP: KERNEL32!SetUnhandledExceptionFilter+001A (77E878C1) User Stack: 00030000 - 00130000 Stack Ptr: 0012FD24 Extended Thread Info for thread 238 KTEB: 82007020 TID: 238 Process: va_thread(11C) Start EIP: KERNEL32!CreateFileA+00C3 (77E92C50) User Stack: 00420000 - 00520000 Stack Ptr: FFFFFFFF Extended Thread Info for thread 30C KTEB: 82007AC0 TID: 30C Process: va_thread(11C) Start EIP: KERNEL32!CreateFileA+00C3 (77E92C50) User Stack: 00530000 - 00630000 Stack Ptr: FFFFFFFF
Листинг 2. Информация о потоках, сообщенная soft-ice (приводится в сокращенном виде).
Вот так номер! Soft-ice не смог определить истинные стартовые адреса потоков, заблудившись в недрах KERNEL32.DLL. Что ж, попробуем другой инструмент - Process Explorer от Марка Руссиновича, весьма нехило разбирающегося во внутренностях операционных систем от Microsoft (и даже участвующем в написании книги "Windows NT Internals". Скачиваем (совершенно бесплатно) Process Explorer, запускаем, наводим курсор на "va_thread.exe", далее в контекстном меню выбираем пункт "Properties" и в открывшемся диалоговом окне переходим к вкладке "Threads".
Рисунок 8. Process Explorer от Марка Руссиновича не смог определить стартовый адрес "нечестного" потока.
Что мы видим? Адреса двух потоков определены верно. Первый: va_thread.exe+0x1405, судя по адресу, представляет основной поток (адрес совпадает с точкой входа, что легко проверить в hiew'е). Второй: va_thread.exe+0x1000 - это "честно" созданный поток (что опять-таки проверяется по адресу в hiew'е), а вот третий - KERNEL32.DLL+0xB700 - это "нечестный" поток (а чем он еще может быть?!), только его стартовый адрес определен неправильно!
Призываем на помощь OllyDbg и пытаемся разобраться в ситуации самостоятельно, без всех этих прелестей автоматизации и прочих чудес технического прогресса. Подключившись к процессу va_thread.exe, в меню "view" выбираем пункт "thread" и... обнаруживаем не три (как ожидалось), а целых четыре потока!
Ident Entry Data block Last error Status Priority ----------------------------------------------------------------------- 050C 7943B700 7FFDB000 ERROR_SUCCESS Active 32 + 0 0558 00000000 7FFDC000 ERROR_SUCCESS Suspended 32 + 0 055C 00000000 7FFDE000 ERROR_SUCCESS Suspended 32 + 0 0578 00000000 7FFDD000 ERROR_SUCCESS Suspended 32 + 0
Листинг 3. Информация о четырех потоках, выданная OllyDbg.
Стартовый адрес (entry) определен только для одного из потоков - 50Ch да и тот, вероятно, служит для связки отлаживаемого процесса с OllyDbg. Стартовые адреса остальных потоков выставлены в ноль, но ведь это же не так!!!
Щелкаем мышью по потоку c идентификатором 558h (естественно, при следующем запуске программы идентификаторы потоков будут другими) и получаем код следующего содержания, который (судя по карте памяти), принадлежит страничному имиджу, следовательно, это легальный поток.
401000 55 PUSH EBP 401001 8B EC MOV EBP,ESP 401003 B8 01000000 MOV EAX,1 401008 85C0 TEST EAX,EAX 40100A 74 02 JE SHORT va_threa.0040100E 40100C EB F5 JMP SHORT va_threa.00401003
Листинг 4. Код потока 558h, находящегося в пределах страничного имиджа.
Переходим к окну стека, перемещая ползунок на самый низ. На дне стека видим аргумент, переданный потоку (второе двойное слово, в данном случае равное - 999h) и... стартовый адрес потока, лежащий в третьем двойном слово, и в данном случае равный 401000h, что полностью согласуется с листингом 1. (На самом деле, в зависимости от способа создания потока стартовый адрес может лежать как в третьем, так и во втором слове, поэтому автоматические утилиты и путаются).
51FFDC FFFFFFFF End of SEH chain 51FFE0 79481F54 SE handler 51FFE4 79432B08 KERNEL32.79432B08 51FFE8 00000000 51FFEC 00000000 51FFF0 00000000 51FFF4 00401000 va_threa.00401000 ; <- стартовый адрес потока 558h 51FFF8 00000999 ; <- аргумент, переданный потоку 51FFFC 00000000 ; <- дно пользовательского стека потока
Листинг 5. На дне пользовательского стека пока 558h лежит стартовый адрес вместе с переданным ему аргументом.
Переходим к следующему потоку - 55Ch. Код выглядит точно так же, как и раньше (ведь мы запустили два экземпляра одной и той же функции!), а вот содержимое дна стека слегка изменилось:
62FFDC FFFFFFFF End of SEH chain 62FFE0 79481F54 SE handler 62FFE4 79432B08 KERNEL32.79432B08 62FFE8 00000000 62FFEC 00000000 62FFF0 00000000 62FFF4 00520000 ; <- стартовый адрес потока 55Сh 62FFF8 00000666 ; <- аргумент, переданный потоку 62FFFC 00000000 ; <- дно пользовательского стека потока
Листинг 6. На дне пользовательского стека пока 55Ch лежит стартовый адрес, вместе с переданным ему аргументом.
Как мы помним, 666h - это аргументы, переданные "нечестной" копии потока, а 520000h - его стартовый адрес, принадлежащий (если верить карте памяти) блоку памяти, выделенному функцией VirtualAlloc:
Address Size Owner Section Contains Type Access Initial -------------------------------------------------------------------------- 400000 1000 va_threa PE header Imag R RWE 401000 4000 va_threa .text code Imag R RWE 405000 1000 va_threa .rdata imports Imag R RWE 406000 2000 va_threa .data data Imag R RWE 410000 2000 Map R R 51E000 1000 Priv RW Guar RW 51F000 1000 stack of thr Priv RW Guar RW 520000 1000 Priv RWE RWE 62E000 1000 Priv RW Guar RW
Листинг 7. Карта памяти процесса va_thread (область памяти, принадлежащая потоку 55Ch, выделена полужирным).
Последний поток - 578h представляет собой основной поток программы и хранит своей стартовый адрес не в третьем, а во втором (!) двойном слове:
12FFE0 FFFFFFFF End of SEH chain 12FFE4 79481F54 SE handler 12FFE8 79432B18 KERNEL32.79432B18 12FFEC 00000000 12FFF0 00000000 12FFF4 00000000 12FFF8 00401405 va_threa.<ModuleEntryPoint> ; <- стартовый адрес потока 578h 12FFFC 00000000 ; <- дно пользовательского стека потока
Листинг 8. Поток 578h хранит свой стартовый адрес не в третьем, а во втором двойном слове!
Свершилось! Мы научились быстро и просто определять стартовые адреса потоков, надежно отличая "левых" от "правых". Кстати, чтобы каждый раз не сверяться с картой памяти можно использовать следующий трюк. Если при нажатии стартового адреса в контекстном меню OllyDbg присутствует строчка "Follow in Disassembler" - он принадлежит страничному имиджу (т.е. легальному потоку) и, соответственно, наоборот.
Рисунок 9. Содержимое дна стека "честного" (слева) и "нечестного" потока (справа), у "нечестного" отсутствует пункт "Follow in Disassembler" в контекстом меню.
На самом деле праздновать победу еще рано. Умная малварь может нас легко обмануть. Самое простое - подменить истинный стартовый адрес так, чтобы он указывал внутрь страничного имиджа целевого процесса (но в этом случае он должен совпадать с началом какой-нибудь процедуры, иначе мы тут же разоблачим обман). Более умная малварь использует хитрый способ внедрения - находит в целевом процессе функцию по стандартному прологу PUSH EBP/MOV EBP, ESP (55h/8Bh ECh), вставляет в ее начало jump на выделенный из кучи блок, где размещено ее тело, создает новый поток, начинающийся с jump, и тут же восстанавливает оригинальное содержимое хакнутой функции убирая jump и возвращая стандартный пролог. Еще остается вариант - загрузить внутрь процесса динамическую библиотеку, принадлежащую малвари и запустить внутри нее новый поток.
Во всех этих случаях анализ стартового адреса не даст никакого результата и внедрение зловредного кода останется незамеченным и чтобы быть уверенным на все 100%, необходимо трассировать каждый из потоков на предмет проверки его лояльности. Потоки, порожденные малварью, либо шпионят за клавиатурой, либо открывают backdoor, либо рассылают спам. Проблема в том, что потоков (легальных) очень много, а современная малварь пишется уже не на ассемблере, а черт знает на чем (DELPHI, Visual BASIC) и полный анализ требует уймы времени, однако, как говорилось выше, умная малварь - большая редкость и подделкой стартовых адресов потоков никто не занимается.
Для сокрытия своего присутствия в системе, малварь нередко внедряется в ядро системы и перехватывает один или несколько сервисов, например, функцию NtQuerySystemInformation, про важность которой мы уже говорили. Ловить малварь на такой системе все равно, что бороться с компартией под ее руководством.
Дизассемблирование NTDDLL.DLL показывает, что большинство низкоуровневых функций реализованы как "переходники" к функциям ядра, интерфейс с которым осуществляется либо посредством прерывания INT 2Eh (NT, W2K), либо машинной командой SYSENTER (XP и выше).
.text:77F95BBD public ZwQuerySystemInformation .text:77F95BBD ZwQuerySystemInformation proc near .text:77F95BBD arg_0 = byte ptr 4 .text:77F95BBD .text:77F95BBD B8 97 00 00 00 mov eax, 97h ; NtQuerySystemInformation .text:77F95BC2 8D 54 24 0 lea edx, [esp+arg_0] .text:77F95BC6 CD 2E int 2Eh .text:77F95BC8 C2 10 00 retn 10h .text:77F95BC8 ZwQuerySystemInformation endp
Листинг 9. Функция ZwQuerySystemInformation в действительности представляет "переходник" к системному сервису 97h.
Когда происходит вызов прерывания, процессор автоматически переключается с прикладного уровня (ring 3) в режим ядра (ring 0), передавая управление функции KiSystemService, реализованной внутри NTOSKRNL.EXE и опирающейся на Таблицу Системных Дескрипторов, она же SDT (System Descriptor Table). Собственно, дескрипторов в ней всего два - один для системных вызовов, другой - для драйвера win32k.sys, куда упрятали весь графический интерфейс. На серверах добавляется и третий дескриптор - IIS, назначение которого ясно из его названия.
Рисунок 10. Механизм реализации системных вызовов.
Дескриптор, отвечающий за системные вызовы, указывает на System Service Table (Таблица Системных Вызовов), представляющую собой простой массив указателей на функции, которые очень легко изменить (естественно, делать это нужно либо из режима ядра, либо с прикладного уровня, обратившись к псевдоустройству PhysicalMemory). Найти таблицу системных вызовов в памяти очень просто. "Скармливаем" NTOSKRNL.EXE функции LoadLibrary и, используя возвращенный ей дескриптор, определяем адрес экспортируемой переменной KeServiceDescriptorTable через GetProcAddress (или разбираем таблицу экспорта вручную). Первое же двойное слово содержит указатель на SST, поэтому эффективный адрес требуемого системного сервиса по его "магическому" номеру определяется так: addr == *(DWORD *)(KeServiceDescriptorTable[0] + N*sizeof(DWORD)), где N - номер сервиса, а addr - его эффективный адрес.
Продемонстрируем эту технику на примере soft-ice:
:dd :d KeServiceDescriptorTable 0008:8046AB80 804704D8 00000000 000000F8 804708BC ..G...........G. :d 804704D8 0008:804704D8 804AB3BF 804AE86B 804BDEF3 8050B034 ..J.k.J...K.4.P. 0008:804704E8 804C11F4 80459214 8050C2FF 8050C33F ..L...E...P.?.P. 0008:804704F8 804B581C 80508874 8049860A 804FC7E2 .XK.t.P...I...O. :u *(804704D8 + 97*4) ntoskrnl!NtQuerySystemInformation 0023:804BF933 PUSH EBP 0023:804BF934 MOV EBP, ESP 0023:804BF936 PUSH FF 0023:804BF938 PUSH 804043A0 0023:804BF93D PUSH ntoskrnl!_except_handler3
Листинг 10. Протокол работы с soft-ice, демонстрирующий получение адреса системного сервиса 97h.
Как мы видим, в данном случае, функция NtQuerySystemInformation никем не перехвачена, что очень хорошо!
Чтобы просмотреть содержимое SST в soft-ice достаточно дать команду "NTCALL". На "стерильной" машине все вызовы указывают внутрь NTOSKRNL.EXE, а если это не так, то их кто-то перехватил. Это может быть как зловредная малварь, так и вполне безобидный драйвер какого-нибудь защитного механизма или, например, брандмауэр.
Для восстановления SST можно использовать ее копию, хранящуюся внутри NTOSKRNL.EXE, правда, найти ее на диске значительно сложнее, чем в памяти. Проще всего использовать отладочные символы (которые можно бесплатно сгрузить с сервера http://msdn.microsoft.com/download/symbols с помощью библиотеки dbghelp.dll, входящей в состав бесплатного пакета Debugging Tools). Адресу SST соответствует метка _KiServiceTable и в моей версии системы она располагается по адресу 4704D8h (в файле):
.data:004704D8 BF B3 4A 00 _KiServiceTable dd offset _NtAcceptConnectPort@24 .data:004704DC 6B E8 4A 00 dd offset _NtAccessCheck@32 .data:004704E0 F3 DE 4B 00 dd offset _NtAccessCheckAndAuditAlarm@44
Листинг 11. Копия таблицы системных вызовов, хранящаяся внутри NTOSKRNL.EXE.
А если отладочных символов нет? Тогда находим все перекрестные ссылки к KeServiceDescriptorTable (т.е. просто ищем ее адрес, записанный с учетом обратного порядка байт на x86, задом наоборот). Одна из них ведет к инструкции типа "mov [mem], imm32" и представляет собой смещение оригинальной SST (imm32), записываемой в KeServiceDescriptorTable[0]. Как нетрудно убедиться дизассемблером, изначально SDT пуста и инициализируется на стадии загрузки ядра не экспортируемой функцией KiInitSystem.
.data:0046AB80 ; Exported entry 516. KeServiceDescriptorTable .data:0046AB80 public _KeServiceDescriptorTable .data:0046AB80 _KeServiceDescriptorTable dd 0
Листинг 12. Неинициализированная SDT-таблица, хранящаяся в NTOSKRNL.EXE.
Ниже, в качестве примера, продемонстрирован поиск SST в hiew'e:
Рисунок 11. Поиск SST в файле NTOSKRNL.EXE по перекрестным ссылкам.
Если лень восстанавливать SST вручную, можно воспользоваться бесплатной утилитой "Win2K/XP SDT Restore" от Tan Chew Keong, результат работы которой продемонстрирован ниже:
Рисунок 12. Официальный сайт утилиты SDT Restore....
Рисунок 13. ...и результат ее работы на зараженной малварью машине.
Пользуясь SDT Restore, следует иметь ввиду, что уже появились rootkit'ы, способные ее обходить. Во-первых, для поиска оригинальной SST, утилита SDT Restore использует простой, но ненадежный способ, обращаясь к KeServiceDescriptorTable[0], которую зловредная малварь может и подменить (см. http://hi-tech.nsys.by/35/), во-вторых, само восстановление SST происходит с прикладного уровня через псевдоустройство PhysicalMemory, отображаемое в память посредством native-API функции NtMapViewOfSection, легко перехватываемую как с прикладного, так и с ядерного уровней, после чего перехватчику остается проверить: не вызывается ли NtMapViewOfSection с дескриптором PhysicalMemory и если да, то либо заблокировать доступ, либо имитировать восстановление, не производя его в действительности (см. http://www.rootkit.com/newsread.php?newsid=200).
Также следует учитывать, что некоторые защиты "вешаются" на вектора прерываний, описанные в таблице IDT, и проверяют перехваченные сервисы, например, каждый тик таймера. В правильной IDT (просмотреть которую можно одноименной командой в soft-ice) все вектора указывают внутрь NTOSKRNL.EXE или HAL.DLL.
:IDT Int Type Sel:Offset Attributes Symbol/Owner IDTbase=80036400 Limit=07FF 0000 IntG32 0008:804625E6 DPL=0 P ntoskrnl!Kei386EoiHelper+0590 0001 IntG32 0008:80462736 DPL=3 P ntoskrnl!Kei386EoiHelper+06E0 0002 IntG32 0008:0000144E DPL=0 P 0003 IntG32 0008:80462A0E DPL=3 P ntoskrnl!Kei386EoiHelper+09B8
Листинг 13. Просмотр IDT в soft-ice.
В дополнение к этому малварь может устанавливать в начало (или даже середину!) некоторых ядерных функций jump на свой обработчик, контролирующий целостность перехваченной SST/IDT. Для выявления такого способа перехвата необходимо сравнить образ ядра с файлом NTOSKRNL.EXE, что можно осуществить при помощи утилиты PE-TOOLS с плагином eXtreme Dumper или сдампить ядро непосредственно из самого soft-ice (что намного надежнее) с установленным расширениями IceExt или IceDump.
Вот два основных пути проникновения малвари на компьютер - файлы, запускаемые самим пользователем и дырявое программное обеспечение (последнее преимущественно относится к IE и линейке NT). И если первое еще можно как-то предотвратить (не открывать никаких потенциально опасных вложений, полученных по почте; пользоваться приложениями только от проверенных поставщиков; не скачивать crack'и, написанные непонятно кем и неизвестно для чего), то от дыр никуда не уйти. Даже если пересесть с IE на Lynx, останутся дефекты оси, коих в NT ну просто дофига и постоянно обнаруживаются все новые, ранее неизвестные. То есть, это нам они неизвестные, а кому-то очень даже хорошо известные и эксплуатируемые.
Никто не может чувствовать себя в безопасности, если не будет регулярно проверять все закоулки системы своими руками, хвостом, ну и конечно, могучим soft-ice со всей его свитой.