Автор: (c)Крис Касперски ака мыщъх
Наложение заплаток на ядро обычно требует перезагрузки системы, что не всегда приемлемо (особенно в отношении серверов), однако ядро можно залатать и "вживую". Аналогичным образом поступают и защитные системы, rootkit'ы, и прочие программы, модифицирующие ядро "на лету", Но практически все они делают это неправильно! Ядро нужно хачить совсем не так! Мыщъх укажет верный путь, пролегающий сквозь извилистый серпантин технических проблем и подводных камней, особенно характерных для многопроцессорных систем.
Самомодифицирующийся код долгое время считался дурным тоном программирования и уделом хакеров-извращенцев. Теоретики от программирования уходили от практических потребностей, порождая сферических коней в вакууме, совершенно не заботясь о проблемах тех, кому на них приходится ездить.
Аналогичным образом обстоят дела и с модификацией ядер операционных систем, разработчики которых предоставляют программисту набор API-функций для управления памятью, процессами и прочими системными ресурсами, но... только не самим ядром! Дурной тон программирования, говорите?! Если бы это было так, никакой вменяемый программист не стал бы извращаться, рискуя нарушить стабильность системы! К модификации ядра прибегают не от хорошей жизни, а от нищеты стандартных механизмов.
Вмешательство во внутреннюю жизнь ядра - это грязный хак, всегда таящий в себе потенциальную опасность развалить все к чертям собачьим. Большинство программ, модифицирующих ядро, делают это настолько небрежно, что при знакомстве с ними остается только удивляться - как же они ухитряются работать и не падать? На самом деле, они падают, причем на многопроцессорных машинах частота падений существенно увеличивается.
Имейте совесть! Если хачите ядро, хачьте его правильно! Корректная модификация отличается от некорректной тем, что гарантирует сохранение работоспособности и потому практически безопасна. Она может применяться не только в хакерских программах, плюющих на стабильность, но и в "промышленных" установках.
Расплатой за корректность становится резко возросшая сложность техники модификации, а также некоторое замедление работы системы, поэтому к hot-patch'у следует прибегать лишь в тех случаях, когда перезагрузка невозможна или крайне нежелательна.
Рисунок 1. Самомодифицирующийся код при неосторожном обращении ведет к развалу системы.
Для создания "горячей" заплатки необходимо иметь diff-файл, показывающий - каким образом была заткнута дыра (см. листинг 1), после чего нам остается только перевести исправления на язык ассемблера, модифицируя ядро непосредственно в оперативной памяти.
--- sys/kern/uipc_mbuf2.c 17 Mar 2006 04:15:51 -0000 1.24 +++ sys/kern/uipc_mbuf2.c 7 Mar 2007 19:21:48 -0000 1.24.2.1 @@ -226,16 +226,14 @@ m_dup1(struct mbuf *m, int off, int len, { struct mbuf *n; int l; - int copyhdr; if (len > MCLBYTES) return (NULL); if (off == 0 && (m->m_flags & M_PKTHDR) != 0) { copyhdr = 1; MGETHDR(n, wait, m->m_type); + M_DUP_PKTHDR(n, m); l = MHLEN; } else { - copyhdr = 0; MGET(n, wait, m->m_type); l = MLEN; } @@ -249,8 +247,6 @@ m_dup1(struct mbuf *m, int off, int len, if (!n) return (NULL); - if (copyhdr) - M_DUP_PKTHDR(n, m); m_copydata(m, off, len, mtod(n, caddr_t)); n->m_len = len;
Листинг 1. Патч для OpenBSD, приведенный с незначительными сокращениями.
К сожалению, раздобыть diff-файл удается далеко не всегда. Зачастую разработчики распространяют кумулятивные обновления, включающие в себя множество исправлений, не имеющих к дыре никакого отношения и модифицирующие внутренние структуры ядра (см. рис. 2), в результате чего "горячая" модификация кода влечет за собой необходимость перестройки данных, с которыми работает ядро, а это уже нереально, особенно с учетом того, что обработка данных не атомарна и в момент наложения заплатки старые данные могут находиться на различных стадиях обработки, будучи загруженными в локальные переменные и регистры.
Рисунок 2. Пример кумулятивной заплатки для Red Hat, исправляющей ошибку в подсистеме SCTP и затыкающей сразу две дыры - одна из которых опасная (important), а другая - не очень (moderate), в результате чего мы имеем множество изменений, значительная часть которых не имеет никакого отношения к безопасности вообще (http://rhn.redhat.com/errata/RHSA-2007-0085.html).
Скачав текущий CVS, можно попробовать отыскать изменения, относящиеся к дыре, заглянув в change-log (см. листинг 2). Если нам повезет, они будут явно обозначены в комментариях. Как будет показано в дальнейшем, совершенно необязательно исправлять отдельные машинные команды. Гораздо проще скопировать всю функцию целиком. Значит, нам нужен список измененных функций, получить который намного проще.
commit b8fa2f3a82069304acac1f9e957d491585f4f49a Author: Michael Chan <mchan@broadcom.com> Date: Fri Apr 6 17:35:37 2007 -0700 [TG3]: Fix crash during tg3_init_one(). The driver will crash when the chip has been initialized by EFI before tg3_init_one(). In this case, the driver will call tg3_chip_reset() before allocating consistent memory. The bug is fixed by checking for tp->hw_status before accessing it during tg3_chip_reset().
Листинг 2. Фрагмент change-log'а с комментариями, поясняющими - каким именно способом была исправлена дыра (http://www.kernel.org/pub/linux/kernel/v2.6/testing/ChangeLog-2.6.21-rc7).
А что делать, если исходные тексты недоступны (как, например, в случае Windows) или в них не удается разобраться?! Тогда необходимо прошвырнуться по security-сайтам, раскурить имеющиеся exploit'ы, в общем - так или иначе разобраться, где прячется уязвимость и как ее устранить.
Достаточно часто первооткрыватели дыры не только сообщают параметры вектора атаки, но и приводят дизассемблерные листинги двоичных модулей (или реконструированный псевдокод), с указанием ошибок (см. листинг 3) или, на худой конец, описывают обстоятельства атаки, позволяющие найти дыру самостоятельно.
Листинг 3. Фрагмент драйвера Ndistapi.sys из Windows XP SP2. Хорошо видно, что при срабатывании условных переходов jb loc_10BC5 функция DoIoctlConnectWork выходит без освобождения SpinLock'а, что ведет к краху системы и для исправления дыры достаточно всего лишь передвинуть вызов KfReleaseSpinLock на одну строку ниже (http://www.reversemode.com/index.php?option=com_remository&Itemid=2&func=fileinfo&id=47).
В операционных системах семейства Linux и NT ядро проецируется на единое 4 Гбайтовое адресное пространство. В Linux ядро занимает 1 Гбайт, располагаясь по адресам C000000h - FFFFFFFFh.
В NT/W2K/XP ядро по умолчанию "отъедает" 2 Гбайта, занимая старшую половину адресного пространства (8000000h - FFFFFFFFh), но если указать ключ /3GB в файле boot.ini (поддерживаемый, начиная с Windows 2000 Advanced Server/Datacenter Server), то ядро ужмется до 1 Гбайта (см. рис. 3).
Ядро FreeBSD вплоть до версии 3.х занимало всего 256 Мбайт, но начиная с версии 4.x разрослось до 1 Гбайта, оккупируя регион C000000h - FFFFFFFFh, однако некоторые эмуляторы, запускающие Free- и NetBSD "поверх" других систем, размещают ядро начиная с адреса A000000h, но подробно вдаваться в эти и другие экзотичные случаи мы не будем, иначе вместо статьи получится настоящий талмуд.
Рисунок 3. Адресное пространство NT/W2K/XP в конфигурации по умолчанию (слева) и Linux/BSD/W2K_3GB.
Память ядра доступна с прикладного уровня через псевдоустройство \Device\PhysicalMemory (NT/W2K/XP) и /dev/kmem (Linux/BSD). В ранних версиях NT псевдоустройство PhysicalMemory было открыто для чтения/записи любому пользователю из группы "Администраторы", однако начиная с Windows 2003 Server SP1 к нему не может получить доступ даже "System" (подробнее об этом рассказывается в заметке "Changes to Functionality in Microsoft Windows Server 2003 Service Pack 1 Device\PhysicalMemory Object": www.microsoft.com/technet/prodtechnol/windowsserver2003/library/BookofSP1/e0f862a3-cf16-4a48-bea5-f2004d12ce35.mspx).
Рисунок 4. Упрощенная архитектура NT/W2K/XP.
UNIX-подобные системы также закрывают доступ к kmem и уже недалек тот день, когда из большинства дистрибутив оно будет полностью изъято. И хотя псевдоустройство /dev/mem (физическая память до линейной трансляции) по-прежнему в строю и отказаться от него никак не получается (поскольку его используют многие приложения - те же X'ы, например), для модификации ядра оно не годится, поскольку не обеспечивает атомарности, а, значит, наложение заплатки может привести к краху системы.
Из драйвера (или, выражаясь терминологией UNIX-подобных систем, "загружаемого модуля"), работающего на нулевом кольце, память ядра защищена от непреднамеренной модификации, однако эту защиту легко отключить (исключение составляют 64-разрядные версии XP и Висты, в которых встроена неотключаемая защита от умышленной модификации под названием PatchGuard, техника обхода которой описана мыщъх'ем в статье Взлом patch-guard).
Рисунок 5. Упрощенная архитектура Linux.
В NT/W2K/XP/Виста-x86 существует два способа отключения защиты от непреднамеренной модификации из нулевого кольца: статический и динамический. Статический сводится к созданию параметра EnforceWriteProtection типа REG_DWORD со значением 0x0 в HKLM\SYSTEM\CurrentControlSet\Control\SessionManager\MemoryManagement, а динамический осуществляется сбросом WP-бита в управляющем регистре CR0, который расшифровывается как Write Protection. Повторная установка бита включает защиту.
Практический пример использования приведен ниже (см. листинг 4):
Листинг 4. Код псевдодрайвера для NT, временно отключающего защиту ядра от модификации, а затем включающего ее обратно.
Аналогичным способом можно отключить и защиту ядра в UNIX-подобных системах. Сброс WP-бита действует на аппаратном уровне, открывая все accessibly-станицы для модификации независимо от того, разрешена ли в них запись или нет. Естественно, текущий уровень привилегий (CPL) не должен превышать CPL модифицируемой страницы, иначе процессор сгенерирует исключения типа "ошибка доступа" (то есть, с прикладного уровня ядро все равно остается недоступно).
Пример реализации KLD-модуля (Dynamic Kernel Linker ) для FreeBSD приведен ниже (см. листинг 5):
Листинг 5. Исходный текст KLD-модуля для FreeBSD, отключающего защиту ядра от записи при загрузке и включающий ее обратно при выгрузке.
Сброс WP-бита носит глобальное воздействие, затрагивающее не только ядро, но также распространяющиеся и на прикладные процессы, поэтому отключать защиту на долгое время крайне нежелательно. Некоторые программы (особенно протекторы исполняемых файлов и некоторые защиты) явно закладываются на генерацию исключения, возникающую при попытке записи в ReadOnly-страницу и после сброса WP-бита перестают работать.
Как вариант, можно поиграться низкоуровневыми функциями семейства pte_x (например, pte_mkwrite), работающих с каталогом страниц. Это более красивый и надежный, однако, увы, системно-зависимый путь, поэтому на практике приходится идти на компромисс, жертвуя надежностью в пользу переносимости.
Ок, теперь мы можем модифицировать ядро, накладывая "горячие" заплатки или перехватывая системные функции, внедряя в их начало команду перехода на свое тело. Большинство rootkit'ов именно так и поступают, забыв о том, что "подопытный" код может исполняться одновременно с его модификацией, приводя к краху системы. Причем, эта "одновременность" довольно относительна. Как известно, на однопроцессорных машинах потоки выполняются последовательно, а не параллельно и иллюзия "одновременности" создается лишь за счет быстрого переключения между ними.
Допустим, поток А был прерван при исполнении функции foo, после чего планировщик передал управление потоку B, выполняющему функцию bar. Вопрос: что произойдет, если мы модифицируем содержимое foo? Очевидно, когда поток А вновь получит управление, он окажется в совершенно другом окружении, возможно, даже пытаясь продолжить выполнение с середины новой машинной команды!!! (Примечание: разумеется, термин "поток" условен, в некоторых операционных системах такой сущности просто нет и планировка осуществляется на уровне процессов, также это может быть и обработчик исключений, и отложенная процедура - да все, что угодно!).
Причем, никакой возможности узнать - находится ли данный участок кода под выполнением, у нас нет! То есть, как это нет?! Очень даже есть - просто просматриваем контексты всех потоков (процессов, отложенных функций), при необходимости дожидаясь момента, когда обозначенный код выйдет из под управления, после чего правим его. Вот и все! Просто, элегантно, но - увы... неработоспособно.
Во-первых, добраться до контекстов процессов/потоков/отложенных функций в одно мгновение невозможно! Поток, анализирующий контексты других потоков, исполняется параллельно с ними и пока мы читаем контекст очередного потока, предыдущие уже могли измениться. Теоретически возможно "замораживать" все потоки на время модификации (предварительно дождавшись, пока они покинут пределы модифицируемого кода), а потом "размораживать" их обратно, однако этот трюк имеет довольно ограниченную область применения. В частности, он не работает с обработчиками аппаратных прерываний, блокирование которых крайне нежелательно или же вовсе недопустимо. Во-вторых, все это слишком системнозависимо, а ковыряться во внутренних (и зачастую недокументированных) структурах оси - гиблое дело.
Существует несколько универсальных решений данной проблемы. Вот, например, одно из них - внедряем в начало модифицируемой функции команду INT 03h, соответствующую однобайтовому опкоду CCh, и тогда при ее вызове процессор будет генерировать отладочное исключение, перехватываемое нашим обработчиком, передающим управление на "отпаченную" версию обозначенной функции, расположенную совсем в другом месте. Оригинальная функция (за исключением первого байта) остается неизменной и потому мы можем не волноваться за то, что какой-то неожиданно проснувшийся поток продолжит ее выполнение.
Поскольку выполнение машинных команд - атомарная операция, то записывать INT 03h можно поверх любой команды и это гарантированно не приведет к развалу систему, даже если модифицируемая команда исполняется в данный момент на другом процессоре! Процессор выполнят либо оригинальную команду, либо INT 03h. "Промежуточное" состояние у него попросту отсутствует.
Достоинство данного решения в том, что оно не требует анализа ассемблерного кода исходной функции. Мы просто пишем INT 03h и все! Недостатки - а) при модификации более чем одной функции, обработчик должен анализировать адрес исключения, чтобы определить, куда передать управление; б) это плохо работает с отладчиками (де-факто, INT 03h представляет собой программную точку останова); в) часто вызываемые функции при такой методике перехвата будут заметно тормозить, снижая общую производительность.
Более сложное, но вместе с тем и более "технологическое", решение заключается в записи команды jmp near target поверх машинной команды равной или большей длины, где target - адрес модифицируемой функции, которой передается управления. В 32-битном режиме длина jmp near target составляет 5 байт, что существенно превышает среднюю длину x86, равную 2,5 байтам.
Рассмотрим код наугад выбранной функции, дизассемблерный листинг которой приведен ниже:
.text:С8001D60 sub_С001D60 proc near .text:С8001D60 .text:С8001D60 55 push ebp .text:С8001D61 89 E5 mov ebp, esp .text:С8001D63 57 push edi .text:С8001D64 56 push esi .text:С8001D65 53 push ebx .text:С8001D66 8B 7D 08 mov edi, [ebp+arg_0] .text:С8001D69 8B 1D CC 64 00 08 mov ebx, ds:dword_C80064CC .text:С8001D6F 85 DB test ebx, ebx .text:С8001D71 74 1E jz short loc_C8001D91
Листинг 6. Фрагмент наугад взятой функции, в которую необходимо внедрить jmp.
Длина первых шести машинных команд варьируется от одного до трех байт и потому ни одна из них не пригодна для патча и jmp near target может быть записана лишь поверх седьмой команды - "mov ebx,ds:dword_C80064CC". Поскольку первые шесть байт модифицируемой функции выполняются до передачи управления на target, они не должны дублироваться в целевой функции, иначе произойдет крах.
Очевидно, что подобным образом может быть модифицирована далеко не всякая функция. Ниже приведен пример функции, в которой между началом и первым условным переходом нет ни одной машинной команды длиннее трех байт (см. листинг 7), а это значит, что существует риск потери управления! Условный переход перепрыгивает через jmp near target и мы остаемся с носом.
.text:C8002310 sub_C8002310 proc near .text:C8002310 .text:C8002310 55 push ebp .text:C8002311 89 E5 mov ebp, esp .text:C8002313 83 EC 04 sub esp, 4 .text:C8002316 57 push edi .text:C8002317 56 push esi .text:C8002318 53 push ebx .text:C8002319 8B 75 0C mov esi, [ebp+arg_4] .text:C800231C 85 F6 test esi, esi .text:C800231E 75 10 jnz short loc_С8002330 ; continue -->---! .text:C8002320 31 C0 xor eax, eax ; ! .text:C8002322 EB 72 jmp short loc_C8002396 ; -to retn --->--!----! .text:C8002324 8D B6 00 00+ align 10h ; [выравнивание] ! ! .text:C8002330 loc_C8002330: ; <--------------! ! .text:C8002330 80 3E 2F cmp byte ptr [esi], 2Fh ; ! .text:C8002333 74 5B jz short loc_C8002390 ; to subroutine--! ! .text:C8002335 6A 2F push 2Fh ; ! ! .text:C8002337 8B 45 08 mov eax, [ebp+arg_0] ; ! ! .text:C800233A 50 push eax ; ! ! .text:C800233B E8 48 E8 FF FF call _strrchr ; <=[FIX_1 HERE] ! ! ... ; ! ! .text:C8002390 loc_8002390: ; ! ! .text:C8002390 56 push esi ; ! ! .text:C8002391 E8 9A 1D 00 00 call sub_C8004130 ; <=[FIX_2 HERE] ! ! .text:C8002396 ; ! ! .text:C8002396 loc_C8002396: ; <--------------! ! .text:C8002396 8D 65 F0 lea esp, [ebp+var_10] ; <-------------------! .text:C8002399 5B pop ebx .text:C800239A 5E pop esi .text:C800239B 5F pop edi .text:C800239C 89 EC mov esp, ebp .text:C800239E 5D pop ebp .text:C800239F C3 retn .text:C800239F sub_C8002310 endp
Листинг 7. Фрагмент функции, в которой между началом и первым условным переходом нет ни одной машинной команды длиннее трех байт.
На самом деле все не так уж и мрачно. Покурив хорошей травы (только где ее взять в это время года? ведь еще не сезон...) и как следует подумав головой, мы догадаемся, что можно записать двухбайтовую команду jnz short C8002324 поверх трехбайтовой команды mov esi, [ebp+arg_4] (после выполнения "sub esp,4" флаг нуля гарантированно сброшен), предварительно разместив по адресу C8002324h (0Сh свободных байт, вставленные компилятором для выравнивания) команду jmp near target.
Хорошо, а если бы свободных байт в нашем распоряжении не было - что бы мы стали делать тогда? Ок, смотрим на первый условный переход - jnz short loc_С8002330. Если он не выполняется - происходит выход из функции, что нас вполне устраивает. От того, что мы не перехватим вызов функции, заканчивающийся немедленным возвратом, мы много не потеряем.
Идем дальше. И... встречаем второй условный переход jz short loc_С8002390, при срабатывании которого управление получает функция sub_C8004130, в противном же случае продолжается нормальное выполнение программы. Короче, ветвление. А раз это ветвление, то мы должны внедрить два перехватчика: один - поверх команды call _strrchr и другой - поверх call sub_C8004130. Впрочем, в зависимости от условий задачи, может хватить и одного перехватчика (например, если нужно исправить код, находящейся в ветке С8002335h. Тем не менее, несложно вообразить себе функцию, целиком состоящую из коротких команд или вызывающую переполнение буфера прежде, чем удастся внедрить jmp на свой обработчик и тогда приходится прибегать к уже описанному трюку с INT 03h.
Другие недостатки данного метода: перехват каждой функции осуществляется индивидуально и написание универсального перехватчика представляет довольно сложную задачу, требующую как минимум наличия встроенного дизассемблера, что плохо подходит для rootkit'ов, однако вполне приемлемо для "горячих" заплаток (не так уж трудно выпустить серию заплаток, по одной для каждой версии операционной системы).
Запись команды jmp near target должна представлять атомарную операцию, выполняемую целиком за один раз, в противном случае может сложиться ситуация, при которой процессор попытается выполнить "недописанную" команду со всеми вытекающими отсюда последствиями, но инструкция вида mov [mem], reg8/16/32 не позволяет записывать более четырех байт, а потому совершенно непригодна для решения поставленной задачи.
Некоторые хакеры используют SSE-инструкции, позволяющие записывать более четырех байт и на однопроцессорных машинах такой трюк работает вполне нормально, но на многопроцессорных системах существует вероятность (пускай и ничтожная) модификации кода в процессе его выполнения, а префикс блокировки шины (LOCK) перед SSE-командами вставлять нельзя.
К счастью, начиная с первопней в лексиконе процессоров существует замечательная команда CMPXCHG8B, поддерживающая префикс LOCK и записывающая одним махом целых восемь байт! Для внедрения пятибайтовой инструкции jmp near target этого более чем достаточно. Естественно, чтобы не затереть оставшиеся три байта, мы сначала должны прочитать восемь байт из памяти, наложить на них jmp near target и записать полученную смесь обратно. Вот тут некоторые спрашивают: зачем это делать, ведь jmp - это безусловный переход и находящиеся за ним команды никогда не получат управления. А затем, что находящиеся за ним команды могли получить управление еще до модификации. (Примечание: некоторые трансляторы не поддерживают инструкцию CMPXCHG8B и в этом случае ее можно задать через директиву DB или _emit в байтном виде: 0Fh C7h 0Eh, соответствующую команде CMPXCHG8B [ESI]).
Готовый пример реализации внедрения jmp near target посредством CMPXCHG8B приведен ниже (см. листинг 8):
Листинг 8. Внедрение jmp near target посредством команды CMPXCHG8B, в регистре EAX передается адрес записи jmp, а в регистре EBX - target.
Чтобы не связываться с ассемблером, достаточно скопировать исправленный вариант функции в свой модуль - нехай транслятор компилирует, тогда нам останется всего лишь передать на нее управление командой jmp near target (естественно, вместе с функцией необходимо скопировать и все макросы, заданные директивой define, а также подключить необходимые заголовочные файлы).
При этом мы наталкивается на следующие проблемы: а) если функция обращается к глобальным переменным, то мы должны подставить адреса переменных оригинальной функции, иначе поведение системы станет непредсказуемым; б) адреса "внутренних" функций ядра, вызываемые данной функцией, также необходимо подставлять вручную; в) мы не можем приказать компилятору исключить уже выполненные команды, поэтому прежде чем передавать управление откомпилированной функции, следует выполнить "откат", повесив на jmp near target промежуточный обработчик, который в случае листинга 9 будет выглядеть так:
POP EBP POP ESI POP EDI POP EBP
Листинг 9. Выполнение "отката" для "нейтрализации" уже выполненных команд функцией sub_С001D60 (см. листинг 6).
Как видно, мы выполняем обратную последовательность команд, восстанавливая стек и содержимое регистров, а при необходимости освобождая выделенную функцией память и прочие системные ресурсы.
С Windows в этом плане сложнее. Исходных текстов нет и вставить исправленную функцию в драйвер не получится. Здесь есть два пути: дизассемблировать ядро и переписать код на Си (трудоемко, зато надежно) или же скопировать функцию прямо в двоичном виде, корректируя ссылки на функции, вызываемые по относительным адресам. Поскольку адрес загрузки драйвера наперед неизвестен, коррекцию приходится осуществлять на "лету": заносим адреса машинных команд call target/jmp target в специальный массив, хранящийся в драйвере, а в процедуре инициализации обрабатываем все элементы, добавляя к непосредственному операнду базовый адрес загрузки, не забыв предварительно отключить защиту от записи, поскольку по умолчанию кодовая секция доступна только на чтение.
Рисунок 6. Проникновение в ядро.
Команда CMPXCHG8B сравнивает EDX:EAX с m64 и, если они равны, устанавливает флаг нуля, записывая ECX:EBX в m64, в противном случае сбрасывает флаг нуля, загружая m64 в ED:EAX. Команда поддерживает префикс блокировки шины LOCK.
В Linux и xBSD существует возможность скомпилировать монолитное ядро, без поддержки загружаемых модулей, что благотворно сказывается на безопасности, но затрудняет наложение "горячих" заплаток. Однако, если псевдоустройство /dev/mem остается доступным (а чаще всего дела обстоят именно так), мы можем найти в памяти таблицу системных вызовов и внедрить в ядро свой собственный код, работающий на нулевом кольце и накладывающий заплатку по описанной мыщъхем методике.
Заштопать ядро операционной системы без перезагрузки очень сложно, но вполне реально. Конечно, далеко не всякому администратору это по силам, однако фирмы, занимающиеся поддержкой, могут выпускать неофициальные "горячие" заплатки, расхватываемые словно пирожки! Ведь это не просто актуальная, а супер-актуальная тема, в которой заинтересованы миллионы пользователей, так что насчет спроса можно не сомневаться.