Перехват системных вызовов в ОС Linux::Журнал СА 3.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6188
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6898
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4182
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 2986
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3793
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3803
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6296
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3151
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3445
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7261
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10627
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12349
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 13979
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9109
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7064
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5373
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4603
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3412
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3142
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3389
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3011
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Перехват системных вызовов в ОС Linux

Архив номеров / 2003 / Выпуск №3 (4) / Перехват системных вызовов в ОС Linux

Рубрика: Администрирование /  Продукты и решения

ВЛАДИМИР МЕШКОВ

Перехват системных вызовов в ОС Linux

За последние годы операционная система Linux прочно заняла лидирующее положение в качестве серверной платформы, опережая многие коммерческие разработки. Тем не менее вопросы защиты информационных систем, построенных на базе этой ОС, не перестают быть актуальными. Существует большое количество технических средств, как программных, так и аппаратных, которые позволяют обеспечить безопасность системы. Это средства шифрования данных и сетевого трафика, разграничения прав доступа к информационным ресурсам, защиты электронной почты, веб-серверов, антивирусной защиты, и т. д. Список, как вы понимаете, достаточно длинный. В данной статье предлагаем вам рассмотреть механизм защиты, основанный на перехвате системных вызовов операционной системы Linux. Данный механизм позволяет взять под контроль работу любого приложения и тем самым предотвратить возможные деструктивные действия, которые оно может выполнить.

Системные вызовы

Начнем с определения. Системные вызовы – это набор функций, реализованных в ядре ОС. Любой запрос приложения пользователя в конечном итоге трансформируется в системный вызов, который выполняет запрашиваемое действие. Полный перечень системных вызовов ОС Linux находится в файле /usr/include/asm/unistd.h. Давайте рассмотрим общий механизм выполнения системных вызовов на примере. Пусть в исходном тексте приложения вызывается функция creat() для создания нового файла. Компилятор, встретив вызов данной функции, преобразует его в ассемблерный код, обеспечивая загрузку номера системного вызова, соответствующего данной функции, и ее параметров в регистры процессора и последующий вызов прерывания 0x80. В регистры процессора загружаются следующие значения:

  • в регистр EAX – номер системного вызова. Так, для нашего случая номер системного вызова будет равен 8 (см. __NR_creat);
  • в регистр EBX – первый параметр функции (для creat это указатель на строку, содержащую имя создаваемого файла);
  • в регистр ECX – второй параметр (права доступа к файлу).

В регистр EDX загружается третий параметр, в данном случае он у нас отсутствует. Для выполнения системного вызова в ОС Linux используется функция system_call, которая определена в файле /usr/src/liux/arch/i386/kernel/entry.S. Эта функция – точка входа для всех системных вызовов. Ядро реагирует на прерывание 0x80 обращением к функции system_call, которая, по сути, представляет собой обработчик прерывания 0x80.

Чтобы убедиться, что мы на правильном пути, напишем небольшой тестовый фрагмент на ассемблере. В нем увидим, во что превращается функция creat() после компиляции. Файл назовем test.S. Вот его содержание:

.globl _start

.text

_start:

В регистр EAX загружаем номер системного вызова:

movl $8, %eax

В регистр EBX – первый параметр, указатель на строку с именем файла:

movl $filename, %ebx

В регистр ECX – второй параметр, права доступа:

movl $0, %ecx

Вызываем прерывание:

int $0x80

Выходим из программы. Для этого вызовем функцию exit(0):

movl $1, %eax movl $0, %ebx int $0x80

В сегменте данных укажем имя создаваемого файла:

    .data

filename:  .string  "file.txt"

Компилируем:

gcc -с test.S

ld -s -o test test.o

В текущем каталоге появится исполняемый файл test. Запустив его, мы создадим новый файл с именем file.txt.

А теперь давайте вернемся к рассмотрению механизма системных вызовов. Итак, ядро вызывает обработчик прерывания 0x80 – функцию system_call. System_call помещает копии регистров, содержащих параметры вызова, в стек при помощи макроса SAVE_ALL и командой call вызывает нужную системную функцию. Таблица указателей на функции ядра, которые реализуют системные вызовы, расположена в массиве sys_call_table (см. файл arch/i386/kernel/entry.S). Номер системного вызова, который находится в регистре EAX, является индексом в этом массиве. Таким образом, если в EAX находится значение 8, будет вызвана функция ядра sys_creat(). Зачем нужен макрос SAVE_ALL? Объяснение тут очень простое. Так как практически все системные функции ядра написаны на С, то свои параметры они ищут в стеке. А параметры помещаются в стек при помощи макроса SAVE_ALL! Возвращаемое системным вызовом значение сохраняется в регистр EAX.

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

Загружаемый модуль ядра

Загружаемый модуль ядра (обозначим его LKM – Loadable Kernel Module) – это программный код, выполняемый в пространстве ядра. Главной особенностью LKM является возможность динамической загрузки и выгрузки без необходимости перезагрузки всей системы или перекомпиляции ядра.

