Эльфы большие и маленькие

         

Программирование без libc— штурм ядра


Интерфейс системных вызовов (они же syscall'ы) это "задний двор" операционной системы, это ее собственная и к тому же недокументированная кухня. Системные вызовы и native-API Windows NT стоят на одной степени, причем у Гейтса native-API намного более предсказуемо и документировано. Вот тут кто-то опять порывается крикнуть, что Linux'у никакая документация совсем не нужна, он распространяется в исходных текстах и документирует себя сам. Чушь! Если не сказать провокация.

Исходный текст — это не документация! Это программа, в которой нужно очень долго и нужно ковыряться, прежде чем удаться хоть что-то понять. Термин "документация" происходит от слова "документ", а документ предполагает внятное описание материала, изложенного в установленной форме. Более того, "документированные API функции" это стандартизированные API-функции! Различные клоны UNIX'а используют свои собственные способы взаимодействия с ядром, число которых намного больше десятка! Даже разные ядра одной и той же системы могут вносить в syscall'ы непредсказуемые изменения. Возьмите Linux и сравните ядро 2.4 с ядром 2.6. А для большинства коммерческих UNIX'ов исходные тексты вообще недоступны. Что толку с того, что мы знаем как вызвать такой-то syscall на отдельно взятом ядре? Где надежда (я уже не говорю о "гарантиях") что наш файл запуститься на соседней машине?

Реально в syscall'ах нуждаются одни лишь черви, распространяющиеся через переполняющиеся буфера и потому очень ограниченные в размерах, чтобы реализовать процедуру поиска libc в памяти. И еще — драйвера. Но драйвера пишутся под конкретные системы и никто не собирается требовать от них переносимости, а мы говорим про прикладные программы! Какой ассемблерный tutor не возьми, там обязательно будут syscall'ы. Что ли мода пошла такая или это просто эпидемия? Ладно, неважно! Рассмотрим и syscall'ы, если народу так будет угодно.

Лучшее руководство по интерфейсам системных вызовов можно найти на сайте Last Stage of Delirium Research Group или сокращенно LSD. Оно так и называется "UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes" (http://www.blackhat.com/presentations/bh-usa-01/LSD/bh-usa-01-lsd.pdf), так же хочется порекомендовать неплохой сайт http://www.lxhp.in-berlin.de/lhpsyscal.html — настоящую энциклопедию системных вызовов.


Если отбросить всякие редкоземельные UNIX'ы, то интерфейсов системных вызов всего два — Linux и BSD. Рассмотрим их поближе.

Linux использует fastcall-соглашение о передаче параметров, это значит, что номер системного вызова помещается в регистр eax, параметры передаются слева направо через регистры ebx, ecx, edx, esi, edi, ebp. Если системный вызов принимает больше шести параметров, они передаются со структурой, указатель на которую заносится в ebx. Передача управления происходит путем вызова прерывания INT 80h.

Разумеется, это только общая схема и на практике постоянно приходится сталкиваться с отступлением от правил. Общение с системными вызовами напоминает хождение по минному полю — один шаг в сторону и ты покойник. Вот как это приблизительно выглядит. Допустим, мы хотим вызвать системный вызов write. Для начала необходимо узнать его номер. Системные вызовы перечислены в файле /usr/include/sys/syscall.h, в BSD-системах номера присутствуют сразу, а вот Linux нас отсылает к файлу /usr/include/bits/syscall.h, в котором номеров нет, зато есть нисходящие определения. Короче, чтобы не парится, номер нужного syscall'а проще выяснить с помощью следующей программы (см. листинг 8). Определения syscall'ов обычно имеют префикс SYS_, в частности, системный вызов write определяется как SYS_write, а номер его — #4.

#include <stdio.h>

#include <sys/syscall.h>

main()

{

       printf("%x\n",SYS_write);

}


Содержание раздела