Карта сайта Хакер в RSS Энциклопедия Хакера PDA версия сайта Почтовые рассылки Хакера    Хакер в Twitter Хакер в ВКонтакте Приложение Хакер для Facebook Хакер на Formspring.me
Журнал Новости Форум Видео Life Xakep Live (блоги)
Bugtrack Статьи Блог Поиск English
Взлом как образ мысли Взлом как образ мысли
Интервью с человеком, который, как оказалось, является не только талантливым пентестером в одной из крупных ИБ-компании, но и хакером-ветераном, который уверенными шагами вышел на свет и прикоснулся к истории российской хак-сцены....
Полный гид по накрутке онлайн-голосований Полный гид по накрутке онлайн-голосований
Конкурсы с голосованием привлекают большое количество посетителей, а трафик, как известно, — это деньги. Особый интерес вызывают конкурсы, где за победу предлагаются лакомые призы....

Подмена системных вызовов в ядрах 2.6.х

Bookmark and Share

Данная статья была написана в долгих и мучительных попытках реализовать подмену системного вызова sys_mkdir на ядре версии 2.6.23. Исходным шагом для нее стала работа dev0id-а о защите от исполнения в стеке ОС Linux (ссылку на статью можно найти в конце данной статьи). И как и говорил уважаемый dev0id, первоочередной задачей для нас стала подмена системных вызовов своими. Для примера (уж не знаю почему, возможно так сошлись звезды в созвездии стрельца) был выбран системный вызов sys_mkdir. Но для начала немного остановимся на том, как вообще писать модули.

Любой модуль должен обязательно состоять как минимум из двух функций: int init_module() и void cleanup_module(). Вообще данные функции можно заменить и своими, но в нашем случае мы не будем это рассматривать, как и то, что функция init_module может в себя принимать параметры как и функция main(). Я думаю, что из названия становится понятно, что функция init_module() вызывается в тот самый момент когда вы делаете /sbin/insmod, т.е. загружаете свой модуль в ядро. Ну и соответственно cleanup_module() происходит при выгрузке модуля из ядра.

Простейший же модуль выглядит так:

/*------------------------------------------------*/
#include <linux/module.h>
#include <linux/kernel.h>
/*------------------------------------------------*/
MODULE_LICENSE("GPL");
/*------------------------------------------------*/
int init_module(void)
{
printk("It`s our rules module :-)\n");
return 0;
}
/*------------------------------------------------*/
void cleanup_module(void)
{
printk("Our module was unload :-(\n");
}

В этом листинге вызывается макрос MODULE_LICENSE("GPL");, он вызывается для того, чтобы при загрузке модуля не было сообщения о неизвестной лицензии модуля. Теперь необходимо скомпилировать наш модуль, для этого создаем простой Makefile, содержащий всего одну строчку:

obj-m := simple_module.o, где simple_module нужно поменять на имя вашего исходника.

Теперь остается сделать

make -C путь_к_исходникам_ядра M=$PWD modules

У меня путь к исходникам /usr/src/kernels/2.6.23.15-80.fc7-x86_64, у вас он может быть другим.

