ТЕХНИКА ДИЗАССЕМБЛИРОВАНИЯ ЯДРА =============================== Усыпляющее шептание алых просторов Сон зовет меня и мои мечты освобождены Оставленная мной действительность Hе важно, если я не проснусь (c) by Anathema Ядро Windows NT написано, в основном, на C и поэтому сам процесс дизассемблирования, в общем, не является сложным. Обычно, обращение к локальным переменным и к параметрам происходит с помощью адресации фрейма стека через EBP. Например: PAGE:801932D4 mov eax, large fs:0 PAGE:801932DA push ebp PAGE:801932DB mov ebp, esp ; фрейм в стеке PAGE:801932DD push 0FFFFFFFFh PAGE:801932DF push offset $T13371 PAGE:801932E4 push offset __except_handler3 PAGE:801932E9 push eax PAGE:801932EA xor eax, eax PAGE:801932EC mov large fs:0, esp PAGE:801932F3 sub esp, 64h ; место в стеке под локальные ; переменные PAGE:801932F6 mov [ebp+Flag], al ; работа с локальными ; переменными Иногда, компилятор генерирует более оптимизированный код, и обращение к стеку идет прямо через ESP. .text:80124320 sub esp, 8 .text:80124323 test byte ptr ds:_NtGlobalFlag+2, 8 .text:8012432A push ebx .text:8012432B push esi .text:8012432C push edi .text:8012432D push ebp .text:8012432E jnz loc_FFFFF000_80124443 .text:80124334 mov esi, [esp+18h+arg_0] ; обращение к 1-му ; параметру Особенностью дизассемблера IDA PRO является возможность отслеживания состояния стека, поэтому создаются соответствующие константы, как в приведенных примерах. При вызове функций, параметры предаются в обратном порядке через стек. Вызываемая функция обязана сама очистить стек. (Так генерируется код компилятором для C функций). PAGE:80193288 push [ebp+ExceptPort] PAGE:8019328B push [ebp+DebugPort] PAGE:8019328E push [ebp+SectionHandle] PAGE:80193291 push [ebp+bInheritHandle] PAGE:80193294 push edx ; ffffffff PAGE:80193295 push [ebp+ObjectAttributes] ;(3 параметр) PAGE:80193298 push [ebp+Access] ; (2 параметр) PAGE:8019329B push ecx ; Handle (1 параметр) PAGE:8019329C call _PspCreateProcess@32 ; PspCreateProcess (Handle,Access, ; ObjectAttributes,-1,bIbjeritHandle.SectionHandle,DebugPort,ExceptPort); PAGE:801932A1 PAGE:801932A1 NtCreateProcessExit: ; CODE XREF: _NtCreateProcess@32+71 j PAGE:801932A1 ; _NtCreateProcess@32+80 j PAGE:801932A1 mov ecx, [ebp+var_10] PAGE:801932A4 pop edi PAGE:801932A5 mov large fs:0, ecx PAGE:801932AC pop esi PAGE:801932AD pop ebx PAGE:801932AE mov esp, ebp PAGE:801932B0 pop ebp PAGE:801932B1 retn 20h ; очистить стек (прибавить к ESP 0x20 после возврата) В целях повышения быстродействия, иногда в ядре ОС используются fastcall функции, параметры к которым передаются через стек. Например: PAGE:8018CC9D mov ecx, [ebp+pObject] ; 1й параметр (сл. Параметр был бы в edx) PAGE:8018CCA0 call @ObfDereferenceObject@4 ; fastcall функция В данном примере используется внутренняя не экспортируемая функция ядра. В следующем примере используется недокументированная экспортируемая HAL.DLL fastcall функция. .text:801335E2 mov ecx, eax ; OldIrql .text:801335E4 call ds:__imp_@KfLowerIrql@4 Использование именно fastcall функция определяется по наличию в имени функции символа 'f'. Microsoft поставляет к SP символьную информацию, которая использовалась как отладочная. Эта информация позволяет определить "истинные" имена внутренних (не экспортируемых) функций и глобальных данных так, как они были названы на C. Это упрощает работу , так как назначение функции или переменной становится понятным из ее названия и, кроме того, по присутствующему постфиксу @N можно определить количество параметров функции. Отладочная информация поставляется в виде .DBG файла для NT 4.0 и .PDB файла для Windows 2K. Инструментальные средства SoftICE и IDA понимают PDB и DBG файлы (причем, для IDA использовался плагин, чтобы загрузить ntoskrnl.pdb) Название функции, переменной или структуры в ядре несет в себе некоторую информацию. Префикс обычно представлен двумя буквами, которые описывают принадлежность функции или данных к подсистеме. Примеры: Mm - подсистема памяти, Cc - кэш, Ob - менеджер памяти, Ps - управление процессами, Se - менеджер памяти, Ke - другие структуры ядра, Ex - исполнительная система. Если функция является инициализирующей (или ее можно перечислить к такого рода функции), к первой букве префикса добавляется литера 'i'. Например: Ki, Mi. Для Fastcall функции к префиксу добавлена литера 'f'. Системные вызовы начинаются с префикса Nt. Эти функции не экспортируются ядром - их адреса записаны в таблице сервисов. Вызов сервиса выполняется с помощью программного прерывания 0x2e. Ядро экспортирует функции Zw, которые являются заглушками - прерываниями. .text:8011A49C _ZwCreateFile@44 proc near ; CODE XREF: _FsRtlpOpenDev@8+4D p .text:8011A49C arg_0 = byte ptr 4 .text:8011A49C mov eax, 17h .text:8011A4A1 lea edx, [esp+arg_0] .text:8011A4A5 int 2Eh ; обработчик прерывания вызовет NtCreateFile .text:8011A4A7 retn 2Ch .text:8011A4A7 _ZwCreateFile@44 endp Не знаю, что означают символы Zw (и, похоже, это знают только разработчики ядра). Возможно, это Zero Wheel (или нулевое кольцо), так как Zw функции вызываются из режима ядра (в DDK описаны некоторые из них). Из пользовательского режима, системные сервисы вызываются из NTDLL (выполняющейся в режиме пользователя), которая экспортирует NtXXX заглушки аналогичные ZwXXX. В ядре присутствуют и другие имена - но в основном для имен применяется только что описанное правило. Ну и, конечно, сами имена несут смысловую нагрузку. Обычно это действие и объект, над которым производится действие. Например : ObCreateObject. Многие функции ОС являются просто заглушками (врапперами) во внутренние функции ядра. Например, NtCreateSection вызывает MmCreateSection с теми же параметрами. Теперь, если учесть то, что многие прототипы Nt функций известны исследователям ядра ОС Windows NT, хотя и не являются документированными, становится понятно, что многие прототипы внутренних функций можно получить и без реверсинга. По прототипу функции на языке C, изучить ее структуру и принцип работы гораздо легче. В принципе, информацию о ядре проще получить дизассемблируя не образ ядра, а другой код. Например, используя код kernel-mode extensions (расширений ядра) для WinDbg. Расширения WinDbg поддерживают дополнительные команды, расширяющие набор команд отладчика. Среди них имеются команды явно работающие с внутренними структурами ядра , или позволяющие понять логику работы внутренних структур ядра. Примеры команд: !ca, !tokenfields, !processfields и др. Путем дизассемблирования кода kdextx86.dll и kdex2x86.dll была получена информация о некоторых структурах. Теперь немного о расширениях WinDbg. (использована инфа от mamaich'a) Расширение отладчика ядра это .DLL. Команды расширения экспортируются под теми же именами, под которыми они задаются в отладчике WinDbg. Например, processfields. DLL расширения экспортирует функцию WinDbgExtensionDllInit, которая вызывается из отладчика WinDbg после загрузки расширения. Прототип функции выглядит так: VOID WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis, USHORT MajorVersion, USHORT MinorVersion) Первым параметром в прототипе функции является указатель на API который используется в модуле .DLL. Структура WINDBG_EXTENSION_APIS содержит следующие члены, которые определяют набор функций доступных расширению: lpOutputRoutine - вывод строки на консоль lpGetExpressionRoutine - вычислить значение выражения lpGetSymbolRoutine - получить адрес символа в памяти lpDisasmRoutine - дизассемблировать память lpCheckControlCRoutine - проверка нажатия CTRL-C (не реализована) lpReadProcessMemoryRoutine - чтение памяти процесса с защитой от GPF lpWriteProcessMemoryRoutine - запись памяти lpGetThreadContextRoutine - получить значения регистров процессора lpSetThreadContextRoutine - установить регистры lpIoctlRoutine - не реализована lpStackTraceRoutine - трассировка стека Таким образом модуль .DLL экспортирует функции, соответствующие командам расширения, и может использовать для взаимодействия с отладчиком ограниченный и известный набор функций. Далее приводится прототип функции, реализующей команду расширения. #define DECLARE_API32(s) \ CPPMOD VOID \ s( \ HANDLE hCurrentProcess, \ HANDLE hCurrentThread, \ ULONG dwCurrentPc, \ ULONG dwProcessor, \ PCSTR args \ ) Представляет интерес параметр args, который указывает на строку с командой введенной в WinDbg. Полученной при дизассемблирования информации достаточно , чтобы изучить логику работы команды расширения. При исследовании в первую очередь были выделены команды, работающие непосредственно со структурами ядра и показывающие содержимое их полей. Например, код команды !ca описывает структуры ядра control area и segment. Такие команды обычно имеют несложную логику: распознать командную строчку, прочесть из памяти ядра интересуемую структуру, распечатать содержимое полей. Однако, часто команды расширения показывают не все содержимое структур ядра . К тому же, не всегда назначение поля понятно из его названия, но дизассемблирование таких команд облегчает анализ внутренних функций ядра. В любом случае, появляется возможность сопоставить информацию, полученную из разных источников. --------------------------------------------------------------------------- (c)Gloomy aka Peter Kosyh, Melancholy Coding'2001 http://gloomy.cjb.net mailto:gl00my@mail.ru