Играя с таблицы системных вызовов от ЛКМ
Я переопределяю 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 ответа:
Если вы хотите выгрузить модуль, который перехватывает системные вызовы, знайте о ситуациях, когда какой-то процесс все еще находится в обработчике системных вызовов и ваш код (текстовый сегмент модуля) уходит из памяти. Это приводит к ошибке страницы, так как когда процесс возвращается из некоторой функции ядра (которая спит) в ваш код, код больше не существует.
Таким образом, правильная схема выгрузки модуля должна проверять процесс, который может спать в подключенных системных вызовах. Разгрузка возможна только при наличии нет ни одного процесса, который спит в крючке syscall.
UPD
Пожалуйста, посмотрите патч, который подтверждает мою теорию. Он добавляет атомарный счетчик, который увеличивается и уменьшается при вызовеhooked_sys_read
. Итак, как я и предполагал, есть процесс, который все еще ждет вread_sys_read
, пока ваш модуль был выгружен. Этот патч показывает, что сprintk(read_counter)
и он печатает1
для меня, что означает, что кто-то не декрементируетread_counter
.
Вот несколько случайных бредней, я далеко не уверен, что все это имеет смысл, но уже поздно, и я лучше запишу это и лягу спать, чем попытаюсь выяснить, в чем именно (если таковые имеются) на самом деле проблема. Надеюсь, что-то поможет:
Я так понимаю, вы проверили, что ваше восстановление действительно восстанавливает указатель-например, выводит содержимое
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