Загрузим наш модуль в ядро командой /sbin/insmod название_модуля. Модули в ядрах 2.6.х имеют расширение .ko. На первый взгляд ничего не произошло, но это только на первый взгляд. Выполним команду dmesg | tail -n 1 для того, чтобы просмотреть сообщения ядра - как видите, наш модуль вывел «It`s our rules module :-)», а это значит у нас все получилось.

Теперь вернемся к основной нашей проблеме - когда динозавры были еще маленькие, а ядра имели версию 2.4.х., можно было без проблем вызывать любой системный вызов с помощью тогда еще экспортируемой Таблицы системных вызовов (sys_call_table). Однако в ядрах версии 2.6.х лафа закончилась и таблицу системных вызовов экспортировать перестали. Однако и ум человеческий не стоит на месте. И dev0id таки придумал, как убрать этот маленький недостаток. Дело в том, что вызов sys_close экспортируется, а так как адрес этого вызова находится как раз в таблице системных вызовов, то достаточно пробежаться в секции данных для того, чтобы найти необходимый нам адрес. Более подробно вы можете прочитать в статье dev0da, замечу лишь, что нужно немножко переделать данную функцию для того, чтобы она заработала на х86-64, как это сделать вы можете посмотреть в прилагаемом листинге.

Однако же на этом все проблемы и душевные терзания не закончились - дело в том, что попытавшись в лоб поменять адрес системного вызова, мне надавали пощечин, сказали «Ошибка сегментирования» и ко всему прочему в ядре остался модуль, который будет продолжать висеть там до перезагрузки. В связи с этим напрашивалось всего два решения: или бросить это гиблое дело, или попробовать переписать вызов sys_mkdir на лету. Но так как времени было много, а пива еще больше, было решено проверить возможность перезаписи системных вызовов. Сохраним первый байт функции sys_mkdir, что бы восстановить его при выгрузке модуля. Как и все гениальное это очень просто.

long* sys_call_table = find_sys_call_table();
// нашли адрес sys_call_table
_sys_mkdir = *(sys_call_table + __NR_mkdir);
// взяли адрес sys_mkdir
char old_first_byte = *((char*)_sys_mkdir);
// а вот и первый байт

Итак это готово, в общем случае можно записать по этому байту что угодно, однако, чтобы не получить при вызове команды mkdir ошибку сегментирования, было решено записать туда опкод команды ret, т.е. 0хс3. Загрузив модуль ошибки сегментирования получено не было, значит писать в эту функцию можно, теперь попробуем вызвать команду mkdir. В лучшем случае мы получаем ответ о том, что пропущен аргумент, в худшем команда вроде бы исполняется, но естественно никакой директории не создает. Отлично, значит можно в эту функцию, дописать что угодно, но что угодно не лучшее решение, поэтому нужно было дописать код, который бы вызывал нашу функцию, а затем делал ret чтобы предотвратить исполнение оставшейся части функции sys_mkdir. Сказано - сделано. Прежде всего пришлось разобраться с опкодами команд для процессоров Intel. В этом помогает статья об опкодах на wasm.ru. Значит дело осталось за малым - взять документацию по процессорам, которую Intel любезно предоставляет всем страждущим. На ассемблере код нашей новой функции выглядит так.

xor eax, eax ;(для х86-84 rax)
mov rax, адрес_нашей_функции
call rax ;вызвали нашу функцию
ret ;вернулись в функцию вызвавшую sys_mkdir

Вот и все, теперь смотрим опкоды инструкций для Интела. Для первой инструкции подходит опкод

31 /r XOR r/m32, r32 для 32 битной системы, и
REX.W + 31 /r XOR r/m64, r64 для 64 битной.

По суффиксу /r становится ясно, что необходимо задать следующим байтом оба операнда, байт этот получается 11 000 000, на wasm его называют ModR/M, не будем от этого отступать, все тонкости как он получается я не буду объяснять, так, как все это подробно описано в статье на wasm.ru. Для 64 битной же системы существует еще и префикс REX - как его получить вы можете также узнать из документации по процессорам Intel. Он просто должен быть равен 0х48. Значит для первой инструкции опкод получается 0х31 0хс0. Аналогично получаем опкод инструкции mov:

B8 +rd MOV r32, imm64 и
REX.W B8 +rd MOV r64, imm64

Так как мы используем регистр eax, то опкод инструкции так и есть 0x0b8 за которым следует адрес нашей функции. Если бы у нас был регистр ecx, то опкод был бы 0хb9, если edx то 0хba, ebx - 0xbb и т. д. Кстати, адрес нашей функции должен быть перевернут, так как в памяти байты хранятся в обратном порядке, т. е. если у вас был адрес 0х00a1b2c3d4e5f611 то он станет 0x11f6e5d4c3b2a100, от такие вот пироги.

Инструкция call:

FF /2 CALL r/m32 и
FF /2 CALL r/m64

Это говорит о том, что данная инструкция одинакова как для 32 так и для 64 битных платформ. Суффикс /2 говорит о том, что в байте ModR/M Reg должно всегда быть числом 2. Получаем Mod(11) Reg(010 = 2) R/M(000 = EAX). Получаем 0хd0. Значит опкод для обоих платформ будет 0xff 0xd0.

Ну и как я уже говорил остается ret. Её опкод 0хс3.

У нас получилось, что весь наш код занимает 16 байт для х86-64 и 10 для 32 битной платформы. Остается только сохранить эти байты для того, чтобы восстановить их после выгрузки модуля.

char old_commands[COMMANDS_COUNT];
memcpy(old_commands, (void *)sys_mkdir_ptr, COMMANDS_COUNT);

Вот вам в принципе и вся теория, позволяющая подменять системные вызовы в ядрах версии 2.6.х. Только не забывайте, что если вам понадобится использовать аргументы, которые передаются системному вызову, то взять их можно из регистров ebx — 1, ecx — 2 и т.д., а для этого функция должна быть как asmlinkage, в противном случае функция будет искать все аргументы в стеке. Ну и соответственно желательно бы, чтобы после отработки ваша функция таки вызывала оригинал, для этого достаточно перенести ее в другую область памяти. Вот и все, пора идти допивать оставшееся пиво :-).

Ссылки

Статья от dev0idа:

http://www.securitylab.ru/contest/212111.php

Статьи на wasm.ru:

http://www.wasm.ru/print.php?article=codech03
http://www.wasm.ru/print.php?article=codech04
http://www.wasm.ru/print.php?article=codech05

Документация от Intel:

http://www.intel.com/products/processor/manuals/index.htm

Исходники



Теги: Linux , программирование , ядро





ПРЕДЫДУЩИЕ СТАТЬИ
Cifratura la Vista: используем криптоядро Windows Vista
Невидимые LKM-атаки на Windows NT: поваренная книга руткитмейкера
Периметр безопасности при помощи Bluetooth
Хардкорная отладка с Linice: учимся работать в консольном отладчике ядра
Написание собственной Операционной Системы №3
Написание собственной Операционной Системы №2
Написание собственной Операционной Системы №1
Написание драйвера в подробностях №4
Написание драйвера в подробностях №3
Написание драйвера в подробностях №2
ОБСУЖДЕНИЕ СТАТЬИ
Логин:
Пароль:
Если у вас есть форумный логин - вы можете использовать его, иначе анонимный гостевой доступ.

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

Обсуждение этой статьи на forum.xakep.ru
Для отправки сообщения введите код, указанный на картинке
Сообщение

UserГость
23.04.2008 18:42:19
Ответить Ссылка
а теперь, внимание, вопрос: зачем все это нужно? или just for fun?
UserГость
23.04.2008 21:18:31
Ответить Ссылка
Хорошая статейка, спасибо автору!
UserГость
23.04.2008 23:02:27
Ответить Ссылка
Эээ, дык это... Линукс капец???
UserГость
23.04.2008 23:05:20
Ответить Ссылка
Эй, а почему я +1 поставил, а как был "0", так и остался? Хорошая статья!
UserГость
24.04.2008 8:47:19
Ответить Ссылка
А нужно это для создания руткитов
UserГость
24.04.2008 9:00:28
Ответить Ссылка
Это нужно, чтобы писать руткиты... или модуля ядра "под свои нужды".

девоид
UserГость
24.04.2008 22:26:12
Ответить Ссылка
Спасибо. Будет повод для тестов...
UserГость
24.04.2008 23:00:05
Ответить Ссылка
>>Это нужно, чтобы писать руткиты... или модуля ядра "под свои нужды".

гм, ну тогда зачет )))
глядишь через годик другой и защита от этого появится...
UserГость
25.04.2008 9:49:06
Ответить Ссылка
Респект автору, хорошая статья
UserXaleandr
25.04.2008 9:57:19
Ответить Ссылка
Маленькая деталь: загрузить модуль в ядро может только рут, права которого в системе и так не ограничены. Так что сей опус--не более чем красивое упражнение ?!
UserГость
25.04.2008 12:15:06
Ответить Ссылка
Почетайте о назначении руткитов, тогда станет понятно для чего это нужно
UserГость
25.04.2008 12:16:12
Ответить Ссылка
А почему исходники не качаются :-(
UserГость
25.04.2008 15:26:12
Ответить Ссылка
ну и сколько можно мусолить одно и тоже? дерьмо...нового нуль, старое изложено коряво.
UserГость
25.04.2008 16:54:26
Ответить Ссылка
А мне например нравится, все что я нашел до этого не работало
UserГость
25.04.2008 16:59:51
Ответить Ссылка
Брехня! Не работает!
КГ/АМ
UserГость
25.04.2008 20:42:53
Ответить Ссылка
Былдо проверено на нескольких ядрах, все работает безупречно, напиши подробней на каком ядре и что говорит когда не работает prof_GCC
UserГость
25.04.2008 20:47:15
Ответить Ссылка
488997480 напиши, что конкретно не работает, все тестировалось на ядрах 2.6.18 - 2.6.24
UserГость
26.04.2008 15:19:40
Ответить Ссылка
зачем заморочка с опкодами когда есть objdump?
UserГость
26.04.2008 16:41:09
Ответить Ссылка
Замарочка с опкодами нужна, для того, чтобы люди научились писать опираясь на документацию, положим человек захотел написать полиморфик, и ему нужна по максимуму вытащить все синонимы для какой либо команды
UserГость
26.04.2008 16:46:00
Ответить Ссылка
Люди наконец должны научится, оперировать своим мозгом, причем получить опкоды из книги двойных слов для меня гораздо быстрее чем из objdump. Хотя если действительно лень разбераться, то просто objdump -S
UserГость
26.04.2008 17:08:02
Ответить Ссылка
Не выспался??? :(
UserГость
27.04.2008 19:38:44
Ответить Ссылка
Статья хорошая, но боян.
Руткит enylkm или как-то так использует именно эту технику, а его сорцы смотрел чуть меньше года назад
UserГость
27.04.2008 22:35:11
Ответить Ссылка
Что можно сделать, чтоб сделать подмену вызовов невозможной? Перекомпилировать ядро исключив какие-то опции? Какие???
UserГость
27.04.2008 23:40:31
Ответить Ссылка
Может и так, только мне она так и не попалась :-( prof_GCC
UserГость
29.04.2008 16:37:26
Ответить Ссылка
> Былдо проверено на нескольких ядрах, все работает безупречно, напиши подробней на каком ядре и что говорит когда не работает prof_GCC
На всех ядрах выше 2.6.13 - стоит дополнительная защита. Этот код не работает.
UserГость
30.04.2008 9:26:22
Ответить Ссылка
А ты проверял, лично нами был проверен этот код на указанных выше ядрах, причем как на 32 так и на 64 битных, дополнительная защита была поставлена в виде невозможности замены адресов системных вызовов в таблице, но мы их и не меняем, а просто переписываем код функции на лету.
UserГость
30.04.2008 9:28:12
Ответить Ссылка
Я дал свою аську, пожалуйста пишите, если не работает, присылайте скриншоты, я проверял все прежде чем тупо слить статью.
UserГость
30.04.2008 17:39:34
Ответить Ссылка
> А ты проверял
Нет. Лично я не проверял.
UserГость
02.05.2008 14:06:05
Ответить Ссылка
Тогда соответственно, логичный вопрос, если ты не проверял, зачем тогда заявляешь, что код не работает, если бы он не работал, я бы и не писал статью.
UserГость
03.05.2008 11:56:02
Ответить Ссылка
> Тогда соответственно, логичный вопрос,
> если ты не проверял, зачем тогда заявляешь,
> что код не работает, если бы он не работал,
> я бы и не писал статью.
Пришли полный исходный текст модуля?
Ссылка:
http://www.securitylab.ru/contest/212111.php
не работает.
UserГость
04.05.2008 20:53:11
Ответить Ссылка
А эта ссылка и не причем ссылка так и называется ИСХОДНИКИ
UserГость
07.05.2008 18:53:18
Ответить Ссылка
> А эта ссылка и не причем ссылка так и называется
> ИСХОДНИКИ
Проверю
UserГость
17.05.2008 14:59:21
Ответить Ссылка
А по-моему статья всё-таки боян.. колько можно уже писать об одном и том же.
UserГость
21.05.2008 15:57:33
Ответить Ссылка
Проверил. Работает.
Извини, я подумал, ты пытаешься записать в таблицу системных вызовов.
функция mkdir при этом перестаёт работать.
Makefile можно написать такой:

obj-m += mod.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules




Keywords: zPOSTz zSOFTz, zHOWz, zOSz, zINFOz, zYANDEXz z43366z
Для Авторов: edit Lock delete Lock



    Rambler's Top100