от pragmatic / THC(21-05-2001)

рейтинг (6)   [ добре ]  [ зле ]

Printer Friendly Вариант за отпечатване

(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) >>