LINUX.ORG.RU

elf, образ ядра


0

0

Передо мной поставили задачу разработать утилиту, позволяющую сравнивать образ ядра в памяти и на диске для определения наличия постороннего кода в загруженном ядре. Пока что собираюсь контролировать целостность таблицы системных вызовов и по возможности самих системных вызовов, так как это излюбленное место внедрения ВПО. Не стал сразу бросаться что-то кодить, хочется выслушать мнения специалистов.

Насчет доступа к образу ядра в памяти есть некоторые наработки, так что тут вроде все понятно. Погуглил в сторону образа ядра на диске (/boot/vmlinuz-2.6.xx.xx). Выяснил, что это запакованный gzip elf файла ядра, перед которым лежит код распаковщика и загрузчика. Выдернул из этого файла архив ядра по статье http://bappoy.pp.ru/2008/09/05/extracting-vmlinux.html , посмотрел на него:), потыкался objdump`ом.

Какие вижу проблемы. Elf файл ядра, как я понимаю, представляет собой нелинкованное ядро. По этому поводу таблицы системных вызовов как таковой там не должно быть - ее должен кто-то заполнить при загрузке. В связи с этим придется каким-то образом самостоятельно рассчитывать вероятные адреса загрузки системных вызовов и сравнивать их тем, что имеет место в таблице системных вызовов. Если бы все системные вызовы лежали в одной секции, то как бы ядро не было загружено, последовательность разностей адресов системных вызовов из соседних ячеек в этой таблице должна была бы оставаться константной. Эту последовательность можно было бы сравнить с тем, что мы видим в памяти. Но лежат ли они всегда в одной секции?

В общем, хочется услышать мнения по поводу того, как можно реализовать такую утилитку.

> Передо мной поставили задачу разработать утилиту, позволяющую сравнивать образ ядра в памяти и на диске для определения наличия постороннего кода в загруженном ядре.

А модули анализировать тебе не надо?

> Elf файл ядра, как я понимаю, представляет собой нелинкованное ядро.

Полностью слинкованное.

> Если бы все системные вызовы лежали в одной секции, то как бы ядро не было загружено, последовательность разностей адресов системных вызовов из соседних ячеек в этой таблице должна была бы оставаться константной.

IIRC, загрузка происходит не по секциям, а по сегментам.

> хочется услышать мнения по поводу того, как можно реализовать такую утилитку.

Тебе дали неформальную и, ИМХО, бессмысленную задачу.

tailgunner ★★★★★
()


нехорошие дяди заюзают отладочные регистры процесора и весь ваш анализ ядра пойдёт сугубо сквозь их фильтр. за подробностями смотрите phrack.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> нехорошие дяди заюзают отладочные регистры процесора

Не, ну вдруг эта утилита предназначена для работы с дампом памяти KVM (или приостановленной KVM).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

>Тебе дали неформальную и, ИМХО, бессмысленную задачу.
обучение разве бессмысленно?

а могут ли нехорошие дяди использовать SMM или там виртуализацию?

dimon555 ★★★★★
()
Ответ на: комментарий от dimon555

>> Тебе дали неформальную и, ИМХО, бессмысленную задачу.

> обучение разве бессмысленно?

Ну, для обучения задача нормальная.

> а могут ли нехорошие дяди использовать SMM или там виртуализацию?

Могут и исплользуют.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Тебе дали неформальную и, ИМХО, бессмысленную задачу.

Это НИР, он редко бывает сильно осмысленным.

> Полностью слинкованное.

То есть все адреса уже известны?

> IIRC, загрузка происходит не по секциям, а по сегментам.

Идея с разностью указателей на системные вызовы работать не будет? Просто хочется быстро сделать что-нибудь, что поймает хотя бы простенький руткит, используя как глобальную идею сравнение образов ядра.

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

> Идея с разностью указателей на системные вызовы работать не будет? Просто хочется быстро сделать что-нибудь, что поймает хотя бы простенький руткит, используя как глобальную идею сравнение образов ядра.

ну дык заведи табличку с сигнатурами заданных простеньких руткитов и лови себе сколько хочешь :)

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от codergeneration

> Такое уже есть. А мне нужна "новизна":) По этому руководитель сказал накодить ему такую штуку

ну накодь. к примеру, подумай на тему того, что перехват системных вызовов или прерываний - это всё конечно здорово. но редкий руткит уместится в сто байт соотв. где-то ему нужно жить. причём в памяти с правами на исполнение. и редкое ядро модифицирует само себя. а кончики не полностью занятых страниц скорее всего забивают каким-то штатным патерном, например, нулями.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от codergeneration

