 |
от pragmatic / THC(21-05-2001)
рейтинг (6)
[ добре ]
[ зле ]
Вариант за отпечатване
(nearly) Complete Linux Loadable Kernel
Modules
-the definitive guide for hackers, virus coders and system
administrators-
Преведено от: Андрю
Иванов. Оригиналното ръководство може да откриете на
този адреса>
СЪДЪРЖАНИЕ
Въведение
I. Основи
1. Какво представляват LKM
2. Какво представляват системните
повиквания (systemcalls)
3. Какво представлява
Kernel-Symbol-Table (таблица
със символите на ядрото)
Въведение
Ползата от Linux в света на сървърите нараства всяка
секунда. Така че да
хакнеш Linux става все по-интересно с всеки изминал ден.
Една от най-добрите
техники за атака на Linux система е чрез използване на
kernel код т.е.
програмен код за ядрото на операционната система.
Благодарение на функцията
наречена "Loadable Kernel Modules" (Заредими Модули на
Ядрото - LKM) е
възможно да се напише код, работещ в рамките на ядрото,
което ни позволява
да достигнем много чувствителни части на операционната
система. Преди време
съществуваха някои текстове и файлове, засягащи LKM
хакването (Phrack,
например), които бяха доста добри. Те They представиха нови
идеи, нови
методи и завършени LKMs, правещи всичко за което един хакер
някога е мечтал.
Също така и някои публични дискусии през 1998 (новинарски
групи, пощенски
списъци) бяха много интересни.
И така, защо да пиша отново за LKMs? Ами, има няколко
причини:
-
предишните текстове, понякога, не даваха добри обяснения за
начинаещите;
този текст има много обширна част посветена на основите,
помагаща на начинаещите
да разберат основните концепции. Срещал съм много хора,
използващи добри
снифъри и тям подобни без дори да разбират как тези
"играчки" работят.
Включих много сорс код в този файл с много коментари,
просто за да помогна
на начинаещите, които знаят, че хакерството е нещо повече
от това да опустошаваш
някакви си мрежи !
-
всеки публикуван текст се концентрираше върху определена
тема, не съществуваше
завършено ръководство за хакери, което да засяга LKMs. Този
текст ще засегне
почти всички аспекти от злоупотребата с ядрото (дори
вирусите)
-
този текст беше написан от гледната точка на хакер /
създател на вируси,
но ще е от полза и за администраторите и обикновените
разработчици, занимаващи
се с ядрото
-
предишните текстове демонстрираха най-важните изгоди и
методи на злоупотреба
с LKMs, но съществуват неща, за които все още не сме и
чували. Този текст
ще демонстрира някои нови идеи (нищо изцяло ново, но все
пак няща, които
могат да ни помогнат)
-
този текст ще покаже някои идеи за прост начин за
предпазване от LKM атаки
-
този текст ще демонстрира и как да бъдат преодоляни LKM
защитите чрез използване
на методи като Runtime Kernel Patching ("закърпване" на
ядрото в реално
време)
Моля, запомнете, че новите идеи за изпълнени под формата на
модули-прототипи
(само за демонстрационни цели), които трябва да бъдат
подобрени, за да
будат използвани в реалността.
Основната мотивация на този текст е да даде на всеки
един голям
текст, покриващ целия проблем LKM. В приложение A давам
няколко съществуващи
LKMs плюс кратко описание на тяхната работа (за начинаещи)
и начини за
тяхното използване.
Целият текст (с изключение на част V) се базира на
Linux 2.0.x машина
(x86). Тествал съм всички програми и фрагменти код. Linux
системата трябва
да поддържа LKM, за да използвате повечето примерни кодове
в този текст.
Само част IV ще демонстрира някои сорсове, които работят
без вградена поддръжка
на LKM. Повечето исеи в този текст че работят и на 2.2.x
системи (вероятно
ще трябва да направите някои минимално корекции); но
спомнете си, че ядро
2.2.x тъкмо беше излязло (1/99) и повечето linux
дистрибуции все още използват
2.0.x (Redhat, SuSE, Caldera, ...). През април някои
дистрибутори, като
SuSE ще покажат техните версии с ядро 2.2.x; така че няма
да имате нужда
да знаете как да хакнете ядро 2.2.x към настоящия момент.
Добрите администратори
също ще изчакат няколко месеца, за да получат по-надеждно
2.2.x ядро. [Забележка
: Повечето системи просто не се нуждаят от ядро 2.2.x, така
че те ще продължат
да използват 2.0.x].
Този текст има специален раздел, занимаващ се с LKMs,
помагащи на администраторите
да обезопасят системата. Вие (хакерите) трябва също да
прочетете този раздел,
вие трябва да знаете всичко, което админите знаят и
дори повече.
Ще получите някои добри идеи от този раздел, които могат да
ви помогнат
да разработите по-напреднали 'хекерски LKMs'. Просто
прочетете целия текст
!
И, моля ви, запомнете : Този текст беше
написан единствено
за учебни цели. Всяко противозаконно действие основано на
този текст си
е лично ваш проблем.
I. Основи
1. Какво представляват LKM
LKM - това са Loadable Kernel Modules (Заредими Модули на
Ядрото. Те се
използват от Linux ядрото за повишаване на функционалността
му. Ползите
от LKM : Tе могат да се зареждат динамично; не се
налага прекомпилиране
на цялото ядро. Поради тези си свойства, LKM се използват
често за драйвери
за специфични устройства (или файлови системи), като
например звукови карти
и подобни.
Всеки LKM се състои от две основни функции (минимум) :
int init_module(void) /*използва се за всичко свързано
с инициализацията*/
{
...
}
void cleanup_module(void) /*използва се за 'чисто' спиране
на модула*/
{
...
}
Зареждане на модул - обикновено извършвано от root - става
чрез командата:
# insmod module.o
Тази команда предизвиква извършването на следните действия
от системата :
-
Зареждане на обектния файл (в случая module.o)
-
Извиква системно повикване create_module (за системни
повиквания -> виж
I.2) за презареждане на паметта
-
неуточнените препратки (references) се превръщат в
Kernel-Symbol-и чрез
системно повикване get_kernel_syms
-
след това се използва системно повикване init_module за
инсталиране на
LKM-а -> изпълнявайки int init_module(void) и т.н.
Kernel-Symbol-ите са обяснени в I.3 (Kernel-Symbol-Table).
И така, струва ми се, че вече може да напишем първият
си малък LKM,
просто за да демонстрираме основните принципи на работа:
#define MODULE
#include <Linux/module.h>
int init_module(void)
{
printk("<1>Hello World\n");
return 0;
}
void cleanup_module(void)
{
printk("<1>Bye, Bye");
}
Може би се учудвате, че използвах printk(...), а не
printf(...). Е добре,
програмирането на ниво ядро
е напълно различно от програмирането на
потребителско ниво !
Разполагате с много ограничен набор от команди (виж I.6). С
тези команди
не можете да направите кой знае какво, така че ще се
научите как да използвате много от
функциите, които познавате от вашите потребителски
програми; те ще ви помагат да хакнете ядрото.
Само бъдете търпеливи, първо трябва да свършим още нещо...
Примерният LKM, даден по-горе може лесно да бъде компилиран
по следният начин:
# gcc -c -O3 helloworld.c
# insmod helloworld.o
Добре, нашият модул е зареден и ни показа един известен
текст. Сега можете да пробвате някои
команди, които ще ви показат, че LKM-а наистина се
разполага в рамките на ядрото.
# lsmod
Module
Pages Used by
helloworld
1 0
Тази команда изчита информацията в /proc/modules, за да ви
покаже кои
модули са заредени в момента. 'Страниците' (pages) дават
информация за паметта (колко страници
от паметта заема модула); Полето 'използван от' (used by)
ни дава информация колко често модулът
се използва от Системата (брой обръщения). Модулът може да
бъде отстранен, само когато
този брояч показва нула; след като проверите това, можете
да премахнете модула със
# rmmod helloworld
Е, това беше нашата първа малка стъпка (наистина мъничка)
към злоупотребите с LKMs. Винаги
съм сравнявал тези LKMs със старите TSR програми под DOS
(да, знам, съществуват много разлики),
те бяха нашата вратичка за пребиваване в паметта и
прихващане на всяко прекъсване, което
пожелаем. Microsoftския WIN 9x има нещо наречено VxD, което
също е много подобно на
LKMs (и тук има много разлики). Най-интересното при тези
резидентни програми
е възможността да прихванеме системните функции, наричани
'системни повиквания' (systemcalls)
в света на Linux
2. Какво представляват системните
повиквания (systemcalls)
Надявам се знаете, че всяка ОС има функции, вградени в
ядрото й, които
се използват за извършването на всяка една операция в тази
система.
Функциите, които Linux използва се наричат системни
повиквания (systemcalls).
Те представляват преход от потребителското пространство към
ниво 'ядро'.
Отварянето на фаил в потребителското пространство е
представено от системно
повикване sys_open на ниво ядро. За изчерпателен списък на
системните повиквания
(systemcalls), които са достъпни за вашата система
погледнете в /usr/include/sys/syscall.h.
Този списък представлява моят syscall.h
#ifndef _SYS_SYSCALL_H
#define _SYS_SYSCALL_H
#define
SYS_setup
0 /* Използва се само от init, за да се 'подкара' системата.
*/
#define
SYS_exit
1
#define
SYS_fork
2
#define
SYS_read
3
#define
SYS_write
4
#define
SYS_open
5
#define
SYS_close
6
#define
SYS_waitpid
7
#define
SYS_creat
8
#define
SYS_link
9
#define
SYS_unlink
10
#define
SYS_execve
11
#define
SYS_chdir
12
#define
SYS_time
13
#define
SYS_prev_mknod
14
#define
SYS_chmod
15
#define
SYS_chown
16
#define
SYS_break
17
#define
SYS_oldstat
18
#define
SYS_lseek
19
#define
SYS_getpid
20
#define
SYS_mount
21
#define
SYS_umount
22
#define
SYS_setuid
23
#define
SYS_getuid
24
#define
SYS_stime
25
#define
SYS_ptrace
26
#define
SYS_alarm
27
#define
SYS_oldfstat
28
#define
SYS_pause
29
#define
SYS_utime
30
#define
SYS_stty
31
#define
SYS_gtty
32
#define
SYS_access
33
#define
SYS_nice
34
#define
SYS_ftime
35
#define
SYS_sync
36
#define
SYS_kill
37
#define
SYS_rename
38
#define
SYS_mkdir
39
#define
SYS_rmdir
40
#define
SYS_dup
41
#define
SYS_pipe
42
#define
SYS_times
43
#define
SYS_prof
44
#define
SYS_brk
45
#define
SYS_setgid
46
#define
SYS_getgid
47
#define
SYS_signal
48
#define
SYS_geteuid
49
#define
SYS_getegid
50
#define
SYS_acct
51
#define
SYS_phys
52
#define
SYS_lock
53
#define
SYS_ioctl
54
#define
SYS_fcntl
55
#define
SYS_mpx
56
#define
SYS_setpgid
57
#define
SYS_ulimit
58
#define
SYS_oldolduname
59
#define
SYS_umask
60
#define
SYS_chroot
61
#define
SYS_prev_ustat
62
#define
SYS_dup2
63
#define
SYS_getppid
64
#define
SYS_getpgrp
65
#define
SYS_setsid
66
#define
SYS_sigaction
67
#define
SYS_siggetmask
68
#define
SYS_sigsetmask
69
#define
SYS_setreuid
70
#define
SYS_setregid
71
#define
SYS_sigsuspend
72
#define
SYS_sigpending
73
#define
SYS_sethostname
74
#define
SYS_setrlimit
75
#define
SYS_getrlimit
76
#define
SYS_getrusage
77
#define
SYS_gettimeofday
78
#define
SYS_settimeofday
79
#define
SYS_getgroups
80
#define
SYS_setgroups
81
#define
SYS_select
82
#define
SYS_symlink
83
#define
SYS_oldlstat
84
#define
SYS_readlink
85
#define
SYS_uselib
86
#define
SYS_swapon
87
#define
SYS_reboot
88
#define
SYS_readdir
89
#define
SYS_mmap
90
#define
SYS_munmap
91
#define
SYS_truncate
92
#define
SYS_ftruncate
93
#define
SYS_fchmod
94
#define
SYS_fchown
95
#define
SYS_getpriority
96
#define
SYS_setpriority
97
#define
SYS_profil
98
#define
SYS_statfs
99
#define
SYS_fstatfs
100
#define
SYS_ioperm
101
#define
SYS_socketcall
102
#define
SYS_klog
103
#define
SYS_setitimer
104
#define
SYS_getitimer
105
#define
SYS_prev_stat
106
#define
SYS_prev_lstat
107
#define
SYS_prev_fstat
108
#define
SYS_olduname
109
#define
SYS_iopl
110
#define
SYS_vhangup
111
#define
SYS_idle
112
#define
SYS_vm86old
113
#define
SYS_wait4
114
#define
SYS_swapoff
115
#define
SYS_sysinfo
116
#define
SYS_ipc
117
#define
SYS_fsync
118
#define
SYS_sigreturn
119
#define
SYS_clone
120
#define
SYS_setdomainname 121
#define
SYS_uname
122
#define
SYS_modify_ldt
123
#define
SYS_adjtimex
124
#define
SYS_mprotect
125
#define
SYS_sigprocmask
126
#define
SYS_create_module 127
#define
SYS_init_module
128
#define
SYS_delete_module 129
#define SYS_get_kernel_syms 130
#define
SYS_quotactl
131
#define
SYS_getpgid
132
#define
SYS_fchdir
133
#define
SYS_bdflush
134
#define
SYS_sysfs
135
#define
SYS_personality
136
#define
SYS_afs_syscall
137 /* Syscall за файлова система Andrew */
#define
SYS_setfsuid
138
#define
SYS_setfsgid
139
#define
SYS__llseek
140
#define
SYS_getdents
141
#define
SYS__newselect
142
#define
SYS_flock
143
#define
SYS_syscall_flock
SYS_flock
#define
SYS_msync
144
#define
SYS_readv
145
#define
SYS_syscall_readv
SYS_readv
#define
SYS_writev
146
#define SYS_syscall_writev
SYS_writev
#define
SYS_getsid
147
#define
SYS_fdatasync
148
#define
SYS__sysctl
149
#define
SYS_mlock
150
#define
SYS_munlock
151
#define
SYS_mlockall
152
#define
SYS_munlockall
153
#define SYS_sched_setparam
154
#define SYS_sched_getparam
155
#define SYS_sched_setscheduler 156
#define SYS_sched_getscheduler 157
#define
SYS_sched_yield
158
#define
SYS_sched_get_priority_max 159
#define
SYS_sched_get_priority_min 160
#define
SYS_sched_rr_get_interval
161
#define
SYS_nanosleep
162
#define
SYS_mremap
163
#define
SYS_setresuid
164
#define
SYS_getresuid
165
#define
SYS_vm86
166
#define
SYS_query_module
167
#define
SYS_poll
168
#define
SYS_syscall_poll
SYS_poll
#endif /* <sys/syscall.h> */
Всяко системно повикване има определен номер (погледни
листинга по-горе), който реално се
използва за системно повикване.
Ядрото използва прекъсване 0x80 за обслужване на всяко
системно повикване. Номерът на
systemcall-а и всички необходими аргументи се преместват в
някои регистри (eax за номер на systemcall,
напр,).
номера на системното повикване представлява номер в масив
на структура на ядрото, наречен
sys_call_table[]. Тази структура съпоставя номерата на
системните повиквания и необходимите
сервизни функции.
Е, това знание трябва да е достатъчно, за да продължим с
четенето. В следващата таблица е
даден списък на най-интересните повиквания заедно с кратко
описание.
Повярвайте ми, трябва да знаете точното действие на тези
повиквания, за да
направите наистина полезни LKMи.
системно повикване |
описание |
int sys_brk(unsigned long new_brk); |
променя размера на използвания DS (сегмент за данни)
->това повикване
ще бъде дискутирано в I.4 |
int sys_fork(struct pt_regs regs); |
systemcall отговарящ на добре познатата в
потребителското пространство
функция fork() |
int sys_getuid ()
int sys_setuid (uid_t uid)
... |
повиквания за управление на UID и под. |
int sys_get_kernel_sysms(struct kernel_sym *table) |
systemcall за достъп до системната таблица на ядрото
(kernel system
table) (-> I.3) |
int sys_sethostname (char *name, int len);
int sys_gethostname (char *name, int len); |
sys_sethostname отговаря за установяването на hostname,
а sys_gethostname
- за получаването му(?) |
int sys_chdir (const char *path);
int sys_fchdir (unsigned int fd); |
и двете функции се използват за установяване на
текущата директория
(cd ...) |
int sys_chmod (const char *filename, mode_t mode);
int sys_chown (const char *filename, mode_t mode);
int sys_fchmod (unsigned int fildes, mode_t mode);
int sys_fchown (unsigned int fildes, mode_t mode); |
функции за управление на правата и под. |
int sys_chroot (const char *filename); |
настройва основната (root) директория за извикващия
процес |
int sys_execve (struct pt_regs regs); |
Много важен systemcall -> отговаря за изпълнението на
файлове (pt_regs
е регистров стек) |
long sys_fcntl (unsigned int fd, unsigned int cmd,
unsigned long arg); |
променя характеристиките на fd (opened file
descr.) |
int sys_link (const char *oldname, const char
*newname);
int sym_link (const char *oldname, const char
*newname);
int sys_unlink (const char *name); |
системно повикване за управление на твърди (hardlinks)
или "меки" (softlinks)
връзки |
int sys_rename (const char *oldname, const char
*newname); |
преименува файлове |
int sys_rmdir (const char* name);
int sys_mkdir (const *char filename, int mode); |
създава и премахва директории |
int sys_open (const char *filename, int mode);
int sys_close (unsigned int fd); |
за всичко свързано с отварянето на файлове (включително
създаването
им), а също и затварянето им |
int sys_read (unsigned int fd, char *buf, unsigned int
count);
int sys_write (unsigned int fd, char *buf, unsigned int
count); |
системни повиквания за запис и четене от файлове |
int sys_getdents (unsigned int fd, struct dirent
*dirent, unsigned
int count); |
systemcall, чрез който се извлича списък на файлове
(командата ls ...) |
int sys_readlink (const char *path, char *buf, int
bufsize); |
чете символични връзки |
int sys_selectt (int n, fd_set *inp, fd_set *outp,
fd_set *exp, struct
timeval *tvp); |
multiplexing на I/O операциите |
sys_socketcall (int call, unsigned long args); |
socket функции |
unsigned long sys_create_module (char *name, unsigned
long size);
int sys_delete_module (char *name);
int sys_query_module (const char *name, int which, void
*buf, size_t
bufsize, size_t *ret); |
използва се за зареждане / отстраняване на LKMи и за
запитвания |
По мое мнение, това са най-интересните системни повиквания
за имащите хакерски
намерения, разбира се, възможно е да се нуждаете от нещо
специално на системата
в която тършувате, но обикновенният хакер има изобилие от
възможности със
списъка даден по-горе. В част II ще се научите как да
използвате системните
повиквания в своя полза.
3. Какво представлява
Kernel-Symbol-Table (таблица със
символите на ядрото)
символ - нещо (напр. графично
изображение), което представлява/олицетворява
обект/събект/нещо абстрактно т.е. всеки символ на ядрото
представлява някаква
негова функция
бел. пр.
Е, добре! Разбираме основната концепция за системните
повиквания (systemcalls)
и модулите. Но има още нещо, и то много важно, което трябва
да разберем
- Kernel Symbol Table (таблица със символите на ядрото.
Погледнете /proc/ksyms.
Всеки ред в този файл представлява експортиран (публичен)
символ на ядрото,
който е достъпен за нашият LKM. Огледайте добре този файл;
ще откриете
много интересни неща в него.
Файлът е наистина интересен и може да ни помогне да
разберем какво
може да използва нашият LKM , но има един проблем. Всеки
символ използван
в нашият LKM (като функция) също се експортира в този
"публичен регистър"
т.е. появява се в този файл-списък. Така един опитен
администратор би могъл
да открие малкият ни LKM и да го отстрани (kill).
Съществуват много методи, чрез които да не позволим на
админите да
видят нашият LKM, вижте раздел II.
Матодите споменати в II могат да бъдат наречени
'хакове', но когато
разгледате съдържанието на раздел II няма да откриете
препратка към "Как
да държиме LKM символите далеч от /proc/ksyms". Причината
да не споменаваме
този проблем в раздел II е следната :
няма да се нуждаете от трик, за да не допуснете
модули-символи си до
/proc/ksyms. Разработчиците на LKM могат да използват
следният отрязък
"нормален" код, за да спрат експортираните символи от
техният модул:
static struct symbol_table module_syms= { /*дефинираме
собствена таблица на символите !*/
#include
<linux/symtab_begin.h>
/*Символите които искаме да експортираме, а искаме ли ?*/
...
};
register_symtab(&module_syms);
/*извършва истинското регистриране*/
Тъй като не искаме да изнасяме никакви символи на показ,
използваме
следната конструкция :
register_symtab(NULL);
Този ред трябва да бъде вмъкнат във функцията
init_module(), запомнете
го !
<< (почти)Пълно ръководство за модулите в Linux ядрото [част 2] | Как да защитите вашият Linux (част 2) >>
|
 |