В наши дни руткитам нужно уделять особое внимание, так как они являются коварным оружием в руках хакера. Особенно, если речь идет о lkm-based руткитах. Так как загружаемые модули - часть ядра, с их помощью можно скрывать файлы и директории, процессы, сетевые соединения и многое другое. Почти все современные руткиты действуют по принципу замены системных вызовов - самому простому и верному способу захватить контроль над системой и скрыть свое присутствие.
Возникает вопрос - а как можно защитится от подмены системных вызовов?
В этой статье я хочу рассмотреть метод защиты сисколов в операционной системе Solaris.
1.Подмена сискола
Сначала мне стало интересно, как же собственно происходит замена системного вызова. Ведь, подумал я, если при замене сискола вызываются какие-то специфичные функции(или функции с какими-нибудь специфичными параметрами), то можно следить за подменой сискола с помощью dtrace. Для этого я сделал модуль, который содержит всего три строчки в функции _init()
_init(void) { int i = modinstall(&modlinkage); sysent[SYS_mkdir].sy_callc=(void *)0xfe8ecff4; /* fe8ecff4 - адрес SYS_mkdir.В вашей системе, скорее всего, адрес будет отличаться*/ return i; }Компилим, линкуем, загружаем
-bash-3.00#gcc -D_KERNEL -DSVR4 -DSOL2 -c mdb_lkm.c -o mdb_lkm.o && ld -r mdb_lkm.o -o mdb_lkm -bash-3.00#modload mdb_lkm -bash-3.00#modinfo 174 f9dec620 a80 156 1 profile (Profile Interrupt Tracing) 175 f9e07620 1744 159 1 sdt (Statically Defined Tracing) 176 feba0bd2 4c4 157 1 systrace (System Call Tracing) 177 f9ed8f39 d8 - 1 mdb_lkm (mod) -bash-3.00#Берем адрес(f9ed8f39) и в mdb
-bash-3.00# mdb -k Loading modules: [ unix krtld genunix specfs dtrace ufs ip sctp usba uhci random fctl nca lofs crypto nfs audiosup sppp ptm ipc ] > f9ed8f39::dis 0xf9ed8f39: addb %al,(%eax) 0xf9ed8f3b: addb %dl,-0x77(%ebp) mdb_lkm`_init+2: inl $0x83 mdb_lkm`_init+4: inb (%dx) mdb_lkm`_init+5: orb %al,0xe0680cec(%ebx) mdb_lkm`_init+0xb: outl (%dx) mdb_lkm`_init+0xc: pushfl mdb_lkm`_init+0xd: aam mdb_lkm`_init+0xf: movl $0x8304abc8,%ebx mdb_lkm`_init+0x14: les (%eax),%edx mdb_lkm`_init+0x16: movl %eax,-0x4(%ebp) > mdb_lkm`_init+0x16 mdb_lkm`_init: pushl %ebp mdb_lkm`_init+1: movl %esp,%ebp mdb_lkm`_init+3: subl $0x8,%esp mdb_lkm`_init+6: subl $0xc,%esp mdb_lkm`_init+9: pushl $0xd49cefe0 mdb_lkm`_init+0xe: call +0x4abc8c0Все то что выше _init+0xe нас не интересует - это относится к mod_install. Нас интересует строчкаmdb_lkm`_init+0x13: addl $0x10,%esp mdb_lkm`_init+0x16: movl %eax,-0x4(%ebp) mdb_lkm`_init+0x19: movl $0xfe8ecff4,0xfec4961c mdb_lkm`_init+0x23: movl -0x4(%ebp),%eax mdb_lkm`_init+0x26: leave mdb_lkm`_init+0x27: ret
movl $0xfe8ecff4,0xfec4961cТ.е видно, что заменить сискол можно простым movl'om. Причем заметьте, fe8ecff4 - адрес SYS_mkdir, т.е мы заменям сискол своим же адресом, а fec4961c - это адрес в таблице sysent
> fec4961c::dis sysent+0x50c: hlt sysent+0x50d: iret sysent+0x50e: movw %esi,2.Защита от подмены сискола Итак, как же будет выглядеть защитный модуль? В принципе, достаточно лишь сравнивать текущие значения указателей и те значения, которые были сделаны раньше. В этом нам поможет dtrace. Я отобрал, на мой взгляд,14 самых подверженных замене в руткитах системных вызовов. Итак,скрипт который будет делать нам дамп адресов сисколовsysent+0x510: addl (%eax),%eax sysent+0x512: addb %al,(%eax) sysent+0x514: addb %al,(%eax) sysent+0x516: addb %al,(%eax) sysent+0x518: addb %al,(%eax) sysent+0x51a: addb %al,(%eax) sysent+0x51c: daa sysent+0x51d: addb %cl,0x5fe(%ebp)
#!/usr/sbin/dtrace -qs :::BEGIN { printf("0x%p,\n",`sysent[3].sy_callc); /* SYS_read*/ printf("0x%p,\n",`sysent[4].sy_callc); /* SYS_write*/ printf("0x%p,\n",`sysent[5].sy_callc); /* SYS_open*/ printf("0x%p,\n",`sysent[15].sy_callc); /*SYS_chmod*/ printf("0x%p,\n",`sysent[16].sy_callc); /*SYS_chown*/ printf("0x%p,\n",`sysent[23].sy_callc); /*SYS_setuid*/ printf("0x%p,\n",`sysent[54].sy_callc); /*SYS_ioctl*/ printf("0x%p,\n",`sysent[59].sy_callc); /*SYS_execve*/ printf("0x%p,\n",`sysent[61].sy_callc); /*SYS_chroot*/ printf("0x%p,\n",`sysent[81].sy_callc); /*SYS_getdents*/ printf("0x%p,\n",`sysent[88].sy_callc); /*SYS_lstat*/ printf("0x%p,\n",`sysent[141].sy_callc); /*SYS_seteuid*/ printf("0x%p,\n",`sysent[216].sy_callc); /*SYS_lstat_64*/ printf("0x%p\n",`sysent[225].sy_callc); /*SYS_open_64*/ exit(0); } -bash-3.00# ./pointers.d 0xfe90a674, 0xfe90aad2, 0xfe8f4c89, 0xfe88fb65, 0xfe88fde6, 0xfe969979, 0xfe8d5397, 0xfe8ba20a, 0xfe88f833, 0xfe8d0027, 0xfe92c2a3, 0xfe969bdf, 0xfe92c6e2, 0xfe8f4ce4 -bash-3.00#Теперь сам syscall guard
#include/ В этой функции идет проверка таблицы сисколов из массива n_p берется номер системного вызова подставляется в sysent и сравниваеться со значением из массива sys_names Если адрес сискола из массива не равен текущему адресу то вызывается функция restore_sysc для восстановления сискола /#include #include #include #include #include //includes #include #include #include #include #define DELAY_N 5 //количество секунд между проверками #define SYSC_TO_CHECK 14 //количество сисколов для проверки extern struct mod_ops mod_miscops; //Далее всякие структуры для корректной работы модуля static struct modlmisc modlmisc = { &mod_miscops, "Syscall checker", }; static struct modlinkage modlinkage = { MODREV_1, (void *) &modlmisc, NULL }; void my_func(void); unsigned long pointers[SYSC_TO_CHECK] //Массив с указателями Их нужно получить с помощью pointers.d и вставить сюда = { //insert pointers here }; int n_p[SYSC_TO_CHECK]={3,4,5,15,16,23,54,59,61,81,88,141,216,225}; // номера сисколов для проверки char *sys_names[SYC_TO_CHECK] = { // названия сисколов для определенности "SYS_read", "SYS_write", "SYS_open", "SYS_chmod", "SYS_chown", "SYS_setuid", "SYS_ioctl", "SYS_execve", "SYS_chroot", "SYS_getdents", "SYS_lstat", "SYS_seteuid", "SYS_lstat_64", "SYS_open_64" }; timeout_id_t time_id; int _fini(void) {//Эта функция вызывается при выгрузке модуля int i; untimeout(time_id); if ((i = mod_remove(&modlinkage)) != 0) { cmn_err(CE_WARN, "Could not remove module\n"); } else { cmn_err(CE_WARN,"ALERT!! Syscall Guard removed!!"); } return i; } int _init(void)//Эта функция вызывается при загрузке модуля { int i; (void)my_func();//собственно функция проверки if ((i = mod_install(&modlinkage)) != 0) cmn_err(CE_WARN, "Could not install module\n"); else cmn_err(CE_WARN, "Syscall guard successfully installed\n");//change this!!! return i; } void restore_sysc(int k) {//функция восстаноления сисколов sysent[n_p[k]].sy_callc=(void *)pointers[k]; cmn_err(CE_WARN,"Restored original value of %s",sys_names[k]); }
void my_func() {//функция проверки
int k;
for(k=0;k<SYSC_TO_CHECK;k++) {
if(sysent[n_p[k]].sy_callc != (void *)pointers[k]) { cmn_err(CE_WARN,"%s was changed",sys_names[k]); restore_sysc(k);
}
}
функция таймаута иначе система начинает потреблять очень много ресурсов / k=0; time_id = timeout((void (*)(void *))my_func,NULL,drv_usectohz(1000000*DELAY_N));
int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }Но стоит учитывать то,что хакер, имея возможность загрузить модуль, имеет доступ ко всей системе, а следовательно может просто выгрузить наш модуль и почистить логи. Поэтому какой бы искушенной подобного рода защита ни была, опытный взломщик скорее всего найдет способ ее обойти.
/* By zZz , rootteam.void.ru Thanks to Xarth,devoid */
5778 К? Пф! У нас градус знаний зашкаливает!