Каждый LKM состоит из двух основных функций (минимум):

  • функция инициализации модуля. Вызывается при загрузке LKM в память:

 int init_module(void) { ... }

  • функция выгрузки модуля:

 void cleanup_module(void) { ... }

Приведем пример простейшего модуля:

#define MODULE

#include <linux/module.h>

int init_module(void)

{

printk("Hello World ");

return 0;

}

void cleanup_module(void)

{

printk("Bye ");

}

Компилируем и загружаем модуль. Загрузку модуля в память осуществляет команда insmod:

gcc -c -O3 helloworld.c

insmod helloworld.o

Информация обо всех загруженных в данный момент в систему модулях находится в файле /proc/modules. Чтобы убедиться, что модуль загружен, введите команду cat /proc/modules либо lsmod. Выгружает модуль команда rmmod:

rmmod helloworld

Алгоритм перехвата системного вызова

Для реализации модуля, перехватывающего системный вызов, необходимо определить алгоритм перехвата. Алгоритм следующий:

  • сохранить указатель на оригинальный (исходный) вызов для возможности его восстановления;
  • создать функцию, реализующую новый системный вызов;
  • в таблице системных вызовов sys_call_table произвести замену вызовов, т.е. настроить соответствующий указатель на новый системный вызов;
  • по окончании работы (при выгрузке модуля) восстановить оригинальный системный вызов, используя ранее сохраненный указатель.

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

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

Примеры перехвата системных вызовов

Запрет создания каталогов

При создании каталога вызывается функция ядра sys_mkdir. В качестве параметра задается строка, в которой содержится имя создаваемого каталога. Рассмотрим код, осуществляющий перехват соответствующего системного вызова.

#include <linux/module.h>

#include <linux/kernel.h>

#include <sys/syscall.h>

Экспортируем таблицу системных вызовов:

extern void *sys_call_table[];

Определим указатель для сохранения оригинального системного вызова:

int (*orig_mkdir)(const char *path);

Создадим собственный системный вызов. Наш вызов ничего не делает, просто возвращает нулевое значение:

int own_mkdir(const char *path)

{

return 0;

}

Во время инициализации модуля сохраняем указатель на оригинальный вызов и производим замену системного вызова:

int init_module()

{

orig_mkdir=sys_call_table[SYS_mkdir];

sys_call_table[SYS_mkdir]=own_mkdir; return 0;

}

При выгрузке восстанавливаем оригинальный вызов:

void cleanup_module()

{

 sys_call_table[SYS_mkdir]=orig_mkdir;

}

Код сохраним в файле sys_mkdir_call.c. Для получения объектного модуля создадим Makefile следующего содержания:

CC = gcc

CFLAGS = -O3 -Wall -fomit-frame-pointer

MODFLAGS = -D__KERNEL__ -DMODULE -I/usr/src/linux/include

sys_mkdir_call.o: sys_mkdir_call.c

$(CC) -c $(CFLAGS) $(MODFLAGS) sys_mkdir_call.c

Командой make создадим модуль ядра. Загрузив его, попытаемся создать каталог командой mkdir. Как вы можете убедиться, ничего при этом не происходит. Команда не работает. Для восстановления ее работоспособности достаточно выгрузить модуль.

Запрет чтения файла

Для того чтобы прочитать файл, его необходимо вначале открыть при помощи функции open. Легко догадаться, что этой функции соответствует системный вызов sys_open. Перехватив его, мы можем защитить файл от прочтения. Рассмотрим реализацию модуля-перехватчика.

#include <linux/module.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <asm/uaccess.h>

extern void *sys_call_table[];

Указатель для сохранения оригинального системного вызова:

int (*orig_open)(const char *pathname, int flag, int mode);

Первым параметром функции open является имя открываемого файла. Новый системный вызов должен сравнить этот параметр с именем файла, который мы хотим защитить. Если имена совпадут, будет сымитирована ошибка открытия файла. Наш новый системный вызов имеет вид:

int own_open(const char *pathname, int flag, int mode)

{

Сюда поместим имя открываемого файла:

char *kernel_path;

Имя файла, который мы хотим защитить:

char hide[]="test.txt"

Выделим память и скопируем туда имя открываемого файла:

kernel_path=(char *)kmalloc(255,GFP_KERNEL);

copy_from_user(kernel_path, pathname, 255);

Сравниваем:

if(strstr(kernel_path,(char *)&hide) != NULL) {

Освобождаем память и возвращаем код ошибки при совпадении имен:

kfree(kernel_path);

return -ENOENT;

}

else {

Если имена не совпали, вызываем оригинальный системный вызов для выполнения стандартной процедуры открытия файла:

kfree(kernel_path);

return orig_open(pathname, flag, mode);

}

}

Далее смотрите комментарии к предыдущему примеру.

int init_module()

{

orig_open=sys_call_table[SYS_open];

sys_call_table[SYS_open]=own_open;

return 0;

}

void cleanup_module()

{

sys_call_table[SYS_open]=orig_open;

}

Сохраним код в файле sys_open_call.c и создадим Makefile для получения объектного модуля:

CC = gcc

