Александр Байрак
Наверняка вы не раз задумывались о том, как именно работает та или иная
используемая вами программа. Разобраться помогут исходные текcты программы. Но
что делать, если они недоступны? Как всегда быть в курсе того, что происходит у
вас в системе?
Systrace – инструмент для ограничения и контроля системных вызовов.
Изначально systrace был написан для NetBSD, позже был портирован под OpenBSD,
а в настоящий момент ведутся работы по переносу на Linux, FreeBSD и OpenDarwin.
Любая программа в процессе свой работы использует
системные вызовы. Что такое системный вызов? Его можно определить как некую
функцию, которая позволяет вашей программе обращаться к ядру ОС для выполнения
некого действия. В современных версиях UNIX-систем реализовано около 300
различных системных вызовов. И если такие инструменты, как ktrace (kernel process
tracing) и truss (tracing system call), позволяют нам выступать лишь в
качестве наблюдателей, systrace позволяет нам вмешиваться в происходящее.
Пример использования
Перейдем к практике. В качестве примера напишем простую программу:
#include <stdio.h>
void proc1();
int main()
{
printf("just a message\n");
mkdir(«test»);
proc1();
}
void proc1()
{
printf("just a dumb procedure\n");
}
Как ясно из исходного текста,
наша подопытная программа выполняет следующие действия:
n Выводит на экран сообщение.
n Создает каталог «test».
n Передает управление процедуре «proc1».
n Процедура proc1 выводит на экран сообщение.
Скомпилируем программу:
#gcc -o proga proga.c
На первый взгляд при выполнении нашей программы
используются два системных вызова: mkdir и write. Хотя логично предположить,
что, перед тем как «что-то» «куда-то» записать с помощью write, это самое
«куда-то» нужно сначала открыть, а после записи закрыть, а значит,
задействованы системные вызовы open и close . Также не лишено смысла
предположение, что используется еще один системный вызов – exit. Итого пять
вызовов. Посмотрим, как обстоят дела на самом деле:
#systrace -A /path/to/program/proga
Ключ -A указывает systrace автоматически создать
политику для указанного исполняемого файла.
Автоматический режим предполагает создание правил
в режиме «разрешить все».
После выполнения команды в домашнем каталоге
пользователя появится подкаталог .systrace, в который будет помещен файл c
правилами. Рассмотрим его:
Policy:
/home/01mer/labs/systr/proga, Emulation: netbsd
netbsd-mmap: permit
netbsd-fsread:
filename eq “/etc/ld.so.conf” then permit
netbsd-__fstat13:
permit
netbsd-close:
permit
netbsd-munmap:
permit
netbsd-fsread:
filename eq “/lib/libc.so.12.114.1” then permit
netbsd-__sysctl:
permit
netbsd-fsread:
filename eq “/etc/mallic.conf” then permit
netbsd-break:
permit
netbsd-ioctl:
permit
netbsd-write:
permit
netbsd-fswrite:
filename eq “/home/01mer/labs/systr/test” then permit
netbsd-exit:
permit
Синтаксис правил достаточно прост:
[системный вызов] [условие]
[действие]
Мы видим, что в процессе исполнения нашей
программы используется несколько больше системных вызовов, чем можно было бы
предположить с самого начала.
Рассмотрим полученные правила.
Policy: /home/01mer/labs/systr/proga,
Emulation: netbsd
Указывается, для какого бинарного файла политика
описана ниже. Полный путь к исполняемому файлу нужен для того, чтобы исключить
возможность применения политики для файла с таким же именем, но располагающимся
в другом месте. Emulation: netbsd показывает, что будет использоваться ABI ОС NetBSD.
netbsd-mmap:
permit
Как видно из названия, системному вызову mmap
разрешено исполняться. Для позволения выполнения системного вызова используется
действие – permit, для запрещения deny.
netbsd-fsread:
filename eq “/etc/ld.so.conf” then permit
Тут мы видим, что системному вызову fsread будет
разрешено выполниться в том случае, если запрошенный им файл будет /etc/ld.so.conf.
Стоп, скажет внимательный читатель, системного вызова fsread не существует! И
будет абсолютно прав. По умолчанию в systrace применяется использование
псевдонимов для системных вызовов. Например: fsread является псевдонимом для stat,
lstat, readlink, access, open. Но при желании режим использования псевдонимов
можно отключить. В дальнейшем комментировании всего файла конфигурации смысла
не вижу – все должно быть понятно и без этого.
Что ж, политика для нашей программы создана,
давайте попробуем ее выполнить под чутким руководством systrace:
#systrace -a /home/01mer/labs/systr/proga
just a message
just a dumb procedure
Как мы видим, все функции нашей программы
выполнились. Предположим, что логика нашей программы изменилась, что будет
происходить в этом случае? Давайте для примера изменим функцию proc1 в нашей
программе. Для начала мы должны точно знать, что будем менять. Просто изменить
пару строчек в исходном коде и перекомпилировать программу, конечно, можно, но
я предлагаю более интересный подход.
#objdump -d proga > proga.lst
Программа objdump показывает информацию из
бинарных/объектных файлов. Ключ -d указывает, что файл нужно дисассемблировать.
Вывод информации для удобства произведем в proga.lst
В полученном листинге находим нашу процедуру
proc1.
08048833
<proc1>:
8048833: 55
push %ebp
8048834: 89
e5 mov %esp,%ebp
8048836: 83
ec 08 sub $0x8,%esp
8048839: 83
ec 0c sub $0xc,%esp
804883c: 68
16 89 04 08 push $0x8048916
8048841: e8
52 fc ff ff call 8048498 <init_fallthru+0x2d>
8048846: 83
c4 10 add $0x10,%esp
8048849: c9
leave
804884a: c3
ret
804884b: 90
nop
Если не вдаваться в подробности, мы видим тут
«подготовку» стека, выполнение вывода на экран текста, очищение стека и возврат
в «основную» программу. Итого 25 байт, которые мы должны изменить на что то
свое. Негусто, но вполне хватит для классического shell-кода вызывающего /bin/sh,
он занимает как раз 23 байта (не забывайте, что 2 байта нам нужны для возврата
в «основную программу»). А это именно то, что нам нужно – используется
системный вызов, описания которого нет в наших правилах. Читаем man 2 execve.
Для запуска /bin/sh через execve мы должны написать следующие:
execve("/bin/sh/",0,0);
Что можно свести к следующему коду на ассемблере:
xorl %eax,%eax
pushl %eax
pushl $0x68732f2f
pushl $0x6e69622f
movl %esp,%ebx
pushl %eax
pushl %esp
pushl %ebx
pushl %eax
movb $0x3b,%al
int $0x80
Все это можно представить в следующих опкодах:
31 c0
50
68 6e 2f 73 68
68 2f 2f 62 69
89 e3
50
54
53
50
b0 3b
cd 80
Вооружившись hex-редактором, открываем
исполняемый файл с нашей программой и меняем содержимое proc1 на представленные
выше опкоды. Само собой, нам нужно оставить команды возврата в «основную»
программу. После успешного редактирования проверяем результат:
# home/01mer/labs/systr/proga
Just a message
#
Как вы видите, вместо вывода на экран «just a dumb
procedure» у нас запустился /bin/sh.
А теперь попробуем выполнить то же самое, но уже
под контролем systrace:
# systrace -a /home/01mer/labs/systr/proga
Just a message
Jul 11 16:53:25
darkthrone.net1 systrace: deny user: 01mer, prog:
/home/01mer/labs/systr/proga,
pid: 395(0)[0], policy:
/home/01mer/labs/systr/proga,
filters: 13, syscall: netbsd-execve(59),
filename:
/bin/sh, argv:
#
Как мы видим, попытка запустить модифицированную
программу провалилась.
Автоматизируем создание правил
Доверить автоматическое создание правил systrace допустимо только в том
случае, когда есть уверенность на 99,9% в том, что мы запускаем. Во всех
остальных случаях рекомендуется создавать правила в интерактивном режиме.
Рассмотрим пример:
# systrace -t /home/01mer/labs/systr/newproga
Ключ -t указывает, чтобы интерактивное создание правил
проходило в текстовом режиме. Если у вас на машине установлены x-windows, вы
можете воспользоваться xsystrace.
/home/01mer/labs/systr/newproga,
pid: 729(0)[0], policy:
/home/01mer/labs/systr/newproga,
filters: 0, syscall: netbsd-mmap(197), args: 32
Answer:
Тут systrace ожидает от нас ввода permit, в
случае если мы желаем разрешить системный вызов или deny для запрета. Конечно,
интерактивное создание правил – процесс достаточно трудоемкий и занимает много
времени, но полученный результат того стоит. Вы будете точно знать, когда и
какие системные вызовы использует программа.
Дополнительные «полезности»
В синтаксисе правил вы можете использовать необязательные опции
протоколирования.
Например:
netbsd-fsread: filename eq
"/lib/libc.so.12.114.1" then permit log
параметр log указывает systrace протоколировать каждое обращение к
указанному фильтру.
По умолчанию протоколируются лишь попытки
выполнить системные вызовы, о которых нет ни слова в правилах. Для
протоколирования systrace использует syslogd. А это значит, что мы можем
немного поправить /etc/syslog.conf, чтобы все сообщения от systrace размещались
в отдельном файле.
При желании даже можно написать маленький парсер
для получаемых логов. Но это уже по желанию, все в ваших руках.
Нельзя не отметить еще одну необязательную опцию
– errorcode. С её помощью мы можем указать, какая ошибка будет возвращаться при
обращении к запрещенному системному вызову.
Например:
netbsd-fswrite: filename eq
"/some/where/file" then deny[EIO]
В данном случае при попытке записи в файл
системному вызову будет возвращена ошибка – input/outpu error. Для ознакомления
с полным списком errno-кодов возврата вы можете прочитать man errno.
Заключение
Мы рассмотрели простой пример использования systrace. Изложенных сведений
вам будет достаточно и для создания более сложных собственных правил для
различных программ. Применение политик ограничения системных вызовов для
конкретных программ поможет вам существенно повысить степень защиты ОС в целом.