Как перехватить системный вызов?
Перехват системного вызова-первое,что обычно делают начинающие программисты ядра, своеобразная проба пера. Здесь важно различать разницу между двумя ветками:2.4 и 2.6.
2.4 kernel
Перехват означает полную замену системного вызова ядра на вашу собственную функцию. Как правило,это достигается с помощью Loadable Kernel Module (LKM),
Ключ здесь лежит в модификации структуры ядра под названием sys_call_table. Эта таблица включает в себя полный набор системных вызовов. Каждый вызов идентифицируется с помощью целочисленной константы. Эти константы фактически являются индексами в таблице sys_call_table.
Когда пользовательской программе нужен системный вызов, прерывание 0x80 загружает в регистр EAX system call number. Далее хэндлер внутри ядра использует содержимое этого регистра в качестве индекса sys_call_table и вызывает нужную функцию из нее.
Если мы изменим адрес в sys_call_table на индекс,соответствующий номеру системного вызова fork, мы сможем заменить ее на свою функциюю из собственного LKM. В следующий раз,когда произойдет вызов fork, будет вызвана наша функция. При этом можно выводить системное сообщение при каждом вызове fork.
В LKM нужно сделать следующее:
Изменить sys_call_table
- Создать в модуле необходимую функцию
Еще раз изменить sys_call_table при выгрузке модуля и возврата оригинального системного вызова
Следующие файлы представляют интерес:
- /include/linux/syscalls.h Сигнатура системных вызовов
- /include/asm-um/unistd.h Хидеры для system calls
/include/asm-um/arch/unistd.h Определения констант __NR ,соответствующим внутренним номерам системных вызовов
LKM для 2.4 с перехватом системного вызова
В этом разделе показан код для 2.4 LKM , способного перехватить fork system call. Работать это будет только на 2.4 kernels. Подробнее об этом можно прочитать в части 8 Linux Kernel Module Programmer's Guide (Salzman, Burian, Pomerantz, 2005/05/26 version 2.6.1, http://www.tldp.org/LDP/lkmpg/2.6/lkmpg.pdf).
// base modules inclusions #include <linux/init.h> #include <linux/module.h> // for getpid() #include <linux/unistd.h> #include <asm/arch/unistd.h> MODULE_LICENSE("GPL"); extern void* sys_call_table[];
Таблица sys_call_table экспортируется в ядро.Нам нужен собственный массив указателей на эти функции.
int (*original_fork)(struct pt_regs); // we define original_fork as a pointer on function w/ the same prototype // as the fork system call. It is meant to store the address of the original // fork function in the kernel while we replace it w/ our own int edu_fork(struct pt_regs regs) { pid_t pid; // loging the syscall printk(KERN_ALERT "[edu] fork syscall intercepted from %d\n", current->pid);
Почему бы не использовать getpid()? current - глобальный указатель ядра,ссылаемый на структуру task_struct текущего процесса. Из этой структуры мы используем поле pid .
// making the call to the original fork syscall pid = (*original_fork)(regs); return pid; }
Наша функция делает враппер для форка. Мы делаем вызов оригинального системного вызова, но перед ним или после него мы можем добавить свой код.
system call-это один из самых критических по скорости вызовов в ядре.
static int edu_init(void) { printk( KERN_ALERT "[edu] Module successfully loaded\n"); printk( KERN_ALERT "[edu] Intercepting fork() syscall... "); original_fork = sys_call_table[__NR_fork]; sys_call_table[__NR_fork] = edu_fork;
Здесь мы используем константу NR_fork для системного вызова.
Загрузка и выгрузка модуля:
execve system call с нашим модулем работать не будет.
Прототип sys_execve() :
Её аргумент - НЕ указатель!
Попытка перехватить sys_execve не будет работать.
Регистры процесса сохраняются в стеке.
Код внутри sys_execve модифицирует этот стек.
Например,код,который модифицирует регистры- start_thread(),вызываемый внутри load_elf_binary().
Обойти эту проблему можно с помощью вызова do_execve() вместо sys_execve,
который дублирует вызов sys_execve(). Но это тоже неправильно.
Включая 2.4, sys_call_table была экспортируемой таблицей.
Это означает что любой LKM мог ссылаться на нее внутри своего кода.
Начиная с 2.6, Linus Torvalds решил более не экспортировать эту таблицу.
Была захлопнута дверь перед разного рода rootkits.
Таблица sys_call_table стала менее доступна,
и простейшие методы перехвата системных функций теперь не работают.
Для 2.6 kernel, вышеуказанный LKM работать не будет.
Но тем не менее альтернатива остается.
Для этого нужно модифицировать ядро,пересобрать его и затем написать LKM.
Подробнее о технике hot patching можно почитать тут: In a nutshell: http://mail.nl.linux.org/kernelnewbies/2002-12/msg00266.html From Phrack, sd & devik, issue # 58,
http://www.phrack.org/issues.html?issue=58&id=7#article
There
SysCallTrack project http://syscalltrack.sourceforge.net/index.html Linux Trace Toolkit http://www.opersys.com/LTT/index.html
В старых версиях ядра управление такими структурами данных,как связные списки,хеш-таблицы,
было прозрачнее.
В последних версиях ядра появилось универсальное API для управления такими структурами.
Понимание этого API поможет вам как в освоении самого ядра, так и в улучшении
собственно навыков с-программирования.
Давайте попробуем рассмотреть linked list API с точки зрения
"как это может пригодиться лично мне ?" (например в Loadable Kernel Module).
Определим структуру данных,которая будет использована в связном списке:
Для того чтобы можно было линковать struct mystruct ,
добавим поле struct list_head :
Обычно для реализации связного списка нужно добавить указатель на следующий элемент в списке.
В ядре все немного по-другому,поскольку ядру нужна возможность для
добавления-удаления элементов из списка.
Создадим экземпляр нашей структуры:
Последняя строка вызывает макрос LIST_HEAD_INIT в /include/linux/list.h:
Полю mylist назначается указатель на список из одного элемента.
Создаем 2-ю переменную и инициализируем ее:
На этот раз мы используем другой макрос для инициализации списка:
Нам нужна переменная для обозначения начала списка,
проинициализируя ее как пустой связный список,и потом добавиви в него 2 элемента:
Этот макрос декларирует переменную типа struct list_head и инициализирует ее:
Добавляем 2 элемента в список:
list_add - макрос :
В нем лежит вложенный макрос list_add:
У нас есть handle на двойной связаный список (mylinkedlist),
в котором 2 элемента.
kernel linked list API реализует несколько макросов для манипуляций с ним.
Внутри этого макроса лежит цикл.
Ему нужны 2 аргумента-указатель на голову списка - (head)
и указатель на элемент,который подвергвется апдэйту -
pos.
Выведем на консоль элементы нашего списка:
Имея указатель на структуру list_head
которая является частью структуры mystruct , нам нужно вернуть адрес следующего элемента.
Макрос list_entry делает это:
container_of определен в /include/kernel/kernel.h :
У нас есть адрес структурыstruct list_head ,
которая находится внутри другой структуры (struct task_struct ).
Первая строка макроса переводит 0 в указатель на тип структуры struct task_struct
в нашем примере.
Мы используем этот указатель для доступа в структуре,
который соответствует list_head
и получаеи его тип с помощью макроса typeof
для декларации указателя mptr .
Следующим шагом нужно определить из адреса ptr ( mptr )
смещение для структуры list_head.
Это делается с помощью макроса,определенного в /include/linux/stddef.h ;
Этот макрос вычисляет адрес MEMBER в структуре TYPE.
Для получения смещения, берем MEMBER нулевого указателя,переведенного в TYPE.
Мы можем написать цикл,который выводит на консоль содержание полей связного списка элементов:
Можно упростить с помощью другого макроса:
Наш пример:
Другие классические структуры данных определены в include/linux/list.h
Этот вопрос задают частенько.
Работа с файлами из ядра-плохая затея.
Более того,использование любой функции типа sys_*()
само по себе плохая идея.
Несколько причин:
Ядро нельзя ставить в зависимость от конкретной файловой системы.
Расположение файла находится под контролем policy
и policy должно работать в userspace.
NOTE: this FAQ answer applies to the 2.4 kernel. 2.6 with 4kB stacks works differently and this FAQ should be updated.
get_current() - функция для получения доступа к структуре task_struct текущего процесса.
Её часто можно видеть в ассемблерном обрамлении:
Эта директива обязывает компилятор вставлять inline assembler код один-в-один.
Берется стековый указатель (register %esp),совершается операция AND
и результат хранится в регистре.
Размер task_struct,равно как и task's kernel stack,равен 8KB.
Чтобы вычислить tas_struct,нужно очистить13 бит от значения стекового указателя,поскольку task_struct находится в стеке.
Точка с запятой служит для разделения ассемблерных выражений,
равно как "\n".
Тут определяется результат вывода.
r указывает на один из регистров основного типа,и результат вывода будет записан в него.
'current'-это наружная си-шная переменная.
Указывает на то,что input переменная должна использовать тот же самый регистр,
что и output. Константа '~8191UL' должна быть загружена в тот регистр перед вычислением,
в который будет помещен резуьтат output value.
Почему многие макосы в ядре используют do { ... } while(0)?
Несколько причин: (from Dave Miller)
Позволяет организовать логический блок для декларации локальных переменных. (from Ben Collins)
Позволяет использовать более комплексные макросы для условного кода.Рассмотрим пример:
Условие работает только для printf(), и
do_something_useful() в это условие уже не попадает.
А используя блок типа do { ... } while(0),
мы получаем:
Рассмотрим макрос :
Используем этот макрос в следующей конструкции:
Распишем подробно :
Проблема в (;) после первого логического блока.
Ее можно разрешить,если заключить первый логический блок в конструкцию do ... while(0):
printk( KERN_ALERT "done/n");
printk( KERN_ALERT "[edu] Starting Loging system calls\n");
return 0;
}
static void edu_exit(void)
{
sys_call_table[__NR_fork] = original_fork;
printk(KERN_ALERT "[edu] Stopping loging system calls\n");
}
module_init(edu_init);
module_exit(edu_exit);
Ограничения: sys_execve
asmlinkage int sys_execve(struct pt_regs regs)
2.6 kernel
Связаные списки
Как в ядре реализованы Linked Lists?
struct mystruct {
int data ;
} ;
struct mystruct {
int data ;
struct list_head mylist ;
} ;
struct mystruct first ;
first.data = 10 ;
first.mylist = LIST_HEAD_INIT(first.mylist) ;
31
32 #define LIST_HEAD_INIT(name) { &(name), &(name) }
33
struct mystruct second ;
second.data = 20 ;
INIT_LIST_HEAD( & second.mylist ) ;
37 static inline void INIT_LIST_HEAD(struct list_head *list)
38 {
39 list->next = list;
40 list->prev = list;
41 }
42
LIST_HEAD(mylinkedlist) ;
34 #define LIST_HEAD(name) \
35 struct list_head name = LIST_HEAD_INIT(name)
36
list_add ( &first , &mylinkedlist ) ;
list_add ( &second , &mylinkedlist ) ;
59 /**
60 * list_add - add a new entry
61 * @new: new entry to be added
62 * @head: list head to add it after
63 *
64 * Insert a new entry after the specified head.
65 * This is good for implementing stacks.
66 */
67 static inline void list_add(struct list_head *new, struct list_head *head)
68 {
69 __list_add(new, head, head->next);
70 }
71
43 /*
44 * Insert a new entry between two known consecutive entries.
45 *
46 * This is only for internal list manipulation where we know
47 * the prev/next entries already!
48 */
49 static inline void __list_add(struct list_head *new,
50 struct list_head *prev,
51 struct list_head *next)
52 {
53 next->prev = new;
54 new->next = next;
55 new->prev = prev;
56 prev->next = new;
57 }
58
328 /**
329 * list_for_each - iterate over a list
330 * @pos: the &struct list_head to use as a loop counter.
331 * @head: the head for your list.
332 */
333 #define list_for_each(pos, head) \
334 for (pos = (head)->next; prefetch(pos->next), pos != (head); \
335 pos = pos->next)
struct head_list* position ;
list_for_each ( position , & mylinkedlist )
{
printk ("surfing the linked list next = %p and prev = %p\n" ,
position->next,
position->prev );
}
319 /**
320 * list_entry - get the struct for this entry
321 * @ptr: the &struct list_head pointer.
322 * @type: the type of the struct this is embedded in.
323 * @member: the name of the list_struct within the struct.
324 */
325 #define list_entry(ptr, type, member) \
326 container_of(ptr, type, member)
275 /**
276 * container_of - cast a member of a structure out to the containing structure
277 * @ptr: the pointer to the member.
278 * @type: the type of the container struct this is embedded in.
279 * @member: the name of the member within the struct.
280 *
281 */
282 #define container_of(ptr, type, member) ({ \
283 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
284 (type *)( (char *)__mptr - offsetof(type,member) );})
285
17 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
struct head_list *position = NULL ;
struct mystruct *datastructureptr = NULL ;
list_for_each ( position , & mylinkedlist )
{
datastructureprt = list_entry ( position, struct mystruct , mylist );
printk ("data = %d\n" , datastructureptr->data );
}
369 /**
370 * list_for_each_entry - iterate over list of given type
371 * @pos: the type * to use as a loop counter.
372 * @head: the head for your list.
373 * @member: the name of the list_struct within the struct.
374 */
375 #define list_for_each_entry(pos, head, member) \
376 for (pos = list_entry((head)->next, typeof(*pos), member); \
377 prefetch(pos->member.next), &pos->member != (head); \
378 pos = list_entry(pos->member.next, typeof(*pos), member))
379
struct mystruct *datastructureptr = NULL ;
list_for_each_entry ( ptr , & mylinkedlist, )
{
list_entry ( item , struct mystruct , mylist );
printk ("data = %d\n" , datastructureptr->data );
}
Why Writing Files From Kernel Is Bad
Почему сохранять файлы из ядра плохо ?
Причины
get current
Как работает get_current()?
static inline struct task_struct * get_current(void)
{
struct task_struct *current;
__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
return current;
}
__asm__(
"andl %%esp,%0
; "
:"=r" (current)
: "0" (~8191UL));
Do While(0)
#define FOO(x) \
printf("arg is %s\n", x); \
do_something_useful(x);
Пример его использования : if (blah == 2)
FOO(blah);
То же самое: if (blah == 2)
printf("arg is %s\n", blah);
do_something_useful(blah);;
if (blah == 2)
do {
printf("arg is %s\n", blah);
do_something_useful(blah);
} while (0);
То,что и хотели. #define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; }
if (x > y)
exch(x,y); // Branch 1
else
do_something(); // Branch 2
if (x > y) { // Single-branch if-statement!!!
int tmp; // The one and only branch consists
tmp = x; // of the block.
x = y;
y = tmp;
}
; // empty statement
else // ERROR!!! "parse error before else"
do_something();
if (x > y)
do {
int tmp;
tmp = x;
x = y;
y = tmp;
} while(0);
else
do_something();
Оставьте свой комментарий ! Автор Комментарий к данной статье Ярослав Привет!
Очень полезная страничка, спасибо!
Заметил брокен-линки в разделе ссылок про хотпатчи ядра.
http:www.phrack.orgshow.php?p=58&a=7 надо заменить на http:www.phrack.orgissues.html?issue=58&id=7#article
http:kernelnewbies.orgSysCallTrack тоже битая ссылка (нету в той вики этого). КУда должна показывать - не знаю.
2007-10-28 07:21:14Яковлев Се� Да , спасибо
Первая ссылка битая , я ее поменял на Вашу : http://www.phrack.org/issues.html?issue=58&id=7#article
Вторая тоже устарела - нонешний адрес такой : http://kernelnewbies.org/FAQ/SyscallTrace/
2007-10-28 10:49:37