Из всех перехватчиков sysenter работающих через перезапись MSR_SYSENTER_EIP, я не видел ни одного правильно написанного.
Ну то есть они конечно работали, перехватывали, но тем не менее своим перехватом они делали возможным создать условие для Denial of Service атаки, то есть к банальному BSOD'у.
Теорию я описывать не буду, да там по сути и описывать нечего, все тривиально, а вот суть атаки опишу:
push EFLAGS_TF
popfd
sysenter
То есть, выполняем sysenter с взведенным TF флагом.
Далее, при неправильно написанном MSR перехватчике будет бсод.
Чтобы разобраться, почему так происходит нужно взглянуть на обработчик Single-step trap'а:
_KiTrap01:
...
// проверяется EIP при исключении, и если он равен KiFastCallEntry, прыгаем на TF_Dispatch
.text:00467393 mov ecx, [ebp+68h] // KTRAP_FRAME.Eip
.text:00467396 cmp ecx, offset _KiFastCallEntry
.text:0046739C jz TF_Dispatch
...
// а в нем переустанавливаем KTRAP_FRAME.Eip в KiFastCallEntry2 и прыгаем на общий код выхода из interrupt/exception:
.text:00467268 TF_Dispatch:
.text:00467268 mov dword ptr [ebp+68h], offset _KiFastCallEntry2
.text:0046726F and byte ptr [ebp+71h], 0FEh
.text:00467273 jmp Kei386EoiHelper@0
Т.е. ядро имеет специальный case для обработки ситуации при single step на sysenter, перенаправляя управление на KiFastCallEntry2.
Самописные MSR hooker's этого не делают, и в этом их ошибка.
Правильно написанный MSR hooker должен пропатчить ядро, т.е. заменить
cmp ecx, offset _KiFastCallEntry на cmp ecx, offset _YourFastCallEntry в KiTrap01.
Интересно как в софтверных компаниях разруливают подобные ситуации, ведь баги подобные этим не обнаружит ни один тестер, как бы хорош он не был. Да и code review ничего не даст.
среда, 10 ноября 2010 г.
воскресенье, 7 ноября 2010 г.
Изменения SSDT на x64
В 64 битных windows внутренности System Service Table изменились, например, вместо адресов в KiServiceTable находятся смещения, а KiArgumentTable не используется вовсе.
Причем глядя в IDA на KiServiceTable все это как-то неочевидно:
.text:000000014005B000 KiServiceTable dq offset NtMapUserPhysicalPagesScatter
.text:000000014005B008 dq offset NtWaitForSingleObject
.text:000000014005B010 dq offset NtCallbackReturn
.text:000000014005B018 dq offset NtReadFile
.text:000000014005B020 dq offset NtDeviceIoControlFile
.text:000000014005B028 dq offset NtWriteFile
.text:000000014005B030 dq offset NtRemoveIoCompletion
Однако стоит в отладчике посмотреть якобы "адреса" в KiServiceTable:
kd> dqs nt!KiServiceTable
fffff800`01c6e000 025ff700`03935200
fffff800`01c6e008 022b7d05`fff95e00
fffff800`01c6e010 0268e605`026b1506
fffff800`01c6e018 021d6440`02389701
... чтобы увидеть, что на адреса это мало похоже(адреса то не полноценно 64х битные, а всего лишь 48ми битные). А если смотреть двойные слова:
kd> dds nt!KiServiceTable
fffff800`01c6e000 03935200
fffff800`01c6e004 025ff700
fffff800`01c6e008 fff95e00
fffff800`01c6e00c 022b7d05 <== 5 это число стековых аргументов ф-ции
fffff800`01c6e010 026b1506
... то видно, что это офсеты.
А KiArgumentTable не используется, потому что аргументы закодированы в 4х младших битах офсета.
Напомню, что 4 аргумента всегда передаются через регистры ( rcx, rdx, r8, r9 ), остальные через стек, таким образом для ф-ций с <= 4 агрументами количество этих самых аргументов нигде не храниться.
Взглянув на обработчик syscall'ов, ф-цию KiSystemCall64 ( получить можно через MSR(LSTAR) ) видно следующее:
KiSystemCall64:
movsxd r11, dword ptr [r10+rax*4] <== берем из KiServiceTable LONG ( именно LONG, ибо оффсет знаковый, может быть отрицательным )
mov rax, r11
sar r11, 4 <== 4 бита убираем, чтобы получить чистый офсет
add r10, r11 <== KiServiceTable + полученный офсет = адрес сервисной ф-ции
...
далее если необходимо копируются стековые аргументы из юзермодного стека в ядерный, и вызывается сервисная ф-ция.
Преобразование таблички из той, что видно в IDA, в ту, которая на самом деле лежит в памяти делает ф-ция KeCompactServiceTable на этапе инициализации ядра.
Если взглянуть на nt!KiArgumentTable, то видно, что она тоже заполнена, однако не используется. Почему сделано так, а не иначе мне неведомо, если кто знает, опишитесь в комментах.
Причем глядя в IDA на KiServiceTable все это как-то неочевидно:
.text:000000014005B000 KiServiceTable dq offset NtMapUserPhysicalPagesScatter
.text:000000014005B008 dq offset NtWaitForSingleObject
.text:000000014005B010 dq offset NtCallbackReturn
.text:000000014005B018 dq offset NtReadFile
.text:000000014005B020 dq offset NtDeviceIoControlFile
.text:000000014005B028 dq offset NtWriteFile
.text:000000014005B030 dq offset NtRemoveIoCompletion
Однако стоит в отладчике посмотреть якобы "адреса" в KiServiceTable:
kd> dqs nt!KiServiceTable
fffff800`01c6e000 025ff700`03935200
fffff800`01c6e008 022b7d05`fff95e00
fffff800`01c6e010 0268e605`026b1506
fffff800`01c6e018 021d6440`02389701
... чтобы увидеть, что на адреса это мало похоже(адреса то не полноценно 64х битные, а всего лишь 48ми битные). А если смотреть двойные слова:
kd> dds nt!KiServiceTable
fffff800`01c6e000 03935200
fffff800`01c6e004 025ff700
fffff800`01c6e008 fff95e00
fffff800`01c6e00c 022b7d05 <== 5 это число стековых аргументов ф-ции
fffff800`01c6e010 026b1506
... то видно, что это офсеты.
А KiArgumentTable не используется, потому что аргументы закодированы в 4х младших битах офсета.
Напомню, что 4 аргумента всегда передаются через регистры ( rcx, rdx, r8, r9 ), остальные через стек, таким образом для ф-ций с <= 4 агрументами количество этих самых аргументов нигде не храниться.
Взглянув на обработчик syscall'ов, ф-цию KiSystemCall64 ( получить можно через MSR(LSTAR) ) видно следующее:
KiSystemCall64:
movsxd r11, dword ptr [r10+rax*4] <== берем из KiServiceTable LONG ( именно LONG, ибо оффсет знаковый, может быть отрицательным )
mov rax, r11
sar r11, 4 <== 4 бита убираем, чтобы получить чистый офсет
add r10, r11 <== KiServiceTable + полученный офсет = адрес сервисной ф-ции
...
далее если необходимо копируются стековые аргументы из юзермодного стека в ядерный, и вызывается сервисная ф-ция.
Преобразование таблички из той, что видно в IDA, в ту, которая на самом деле лежит в памяти делает ф-ция KeCompactServiceTable на этапе инициализации ядра.
Если взглянуть на nt!KiArgumentTable, то видно, что она тоже заполнена, однако не используется. Почему сделано так, а не иначе мне неведомо, если кто знает, опишитесь в комментах.
четверг, 4 ноября 2010 г.
Важность дополнительной информации при анализе крешдампов
IRQL_NOT_LESS_OR_EQUAL (a)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If a kernel debugger is available get the stack backtrace.
Arguments:
Arg1: e25393ec, memory referenced
Arg2: 0000001c, IRQL
Arg3: 00000000, bitfield
Arg4: 804fac65, address which referenced memory
Debugging Details:
READ_ADDRESS: e25393ec Paged pool
CURRENT_IRQL: 1c
Именно такой BSOD начал вылезать на winXP sp2 в моем драйвере.
Анализ крешдампа проблему падения драйвера не решил, напротив, возникли новые, еще более интересные вопросы =]
Итак, из крешдампа стало ясно, что при вызове системой ф-ции KeWaitForSingleObject происходил доступ к выгружаемой памяти на высоком IRQL.
И все бы ничего, кроме двух мистических вещей.
Первая заключалась в том, что память к которой было обращение принадлежала первому параметру ф-ции KeWaitForSingleObject, а именно объекту.
Причем память эта была выгружаемой, а как известно, объекты ядра находятся в невыгружаемой памяти. Т.е. память явно не принадлежала объекту, что и подтвердила команда !object.
Вторая мистическая вещь - это запредельный IRQL, равный CLOCK LEVEL.
Самым очевидным выводом всей этой истории было предположение, что проблема в железе.
Однако спустя некоторое время у меня появился другой дамп, тоже с этим же багчеком, но стек вызовов был другим, уже без вызова KeWaitForSingleObject:
a334fbc4 804f9fdb badb0d00 e24801bc 804f9fbe ntKiTrap0E+0x238
a334fc40 806e498e e24801b4 00000000 00000000 ntKeSetEventBoostPriority+0x1f
a334fc50 80569d29 e243a4e0 e2480198 805593a0 halExReleaseFastMutex+0x1a
a334fc64 80569d77 e2480198 a334fc80 00000000 ntCmpCheckRecursionAndRecordThreadInfo+0x47
a334fc94 80634f9c 0000000e a334fcb8 00000000 ntCmpCallCallBacks+0x3b
a334fcb0 805b9e2d e38818f8 00000000 e38818e0 ntCmpDeleteKeyObject+0x22
a334fccc 805257b8 e38818f8 00000000 000004fc ntObpRemoveObjectRoutine+0xdf
a334fce4 805bad03 883a3020 e32ebc58 879c8020 ntObfDereferenceObject+0x4c
a334fcfc 805bad99 e32ebc58 e38818f8 000004fc ntObpCloseHandleTableEntry+0x155
a334fd44 805baed1 000004fc 00000001 00000000 ntObpCloseHandle+0x87
a334fd58 8054060c 000004fc 04e1fe10 7c90eb94 ntNtClose+0x1d
a334fd58 7c90eb94 000004fc 04e1fe10 7c90eb94 ntKiFastCallEntry+0xfc
04e1fdfc 7c90d592 77dc6bcc 000004fc 00000000 ntdllKiFastSystemCallRet
04e1fe00 77dc6bcc 000004fc 00000000 04e1fe1c ntdllZwClose+0xc
Дальше было просто, вместо отладчика я открыл...google! Да-да, он иногда бывает полезнее отладчика. И через несколько минут нашел решение своей проблемы: http://support.microsoft.com/kb/936456/ - виноват был не драйвер, а система.
Но вопрос о высоком IRQL оставался открытым.
Изучение ядерного дампа, ничего не изменило, проблема оставалась.
Оставалось смотреть внутренности KeWaitForSingleObject и там я обнаружил одну интересную вещь, а именно, ф-цию KiUnlockDispatcherDatabase.
Однако захват и освобождение базы данных диспатчера делались на DISPATCH LEVEL'е.
Я знал, что решение где-то рядом, поэтому следующим шагом был обзор всех подобных ф-ций, и через определенное время забрежжил свет в конце туннеля. Нашлась ф-ция KeRaiseIrqlToSynchLevel, которая задирала IRQL до SYNCH_LEVEL.
#define IPI_LEVEL 29
#define POWER_LEVEL 30
#define HIGH_LEVEL 31
#define SYNCH_LEVEL (IPI_LEVEL-1) <= как раз 28 или 1Ch
Казалось, кусочки паззла наконец встали на свои места, для уверенности я полез в hal.dll, и увидел там совсем не то, что ожидал:
.text:80012700 @KeAcquireSpinLockRaiseToSynch@4 proc near
.text:80012700 mov edx, ds:0FFFE0080h
.text:80012706 mov eax, edx
.text:80012708 shr eax, 4
.text:8001270B movzx eax, ds:_HalpVectorToIRQL[eax]
.text:80012712 mov dword ptr ds:0FFFE0080h, 41h <=== APIC[TASK PRIORITY REGISTER] = DPC VECTOR
.text:8001271C retn
.text:8001271C @KeAcquireSpinLockRaiseToSynch@4 endp
Никакого SYNCH_LEVEL'a не было и в помине...
Однако, открыв полный дамп с машины на которой был бсод, увидел следующее:
hal!KeAcquireSpinLockRaiseToSynch:
806e4870 mov edx,dword ptr ds:[0FFFE0080h]
806e4876 mov eax,edx
806e4878 shr eax,4
806e487b movzx eax,byte ptr hal!HalpVectorToIRQL (806ef218)[eax]
806e4882 test dword ptr [ecx],1
806e4888 jne hal!KeAcquireSpinLockRaiseToSynch+0x34 (806e48a4)
806e488a mov dword ptr ds:[0FFFE0080h],0D1h <=== APIC[TASK PRIORITY REGISTER] = CLOCK VECTOR
Т.е. реализации hal также как и ядра, различаются на однопроцессорных и многопроцессорных системах, и вначале я смотрел однопроцессорную версию, в которой DISPATCH LEVEL'a вполне достаточно.
Теперь все встало на свои места, система действительно может вызывать код на запредельном IRQL( на MP системах ) и к поломке железа мой бсод не имел никакого отношения.
Выводы всей этой истории банальны, но это не отменяет их важности - чем больше различных источников информации об ошибке вы имеете, тем быстрее вы найдете решение проблемы. В моем случае это был второй крешдамп, с другим колстеком.
End of story... =]
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If a kernel debugger is available get the stack backtrace.
Arguments:
Arg1: e25393ec, memory referenced
Arg2: 0000001c, IRQL
Arg3: 00000000, bitfield
Arg4: 804fac65, address which referenced memory
Debugging Details:
READ_ADDRESS: e25393ec Paged pool
CURRENT_IRQL: 1c
Именно такой BSOD начал вылезать на winXP sp2 в моем драйвере.
Анализ крешдампа проблему падения драйвера не решил, напротив, возникли новые, еще более интересные вопросы =]
Итак, из крешдампа стало ясно, что при вызове системой ф-ции KeWaitForSingleObject происходил доступ к выгружаемой памяти на высоком IRQL.
И все бы ничего, кроме двух мистических вещей.
Первая заключалась в том, что память к которой было обращение принадлежала первому параметру ф-ции KeWaitForSingleObject, а именно объекту.
Причем память эта была выгружаемой, а как известно, объекты ядра находятся в невыгружаемой памяти. Т.е. память явно не принадлежала объекту, что и подтвердила команда !object.
Вторая мистическая вещь - это запредельный IRQL, равный CLOCK LEVEL.
Самым очевидным выводом всей этой истории было предположение, что проблема в железе.
Однако спустя некоторое время у меня появился другой дамп, тоже с этим же багчеком, но стек вызовов был другим, уже без вызова KeWaitForSingleObject:
a334fbc4 804f9fdb badb0d00 e24801bc 804f9fbe ntKiTrap0E+0x238
a334fc40 806e498e e24801b4 00000000 00000000 ntKeSetEventBoostPriority+0x1f
a334fc50 80569d29 e243a4e0 e2480198 805593a0 halExReleaseFastMutex+0x1a
a334fc64 80569d77 e2480198 a334fc80 00000000 ntCmpCheckRecursionAndRecordThreadInfo+0x47
a334fc94 80634f9c 0000000e a334fcb8 00000000 ntCmpCallCallBacks+0x3b
a334fcb0 805b9e2d e38818f8 00000000 e38818e0 ntCmpDeleteKeyObject+0x22
a334fccc 805257b8 e38818f8 00000000 000004fc ntObpRemoveObjectRoutine+0xdf
a334fce4 805bad03 883a3020 e32ebc58 879c8020 ntObfDereferenceObject+0x4c
a334fcfc 805bad99 e32ebc58 e38818f8 000004fc ntObpCloseHandleTableEntry+0x155
a334fd44 805baed1 000004fc 00000001 00000000 ntObpCloseHandle+0x87
a334fd58 8054060c 000004fc 04e1fe10 7c90eb94 ntNtClose+0x1d
a334fd58 7c90eb94 000004fc 04e1fe10 7c90eb94 ntKiFastCallEntry+0xfc
04e1fdfc 7c90d592 77dc6bcc 000004fc 00000000 ntdllKiFastSystemCallRet
04e1fe00 77dc6bcc 000004fc 00000000 04e1fe1c ntdllZwClose+0xc
Дальше было просто, вместо отладчика я открыл...google! Да-да, он иногда бывает полезнее отладчика. И через несколько минут нашел решение своей проблемы: http://support.microsoft.com/kb/936456/ - виноват был не драйвер, а система.
Но вопрос о высоком IRQL оставался открытым.
Изучение ядерного дампа, ничего не изменило, проблема оставалась.
Оставалось смотреть внутренности KeWaitForSingleObject и там я обнаружил одну интересную вещь, а именно, ф-цию KiUnlockDispatcherDatabase.
Однако захват и освобождение базы данных диспатчера делались на DISPATCH LEVEL'е.
Я знал, что решение где-то рядом, поэтому следующим шагом был обзор всех подобных ф-ций, и через определенное время забрежжил свет в конце туннеля. Нашлась ф-ция KeRaiseIrqlToSynchLevel, которая задирала IRQL до SYNCH_LEVEL.
#define IPI_LEVEL 29
#define POWER_LEVEL 30
#define HIGH_LEVEL 31
#define SYNCH_LEVEL (IPI_LEVEL-1) <= как раз 28 или 1Ch
Казалось, кусочки паззла наконец встали на свои места, для уверенности я полез в hal.dll, и увидел там совсем не то, что ожидал:
.text:80012700 @KeAcquireSpinLockRaiseToSynch@4 proc near
.text:80012700 mov edx, ds:0FFFE0080h
.text:80012706 mov eax, edx
.text:80012708 shr eax, 4
.text:8001270B movzx eax, ds:_HalpVectorToIRQL[eax]
.text:80012712 mov dword ptr ds:0FFFE0080h, 41h <=== APIC[TASK PRIORITY REGISTER] = DPC VECTOR
.text:8001271C retn
.text:8001271C @KeAcquireSpinLockRaiseToSynch@4 endp
Никакого SYNCH_LEVEL'a не было и в помине...
Однако, открыв полный дамп с машины на которой был бсод, увидел следующее:
hal!KeAcquireSpinLockRaiseToSynch:
806e4870 mov edx,dword ptr ds:[0FFFE0080h]
806e4876 mov eax,edx
806e4878 shr eax,4
806e487b movzx eax,byte ptr hal!HalpVectorToIRQL (806ef218)[eax]
806e4882 test dword ptr [ecx],1
806e4888 jne hal!KeAcquireSpinLockRaiseToSynch+0x34 (806e48a4)
806e488a mov dword ptr ds:[0FFFE0080h],0D1h <=== APIC[TASK PRIORITY REGISTER] = CLOCK VECTOR
Т.е. реализации hal также как и ядра, различаются на однопроцессорных и многопроцессорных системах, и вначале я смотрел однопроцессорную версию, в которой DISPATCH LEVEL'a вполне достаточно.
Теперь все встало на свои места, система действительно может вызывать код на запредельном IRQL( на MP системах ) и к поломке железа мой бсод не имел никакого отношения.
Выводы всей этой истории банальны, но это не отменяет их важности - чем больше различных источников информации об ошибке вы имеете, тем быстрее вы найдете решение проблемы. В моем случае это был второй крешдамп, с другим колстеком.
End of story... =]
Подписаться на:
Сообщения (Atom)