Контрразведка с Soft-ice в руках

Автор: (c)Крис Касперски ака мыщъх

Антивирусы (даже со всеми апдейтами) далеко не всегда распознают малварь и опытные хакеры доверяют только своим собственному хвосту, отладчику soft-ice и другому низкоуровневому инструментарию, позволяющему пробурить нору до самого ядра и разоблачить зловредные программы, где бы они ни скрывались!

Введение

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

Обнаружить качественно спроектированную и грамотно заложенную закладку усилиями одного человека за разумное время - навряд ли возможно. Намного проще (да и быстрее) переустановить Windows с нуля. К счастью, качественная малварь - огромная редкость, практически не встречающая в живой природе и в основном приходится сталкиваться с пионерскими поделками, оставляющими после себя кучу следов и легко различимыми с помощью soft-ice и сопутствующих ему утилит.

Весь вопрос в том - как правильно ими пользоваться. Ну, установили мы soft-ice, нажали <CTRL-D>, увидели черный экран... Дальше-то что?! А вот дальше на сцене появился мыщъх и, плюхнувшись в кресло (предварительно подтолкнув под себя хвост), начинает делиться хакерскими секретами.

Windows

Рисунок 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 и для отображения списка процессов самостоятельно разбирает базовые структуры операционной системы, а потому легко выявляет скрытые процессы.

Soft-ice

Рисунок 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.

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

#include <stdio.h>
#include <windows.h>

// код потока, который ничего не делает, а только мотает цикл
thread(){while(1);}

main()
{
        void *p; // переменная многоцелевого назначения

        // создаем "честный" поток

        CreateThread(0, 0, (void *)&thread, 0x999, 0, &p);

        // создаем "нечестный" поток так, как это делает malware:
        // выделяем блок памяти из кучи, копируем туда код потока
        // и вызываем CreateThread

        p = VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        memcpy(p, thread, 0x1000); CreateThread(0, 0, p, 0x666, 0, &p);

        // ждем нажатия на ENTER
        gets(&p);
}

Листинг 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".

Process Explorer

Рисунок 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) и полный анализ требует уймы времени, однако, как говорилось выше, умная малварь - большая редкость и подделкой стартовых адресов потоков никто не занимается.

Восстановление SST

Для сокрытия своего присутствия в системе, малварь нередко внедряется в ядро системы и перехватывает один или несколько сервисов, например, функцию 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:

Поиск SST в файле NTOSKRNL.EXE

Рисунок 11. Поиск SST в файле NTOSKRNL.EXE по перекрестным ссылкам.

Если лень восстанавливать SST вручную, можно воспользоваться бесплатной утилитой "Win2K/XP SDT Restore" от Tan Chew Keong, результат работы которой продемонстрирован ниже:

Официальный сайт утилиты SDT Restore

Рисунок 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 со всей его свитой.

Ссылки на программы, упомянутые в статье