CFLAGS = -O2 -Wall -fomit-frame-pointer

MODFLAGS = -D__KERNEL__ -DMODULE -I/usr/src/linux/include

sys_open_call.o: sys_open_call.c

$(CC) -c $(CFLAGS) $(MODFLAGS) sys_open_call.c

В текущем каталоге создадим файл с именем test.txt, загрузим модуль и введем команду cat test.txt. Система сообщит об отсутствии файла с таким именем.

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

Сокрытие записи о файле в каталоге

Определим, какой системный вызов отвечает за чтение содержимого каталога. Для этого напишем еще один тестовый фрагмент, который занимается чтением текущей директории:

/* Файл dir.c*/

#include <stdio.h>

#include <dirent.h>

int main ()

{

DIR *d;

struct dirent *dp;

d = opendir(«.»);

dp = readdir(d);

           return 0;

    }

Получим исполняемый модуль:

gcc -o dir dir.c

и выполним его трассировку:

strace ./dir

Обратим внимание на предпоследнюю строку:

getdents (6, /* 4 entries*/, 3933) = 72;

Содержимое каталога считывает функция getdents. Результат сохраняется в виде списка структур типа struct dirent. Второй параметр этой функции является указателем на этот список. Функция возвращает длину всех записей в каталоге. В нашем примере функция getdents определила наличие в текущем каталоге четырех записей – «.», «..» и два наших файла, исполняемый модуль и исходный текст. Длина всех записей в каталоге составляет 72 байта. Информация о каждой записи сохраняется, как мы уже сказали, в структуре struct dirent. Для нас интерес представляют два поля данной структуры:

  • d_reclen – размер записи;
  • d_name – имя файла.

Для того чтобы спрятать запись о файле (другими словами, сделать его невидимым), необходимо перехватить системный вызов sys_getdents, найти в списке полученных структур соответствующую запись и удалить ее. Рассмотрим код, выполняющий эту операцию (автор оригинального кода – Michal Zalewski):

extern void *sys_call_table[];

int (*orig_getdents)(u_int, struct dirent *, u_int);

Определим свой системный вызов.

int own_getdents(u_int fd, struct dirent *dirp, u_int count)

{

unsigned int tmp, n;

int t;

Назначение переменных будет показано ниже. Дополнительно нам понадобятся структуры:

struct dirent *dirp2, *dirp3;

Имя файла, который мы хотим спрятать:

char hide[]=»our.file»;

Определим длину записей в каталоге:

tmp=(*orig_getdents)(fd,dirp,count);

if(tmp>0){

Выделим память для структуры в пространстве ядра и скопируем в нее содержимое каталога:

dirp2=(struct dirent *)kmalloc(tmp,GFP_KERNEL);

сopy_from_user(dirp2,dirp,tmp);

Задействуем вторую структуру и сохраним значение длины записей в каталоге:

dirp3=dirp2;

t=tmp;

Начнем искать наш файл:

while(t>0) {

Считываем длину первой записи и определяем оставшуюся длину записей в каталоге:

n=dirp3->d_reclen;

t-=n;

Проверяем, не совпало ли имя файла из текущей записи с искомым:

if(strstr((char *)&(dirp3->d_name),(char *)&hide) != NULL) {

Если это так, затираем запись и вычисляем новое значение длины записей в каталоге:

memcpy(dirp3,(char *)dirp3+dirp3->d_reclen,t);

tmp-=n;

}

Позиционируем указатель на следующую запись и продолжаем поиск:

dirp3=(struct dirent *)((char *)dirp3+dirp3->d_reclen);

}

Возвращаем результат и освобождаем память:

copy_to_user(dirp,dirp2,tmp);

kfree(dirp2);

}

Возвращаем значение длины записей в каталоге:

return tmp;

}

Функции инициализации и выгрузки модуля имеют стандартный вид:

int init_module(void)

{

orig_getdents=sys_call_table[SYS_getdents];

sys_call_table[SYS_getdents]=own_getdents;

return 0;

}

void cleanup_module()

{

sys_call_table[SYS_getdents]=orig_getdents;

}

Сохраним исходный текст в файле sys_call_getd.c и создадим Makefile следующего содержания:

CC = gcc

module = sys_call_getd.o

CFLAGS = -O3 -Wall

LINUX = /usr/src/linux

MODFLAGS = -D__KERNEL__ -DMODULE -I$(LINUX)/include

sys_call_getd.o: sys_call_getd.c $(CC) -c

$(CFLAGS) $(MODFLAGS) sys_call_getd.c

В текущем каталоге создадим файл our.file и загрузим модуль. Файл исчезает, что и требовалось доказать.

Как вы понимаете, рассмотреть в рамках одной статьи пример перехвата каждого системного вызова не представляется возможным. Поэтому тем, кто заинтересовался данным вопросом, рекомендую посетить сайты:

Там вы сможете найти более сложные и интересные примеры перехвата системных вызовов. Обо всех замечаниях и предложениях пишите на форум журнала.

При подготовке статьи были использованы материалы сайта http://www.thehackerschoice.com.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru