CODINGАлександр Эккерт aleksandr-ehkkert@rambler.ruÏÎÒÀÅÍÍÛÅÑÀÄÛ WINDOWSÈññëåäóåì íåäðà îïåðàöèîííîé ñèñòåìûñ ïîìîùüþ äåáàããåðà è íå òîëüêîÓ Ñòèâåíà Êèíãà åñòü ïðîèçâåäåíèå «Ïîòàåííîå îêíî, ïîòàåííûéñàä». Íå ìîãó ñêàçàòü, ÷òî ÿ ëþáëþ òâîð÷åñòâî ýòîãî ïèñàòåëÿ, íîåñëè òû íå ÷èòàë ýòó êíèãó, íàñòîÿòåëüíî ñîâåòóþ íàéòè è ïðî÷åñòü.Î÷åíü çàíèìàòåëüíàÿ è îäíîâðåìåííî ïóãàþùàÿ êíèãà.  íåé ñîâñåé ïðèñóùåé Ñòèâåíó Êèíãó óæàñàþùåé êðàñîòîé èçëîæåíèÿðàññêàçûâàåòñÿ î òîì, êàêèå òàéíû ìîæåò õðàíèòü â ñåáå ñîçíàíèåëþáîãî ÷åëîâåêà.Вот и сегодня мы, наверное, не будем разговаривать на какую-то конкретнуютему. Мы просто немного полазаем в потаенном саду Windows,забравшись туда через потаенное окно дебаггера :). Я попробую рассказатьо скрытых местах, странностях и неизвестностях операционнойсистемы Windows. Эти знания помогут тебе, как программисту, лучшезнать, понимать и использовать эти самые потаенные места в своихгрязных целях.ÂÂÅÄÅÍÈÅДаже по прошествии многих лет, потраченных на изучение внутренностейоперационной системы и системного кодинга, понимаешь, что постичьвсе тонкости ОС вряд ли удастся. Я не имею в виду именно себя — такогомнения придерживаются многие программисты, с которыми я знаком.При этом зачастую единственным инструментом, позволяющим выпытатьте или иные секреты операционной системы, становится отладчик илидебаггер. Хотя не все любят возиться с отладчиком, положения дел это неменяет — если хочешь находить, простите за каламбур, потаенные окна вWindows — без него не обойтись. Итак, начнем.ÇÀÃÀÄÎ×ÍÛÉ ÏÀÐÀÌÅÒÐ LPRESERVED  DLLMAINВсем нам известна точка входа при старте библиотек — DllMain:BOOL WINAPI DllMain(__in HINSTANCE hinstDLL,112__in DWORD dwReason,__in LPVOID lpReserved);Принимает она (точка входа) три параметра. С первыми двумя всепонятно, но как быть с третьим? И действительно, зачем нужен этотпараметр lpReserved, если нигде в коде при инициализации библиотекион больше не используется? Оказывается не все так просто, как пытаетсяэто показать Microsoft.MSDN утверждает, что этот параметр используется при загрузке/выгрузкебиблиотеки; в частности, при статических операциях библиотеки этотпараметр содержит отличное от нуля значение. И, наоборот, при динамическихоперациях lpReserved будет равным нулю.Открою страшную тайну: lpReserved есть ничто иное, как указательна контекст стартующего процесса, который грузит библиотеку!Подробности таковы: при старте нового потока ядро ставит его вочередь для исполнения в виде APC — AsyncProcedureCall, которыйпередается в функцию LdrInitializeThunk, который вызывается Ntdll.dll.Одним из параметров, который передается LdrInitializeThunk, являетсяуказатель на структуру CONTEXT, которая описывает начальное состояниепотока — регистры, данные и т.п. После выполнения APC, контрольпередается LdrInitializeThunk. Раз уж исполнение нового потока начинаетсяс вызова ntdll!LdrInitializeThunk, то этой функции передаетсястартовый адрес, определенный функцией CreateThread. Таким образомXÀÊÅÐ 08 /139/ 10
codingАдреса загрузки ntdll.dll и kernel32.dll в процессах explorer.exe и firefox.exeстановится понятно (а уж под отладчиком — тем более!),что CreateThread должен передать через APC в вызовLdrInitializeThunk параметры старта процесса.Подведем итоги: в случае, если dwReason равен DLL_PROCESS_ATTACH (при загрузке библиотеки), lpReservedравен NULL для динамической загрузки и non-NULL длястатической загрузки.В случае, если fdwReason равен DLL_PROCESS_DETACH(при выгрузке библиотеки), lpReserved равен NUL привызове FreeLibrary и при ошибке загрузки DLL, и non-NULL — при окончании процесса.Зачем Microsoft скрывать этот факт? На самом деле, я бытоже его скрыл :). Подумай сам, сколько возможностейподмены контекста открывается при этом! Что? Ты никогдане слышал о контексте процесса? И системный вызовSetThreadContext тебе тоже ни о чем не говорит? Окей,рассмотрим. Во-первых, контроль над структурой CONTEXTдаст нам контроль над регистрами процессора. Все регистрыпроцессора при старте указываются в структуреCONTEXT (смотри описание этой структуры). Это могут бытьDEBUG-регистры для контроля над определенным приложениемили же установки перехватов вызовов функций.Или, кстати, установки флага TF в регистре EFLAGSВо-вторых, путем изменения lpReserved->Eip можно изменитьточку старта библиотеки. Эта особенность также можетбыть использована в определении версии ОС, котораяиспользуется на целевой машине путем выбора точки входав зависимости от версии ОС. Незаменимое свойство дляобеспечения переносимости кода, кстати.ÀÍÀËÎÃÈ×ÍÛÅ ÀÄÐÅÑÀ ÇÀÃÐÓÇÊÈ DLLИ действительно, если ты обращал внимание, такиебиблио теки как ntdll.dll, kernel32.dll и user32.dll для всехпроцессов всегда загружаются по одному и тому системномуадресу, хотя Microsoft это никак не объясняет. Почему?Как ты знаешь, указанные библиотеки представляютпрограммисту набор системных функций для работы ссистемой. К примеру, ntdll.dll является самой важной изюзермодных библиотек. Она представляет собой своеобразнуюзаглушку для вызова системных сервисов. И онадолжна быть загружена по одному и тому же адресу именнопо этой причине. Например, создание любого юзермодногопотока всегда происходит через вызов функцииntdll!LdrInitializeThunk. Функция ntdll!KiUserApcDispatcherXÀÊÅÐ 08 /139/ 10нужна системе для того, чтобы поставить в очередьисполнение юзермодных асинхронных вызовов. Ядрооперационной системы определяет адреса этих функцийеще на стадии инициализации системы. И, так как ядроиспользует скэшированные указатели на эти функции(для быстродействия), ntdll.dll уже не может быть загруженапо другим адресам. Kernel32.dll не может бытьзагружен по различным адресам, потому что большоеколичество предоставляемых этой библиотекой сервисовиспользуются системой для кросспроцессовых инъекцийкода. Например, kernel32.dll ответственна за обработчиксобытий консоли (что делает команда Ctrl+C в консоли,помнишь?). Так как консоль могут запустить многие программы,адрес обработчика Ctrl+C должен быть одним итем же. Ну а user32.dll постоянно загружается по одномуи тому же адресу по той простой причине, что онапредоставляет кучу сервисов, используемых win32k.sys— драйвера, реализующего оконную подсистему Windows.Указатели на эти функции win32k.sys получает черезвызов NtUserInitializeClientPfnArrays во время загрузки.ÎÄÍÎÏÎÒÎ×ÍÎÑÒÜ? ÍÅ ÒÓÒ-ÒÎ ÁÛËÎ!Часто ли ты используешь в своих программах отдельныепотоки? Если программа простая, и ей не требуется обрабатыватьбольшие массивы данных, вряд ли она для тебябудет многопотоковой. Но это только на первый взгляд.Потому что многие (если не все) Win32-приложения насамом деле являются многопотоковыми программами,даже если их разработчик утверждает обратное. К примеру,при старте программы сервисом подсистемы CSRSS впрограмме по умолчанию создается отдельный поток дляобработки консольных событий типа Ctrl+C/Ctrl+Break.Во-вторых, большинство Win32-API-функций длявыполнения своего кода используют отдельные потоки.Например, вызов WSAAsyncGetHostByName исполь зуетсинхронный вызов gethostbyname в отдельном потоке,после чего возвращает результаты запрашивающемучерез оконные сообщения.ÐÀÇÍÈÖÀ ÌÅÆÄÓ ÍÀÒÈÂÍÛÌÈ X86-ÂÅÐÑÈßÌÈ ÁÈÁËÈÎÒÅÊ È ÈÕ WOW64-ÀÍÀËÎÃÀÌÈМеханизм Wow64 включает в себя полный набор 32-битныхсистемных dll, реализующий Win32 API-функцииDVDdvdНа DVD ты сможешьнайти последнююверсию WinDBG,незаменимого отладчикапод ОС Windows,а также кое-какойинтересный код, которыйпозволит тебесделать свою системуболее защищенной.HTTP://WWWlinksДля более конкретногоизучения внутренностейОС Windowsобычных форумовнедостаточно.Очень часто золотыекрупинки можноотыскать в блогахсистемных программистов,таких какwww.alex-ionescu.com или http://j00ru.vexillium.org.113