Перехват системных вызовов в linux под x86-64

Введение


В интернете опубликовано множество статей по перехвату системных вызовов под x32. В рамках решения одной задачи появилась необходимость в перехвате системных вызовов под архитектурой x86-64 при помощи загружаемого модуля ядра. Приступим:

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


Алгоритм:
  • Поиск адреса таблицы системных вызовов
  • Подмена на адреса новых системных вызовов


Поиск адреса таблицы системных вызовов

Первый вариант: можно найти через таблицу дескрипторов прерываний (IDT), IDT — служит для связи обработчика прерывания с номером прерывания. В защищённом режиме адрес в физической памяти и размер таблицы прерываний определяется 80-битным регистром IDTR.В защищённом режиме элементом IDT является шлюз прерывания длиной 10 байт, содержащий сегментный (логический) адрес обработчика прерывания, права доступа и др. Нам такой метод не интересен, т.к. мы получим адрес обработчика, который сделан для совместимости с х32

Второй вариант, более интересен.

Для начала не большой экскурс: MSR – machine state register это набор регистров процессоров Интел, используемых в семействе x86 и x86-64 процессоров. Эти регистры предоставляют возможность контролировать и получать информацию о состоянии процессора. Все MSR регистры доступны только для системных функций и не доступны из пользовательских программ. Нас в частности интересует следующий регистр:MSR_LSTAR — 0xc0000082 (long mode SYSCALL target)
(полный список можно посмотреть в /usr/include/asm/msr-index.h).
В этом регистре хранится адрес обработчика прерываний для x86-64.
Получить адрес можно следующим образом:
int i, lo, hi;
asm volatile("rdmsr" : "=a" (lo), "=d" (hi) : "c" (MSR_LSTAR));
system_call = (void*)(((long)hi<<32) | lo);

Далее найдем адрес самой таблицы. Перейдем на только что полученный адрес и найдем в памяти последовательность \xff\x14\xc5(эти магические числа берутся, если посмотреть на код ядра, в частности, на код функции system_call, в которой происходит вызов обработчика из искомой). Считав следующие за ней 4 байта, мы получим адрес таблицы системных вызовов syscall_table. Зная ее адрес, мы можем получить содержимое этой таблицы (адреса всех системных функций) и изменить адрес любого системного вызова, перехватив его.
код для нахождения адреса таблицы системных вызовов:
unsigned char *ptr;
for (ptr=system_call, i=0; i<500; i++) {
if (ptr[0] == 0xff && ptr[1] == 0x14 && ptr[2] == 0xc5)
return (void*)(0xffffffff00000000 | *((unsigned int*)(ptr+3)));
ptr++;
}


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

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

Для снятие и установки защиты необходимо знать следующее: регистр CR0 — содержит системные флаги управления, управляющие поведением и состоянием процессора.Флаг WP — защита от записи (Write Protect), 48-й бит CR0. Когда установлен, запрещает системным процедурам запись в пользовательские страницы с доступом только для чтения (когда флаг WP сброшен — разрешает). В отличие от х32 изменился только размер регистра и номер флага
код снятия защиты:
asm("pushq %rax");
asm("movq %cr0, %rax");
asm("andq $0xfffffffffffeffff, %rax");
asm("movq %rax, %cr0");
asm("popq %rax");

код включения защиты:
asm("pushq %rax");
asm("movq %cr0, %rax");
asm("xorq $0x0000000000001000, %rax");
asm("movq %rax, %cr0");
asm("popq %rax");


Этих знаний достаточно для подмены системных вызовов в Linux x86-64. Надеюсь кому-нибудь это будет полезным.
Спасибо за внимание.

UPD:
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 14

    0
    >x32

    >x86

    вы уже определитесь

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

    для отладки же можно более просто и платформонезависимо подменять юзерспейсовую функцию либцы через LD_PRELOAD? это не работает для каких-то случаев?
      0
      в x86-64 есть две функции обработчика прерываний первый способ, позволяет получить адрес обработчика, который сделан для совместимости с х32, второй сопособ позволяет получить адрес для х64 обработчика.
        0
        Стоит рассматривать это как спортивно/академический интерес, как вариант руткит.
          0
          Ну вы перехватите обращение резолвера к /etc/hosts через LD_PRELOAD, тогда поговорим.
            +1
            gethostbyname прекрасно фейкается через LD_PRELOAD.
            0
            Если бинарник статически слинкован
              0
              покажите бинарник, статически слинкованный, который не импортирует gethostbyname
                0
                Могу показать статически слинкованный бинарник, который не импортирует connect, open и прочие сисколлы.
              0
              Для перехвата функций внутри любого юзерспейсовского процесса вполне достаточно хорошего знания ELF.
              В конце концов, так работает сам загрузчик (ld.so). LD_PRELOAD тут не панацея, ибо перехватывает ВСЕ обращения из всех модулей процесса к некоторой функции.
              Другой вопрос — когда дело касается хуков сторонней программы.
              +4
              Хм, уже полгода использую аналогичный метод в одном большом проекте :)
              Если кому интересно, на Linux ARM найти таблицу вызовов можно так:

              unsigned long **FindSysCallTable(void)
              {
              unsigned long **sctable;
              unsigned long ptr;
              extern unsigned long loops_per_jiffy;

              sctable = NULL;

              for (ptr = (unsigned long)&elf_check_arch;
              ptr < (unsigned long)&loops_per_jiffy;
              ptr += sizeof(void *)
              )
              {
              unsigned long *p = (unsigned long*)ptr;

              if (p[__NR_close] == (unsigned long) sys_close)
              {
              sctable = (unsigned long**)p;
              return &sctable[0];
              }
              }

              return NULL;
              }

              Далее просто заменяем нужный вызов, не беспокоясь о защите памяти, т.к. её нет.

              Конечно все это есть не очень гуд и следует стараться обходится без таких вот хаков…
                +2
                ptrace(2) и не нужно никаких модулей ядра.
                  0
                  смотря какие цели преследовать, если, как уже предлагали, делать руткит, то такой способ будет интересней.
                  –1
                  Черт. Ни черта не понятно (
                    +4
                    мдауж. 9 лет все наступали на грабли под windows, а теперь и под никсами стали на них наступать.
                    Уже везде 100 раз было сказано, что сброс WP бита — это опасное действие, которое может натварить очень много плохих дел. И если надо писать в защищенные участки памяти под ядром, то лучше использовать другой механизм: По виртуальному адресу страницы получить физические страницы. Для полученных физических страниц выделить виртуальную память без защиты. т.е. на одну и туже физическую страницу памяти будет ссылаться 2 страницы виртуальной памяти, но одна из них будет не защищена.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое