Стек теневых калбэков.

Тема в разделе "WASM.WIN32", создана пользователем Clerk, 15 май 2010.

  1. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Уже описывал это, на крабе в частности. Вчера PSR1257 попытался решить, опишу подробно.
    Существует механизм доставки сообщений шадовом в юзермод http://virustech.org/f/viewtopic.php?id=38
    Знакомая многим таблица apfnDispatch - через неё шадов вызывает хэндлеры первого уровня. При вызове теневых сервисов часто до возврата из сервиса управление возвращается в юзермод. Этот механизм допускает рекурсивные вызовы. При входе в ядро формируется трап-фрейм. Ссылка на него сохраняется в текущем ядерном стеке, ниже трап-фрейма как часть фрейма описывающего калбэк. Этот фрейм содержит ссылку на следующий фрейм, на трап-фрейм и часть стека ядра, сформированного при вызове KiCallUserMode(). Таким образом формируется цепочка, подобно цепочке трап-фреймов. Состояние задачи(трап-фрейм) восстанавливается посредством сервиса NtCallbackReturn или прерывания KiCallbackReturn(0x2B) в состояние, которое было сразу после формирования трап-фрейма при вызове сервиса, можно понимать как контекст потока на момент вызова сервиса с соответствующими поправками. Тогда при возврате в ядро поток продолжет исполнять код после сервиса, тоесть происходит нормальный возврат. Уровень вложенности ограничен размером ядерного стека.
    Можно использовать практически любые сервисы из которых вызываются шадовом калбэки. Как пример использую NtUserEnumDisplayMonitors, так как это голый стаб для функи EnumDisplayMonitors() удобный для вызова. Итак вызываем этот сервис, поток вернётся в юзермод для вызова калбэка для енума. Из калбэка восстанавливаем стек и выполняем возврат вручную, это делает код:
    Код (Text):
    1. ; +
    2. ; Сохранение контекста.
    3. ; o Eax: ID NtUserEnumDisplayMonitors.
    4. ; o Esi: адрес возврата.
    5. ; o Edi: ссылка на стек.
    6. ;
    7. TsSave proc C
    8.     xor ecx,ecx
    9.     push esp    ; Параметр для калбэка - ссылка на стек для его восстановления.
    10.     Call @f ; Ссылка на калбэк.
    11. ; Калбэк.
    12. ; typedef BOOL (CALLBACK* MONITORENUMPROC)(HMONITOR, HDC, LPRECT, LPARAM);
    13.     mov esp,dword ptr [esp + 4*4]   ; Восстанавливаем стек, ссылка для восстановления передаётся параметром.
    14.     xor eax,eax
    15.     retn
    16. @@:
    17.     push ecx
    18.     push ecx
    19.     mov edx,esp
    20. ; BOOL
    21. ; NtUserEnumDisplayMonitors(
    22. ;     IN HDC             hdc,
    23. ;     IN LPCRECT         lprcClip,
    24. ;     IN MONITORENUMPROC lpfnEnum,
    25. ;     IN LPARAM          dwData)
    26.     Int 2EH ; NtUserEnumDisplayMonitors
    27. ; При восстановлении контекста возвращается не ноль.
    28.     .if !Eax    ; Калбэк не был вызван изза исчерпания лимита вызовов/вложенности(0x2C).
    29.     add esp,4*4 ; Удаляем параметры сервиса.
    30.     mov eax,STATUS_STACK_OVERFLOW
    31.     retn
    32.     .else
    33.     mov esp,edi
    34.     add eax,3*4 ; Ссылка на стек, который был при вызове TsLoad(для передачи параметров).
    35.     jmp esi
    36.     .endif
    37. TsSave endp
    Для восстановления контекста вызов такой:
    Код (Text):
    1. ; +
    2. ; Восстановление контекста.
    3. ;
    4. TsLoad proc C   ; stdcall
    5.     xor eax,eax
    6.     mov edx,3*4
    7.     push eax
    8.     push eax
    9.     push esp
    10.     mov ecx,esp
    11.     Int 2BH
    12.     add esp,3*4
    13.     retn        ; В случае ошибки возвратит STATUS_NO_CALLBACK_ACTIVE.
    14. TsLoad endp
    Теперь нюансы.
    o Передача ссылки в пользовательский калбэк. Так заранее не известно сколько фреймов расположено в стеке после вызова сервиса и сколько локальных переменных передаём непосредственно ссылку на стек. Это универсально, ибо прототип калбэка фиксирован.
    o Трассировка. Прерывание при вызове сервиса должно быть вызвано со сброшенным TF, тоесть код не должен трассироваться. Это достигается посредством сервиса NtContinue, либо размещение данного кода гдето глубоко в коде, чтобы реверсер не трассировал его. Тогда при восстановлении контекста TF окажется сброшен даже при трассировке TsLoad(), что есть выход из под трассировки.
    o Переключение стека. При восстановлении контекста стек восстанавливается в состояние, которое было на момент вызова сервиса. При возврате регистр Esp восстанавливается, но без обращений к стеку сразу перезагружается инструкцией mov esp,edi, таким образом обращения к начальному стеку не происходит.
    o Сторожевые страницы. Для отлова обращений к памяти используются сторожевые страницы. Страница помечается атрибутом PAGE_GUARD, далее при обращении к ней генерируется #STATUS_GUARD_PAGE_VIOLATION, атрибут снимается и отладчик получает уведамление. В ядре ISR проверяет адрес к которому произошло обращение и если он принадлежит стеку, данный атрибут снимается, стек расширяется и выполняется возврат. Таким образом отладчик не получит уведомление при доступе к страницам стека которые помечены как сторожевые.
    o Блокирование стека. Сделать не доступными(PAGE_NOACCESS) страницы стека нельзя. В этом случае поток будет завершён ядром, под отладчиком возникнет исключение и сервис возвратит ошибку.
    o Восстановление сегментных регистров. Сегмент стека нельзя сделать инвалидным, так как возникнет ошибка и контекст восстановлен не будет. Остальные регистры будут восстановлены при возврате, таким образом посредством этого нельзя отследить куда выполняется возврат.
    -
    Вопрос как отловить куда будет выполнен возврат.
     
  2. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
  3. lhc645

    lhc645 New Member

    Публикаций:
    0
    Регистрация:
    9 авг 2009
    Сообщения:
    106
    bp KiUserCallbackDispatcher
     
  4. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    lhc645
    Int 0x2B не дёргает KiUserCallbackDispatcher(), это не вызов калбэка, а возврат из него, я ведь описал.
     
  5. Phyber

    Phyber New Member

    Публикаций:
    0
    Регистрация:
    27 мар 2010
    Сообщения:
    96
    lhc645
    Ему надо из юзер мода. к примеру через отладку в ольке.
     
  6. lhc645

    lhc645 New Member

    Публикаций:
    0
    Регистрация:
    9 авг 2009
    Сообщения:
    106
    Clerk, я так понимаю в данном случае решением будет использовать ядерный дебаггер.
     
  7. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    lhc645
    Не все юзают ядерные. Интересен возможный способ из юзермода.
     
  8. TSS

    TSS New Member

    Публикаций:
    0
    Регистрация:
    13 апр 2009
    Сообщения:
    494
    Врятли это возможно из юзермода, не поставишь же бряк на KiCallbackReturn :) Как альтернатива может быть использован разбор и анализ кода в ClientMonitorEnumProc, но это сработает только для простейших случаев, т.к. внутри колбека могут быть вызваны другие сервисные ф-ции с установленными клиентскими колбеками и логика всех этих прыжков в ядро->юзермоде->ядро->... может быть нетривиальной для разбора.
     
  9. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    TSS
    Это для примера я использовал, можно многие другие калбэки заюзать.
     
  10. TSS

    TSS New Member

    Публикаций:
    0
    Регистрация:
    13 апр 2009
    Сообщения:
    494
    Clerk
    Я вкурсе, всего их под 100 штук (97 на хп сп3).
     
  11. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    TSS
    Всё нельзя, так как некоторые требуют возврат в ядро. Даже в этом случае EnterCrit() исполняется, что весьма не тру :dntknw:
    Были мысли использовать TIB, где границы стека определены, но без результатно. Мб у вас получится ;)
     
  12. PSR1257

    PSR1257 New Member

    Публикаций:
    0
    Регистрация:
    30 ноя 2008
    Сообщения:
    933
    Clerk

    ... Начал пытацо понять как это работает - немного потрассировал в Олли и немного думал.

    Вопрос. Верно ли я понял что вызов TsSave->Int 2Eh устанавливает обработчег по адресу cbRet (lea esi,offset cbRet), а после "возврата" (?) - вы называете это "возврат из колбэка" произойдет собственно shadoved call - cbRet, и именно в этом тонкость - допустим инициализация (TsSave) мной пропущена в процессе трассировки (те я не знаю где cbRet), но в момент TsLoad я не смогу выяснить это?

    Верно ли я понял?

    Выяснил что можно ставить BPM в Олли на некоторые части этого алгоритма (но не все). Точно стабильно работает BPM на cbRet. Следовательно задачу можно переформулировать - как отловить момент инициализации (т.е. TsLoad/что-то типа Int 2Eh)? Возможно (?) следующее решение: мониторить момент установки callback'а. Для этого - согласно сырцу - можно использовать тот факт что число callback'ов лимитировано (0x2C?). Тогда при неустановленном callback мы можем установить максимально возможное их число, но если уже есть один - мы не сможем. Не совсем понятно как в такой процедуре снимать установленные callback'и. Для поиска кода устанавливающего коллбэк можно использовать "деление кода пополам" - постепенно сужая диапазон поиска каждый раз следя за числом установленных callback'ов.
     
  13. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    PSR1257
    Про отслеживание реги калбэка просто забудьте, вы это место не найдёте. Всё что вы имеете это процедуру TsLoad().
    Можно несколько десятков калбэков поставить.
     
  14. PSR1257

    PSR1257 New Member

    Публикаций:
    0
    Регистрация:
    30 ноя 2008
    Сообщения:
    933
    Clerk

    О, еще три мелких вопроса - если можно:

    1) Допустим я не знаю где находится TsSave, но я точно знаю что после вызова int 2Eh внутри TsSave() байтики _ПОСЛЕ_ int 2Eh должны быть _нетронуты_ (или изменены на эквивалентные) - они будут вызываны в момент вызова TsLoad() (верно?). В таком случае нужно обязательно килять int 2Eh _ПОСЛЕ_ вызова TsSave() - иначе это неплохой паттерн для поиска.

    Появляется возможность следить за записью в память с фильтром на int 2Eh.

    2) Можно ли установить более 1 колбэка на один и тот же сервис (допустим на ту же NtUserEnumDisplayMonitors)? У меня повторная попытка установки второго callback'а аннулировала установку первого;

    3)
    Это против BPM'ов которых только 4?
     
  15. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    PSR1257
    1. Не имеет значения способ вызова сервиса, это шлюз прерывания, сисентер или сискол. Сколько раз нужно повторить - код возврата из теневого калбэка вы не найдёте(не NtCallbackReturn).
    2. Ничего не анулирует, вы криво вызывали. Сказал ведь что механизм калбэков основан на рекурсивных вызовах. Число их ограничено размером стека ядра.