В мире кибербезопасности продолжается извечное противостояние создателей вредоносного ПО и разработчиков защитных механизмов. Недавнее обновление ядра Linux версии 6.9 принесло фундаментальные изменения в механизм обработки системных вызовов для архитектуры x86-64, что сделало неработоспособными традиционные методы перехвата, десятилетиями использовавшиеся в руткитах. Однако исследователи безопасности уже нашли способ обойти эти ограничения с помощью новой техники под названием FlipSwitch.
Описание
Перехват системных вызовов, особенно путем перезаписи указателей на обработчики, долгое время был основой Linux-руткитов вроде Diamorphine и PUMAKIT, позволяя им скрывать свое присутствие и контролировать поток информации. Хотя существуют и другие механизмы перехвата, такие как ftrace и eBPF, у каждого есть свои преимущества и недостатки, и большинство имеют определенные ограничения. Перезапись указателей функций оставалась наиболее эффективным и простым способом перехвата системных вызовов в ядре.
Ситуация кардинально изменилась с выходом Linux kernel 6.9, который представил фундаментальное изменение механизма диспетчеризации системных вызовов для архитектуры x86-64, фактически нейтрализовав традиционные методы перехвата. Ранее ядро использовало простой массив указателей на функции под названием sys_call_table для обработки системных вызовов. Логика была предельно простой - прямое обращение к массиву. Руткит мог найти эту таблицу в памяти, отключить защиту от записи и перезаписать адрес системного вызова вроде kill или getdents64 указателем на свою контролируемую злоумышленником функцию. Это позволяло фильтровать вывод команды ls для скрытия вредоносных файлов или предотвращать завершение определенного процесса.
С версией 6.9 игра полностью изменилась - прямое обращение к массиву было заменено более эффективным и безопасным механизмом диспетчеризации на основе оператора switch. Это изменение, хотя и кажется незначительным, стало смертельным ударом для традиционного перехвата системных вызовов. Таблица sys_call_table сохранилась для совместимости с инструментами трассировки, но больше не используется для фактической обработки системных вызовов. Любые ее модификации просто игнорируются.
Ответом на эти изменения стала разработка техники FlipSwitch, которая обходит новую реализацию с оператором switch путем прямого исправления скомпилированного машинного кода диспетчера системных вызовов ядра. Методология включает несколько ключевых этапов. Сначала необходимо найти адрес оригинальной функции системного вызова, который нужно перехватить. Ирония заключается в том, что теперь нерабочая таблица sys_call_table стала идеальным инструментом для этой цели - мы все еще можем найти в ней адрес sys_kill для получения надежного указателя на оригинальную функцию.
Для поиска символов ядра обычно используется функция kallsyms_lookup_name, предоставляющая программный способ найти адрес любого экспортированного символа ядра по его имени. Важно отметить, что kallsyms_lookup_name обычно не экспортируется по умолчанию, что повышает безопасность ядра. Однако распространенной техникой косвенного доступа к ней является использование kprobe - механизма ядра для установки точек останова. Размещая kprobe на известной функции ядра, модуль может использовать внутреннюю структуру kprobe для получения адреса исходной функции.
После получения целевого адреса внимание переключается на функцию x64_sys_call - новое место расположения логики диспетчеризации системных вызовов. Происходит сканирование ее исходного машинного кода байт за байтом в поиске инструкции call. На архитектуре x86-64 инструкция call имеет определенный однобайтовый опкод 0xe8, за которым следует 4-байтовое относительное смещение, указывающее процессору куда переходить. Критически важно найти не любую инструкцию call, а именно ту, которая в сочетании с ее 4-байтовым смещением указывает непосредственно на адрес оригинальной функции sys_kill, найденный ранее. Эта комбинация опкода 0xe8 и специфического смещения представляет собой уникальную сигнатуру внутри функции x64_sys_call.
После обнаружения этой уникальной инструкции находится точка внедрения. Но перед модификацией кода ядра необходимо обойти его защиту памяти. Поскольку выполнение происходит внутри ядра (уровень привилегий ring 0), можно использовать классическую мощную технику - отключение защиты записи путем изменения бита в регистре CR0. Этот регистр управляет базовыми функциями процессора, и его 16-й бит (Write Protect) предотвращает запись ЦП в страницы только для чтения. Временно очистив этот бит, становится возможным модифицировать любую часть памяти ядра.
С отключенной защитой записи перезаписывается 4-байтовое смещение инструкции call новым смещением, указывающим на собственную функцию fake_kill. По сути, происходит "переключение переключателя" внутри собственного диспетчера ядра, перенаправляя одиночный системный вызов на вредоносный код, оставляя остальную систему нетронутой. Эта техника является одновременно точной и надежной. Значительно, что все изменения полностью отменяются при выгрузке модуля ядра, не оставляя следов своего присутствия.
Разработка FlipSwitch свидетельствует о продолжающейся "игре в кошки-мышки" между атакующими и защитниками. Поскольку разработчики ядра продолжают укреплять Linux, злоумышленники будут находить новые и творческие способы обхода этих защит. Обнаружение руткитов после их загрузки в ядро исключительно сложно, поскольку они разработаны для скрытной работы и уклонения от обнаружения средствами безопасности. Исследователи разработали сигнатуру YARA для идентификации доказательства концепции FlipSwitch, которую можно использовать для обнаружения присутствия этого руткита в памяти или на диске.
Эволюция техник перехвата системных вызовов демонстрирует постоянную адаптацию угроз к изменениям в защите. В то время как обновление ядра 6.9 значительно усложнило жизнь создателям руткитов, появление методов вроде FlipSwitch показывает, что абсолютной защиты не существует. Для специалистов по безопасности это означает необходимость постоянного мониторинга новых техник атак и разработки соответствующих контрмер, включая поведенческий анализ и мониторинг целостности ядра.
YARA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | rule Linux_Rootkit_Flipswitch_821f3c9e { meta: author = "Elastic Security" description = "Yara rule to detect the FlipSwitch rootkit PoC" os = "Linux" arch = "x86" category_type = "Rootkit" family = "Flipswitch" threat_name = "Linux.Rootkit.Flipswitch" strings: $all_a = { FF FF 48 89 45 E8 F0 80 ?? ?? ?? 31 C0 48 89 45 F0 48 8B 45 E8 0F 22 C0 } $obf_b = { BA AA 00 00 00 BE 0D 00 00 00 48 C7 ?? ?? ?? ?? ?? 49 89 C4 E8 } $obf_c = { BA AA 00 00 00 BE 15 00 00 00 48 89 C3 E8 ?? ?? ?? ?? 48 89 DF 48 89 43 30 E8 ?? ?? ?? ?? 85 C0 74 0D 48 89 DF E8 } $main_b = { 41 54 53 E8 ?? ?? ?? ?? 48 C7 C7 ?? ?? ?? ?? 49 89 C4 E8 ?? ?? ?? ?? 4D 85 E4 74 2D 48 89 C3 48 85 } $main_c = { 48 85 C0 74 1F 48 C7 ?? ?? ?? ?? ?? ?? 48 89 C7 48 89 C3 E8 ?? ?? ?? ?? 85 C0 74 0D 48 89 DF E8 ?? ?? ?? ?? 45 31 E4 EB 14 } $debug_b = { 48 89 E5 41 54 53 48 85 C0 0F 84 ?? ?? 00 00 48 C7 } $debug_c = { 48 85 C0 74 45 48 C7 ?? ?? ?? ?? ?? ?? 48 89 C7 48 89 C3 E8 ?? ?? ?? ?? 85 C0 75 26 48 89 DF 4C 8B 63 28 E8 ?? ?? ?? ?? 48 89 DF E8 } condition: #all_a>=2 and (1 of ($obf_*) or 1 of ($main_*) or 1 of ($debug_*)) } |