Марк Руссинович недавно перешел на работу
в Microsoft, однако он продолжает радовать нас любопытными исследованиями тайной жизни
компьютеров. Сегодня мы представляем его
очередное дело. 

… Подобно большинству пользователей Windows
я часто страдаю от неожиданных подвисаний
системы которые могут возникнуть даже
казалось бы в ходе выполнения стандартных
задач типа запуска программ или открытия web-страниц.
Перейдя на работу в Microsoft я включил ноутбук
во внутренний домен и начал испытывать такие
тормоза регулярно. Вооружившись набором
программ, доставшимся мне с моего сайта
Sysinternals, я решил обнаружить причину их
возникновения, подозревая что как раз
недавнее подключение к домену сыграло свою
роль.

Я начал мой поиск с исходной точки —  я заметил
примерно двухсекундную задержку при старте
нового процесса. При этом запуск другой
программы в течении 30 секунд происходил
практически мгновенно. Я запустил Process
Explorer
, подождал 30 секунд и через Run
запустил Notepad. Блокнот не отобразился в
дереве процессов Process Explorer в связи с
вышеописанной задержкой, что означало
задержку соответствующего потока запуска
программы Explorer-а, а не самого Notepad-а.

Просмотр стека запускающего треда Explorer-а
мог дать мне подсказку о происходящем,
однако мне лениво было просматривать
каждый поток. Я прицепил дебаггер Windbg из Microsoft
Debugging Tools for Windows
к Process Explorer, запустил Notepad
через Run самого PE и перешел в дебаггер. В Windbg
через View меню я вызвал список потоков
командой Processes and Threads, выбрал первый и через
Call Stack увидел:

Стек сверху показывает последние
вызываемые функции, так что появление
ZwWaitForSingleObject означало, что поток дожидался
от какого-то объекта сигнала. Дальнейшее
развитие стека указывает на RPCRT4 (Remote Procedure
Call Runtime Version 4) DLL и функцию OpenLpcPort, что
означает попытку потока инициировать RPC к
другому процессу в систем. Похоже на то, что
пауза была вызвана вызовом 🙂 GetMachineAccountSid.
Как и пользователь домена, компьютер,
принадлежащий домену, имеет на нем аккаунт
и через GetMachineAccountSid получает Security Identifier (SID)
компьютера в домене.

Я установил брейпоинт  на возврат из
вызова GetMachineAccountSid в функции OpenLpcPort и после
короткой паузы, вызванной стартовой
задержкой, получил командную строку
дебаггера. По правилам функция возвращает
полученное значение через регистр EAX, так
что я просмотрел его значение:

Переведя полученное значение в
десятичный формат я поискал 1789  в WinError.h в
Platform SDK.

Я перерыл MSDN и Web в поисках дополнительной
информации, но не нашел причин
возникновения такой ошибки. Однако термин
"trusted relationship failure" указывал на то, что
домен, с которым соединен компьютер не
доверяет домену компьютера. Но в
обстоятельствах, когда компьютер отключен
от сети, это в общем не имело значения, даже
если компьютер пытался соединиться с
доменом, то единственный домен этот тот,
которому он принадлежит.

В догадке я открыл командную строку и
запустил PsGetSid —
посмотреть какую ошибку он получит при
попытке обнаружить доменный SID (доменный
аккаунт компьютера это имя компьютера с $
перед ним):

При такой попытке я обнаружил те же самые
паузы, что и при запуске приложений,
вероятно это сетевой таймаут. Я использовал
удаленный доступ для соединения с доменом и
запустил программу снова:

Далее, после соединения с доменом, я
больше не испытывал задержек с запуском. Я
отключился, но задержки так и не появились.
Только после перезагрузки и отсутствия
соединения задержки с запуском
возобновились.

На этом я решил исследовать внутренности
GetMachineAccountSid. Трассировка стека показала
вызов Netlogon DLL, который пытается установить
свой собственный RPC к функции NetrLogonGetTrustRid. Я
знаю, что сервис Netlogon работает внутри Local
Security Authority Subsystem (LSASS):

