Стоит передо мной такая задачка - перехватывать обращения к портам ввода/вывода. Делаю это, используя DRx. Заменяю первый элемент таблицы IDT своей ловушкой, содержащей адрес точки входа обработчика прерывания. Но из обработчика управление не возвращается! Просто жестокий молчаливый ребут... Подскажите, пожалуйста, как написать минимальный корректный обработчик или скажите, где об этом можно прочесть.
Приведи код обработчика. Попробовать почитать можно тут: http://www.wasm.ru/print.php?article=protect_by_hasp01 Также смотри в дизассмеблере разные эмуляторы типа sspro.vxd.
код обработчика просто возврат: my_handler: iretd я под ХР это делаю и именно по упомянутой статье, но никак... Можно, конечно, смотреть Идой оригинальный обработчик в toskrnl.exe, но там он просто огромен. Да и, судя по статье, должно это работать
Приведи следующие данные: - код, реализующий вставку обработчика в idt, как ты описываешь дескриптор ловушки; - код, вызвавший исключение (наверное, это команда in/out ?), где он находится, его адрес (возможно, можно воткнуть отладку внутрь твоего обработчика, ибо с софтайсом исследовать не получится) ? - Что в стеке в момент вызова int 1 (также только debug_print)
А установка собственного обработчика у меня происходит так: это - ловушка, которую помещаю в IDT ttrap struc offs_l dw ? ; [0] sel dw ? ; [2] rsrv db ? ; [4] attr_ db ? ; [5] offs_h dw ? ; [6] ttrap ends так я ее устанавливаю: lea esi, new_trap ; создаем новый элемент для IDT assume esi:ptr ttrap mov eax, offset new_h mov [esi].offs_l, ax shr eax, 16 mov [esi].offs_h, ax mov ax, cs mov [esi].sel, ax xor al, al mov [esi].rsrv, al mov [esi].attr_, 8Fh assume esi:nothing sidt [esp-6] mov esi, [esp-4] ; eax = IDT Base add esi, 8 ; eax -> Int1h-handler lea edi, orig_trap ; сохраняем оригинальный обработчик lodsd stosd lodsd stosd mov edi, esi ; устанавливаем новый элемент sub edi, 8 lea esi, new_trap lodsd stosd lodsd stosd invoke DbgPrint, addr ms0 ; Выставляем бит DE mov eax, cr4 or eax, 01000b mov cr4, eax ; Port set xor ebx, ebx mov bx, Port mov dr0, ebx add ebx, 2 mov dr1, ebx add ebx, 2 mov dr2, ebx add ebx, 2 mov dr3, ebx ; Включение ловушек GLGLGLGLGL ; 3333222211110000--D---EE33221100 mov eax, 01100110011001100010011111111111b mov dr7, eax код, вызывающий исключение - out в стеке лежит адрес инструкции out (на самом деле должна, ибо с ловушками у меня пока тоже не срабатывает, срабатывает лишь на инсьрукции int 1)
Ну что я могу сказать ? Очень похоже на мой код и ошибок пока вроде не нашел. Дай весь исходник, будет время - проверю под NT (я писал для 98). Настораживает только вот эта фраза: > (на самом деле должна, ибо с ловушками у меня пока тоже > не срабатывает, срабатывает лишь на инсьрукции int 1) Почему не работает ? Ты какие порты хочешь ловить ? Проверь те порты, воткнув команду in прямо сразу после установки прерывания. Да, и вот мой код (из тех же статей): Код (Text): ; подпрограмма - ловушка #1 - debug BD equ 0010000000000000b B0 equ 0001b B1 equ 0010b B2 equ 0100b B3 equ 1000b CMD_IN_AL_DX equ 0ECh ; FEDCBA9876543210 TrapPort proc push ebp mov ebp,esp push eax pushad push ds push es pushfd mov ax,ss mov ds,ax inc dword ptr ds:Counter ; Адрес возврата EIP mov ecx,[ebp+4] ; Адрес возврата CS->ES mov es,word ptr [ebp+8] ; Причина отладочного прерывания mov eax,dr6 test eax,BD jz @@NotDebugRegs ; Запомнить байты инструкции ; Адрес возврата EIP ; Команды мусоренья не запоминаем ;; mov ecx,[ebp+4] ;; call Store_LogRecord inc dword ptr ds:Counter_DRX ; Очищаем бит BD в регистре dr6 and eax, not BD mov dr6,eax ; Если это было обращение к DRx, не нужно выполнять эти команды ; Запоминаем адрес возврата и возвращаемся в наш код add ecx,3 jmp @@AfterDebugRegs @@NotDebugRegs: ; Запомнить байты инструкции ; Адрес возврата EIP mov ecx,[ebp+4] ; Запоминаем только отклики порта LPT ; Адрес возврата CS mov es,word ptr [ebp+8] ; cmp byte ptr es:[ecx-1],CMD_IN_AL_DX ; jnz @@SkipLogPortsCommands sub ecx,1 ; in al,dx... etc (1 байт) call Store_LogRecord add ecx,1 ; back to command @@SkipLogPortsCommands: ; Выполнить команду прямо тут ? ; Причина отладочного прерывания в доступе к портам ? mov eax,dr6 test eax,B0 jz @@NotDR0 ; Очищаем бит B0 в регистре dr6 and eax, not B0 @@NotDR0: test eax,B1 jz @@NotDR1 ; Очищаем бит B1 в регистре dr6 and eax, not B1 @@NotDR1: test eax,B2 jz @@NotDR2 ; Очищаем бит B2 в регистре dr6 and eax, not B2 @@NotDR2: test eax,B3 jz @@NotDR3 ; Очищаем бит B3 в регистре dr6 and eax, not B3 @@NotDR3: mov dr6,eax @@AfterDebugRegs: ; Запоминаем адрес возврата и возвращаемся в наш код mov dword ptr RetAddr_UD01_EIP,ecx mov word ptr RetAddr_UD01_CS,es mov [ebp+4],offset32 After_DebugRegisterUsed mov word ptr [ebp+8],cs popfd pop es pop ds popad pop eax pop ebp iretd TrapPort endp After_DebugRegisterUsed proc pushad pushfd ; Enable DE flag mov eax,cr4 or eax,01000b mov cr4,eax ; FEDCBA9876543210FEDCBA9876543210 mov eax,01100110011001100010011111111111b mov dr7,eax popfd popad jmp dword ptr RetAddr_UD01_EIP After_DebugRegisterUsed endp Обрати внимание вот на это: Код (Text): ; Если это было обращение к DRx, не нужно выполнять эти команды ; Запоминаем адрес возврата и возвращаемся в наш код add ecx,3 Те (я уже всего не помню) после выполнения обращения к dr7 в коде возникает исключение - int 1 - ну у меня там борьба за перехват DRx - и адрес возврата (в стеке) указывает на ту команду, которая вызывала исключение. Возможно (пока версия) в случае int 1 происходит то же самое - ты по iretd возвращаешь код в int 1. А вот если in/out, то адрес возврата на следующую за ней команду.
Плюс еще вот что: попробуй все же воткнуть дебаг принт прямо в код - обработчик прерывания. Ну ведь айс может по прерыванию выводить что-то на экран Так все станет гораздо яснее. Ну или хотя бы Beep какой-нибудь: ; Code that call int 1 nop nop int 1 nop nop ... my_int1_handler: ... ; assume that es:ecx=ret addr cmp byte ptr es:[ecx],90h ; Check for nop jz @@Ok_Return ; here we are strange state - ret_addr is ptr to ... ; what ? while ( 1 ) { Beep(); } @@Ok_Return:
Так это и есть твой код, я его за основу взял Сейчас заработало, хотя ничего принципиально не сменил... Шаманство какое-то То есть обращения к порту теперь перехватываются. Снифаю я последовательный порт. Еще есть несколько вопросов: 1. в регистры dr0, dr1, dr2, dr3 в твоем коде вносятся значения Port, Port+2, Port+4 и Port+6. Это вроде как диапазон базовых адресов последовательного порта. А при обращении к Port+1 отладочное прерывание произойдет? 2. почему-то по закрытии и открытии последовательного порта пользовательской программой перехват прекращается, с чем это может быть связано? Драйвер порта сбрасывает cr4? 3. Ты в своем коде не реализовывал дальнейшую передачу управления оригинальному обработчику? А то в момент снифа портов нельзя трейситься в отладчике. Спасибо, что ответил
Wolfgang Так почему заработало-то ? 1. в регистры dr0, dr1, dr2, dr3 в твоем коде вносятся значения Port, Port+2, Port+4 и Port+6. Это вроде как диапазон базовых адресов последовательного порта. А при обращении к Port+1 отладочное прерывание произойдет? Ну судя по книгам (Зубков СВ, "Ассемблер - язык неограниченных возможностей", есть в инете в chm), должно быть. У меня размер диапазона - 2 байта (см. иницализацию DR7), поэтому я так перекрываю 16 байт адресов портов, по идее можно сделать 4 байта, тогда будет 32 байта. Да вроде бы работало все (+0, +1). 2. почему-то по закрытии и открытии последовательного порта пользовательской программой перехват прекращается, с чем это может быть связано? Драйвер порта сбрасывает cr4? Ну обычно cr4 сбрасывает защита, для которой критичен перехват in/out В тех статьях описывается именно такая, это LPT Hasp3 Key. В твоем случае (имхо) более вероятна замена системой idt (смотри за ней). Мне приходилось следить за системыми сообщениями в своем драйвере (VxD). Ну или же (опять защита) виноват сброс DR7. У меня как раз защита от этого - при сбросе DR7 я получаю руль опять по int 1. Попробуй найти место, в котором во внешнем коде уже не выполняются твои вызовы и попробуй узнать, что там с: idt, CR4, DR7(DRx). 3. Ты в своем коде не реализовывал дальнейшую передачу управления оригинальному обработчику? А то в момент снифа портов нельзя трейситься в отладчике. Разумеется, отдавал - это я эмулировал hasp key так. А трейсится, конечно, нельзя - ты забираешь у айса его ресурсы. Просто возвращаешься назад - in/out уже выполнена в этот момент, если нужно, то меняешь только ответ порта (al/ax/eax). Подумай еще о перехвате не in/out, а более высокого уровня - абстракция HAL, это процедуры HAL.DLL - ReadPortUshort и т.п. Возможно, в твоей задаче можно обойтись без DRx, это удобнее.
Почему заработало? Стал восстанавливать значения отладочных регистров при выходе, только. Да! Самое главное - как я выяснил, мне не удается выставить значение 13-го бита dr7. Проверяю его значение сразу после установки (DbgPrint) - нет его. Такое ощущение, что система не позволяет. А возможна ситуация, когда система сама восстанавливает IDT????
Да! Самое главное - как я выяснил, мне не удается выставить значение 13-го бита dr7. Проверяю его значение сразу после установки (DbgPrint) - нет его. Такое ощущение, что система не позволяет. При инициализации обработчика или в самом обработчике ? Если в самом обработчике, то смотри мой код, а если сразу, то... воспользуйся техникой защиты - сбрось cr4 Перед установкой. А возможна ситуация, когда система сама восстанавливает IDT???? Ну не восстанавливает, а как бы делает новую. Я не силен в nt, но в 98-ой при загрузке процессов и тп манагер переодически меняет idt, и приходится следить за этим. Исследуй это в айсе: idt -> ...
Действительно! База IDT время от времени меняется, а в новой IDT в первой ячейке оригинальный дескриптор теперь думаю, как следить за этим программно... Еще один интересный момент - восстановление dr7. В книге Зубкова сказано, что при первом же срабатывании первого прерывания этот регистр сбрасывается, то есть каждый раз его приходится восстанавливать. В твоей статье сказано, что выставить значение dr7 в обработчике первого прерывания нельзя. Почему? Если выставлять значение после iretd из обработчика первого прерывания (кстати, значит, эта команда не только меняет селектор и осуществляет возврат, но и сбрасывает какой-то флаг, позволяющий выставлять dr7?), то в ХР это у меня не срабатывает. Ты не мог бы рассказать об этом подробнее?
Все понял! Не учитывал тот факт, что у каждого процессора свои системные регистры, в том числе отладочные и idtr. А процессора у меня два. Следовательно, и таблиц IDT (как и GDT) тоже две. Осталось только придумать способ привязывать свой поток к определенному процессору, а в обработчике прерывания определять, какой процессор выполняет поток.
Тут несколько сложновато написано, но смысл такой: всякий раз при выполнении такого исключения все биты в DR7 скидываются, и мы получаем в исключении #1 его "чистым". Попытка проинициализировать его внутри обработчика приведет к краху системы... Это - выдержка из статьи. Так вот, уважаемый Chingachguk, почему dr7 нельзя инициализировать внутри оработчика?????