Марк Руссинович недавно перешел на работу
в 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 зарегистрировал этот
перехват:
Соберем все части головоломки вместе:
- Диалог запуска Explorer-а обращается к
ShellExecuteCmdLine. - ShellExecuteCmdLine ловится хуком.
- Windows Defender выполняет хук , MpShHook.Dll вызывает RPC для
связи с сервисом Windows Defender, передавая SID
сервиса в качестве аргумента. - RPC библиотека вызывает GetMachineAccountSid для
опознания - совпадает ли SID с доменным, в
таком случае SID назначается системному
аккаунту. - GetMachineAccountSid выполняет RPC к Netlogon для
получения компьютерного SID. - Если компьютерный идентификатор еще не
получен, Netlogon пытается соединиться с
контроллером домена. - Если соединение по таймауту не
установлено, Netlogon возвращает ошибку о
невозможности наладить доверительные
отношения. - Windows Defender RPC продолжается с не назначенным
SID. - Сервис 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 работает над
устранением ошибки, но я уже сейчас понял ее
причину и могу сам побороть.