воскресенье, 20 февраля 2011 г.

Высокопроизводительный трейсер


Совершенно очевидно, что если говорить о высокопроизводительном трейсере, то вариант с Debugging API сразу отпадает, т.к. использует высокоуровневую модель сверху и LPC сообщения снизу.

Отладка посредством порта ислючений также не подходит под определение "высокопроизводительно", т.к. в основе лежит все тот же механизм LPC.

Т.е. вариант один - спускаться в ядро, на самый низкий уровень.

Путей реализации ring0 трейсера тоже несколько, один из них это перехват 1го прерывания и установка в обработчике TF флага, также можно установить свою KiDebugRoutine и так далее, вариантов масса.

Однако замена в IDT хендлера 1го прерывания плоха тем, что нужно самостоятельно строить трап фрейм, а это время.

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

Поэтому мне видится оптимальным вариант вобще без задействования ядерного, а стало быть и юзермодного дисперчеров исключений, а также без замены хендлера в IDT, идея в том, чтобы перехватить сам код обработчика int 1, встроив свою ф-цию, которая будет вызывать ф-цию с реализацией логики трейсера, а после этого передавать управление не на ядерный диспетчер исключений, а сразу на код выхода из ISR (Kei386EoiHelper). В пользовательскую ф-цию передается указатель на трап фрейм, таким образом на руках будет вся нужная трейсеру информация, а на выходе из пользовательской ф-ции устанавливается трейс флаг.

Таким образом, такой способ реализации дает возможность избежать тяжеловесного кода по разруливанию ядром трассировочного исключения, и переключения контекста r0->r3 при передаче управления на юзермодный диспетчер исключений(KiUserExceptionDispatcher).

Никаких сложностей и проблем с поиском места для патча, и ф-ции Kei386EoiHelper нет.

Саму логику также предпочтительнее реализовывать в ядре(для того, чтобы избежать ненужных переключений r0-r3), все что нужно сделать в юзермоде, это передать установки для трейсера в ядро, и запустить приложение с введенным трейс флагом(хотя последнее можно делать и из нотификатора в ядре).

Что же касается темы выхода из трассировки, то это отдельная тема, о ней как-нибудь в другой раз.

Варианты использования трейсера наверно объяснять не нужно. Ниже пара картинок, первая это entry point нодов из блокнота, вторая тоже самое, но пропущенная через трейсер, цветом показана интенсивность попадания управления на данный нод(красный, как наверно понятно, взялся изза цикла). Одинокий нод справа - кусок от реализации высокоуровнего seh'a( хендлер из scopetable если точнее).



четверг, 10 февраля 2011 г.

Определение id сервисной ф-ции

Предположим, перехвачена ф-ция на уровне менеджера объектов, к примеру ObCreateObject.
Как определить, какая сервисная ф-ция вызвала перехваченную ObCreateObject?
Ну, логично брякнуться внутри перехваченной ф-ции, и посмотреть стек вызовов:

kd> k
ChildEBP RetAddr 
ee6aece0 f7966345 Template!InternalObCreateObject+0xee
ee6aece4 8060528e Template!NewObCreateObject+0x5
ee6aed48 8053d648 nt!NtCreateEvent+0x90
ee6aed48 7c90e514 nt!KiFastCallEntry+0xf8
00abfd74 7c90d09a ntdll!KiFastSystemCallRet
00abfd78 7c80a78a ntdll!NtCreateEvent+0xc
00abfdc4 76d525a0 kernel32!CreateEventW+0x67
WARNING: Frame IP not in any known module. Following frames may be wrong.
00abfe68 7c917719 0x76d525a0
00abffa0 77de354b ntdll!LdrpGetProcedureAddress+0xa6
00abffbc 00000000 ADVAPI32!ScSvcctrlThreadA+0x12

А дальше казалось бы, логично проделать теже действия, что и WinDbg, и достать id из стаба ntdll, т.е. выполнить стек бектрейс, например, используя RtlCaptureStackBackTrace. И все бы хорошо, но данная ф-ция вернет лишь три верхних строчки из приведенного лога. И это понятно, т.к. ф-ция прыгает по цепочкам ebp фреймов, но допрыгать ф-ция может лишь до места, где копируется юзермодный стек в ядерный ( в обработчике sysenter ).

Где же взять информацию о юзермодном стеке? Ответ прост: в трап фрейме:

kd> dt nt!_KTRAP_FRAME 0xee6aed64
...
   +0x070 EFlags           : 0x202
   +0x074 HardwareEsp      : 0xabfd78 <========== юзермодный стек
   +0x078 HardwareSegSs    : 0x23

а вот и то, что нужно:

kd> dds 0xabfd78
00abfd78  7c90d09a ntdll!NtCreateEvent+0xc
00abfd7c  7c80a78a kernel32!CreateEventW+0x67

kd> u 7c90d09a
ntdll!NtCreateEvent+0xc:
7c90d09a c21400          ret     14h
7c90d09d 90              nop

Теперь можно прочитать id:

kd> u NtCreateEvent
ntdll!ZwCreateEvent:
7c90d08e b823000000      mov     eax,23h <== id
7c90d093 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c90d098 ff12            call    dword ptr [edx]
7c90d09a c21400          ret     14h

То есть, бектрейс в данном случае оказался совсем не нужным.
На х64 все еще проще, id сискола сохраняется в KTHREAD_SystemCallNumber(название по памяти пишу, могу ошибаться в имени поля).

вторник, 8 февраля 2011 г.

Как опознать TrapFrame в памяти (х86)

Сделать это довольно просто, т.к. трап фрейм имеет некую метку, по которой его можно опознать в памяти ( ну и дополнительные признаки, чтобы быть полностью уверенным - значение сегментных регистров, eflags/eip и т.д.).

Как известно, при любом прерывании/sysenter, обработчик прерывания/sysenter'a строит трап фрейм - заполняет структуру KTRAP_FRAME в стеке, точнее дополняет, т.к. некоторые члены структуры оказываются в стеке автоматически при прерывании(eflags, cs, eip, error code для без кольцевого переключения например), при этом в поле TsDbgArgMark заносится маска 0BADB0D00h ( макрос SET_DEBUG_DATA в сорцах ).

Таким образом, к примеру:

kd> dds 0xee8a9d64
ee8a9d64  00c0f7dc
ee8a9d68  7c90e514
ee8a9d6c  badb0d00 <== та самая метка трапфрейма
ee8a9d70  00c0f7b8
ee8a9d74  00000000

Чтобы убедиться:

kd> dt nt!_KTRAP_FRAME 0xee8a9d64
   +0x000 DbgEbp           : 0xc0f7dc
   +0x004 DbgEip           : 0x7c90e514
   +0x008 DbgArgMark       : 0xbadb0d00<== метка
   +0x00c DbgArgPointer    : 0xc0f7b8
   +0x010 TempSegCs        : 0
   +0x014 TempEsp          : 0
   +0x018 Dr0              : 0
   +0x01c Dr1              : 0
   +0x020 Dr2              : 0
   +0x024 Dr3              : 0
   +0x028 Dr6              : 0
   +0x02c Dr7              : 0
   +0x030 SegGs            : 0
   +0x034 SegEs            : 0x23         <=== дополнительный признак
   +0x038 SegDs            : 0x23         <=== дополнительный признак
   +0x03c Edx              : 0x15f0002
   +0x040 Ecx              : 0xc6270
   +0x044 Eax              : 0xc6270
   +0x048 PreviousPreviousMode : 1  <=== дополнительный признак
   +0x04c ExceptionList    : 0xffffffff    <=== дополнительный признак
   +0x050 SegFs            : 0x3b         <=== дополнительный признак
   +0x054 Edi              : 0
   +0x058 Esi              : 0
   +0x05c Ebx              : 0xc0f7f4
   +0x060 Ebp              : 0xc0f7dc
   +0x064 ErrCode          : 0
   +0x068 Eip              : 0x7c90e514  <=== дополнительный признак (валидный адрес)
   +0x06c SegCs            : 0x1b          <=== дополнительный признак
   +0x070 EFlags           : 0x246         <=== дополнительный признак
   +0x074 HardwareEsp      : 0xc0f7b0
   +0x078 HardwareSegSs    : 0x23     <=== дополнительный признак
   +0x07c V86Es            : 0
   +0x080 V86Ds            : 0
   +0x084 V86Fs            : 0
   +0x088 V86Gs            : 0

Т.к. селекторы известны и постоянны:

#define KGDT_NULL           0
#define KGDT_R0_CODE     8
#define KGDT_R0_DATA     16
#define KGDT_R3_CODE     24
#define KGDT_R3_DATA     32
#define KGDT_TSS              40
#define KGDT_R0_PCR        48
#define KGDT_R3_TEB        56
#define KGDT_VDM_TILE    64
#define KGDT_LDT              72
#define KGDT_DF_TSS       80
#define KGDT_NMI_TSS      88

Это дает нам практически 100%ю гарантию, что найден именно трап фрейм, а не участок памяти с совпавшей TsDbgArgMark.

Метка эта нигде не проверяется(в релизной версии винды) и была введена для отладки.
Для x64 она отсутствует.