Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

articles:vmlinuz [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== vmlinuz ======
 +<​sub>​{{vmlinuz.odt|Original file}}</​sub>​
 +
 +====== исследования ядра LINUX'​а ======
 +
 +крис касперски ака мыщъх
 +
 +**ядро — это фундамент всей системы. на багистром ядре хорошего линуха не постоишь. разработчики не отходят от клавиатуры,​ выявляя все новые и новые ошибки,​ но баги размножаются быстрее! далеко не все ошибки "​заразы"​ и лишь немногие из них допускают удаленное проникновение в систему. найти такой баг — большая удача! как хакеры исследуют ядро? какие инструменты используют?​ во об этом мы сейчас и поговорим!**
 +
 +===== введение =====
 +
 +Линуховое ядро это довольно сложное инженерное сооружение,​ исходные тексты которого занимают свыше сотни мегабайт. Чего тут только нет! Драйвера,​ TCP/IP стек, менеджер виртуальной памяти,​ планировщик потоков,​ загрузчик ELF-файлов и прочее барахло. Все это хозяйство не свободно от ошибок,​ над поиском которых работают десятки хакерских групп и тысячи независимых кодокопателей по всему миру. Хотите к ним приобщится?​ Что за вопрос! Кто же этого не хочет! Правда,​ не у всех получается,​ особенно с первого раза, но лиха беда начало!
 +
 +===== снаружи ядра =====
 +
 +Существуют по меньшей мере методики поиска багов, но обе они порочные и неправильные. Одни хакеры предпочитают просматривать исходные коды ядра, анализируя строку за строкой,​ другие — дизассемблируют готовое ядро. Вот неполный перечень недостатков первого способа:​
 +
 +а) вместо фактического значения переменной в Си сплошь и рядом используются макросы,​ определяемые неизвестно где, причем макрос может переопределяться многократно или, что еще хуже, различные включаемые файлы содержат несколько независимых макросов с одинаковым именем,​ так что глобальный контекстный поиск, практикуемый многими исследователями,​ не помогает (можно,​ правда,​ прогнать исходный текст через препроцессор — cpp имя_файла.c –,​ но от этого его объем, а, значит,​ и время анализа только возрастет);​
 +
 +б) ни одна известная мне IDE, не способна отображать перекрестные ссылки на функции/​данные,​ трассировать поток управления и делать множество других полезных вещей с которыми легко справляется любой приличный дизассемблер;​
 +
 +в) в процессе компиляции могут "​маскироваться"​ одни ошибки и добавляться другие,​ к тому же никогда нельзя сказать наперед по каким адресам и в каком порядке компилятор расположит переменные и буфера в памяти,​ а для написания shell-кода это критично!
 +
 +С другой стороны,​ дизассемблерный листинг ядра не просто велик. Он огромен! Это миллионы строк ассемблерного кода, и даже если на каждую команду потратить всего несколько секунд,​ даже поверхностный анализ растянется как минимум на сезон. Но ведь нам и не нужно дизассемблировать все ядро целиком! Ошибки не размазаны тонким слоем по машинному коду, а гнездятся во вполне предсказуемых местах. Никто не говорит,​ что ловить багов это просто. Зато интересно! Сознайтесь,​ разве вам никогда не хотелось заглянуть в ядро, потрогать машинный код руками и посмотреть как все это выглядит в живую (то есть "на самом деле"​),​ а не в исходных текстах,​ которые любой "​чиста хакер"​ может сказать из сети? И эта возможность сейчас представится!
 +
 +===== штурм ядра =====
 +
 +Для штурма ядра нам, во-первых,​ понадобится само ядро, которое мы собрались штурмовать. Какой дистрибьютив выбрать?​ Луче взять тот, что поновее,​ хотя особой разницы между ними нет, ведь ядро разрабатывается независимо от остальной "​начинки"​. Главное,​ чтобы он был широко распространен,​ иначе, какой прок от дырки, которая есть только на одной-двух машинах во всем мире?
 +
 +Ядро будет лежать в директории /boot под именем vmlinuz. В действительности,​ это еще не ядро, а только символическая ссылка на него. Само же ядро лежит рядом под именем vmlinuz.x.y.z,​ где xyz – версия ядра. Мы покажем как распотрошить ядра с **2.4.27** и **2.6.7**, входящие в мой любимый дистрибьютив KNOPPIX 3.7. Остальные потрошатся аналогично,​ только смещения,​ естественно,​ будут другими.
 +
 +Кроме самого двоичного файла нам так же потребуется его исходные тексты,​ с которыми мы будет сверяться в случае чего. Если они не входят в дистрибьютив (а большинство популярных дистрибутивов занимают всего один CD и распространяются без исходных текстов) их можно скачать с сервера:​ http://​www.kernel.org/​pub/​linux/​kernel/​. Нам ​ придется принять от 25 до 45 мегабайт и освободить на жестом диске по крайней мере 150 – 300 мегабайт для распаковки архива. Все ядра поставляются в упакованном виде в двух форматах — стандартном gzip'​е и более продвинутом bzip 2, который жмет на 25% плотнее,​ что уменьшает размер ядра чуть ли не на 10 Мегабайт,​ а для модемного соединения это очень ощутимая величина!
 +
 +Дизассемблер — ну лучше, чем IDA Pro вы вряд ли что-то найдете. До недавнего времени IDA Pro работала только под MS-DOS\OS/​2\Windows,​ но теперь она перенесена и на LINUX, что очень здорово! Обладателям более древних версий можно посоветовать скопировать ядро на дискету и дизассемблировать его под Windows или воспользоваться эмулятором Wine — IDA Pro замечательно работает и под ним. Кстати говоря,​ на LINUX перенесена только консольная версия,​ которая лишена всех графических "​вкусностей",​ например,​ диаграмм. Однако,​ гуевый интерфейс с точки зрения хакеров скась и маст дай. Текстовой режим форевер!
 +
 +{{vmlinuz_Image_0.png}}
 +
 +Рисунок 1 дизассемблирование ядра в консольной версии IDA Pro 4.7 под LINUX
 +
 +{{vmlinuz_Image_1.png}}
 +
 +Рисунок 2 дизассемблирование ядра в графической версии IDA Pro 4.7 под Windows 2000
 +
 +Если нет денег на IDA Pro, можно попробовать HT-editor – бесплатный hex-редактор и дизассемблер в одном флаконе. Он автоматически восстанавливает перекрестные ссылки,​ трассирует поток управления,​ поддерживает символьные имена и комментарии. Грубо говоря,​ это усеченная IDA Pro в миниатюре. Исходные тексты последней версии можно скачать с: http://​hte.sourceforge.net/​. Они успешно компилируются под Linux, FreeBSD, OpenBSD и, конечно же, Win32. Но если вам лень компилировать,​ можно скачать уже готовый бинарный файл, правда,​ далеко не первой свежести.
 +
 +{{vmlinuz_Image_2.png}}
 +
 +Рисунок 3 дизассемблирование ядра в hex-редакторе THE
 +
 +{{vmlinuz_Image_3.png}}
 +
 +Рисунок 4 редактор HTE за комплексным поиском
 +
 +===== внутри ядра =====
 +
 +Наступает волнующий миг: файл vmlinuz загружается в дизассемблер! Начинается самой интересное:​ IDA Pro не может опознать формат и загружает его как бинарный,​ а это уже нехорошо! Ядро имеет сложную структуру,​ состоящую из нескольких загрузчиков,​ последовательно отрабатывающих один за другим (ну прямо как ступни ракеты),​ а основная часть ядра упакована. Как разобраться с этим хозяйством?​ Задача-минимум:​ распотрошить ядро на модули,​ определив базовый адрес загрузки и разрядность каждого из них. Кто-то может сказать,​ а в чем, собственно проблема?​ Ведь у нас есть исходные тексты! Что ж, исходные тексты это, конечно,​ хорошо,​ но вот вопрос — какой файл какой части ядра соответствует?​ Так что без хорошего путеводителя здесь никуда!
 +
 +Первые 200h байт файла vmlinuz принадлежат boot-сектору,​ который грузится по адресу 0000:7C00 и выполняется в 16-разряном режиме. Нажимаем <​Alt-S>​ или обращаемся к меню Edit  Segment  Edit Segment (здесь и далее горячие комбинации указаны для IDA Pro 4.7,​ в других версиях они могут слегка отличаться). Вводим имя сегмента:​ boot, начальный адрес оставляем без изменений,​ а конечный меняем на 200h. На все грозные предупреждения отвечаем однозначным "​yes"​. Затем, подводим курсор к первому байту кода и нажимаем <C>, чтобы IDA Pro превратила ячейки памяти в код. После этого дизассемблирование можно продолжать как обычно. Исходный код загрузчика можно найти в файле \arch\i386\boot\bootsect.S,​ а можно и не искать — нам он не интересен. За долгие годы он вылизан дочиста. Даже если какие-то баги в нем есть, пробить в них дыру удастся навряд ли.
 +
 +{{vmlinuz_Image_4.png}}
 +
 +Рисунок 5 изменение атрибутов сегмента в IDA Pro
 +
 +Мы видим, что boot-сектор перемещается по адресу 9000h:0000h и считывает с диска вторичный загрузчик,​ который так же расположен внутри vmlinuz, сразу вслед за boot-сектором. Здесь расположены модули setup.S и video.S, загружающиеся по адресу 1000h:0000h и работающие в 16-разрядном режиме. Начало модуля setup.S опознается по сигнатуре "​HdrS",​ следующей после jmp'​а. Конец video.S легко определить по строкам:​ CGA/​MDA/​HGA/​EGA/​VGA/​VESA/​Videoadapter,​ вслед за которыми идет "​магическая последовательность"​ 00 00 B8 00 00. В обоих ядрах он расположен по смещению 14FF от начала файла. Таким образом,​ вторичный загрузчик начинается со смещения 200h и заканчивается в 14FFh. Он так же исполняется в 16-разрядном режиме и представляет собой смесь кода и данных,​ поэтому дизассемблировать его приходится с большой осторожностью (см. рисунок 6). Но прежде необходимо создать сегмент,​ ведь прошлый сегмент был усечен! Говорим Edit  Segment  New Segment,​ вводим имя сегмента (например,​ "​ldr"​),​ адрес начала (200h) и конца (1500h), а так же базовый адрес, равный стартовому адресу,​ деленному на 10h. Форсируем 16-битный режим и давим ОК.
 +
 +{{vmlinuz_Image_5.png}}
 +
 +Рисунок 6 вторичный загрузчик,​ представляющий собой смесь кода и данных
 +
 +За вторичным загрузчик идет 100h ничейных байт, забитых нулями,​ а вот затем… со смещения 1500h начинается как-то дикий код, который никак не удается дизассемблировать. IDA выводит всего несколько строк, жалобно пришит и отказывается продолжать работу (см. листинг 1):​
 +
 +1600cld
 +
 +1601cli
 +
 +1602movax, 18h
 +
 +1605db0
 +
 +1606db0
 +
 +1607db8Eh ; О
 +
 +1608db0D8h ; ╪
 +
 +Листинг 1 IDA Pro дизассемблирует "​дикий"​ код — облом
 +
 +HTE и HIEW как будто бы дизассемблируют дикий код, но делают это неправильно! (см. листинг 2).
 +
 +1600 fccld
 +
 +1601 facli
 +
 +1602 b81800movax,​ 0x18
 +
 +1605 0000add [bx+si], al
 +
 +1607 8ed8movds, ax
 +
 +1609 8ec0moves, ax
 +
 +160b 8ee0movfs, ax
 +
 +160d 8ee8movgs, ax
 +
 +Листинг 2 HTE дизассемблирует "​дикий"​ код — неправильный результат
 +
 +А все потому,​ что начиная с этого места, ядро начинает исполнятся в 32-разрядном защищенном режиме и для правильного дизассемблирования разрядность сегмента необходимо изменить. После чего, IDA Pro заработает как ни в чем ни бывало. Сейчас мы находимся в распаковщике,​ подготавливающим основной ядерный код к работе. Он реализован в файлах \arch\i386\boot\compressed\head.S и misc.c. "​Персонального"​ адреса загрузки не имеет и грузится вместе с первичным загрузчиком по адресу 1000h:​0000h. Таким образом,​ первый байт распаковщика расположен в памяти по адресу 1000h:​0000h + sizeof(ldf) == 1000h:​01300h,​ что соответствует физическому адресу 101300h. Распаковщик настраивает сегментные регистры DS/​ES/​SS/​GS/​FS на селектор 18h, а регистр CS на селектор 10h. 
 +
 +За концом распаковщика идут текстовые строки "​Systemhalted",​ "Ok, bootingthekernel",​ "​invalidcompressedformat (err=1)",​ за ними следует длинная цепочка нулей, а потом начинается упакованный код, дизассемблировать который без предварительной распаковки невозможно. А как его распаковать?​ Поскольку,​ Линуксоиды не любят изобретать велосипед и всегда стремятся использовать готовые компоненты,​ ядро упаковывается в формате gzip.
 +
 +Упакованный код начинается с "​магической последовательности"​ 1F 8B 08 00,​ которую легко найти в любом hex-редакторе. В ядре 2.4.27 она расположена по смещению 4904h, а в ядре 2.6.7 по смещению 49D4h от начала файла. Выделим область отсюда и до конца файла, и запишем ее в файл с расширением gz (например,​ kernel.gz). Пропустив ее через gzip (gzip -d kernel.gz) мы получим на выходе готовый к дизассемблированию образ ядра. IDA Pro уже ждет когда он будет загружен в нее.
 +
 +Основной код ядра исполняется в 32-разрядном режиме и грузится в память по адресу 10:​C0100000h. В самом начале идет модуль \arch\i386\kernel\head.S,​ а затем init.c, подгружающий все остальные модули. Как определить какому именно модулю соответствует данная часть дизассемблерного кода?
 +
 +В директории /boot лежит замечательный файл System.map-x.y.z (где x.y.z номер версии ядра), в котором перечислены адреса публичных символьные имен, они же метки (см. листинг 3):​
 +
 +c0108964 T system_call
 +
 +c010899c T ret_from_sys_call
 +
 +c01089ad t restore_all
 +
 +c01089bc t signal_return
 +
 +c01089d4 t v86_signal_return
 +
 +c01089e4 t tracesys
 +
 +c0108a07 t tracesys_exit
 +
 +c0108a11 t badsys
 +
 +Листинг 3 фрагментфайла System.map-2.4.27
 +
 +В частности,​ в ядре 2.4.27 метке ret_from_sys_call соответствует адрес C010899Ch. Отняв отсюда базовый адрес, мы получим смещение метки от начала файла: 899Ch, ну а саму метку нетрудно найти в исходных текстах глобальным поиском. Она определена в файле \arch\i386\kernel\entry.S. Остальные метки обрабатываются аналогично.
 +
 +А вот другой трюк: если в ядре встретилась текстовая строка или "​редкоземельная"​ команда вроде lss или mov cr4,​xxx,​ глобальный поиск легко обнаружит ее в исходных текстах. Поскольку компилятор таких команд заведомо не понимает,​ здесь явно имела место ассемблерная вставка,​ а, значит,​ дизассемблерный код будет практически полностью совпадать с соответствующим фрагментом исходного текста!
 +
 +В общем, в дизассемблировании ядра нет ничего сверхъестественного и эта задача вполне по силам рядовому кодокопателю.
 +
 +===== где гнездятся ошибки =====
 +
 +В прикладных программах и серверных приложениях наибольшее количество ошибок сосредоточено в переполняющихся буферах (атака типа bufferoverflow или bufferoverrun). В ядре так же имеются буфера,​ некоторые из которых могут быть переполнены,​ однако,​ атаки этого типа для него не так характерны.
 +
 +Вот пять основных источников ошибок — спинлуки (spinlock), неожиданные выходы из функции,​ ELF-загрузчик,​ менеджер виртуальной памяти и TCP/​IP-стек. Рассмотрим всех кандидатов поподробнее.
 +
 +Спинлуками называют ячейки памяти,​ защищающими многозадачный код от воздействия посторонних потоков. При входе в охраняемую зону, процессор устанавливает флаг, а при выходе — сбрасывает. До тех пор пока флаг не будет сброшен,​ остальные потоки топчутся у выхода и не могут выполнять код. На многопроцессорных ядрах, спинлуки начинаются с префикса LOCK, который легко найти в дизассемблерном тексте,​ если нажать <​ALT-T>​. Как мы уже говорили в статье "​захватыват ring 0 в Linux"​ – поддержка многозадачности очень сложная задача и ошибок здесь просто тьма, так что жаловаться на то, что "​всех багов уже переловили до нас"​ никому не приходится. К сожалению,​ большинство "​многозадачных"​ ошибок имеют многоступенчатый характер,​ наглядно продемонстрированный в уже упомянутой статье (см. "​проблемы многопоточности"​),​ поэтому никаких универсальных методик их поиска не существует. Это работа для настоящих хакеров,​ способных удержать все ядро в голове и сложить разрозненную мозаику в единую картину. В общем, настоящий хардкор. Это сложно?​ Ну еще бы! Но мы ведь не ищем легких путей, верно? Зато и удовлетворение от найденной дыры намного больше,​ чем от просмотра порно.
 +
 +ernel:​C010A65E loc_C010A65E:;​ CODE XREF: sub_C010A984+108↓j
 +
 +ernel:​C010A65Elockdec byte ptr [ebx-3FCE77F0h]
 +
 +ernel:​C010A665jsloc_C010AA81
 +
 +Листинг 4 классический спинлук
 +
 +Неожиданные выходы из функции (они же преждевременные) происходят всякий раз, когда из-за какой-то ошибки функция уже не может (не хочет) продолжить работу делает немедленный return. Часть работы к этому моменту уже выполнена,​ а часть еще нет. Если программист допустит даже крошечную неаккуратность,​ структуры данных превратятся в кашу. Одна из таких ошибок содержится в функции create_elf_tables,​ описанной в прошлой статье.
 +
 +Для поиска внеплановых выходов достаточно перейти в конец функции и проанализировать перекрестные ссылки,​ которые ведут наверх. Чем их больше,​ тем выше вероятность,​ что здесь окажется что-то не так. Ну а там и до дыры уже недалеко.
 +
 +kernel:​C010A810 loc_C010A810:;​ CODE XREF: kernel:​C010A7F1↑j
 +
 +kernel:​C010A810moveax,​ 0FFFFFFEAh
 +
 +kernel:​C010A815
 +
 +kernel:​C010A815 loc_C010A815:;​ CODE XREF: kernel:​C010A7CF↑j
 +
 +kernel:​C010A815;​ kernel:​C010A809↑j
 +
 +kernel:​C010A815popebx
 +
 +kernel:​C010A816popesi
 +
 +kernel:​C010A817popedi
 +
 +kernel:​C010A818popebp
 +
 +kernel:​C010A819popecx
 +
 +kernel:​C010A81Aretn
 +
 +Листинг 5 перекрестные ссылки в конце функции ведут к местам внезапного выхода
 +
 +Загрузчик ELF-файлов,​ менеджер виртуальной памяти и TCP/IP стек — это настоящие айсберги,​ которые словно ледяные горы точат из ядра кишками наружу,​ но основная масса скрыта в глубине воды. Это сотни тысяч строк кода, сложным образом взаимодействующего между собой. Это плодотворная почва для всевозможных багов, кочующих из одну версию ядра в другую. Некоторые из них уже выявлены,​ некоторые только предстоит найти. В первую очередь следует обратить внимание на обработку нестандартных полей или дикое сочетание различных атрибутов (см. "​эльфы падают в дамп"​). Чтобы действовать не вслепую,​ имеет смысл скачать свежую подшивку RFC и обзавестись спецификацией на ELF формат. И то, и другое легко найти в сети.
 +
 +===== >>>​ врезка ссылки по теме =====
 +
 +  - **www****.****idapro****.****com****:​**
 +    - официальный сайт лучшего в мире коммерческого дизассемблера IDA Pro (на английском и русских языках);​
 +  - **http****://​****hte****.****sourceforge****.****net****:​**
 +    - официальный сайт некоммерческого HEX-редактора,​ по возможностям приближающегося к IDA Pro (на английском языке);​
 +  - **http://​www.kernel.org/​pub/​linux/​kernel:​**
 +    - официальный сервер,​ раздающий исходные тексты линуховых ядер;
 +  - **www****.****rfc****-****editor****.****org****:​**
 +    - сборник RFC-стандартов,​ описывающих сетевые протоколы,​ бесплатно (на английском языке);​
 +  - **www.skyfree.org/​linux/​references/​ELF_Format.pdf — ELF:**
 +    - спецификация ELF-формата,​ бесплатно (на английском языке);​
 +===== заключение =====
 +
 +Вот мы и добрались до ядра! Погрузились в настоящий дизассемблерный мир и увидели как выглядит LINUX не только извне, но и изнутри. Теперь самое главное запастись пивом, пакетными супами и терпением. Не стоит рассчитывать на быстрый успех. На поиск первой дыры могут уйти месяцы,​ особенно,​ если дизассемблер еще подрагивает в неуверенных рукам и постоянно перелистывается потрепанный справочник по машинным командам. В режиме глубокого хачинья,​ хакеры не отрываются от компьютера по 30 и даже 40 часов. Дизассемблирование затягивает! Попасть к нему в лапы легко, а вот вырваться очень сложно!
 +
 +