Я прикрепил Windbg к LSASS и установил
брейкпоинт на NetrLogonGetTrustRid. После запуска
нового процесса я получил останов и увидел:
если определенное поле в структуре данных
NULL, то Netlogon пытается соединиться с
контроллером домена, но если соединение не
устанавливается по каким либо причинам, он
просто возвращает ошибку 1789. Однако, когда
компьютер соединялся с доменом вызов был удачным и
значение в структуре данных было заполнено
SID-ом компьютера, который существовал даже
после дисконнекта. Это объясняло изменения
в поведении компьютера после отключения
компьютера от домена. 

Возвращаясь к GetMachineAccountSid я обнаружил, что
он кеширует результаты ошибки на 30 секунд
перед тем, как запросить у Netlogon-а соединение
с доменом заново. Это объясняло 30 секундный
период быстрого старта. Анализ кода в
дебаггере так же показал — OpenLpcPort
запрашивает SID в качестве подтверждения
переданного ему в параметрах SID. Если так,
OpenLpcPort изменяет SID на SID равный аккаунту Local
System перед вызовом NtSecureConnectPort, назначая
доменный SID локальному. NtSecureConnectPort берет SID
как параметр и соединяется только со
специфическим Local Procedure Call (LPC) портом если
порт был создан аккаунтом с подходящим SID.

Многое прояснилось, однако остался
главный вопрос — почему RPC случается в
процессе запуска? Начальная трассировка
довела меня только до фрейма NegotiateTransferSyntax,
однако очевидно, что есть и другие фреймы,
который движок не смог определить. Стек
очевидно вышел  когда я поставил
брейкпоинт с OpenLpcPort:

Ближе к концу виден вызов ShellExecCmdLine класса
CRunDlg, ответственного за диалог Run. Это в
конечном счете приводит к коду, похожему на
выполнение оболочкой хука, того
который делает RPC вызов, осуществленый в MpShHook DLL.
Я не знал кому принадлежит библиотека, но
Process Explorer показал ее принадлежность Windows
Defender:

Я подозревал, что хук это часть защиты Windows
Defender, что и подтвердила благостная команда
разработчиков. Autoruns
доложил, что Windows Defender зарегистрировал этот
перехват:

Соберем все части головоломки вместе:

  1. Диалог запуска Explorer-а обращается к
    ShellExecuteCmdLine.
  2. ShellExecuteCmdLine ловится хуком.
  3. Windows Defender выполняет хук , MpShHook.Dll вызывает RPC для
    связи с сервисом Windows Defender, передавая SID
    сервиса в качестве аргумента.
  4. RPC библиотека вызывает GetMachineAccountSid для
    опознания — совпадает ли SID с доменным, в
    таком случае SID назначается системному
    аккаунту.
  5. GetMachineAccountSid выполняет RPC к Netlogon для
    получения компьютерного SID.
  6. Если компьютерный идентификатор еще не
    получен, Netlogon пытается соединиться с
    контроллером домена.
  7. Если соединение по таймауту не
    установлено, Netlogon возвращает ошибку о
    невозможности наладить доверительные
    отношения.
  8. Windows Defender RPC продолжается с не назначенным
    SID.
  9. Сервис Windows Defender проводит проверку в
    реальном времени и процесс запускается.

Некоторые исследования позволили мне
заключить, что такая ситуация случается в
довольно редких обстоятельствах:

  • Windows XP 64 для x64 или Windows Server 2003 SP1
  • Активный Windows Defender Beta 2
  • Система включена в домен, но не
    соединена с ним  в текущей сессии

32-битный Windows XP не выполняют назначение SID
в OpenLpcPort и Windows Defender не использует shell execute hook
в Windows Vista. Команда Microsoft работает над
устранением ошибки, но я уже сейчас понял ее
причину и могу сам побороть.

 

Оставить мнение

Check Also

Как шифровать переписку в Jabber: пошаговая инструкция

Я думаю, не нужно объяснять, зачем человеку может понадобиться надежный и защищенный канал…