Играя с таблицы системных вызовов от ЛКМ


Я переопределяю SYS_READ из таблицы syscall в Linux (3.x) но у меня возникли некоторые проблемы при разгрузке самого модуля. Сначала я загружаю свой модуль, который находит таблицу syscall, затем включает RW, переопределяет SYS_READ с моей собственной функцией SYS_READ (которая на самом деле не делает ничего, кроме вызова исходного SYS_READ), Затем я жду несколько мгновений, а затем выгружаю модуль. В методе выгрузки моего модуля я восстанавливаю исходную функцию SYS_READ обратно в таблицу syscall и устанавливаю таблица syscall для RO.

Исходная функция SYS_READ восстановлена правильно, но я получаю это, когда выгружаю модуль: http://pastebin.com/JyYpqYgL

Что я упускаю? Должен ли я делать что-то еще после восстановления реального SYS_READ ?

EDIT: GitHub ссылка на проект: https://github.com/alexandernst/procmon

Правка:

Вот как я получаю адрес таблицы syscall:

void **sys_call_table;

struct idt_descriptor{
    unsigned short offset_low;
    unsigned short selector;
    unsigned char zero;
    unsigned char type_flags;
    unsigned short offset_high;
} __attribute__ ((packed));


struct idtr{
    unsigned short limit;
    void *base;
} __attribute__ ((packed));


void *get_sys_call_table(void){
    struct idtr idtr;
    struct idt_descriptor idtd;
    void *system_call;
    unsigned char *ptr;
    int i;

    asm volatile("sidt %0" : "=m" (idtr));
    memcpy(&idtd, idtr.base + 0x80 * sizeof(idtd), sizeof(idtd));
    system_call = (void*)((idtd.offset_high<<16) | idtd.offset_low);
    for(ptr=system_call, i=0; i<500; i++){
        if(ptr[0] == 0xff && ptr[1] == 0x14 && ptr[2] == 0x85)
            return *((void**)(ptr+3));
        ptr++;
    }

    return NULL;
}

sys_call_table = get_sys_call_table();

И вот как я устанавливаю RW/RO:

unsigned long set_rw_cr0(void){
    unsigned long cr0 = 0;
    unsigned long ret;
    asm volatile("movq %%cr0, %%rax" : "=a"(cr0));
    ret = cr0;
    cr0 &= 0xfffffffffffeffff;
    asm volatile("movq %%rax, %%cr0" : : "a"(cr0));
    return ret;
}

void set_ro_cr0(unsigned long val){
    asm volatile("movq %%rax, %%cr0" : : "a"(val));
}

Наконец, вот как я определяю свои syscalls и изменяю таблицу syscall:

asmlinkage ssize_t (*real_sys_read)(unsigned int fd, char __user *buf, size_t count);
asmlinkage ssize_t hooked_sys_read(unsigned int fd, char __user *buf, size_t count);

//set my syscall
real_sys_read = (void *)sys_call_table[__NR_read];
sys_call_table[__NR_read] = (void *)hooked_sys_read;

//restore real syscall
sys_call_table[__NR_read] = (void *)real_sys_read;
2 3

2 ответа:

Если вы хотите выгрузить модуль, который перехватывает системные вызовы, знайте о ситуациях, когда какой-то процесс все еще находится в обработчике системных вызовов и ваш код (текстовый сегмент модуля) уходит из памяти. Это приводит к ошибке страницы, так как когда процесс возвращается из некоторой функции ядра (которая спит) в ваш код, код больше не существует.

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

UPD

Пожалуйста, посмотрите патч, который подтверждает мою теорию. Он добавляет атомарный счетчик, который увеличивается и уменьшается при вызове hooked_sys_read. Итак, как я и предполагал, есть процесс, который все еще ждет в read_sys_read, пока ваш модуль был выгружен. Этот патч показывает, что с printk(read_counter) и он печатает 1 для меня, что означает, что кто-то не декрементирует read_counter.

Http://pastebin.com/1yLBuMDY

Вот несколько случайных бредней, я далеко не уверен, что все это имеет смысл, но уже поздно, и я лучше запишу это и лягу спать, чем попытаюсь выяснить, в чем именно (если таковые имеются) на самом деле проблема. Надеюсь, что-то поможет:

Я так понимаю, вы проверили, что ваше восстановление действительно восстанавливает указатель-например, выводит содержимое sys_call_table[__NR_read]?

Я определенно восстановил бы CR0, вернув бит, который вы очистили, вместо того, чтобы восстанавливать старое значение-it возможно, это не имеет значения большую часть времени, но есть другие биты в CR0, которые могут меняться время от времени - вероятно, только действительно бит TS, но это достаточно плохо - получить случайное восстановление устаревшей плавающей точки или пропустить восстановление с плавающей точкой-это плохо [и угадайте, насколько легко выяснить, что причина, по которой некоторые длительные математические вычисления внезапно получили совершенно неправильные результаты, потому что ваш код выгрузился несколькими часами ранее?]. Это почти наверняка не причина сбоя вашего кода, но он будет почти наверняка возникают проблемы в тот или иной момент, если вы загружаете/выгружаете модуль достаточно раз. [Кроме того, убедитесь, что вы не меняете местами процессоры, когда меняете CR0 - вероятно, лучше сделать какую-то блокировку, чтобы гарантировать, что вы остаетесь на том же процессоре, делая все обновление sys_call_table материал].

Я думаю, что причина сбоя вашего кода, однако, заключается в отсутствии очистки кэша (ОС не ожидает, что эта память изменится - и процесс видит ее только для чтения, поэтому она не должна нужно проверить на недействительность]. Вам нужно очистить кэш на всех процессорах для записи sys_call_table. Я не уверен, что это самый простой/лучший способ сделать это. Я думаю, что void flush_icache_range(unsigned long start, unsigned long end) - это вызов, который вам нужен , Но я не уверен, является ли это текущей или старой функцией. Отсюда: https://www.kernel.org/doc/Documentation/cachetlb.txt

Как я уже сказал вначале, это скорее бред, чем фактическое изучение того, как все работает глубоко внутри ядра и т. д. Время для моего прекрасного сна-я мне нужно как можно больше этого... ;)