>По этому руководитель сказал накодить ему такую штуку
а защита-то когда? если 10 июня этого года, то у тебя дела не очень
а если она не летом, то ты рано суетишься ;)

dimon555 ★★★★★
()
Ответ на: комментарий от codergeneration

>> Полностью слинкованное.

> То есть все адреса уже известны?

Нет у меня под рукой собранного vmlinux :) Вполне возможно, что известны.

>> IIRC, загрузка происходит не по секциям, а по сегментам.

> Идея с разностью указателей на системные вызовы работать не будет?

На первый взгляд, будет. И разбивка функций по секциям не повредит.

tailgunner ★★★★★
()
Ответ на: комментарий от klalafuda

мне надо всего лишь сказать, заражена система или нет. А уж как руткитописатили извратились, чтобы разместить код - это тема другого исследования. Но мимо перехвата им будет не пройти. Так что контроль адресов + джампов в начале вызовов 90% руткитов пропалит.

codergeneration
() автор топика
Ответ на: комментарий от tailgunner

В таком случае, пользуясь случаем, задам еще один вопрос. В Linux код одной функции располагается в единой области памяти или разбросан кусками? Просто как программа максимум у меня еще сравнить код функций. Искать их куски времени точно уже нет. А если он единым блоком, то циклик сравнения я быстро накатаю:)

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

> В Linux код одной функции располагается в единой области памяти или разбросан кусками?

Я бы сильно удивился, если бы он был разбросан.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Я бы сильно удивился, если бы он был разбросан.

мало ли. Какая-нибудь оптимизация.

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

> мне надо всего лишь сказать, заражена система или нет. А уж как руткитописатили извратились, чтобы разместить код - это тема другого исследования. Но мимо перехвата им будет не пройти. Так что контроль адресов + джампов в начале вызовов 90% руткитов пропалит.

Бросая камни в воду, смотри на круги, ими образуемые. Иначе твое занятие будет пустою забавою. (с) К.Прутков.

если уж вы взялись за тему - ведите её до конца. внедриться руткит может в любое место ядра. предположение о том, что если таблица IVT чиста значит никого нет - это весьма наивное предположение.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

Насколько я представляю, более-менее универсальный руткит наиболее вероятно будет внедряться именно туда, потому что внедряться в более глубокие слои неизвестно кем скомпиленного ядра в автоматическом режиме - задача не тривиальная. Я не рассматриваю того случая, как описан в какой-то статье, где предлагалось качать дамп памяти ядра, исследовать его у себя на машине, модифицировать код руткита, собирать его и устанавливать на удаленную машину. Против такого лома, конечно, моя утилитка ничего не сможет сделать. Да перед ней и не ставится пока задача творить чудеса.

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

пока что удалось получить elf файл ядра. Решил для парсинга elf поискать готовое решение. Нашел парсер elfviz - довольно интересная консольная программа, имеющая gui фронтэнд для желающих. Особенность ее интерфеса заключается в том, что навигация по файлу осуществляется с помощью команд ls, cd, cat в интерактивном режиме. Проблема в том, что при попытке перейти в некоторые области файла, программа заваливается. По этой причине не пашет и гуишная оболочка, которая при запуске проходится по всему дереву. Сейчас сижу разбираюсь с кодом в надежде исправить ошибку.

По ходу возник вопрос, ответ на который пытаюсь найти. В каких именно секциях надо искать system_call_table и сами системные вызовы? И можно ли вообще их там найти по символам, учитывая то, что я работаю с stripped файлом? Есть мысль попробовать использовать System.map файл, хотя пока я не до конца понимаю связь адресов загруженного ядра и его elf-файла.

codergeneration
() автор топика
Ответ на: комментарий от LamerOk

а все экспортируемые имена есть в секциях? У меня что-то появились сомнения по поводу того, что system_call_table заполнена в elf образе. В драйверной части моей программы мне пришлось вручную искать адрес этой таблицы с помощнью адреса известного вызова аналогично предложенной в http://kldp.org/node/84891 функции find_system_call_table. Возможно, что можно использовать аналогичный способ. Хотя если разобраться с соотношением адресов загруженного ядра и секций и смещений в них elf файла, то можно вообще прочитать все адреса из system.map файла и не париться.

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

> У меня что-то появились сомнения по поводу того, что system_call_table заполнена в elf образе.

Всё верно - сама таблица заполняется рантайм.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Тогда мне не ясен механизм заполнения. Я понимаю, если бы был список символов, соответствующих системным вызовам. Тогда при загрузке ядра можно было бы пробежаться по этим символам, получить адреса соответствующих областей памяти и записать результаты в таблицу system_call_table. Но учитывая один из предыдущих постов, не экспортируемые функции не присутствуют среди символов elf файла. В таком случае, по какому принципу заполняется таблица? Откуда загрузчик узнает, чьи адреса надо записать? Опять же, в System.map записаны конкретные адреса, значит, заранее известно, куда загрузятся функции. Зачем тогда динамически заполнять?

В общем, пока я думаю реализовать такой алгоритм: 1) Модуль ядра по запросу из юзермодной программы находит адрес таблицы системных вызовов и дампит ее в виртуальный файл в /proc куда-нибудь 2) Юзермодная программа последовательно читает этот файл, выбирает из System.map адреса соответствующих системных вызовов с сравнивает. Если равны, то эта часть проверки прошла. 3) Юзермодная программа парсит elf образ ядра и смотрит адреса системных вызовов, сравнивает с данными, полученными из модуля ядра. Как найти все это дело в elf пока мыслей нет. 4) По-хорошему надо бы посчитать пару разных хешей от кода каждого системного вызовова, но тут встает еще проблема определения размера памяти, занятой под эти вызовы. Да в elf эта память может оказаться с дырками, так как некоторые адреса будут поправлены при загрузке, так что хэши пойдут лесом :(

codergeneration
() автор топика
Ответ на: комментарий от LamerOk

> Всё верно - сама таблица заполняется рантайм.

нет
на выходе компоновщика файл vmlinux - elf образ ядра
адреса всех символов на этом этапе уже известны, в чем можно убедиться через readelf
другое дело, что потом через objcopy вся информация о символах удаляется
в общем см. linux/arch/*/boot/compressed/Makefile

rei3er
()
Ответ на: комментарий от rei3er

Ок. В таком случае что если применить следующий подход? Модуль ядра получает адрес system_call_table, создает псевдофайл, передает через него дамп этой таблицы и ее адрес. Моя программа из юзермода читает этот файл, берет адрес таблицы. Далее программа пробегается по сегментам elf файла и по полям sh_addr и sh_size определяет, в каком сегменте должна располагаться область памяти, соответствующая этому адресу, то есть system_call_table. Далее программа переходит на этот сегмент по вычисленному смещению и сравнивает дамп system_call_table ядра и ее образ по полученному смещению в файле.

К примеру, elfviz для секции .text.head дает такую информацию (для остальных секций падает, пока не понял, почему):

elfviz/./vmlinuz.elf/shdr/>inf .text.head

sh_name=15

sh_type=SHT_PROGBITS

sh_flags=SHF_ALLOC | SHF_EXECINSTR

sh_addr=-1072693248

sh_offset=4096

sh_size=945

sh_link=0

sh_info=0

sh_addralign=16

sh_entsize=0

codergeneration
() автор топика
Ответ на: комментарий от rei3er

А ты можешь показать механизм заполнения arch/*/kernel/syscall_table*.S ? И заодно объяснить смысл __attribute__((weak,alias("sys_ni_syscall"))) (который cond_syscall из kernel/sys_ni.c )?

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

> А ты можешь показать механизм заполнения arch/*/kernel
> /syscall_table*.S ?

при компиляции создается syscall_table*.o
секция .rodata содержит пока еще нулевые значения адресов sys_* функций
в .rela.rodata на каждый sys_* есть релокация с соответствующим смещением в .rodata
когда происходит сборка ядра на вход компоновщика вместе со всеми объектниками подается vmlinux_*.lds.S, где описан мэппинг входных секций на виртуальные адреса выходных
дальше он тупо в соответствии с мэппингом формирут файл образа ядра
т. к адреса выходных секций известны, то вычислить абсолютные адреса всех символов входных объектников просто
на основе адресов вычисляются релокации

rei3er
()
Ответ на: комментарий от rei3er

> дальше он тупо в соответствии с мэппингом формирут файл образа ядра

Кто "он"?

> т. к адреса выходных секций известны, то вычислить абсолютные адреса всех символов входных объектников просто на основе адресов вычисляются релокации

Я правильно понимаю - именно так адреса заносятся в sys_call_table?

tailgunner ★★★★★
()
Ответ на: комментарий от LamerOk

> И заодно объяснить смысл 
> __attribute__((weak,alias("sys_ni_syscall"))) (который cond_syscall 
> из kernel/sys_ni.c )?

я могу предположить, что в таком контексте weak применяется для того, чтобы, если не определена одна из sys_*, то вместо нее вызывалась sys_ni_syscall

rei3er
()
Ответ на: комментарий от tailgunner

> Кто "он"? 

ld

> Я правильно понимаю - именно так адреса заносятся в sys_call_table?

да
можно посмотреть syscall_table*.o

rei3er
()
Ответ на: комментарий от rei3er

А что насчет алгоритма поиска system_call_table? Удастся ее найти в файле, зная ее адрес, просмотрев все секции elf, учитывая адрес их загрузки и длину?

codergeneration
() автор топика
Ответ на: комментарий от rei3er

Бинарный поиск последовательности байт, полученной из дампа таблицы системных вызовов работающей системы, в elf файле ядра показал, что таблица там лежит и она заполнена.

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

Теперь мой антируткит умеет проверять целостность таблицы системных вызовов и первых 5 байт самих системных вызовов. При тестировании оказалось, что код 158го системного вызова в исполняющемся ядре отличается от того, что находится в elf образе ядра на диске. Причем совпадают первые 4 байта, потом несколько байт отличаются, а потом снова совпаюат. Из System.map выяснил, что это c012e100 T sys_sched_yield. Дамп первых 50 байт следующий: из ядра: 55 89 e5 53 fa 90 8d b4 26 00 00 00 00 90 64 a1 80 15 59 c0 ba 80 83 59 c0 8d 1c 02 89 d8 e8 3d cb 25 00 83 83 f8 04 00 00 01 64 a1 00 10 59 c0 8b 50;

из elf файла: 55 89 e5 53 51 52 ff 15 44 ef 49 c0 5a 59 64 a1 80 15 59 c0 ba 80 83 59 c0 8d 1c 02 89 d8 e8 3d cb 25 00 83 83 f8 04 00 00 01 64 a1 00 10 59 c0 8b 50.

Дизассемблированные версии: Ядро:

push ebp

seg000:00000001 mov ebp, esp

seg000:00000003 push ebx

seg000:00000004 cli

seg000:00000005 nop

seg000:00000006 lea esi, [esi+0]

seg000:0000000D nop

seg000:0000000E mov eax, fs:0C0591580h

seg000:00000014 mov edx, 0C0598380h

seg000:00000019 lea ebx, [edx+eax]

seg000:0000001C mov eax, ebx

seg000:0000001E call near ptr 25CB60h

seg000:00000023 add dword ptr [ebx+4F8h], 1

seg000:0000002A mov eax, fs:0C0591000h

elf образ:

push ebp

seg000:00000001 mov ebp, esp

seg000:00000003 push ebx

seg000:00000004 push ecx

seg000:00000005 push edx

seg000:00000006 call dword ptr ds:0C049EF44h

seg000:0000000C pop edx

seg000:0000000D pop ecx

seg000:0000000E mov eax, fs:0C0591580h

seg000:00000014 mov edx, 0C0598380h

seg000:00000019 lea ebx, [edx+eax]

seg000:0000001C mov eax, ebx

seg000:0000001E call near ptr 25CB60h

seg000:00000023 add dword ptr [ebx+4F8h], 1

seg000:0000002A mov eax, fs:0C0591000h

Что-то не верится, что это руткит поймался. Попросил знакомого запустить мою прожку - у него такая же фигня с этим системным вызовом. Есть какие-нибудь идеи?

codergeneration
() автор топика
Ответ на: комментарий от idle

>>у едра код самомодифицирующийся... это реально так или предположение?

Запустил еще на другой системе - снова такие же результаты.

codergeneration
() автор топика
Ответ на: комментарий от codergeneration

> это реально так или предположение?

это реально. см напр arch/x86/include/asm/alternative.h

другое дело, что я не могу понять конкретно этот код...
ну никак этот ассемблер (ни до, ни после) не соответствет
sys_sched_yield().

да и вообще как-то странно. "lea esi, [esi+0]" это, вроде
бы, GENERIC_NOP3. однако у этой команды длина 3 байта, а
у вас 0xD - 0x6 == 7.

сколько CPU на машине, на которой ядро запускается? версия
ядра какая?

покажите С код этой функции, и соответствующий кусок из
"make kernel/sched.s" на человеческом асме.

idle ★★★★★
()
Ответ на: комментарий от idle

Всем спасибо за помощь! работу защитил. Дальнейшие исследования буду проводить в фоне и стараться самостоятельно разбираться.

codergeneration
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.