Un viejo conocido
Avatar de Usuario
newlog
El Eterno Alumno
 
Mensajes: 170
Registrado: Lun Jun 23, 2008 7:28 pm

Syscall hooking en espacio de kernel

por newlog Mié Mar 07, 2012 1:19 pm

Buenas!

Si os tengo que ser sincero, llevo años teniendo una espina clavada. Un especie de pensamiento en mi subconsciente. A qué se debe? Muchas han sido las veces que he intentado hookear llamadas al sistema (en espacio de kernel) en kernels que ya no exportaban la dirección de la syscall_table. Mis resultados? Fracaso.

Durante mucho tiempo sólo encontraba información sobre el tema que no era vigente o que no funcionaba. Imagino que existía gente que no tenía ningún problema para hacer este tipo de cosas, sin embargo, a mi me superaba y, además, tal y como he dicho no era capaz de encontrar información válida. Pero eso se acabo el otro día, justo durante las conferencias de la RootedCon (y no, ningún ponente habló sobre ello, simplemente encontré el tiempo y la motivación para volver a ponerme - gracias sobretodo a la charla de @nighterman).

En fin, no me enrollo más. Perdonadme por el idioma de los comentarios, creo importante que este código pueda servir al máximo número de personas posible.


Este es el código utilizado para hookear la llama al sistema write. Como veréis, la dirección de la tabla de llamadas al sistema está hardcoded, sin embargo, a continuación os pondré un código con el que la podréis extraer dinámicamente en vuestro sistema.

write_hook.c
Código: Seleccionar todo
#include <linux/version.h>   // Needed by all modules.
#include <linux/module.h>   // For the KERN_INFO.
#include <linux/unistd.h>   // Needed to know the syscall numbers (instead of hardcoding them).
#include <linux/highmem.h>   // Needed to change page permissions (because the sys_call_table is in a read-only page).
            // (It also includes linux/fs.h.

#define STDOUT_CTE    1

int make_rw(unsigned long address);
int make_ro(unsigned long address);

/***********************/
/*   START HOOK CODE   */
/***********************/

/*
 * This is the address for the syscall table. It can be obtained, depending on your kernel, by:
 * grep "sys_call_table" /boot/System.map-2.6.35-22-generic
 */
unsigned long *sys_call_table = (unsigned long*)0xC05D2180;


/*
 * You can get the syscall function declaration from:
 * /usr/src/linux-headers-(kernel_version)-generic/include/linux/syscalls.h
 * In order to know which parameters come from user space. (Macro __user)
 */

/* Pointer to the original sys_write */
asmlinkage long (*real_write)(unsigned int, const char __user *, size_t);

/* Our syscall replacement */
asmlinkage long custom_write(unsigned int fd, const char __user *buf, size_t count)
{
         if ( fd == STDOUT_CTE )
   {
      printk(KERN_EMERG "8==D--. \n");
   }
   return real_write(fd, buf, count);
}


/*
 * Flag used to know if the syscall table is patched.
 */
int is_set = 0;


/*
 * This function modifies the sys_write syscall pointer.
 */
static void modify_write_syscall(void)
{
   printk(KERN_INFO "[+] Modifying the system call table...\n");
        make_rw((unsigned long)sys_call_table);
        real_write = (void*)*(sys_call_table + __NR_write);
        *(sys_call_table + __NR_write) = (unsigned long)custom_write;
        make_ro((unsigned long)sys_call_table);
        is_set = 1;
   printk(KERN_INFO "[+] The system call table has been modified.\n");
}


/*
 * This function restores the sys_write syscall pointer.
 */
static void fix_write_syscall(void)
{
   printk(KERN_INFO "[+] Restoring the write syscall...\n");
        make_rw((unsigned long)sys_call_table);
        *(sys_call_table + __NR_write) = (unsigned long)real_write;
          make_ro((unsigned long)sys_call_table);
        is_set = 0;
   printk(KERN_INFO "[+] The write syscall has been fixed.\n");
}

/**********************/
/*   END HOOK CODE    */
/**********************/


/***********************/
/*   START PAGE CODE   */
/***********************/   

/* Make the page writable */
int make_rw(unsigned long address)
{
   unsigned int level;
   pte_t * pte = NULL;
   printk(KERN_INFO "[+] Setting r-w permission to syscall table page.\n");
         pte = lookup_address(address, &level);
         if(pte->pte &~ _PAGE_RW)
            pte->pte |= _PAGE_RW;
   return 0;
}

/* Make the page write protected */
int make_ro(unsigned long address)
{
       unsigned int level;
   pte_t * pte = NULL;
   printk(KERN_INFO "[+] Setting read only permission to syscall table page.\n");
         pte = lookup_address(address, &level);
        pte->pte = pte->pte &~ _PAGE_RW;
   return 0;
}

/***********************/
/*    END PAGE CODE    */
/***********************/



/*
 * This method initializes the kernel module.
 * It returns 0 if everything goes well, or a negative number if not.
 */
static int init_write_hook(void) // It could take any other name.
{
   printk(KERN_INFO "[+] Hooking write syscall...\n");
   modify_write_syscall();
   printk(KERN_INFO "[+] Write syscall hooked.\n");
   return 0;
}


/*
 * This method is called before the kernel module is unloaded.
 * It returns 0 if everything goes well, or a negative number if not.
 */
static void cleanup_write_hook(void)    // It could take any other name.
{
   printk(KERN_INFO "[+] Cleaning up syscall module...\n");
   if(is_set)
      {
      printk(KERN_INFO "[+] The write syscall is hooked. Retoring it.\n");
      fix_write_syscall();
      }

   printk(KERN_INFO "[+] See you, space cowboy.\n");
}



module_init(init_write_hook);
module_exit(cleanup_write_hook);

/*
 * Beware, that some of  kernel functions may not be available
 * to your code if you use license other then GPL.
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Newlog");
MODULE_VERSION("1.0");


Como habréis visto si habéis leido el código, he conseguido hacer el hook en un kernel 2.6.35 en una arquitectura de 32 bits. En vuestra mano queda probarlo en kernels más recientes (tendría que funcionar) y comentarme si también funciona.


Aquí tenéis el makefile necesario para compilar el módulo:
Makefile
Código: Seleccionar todo
#obj stands for object
#m stands for module/driver
#this is the list of modules that the kernel building system
#needs to build
   obj-m := write_hook.o
#Kernel building system (include files mostly)
#uname -r gives the version of the running kernel
   KDIR := /lib/modules/`uname -r`/build
#current working directory - where to store the output
   PWD := `pwd`
#default build rule
default:
   make -C $(KDIR) M=$(PWD) modules


Y por último, lo más interesante:
Código: Seleccionar todo
struct dt {
        u16 limit;
        u32 base;
} __attribute__((__packed__));

struct idt_entry {
        u16 offset_low;
        u16 selector;
        u8 zero;
        u8 attr;
        u16 offset_high;
} __attribute__((__packed__));

struct gdt_entry {
        u16 limit_low;
        u16 base_low; u8 base_mid; u8 access;
        u8 atrr;
        u8 base_high;
} __attribute__((__packed__));

/** Start of code for retrieve the syscall table address **/

static void ** get_syscall_table_addr(void)
{
   
   void ** sys_call_table;
   struct dt gdt;
   struct dt idt;
   __asm__("sgdt %0\n" : "=m"(gdt));
   __asm__("sidt %0\n" : "=m"(idt));

   struct idt_entry * idt_entry = (struct idt_entry *)(idt.base);

   idt_entry += 0x80; /* 0x80: linux syscall */
   u32 syscall_offset = (idt_entry ->offset_high << 16) | idt_entry->offset_low;

   struct gdt_entry *gdt_entry = (struct gdt_entry *)(gdt.base);
   gdt_entry += idt_entry->selector;
   u32 syscall_base =    (gdt_entry->base_high << 24) |
            (gdt_entry->base_mid << 16) |
            (gdt_entry->base_low);
   u8 * system_call = (u8 *)(syscall_base + syscall_offset);
   /* search call to sys_call_table */
   /* FF 14 85 off4: jmp off4(,%eax,4) */
   while ((*(u32 *)(system_call++) & 0xFFFFFF) != 0x8514FF);
   sys_call_table = * (void ***) (system_call + 2);   /** End of code for retrieve the syscall table address **/

   return sys_call_table;
}


Comentar que he visto por ahí otro código que en teoría también funcionaba y quizá, se podría decir que era más elegante, sin embargo, aun no lo he probado.

Ahora sólo falta dar el crédito a aquellos que me han abierto las puertas:
A Maurice Leclaire por su documento "Kernel Hacking" que no recuerdo de dónde lo bajé.
A Alexey Lyashko por su nuevo blog que es realmente bueno.

See you, space cowboys!
Imagen
fefafefa
 
Mensajes: 2
Registrado: Jue Mar 08, 2012 10:23 pm

Re: Syscall hooking en espacio de kernel

por fefafefa Jue Mar 08, 2012 10:30 pm

Tienes un mail.

"See you, space cowboys!"
See you, on the internet space.
http://www.youtube.com/watch?v=n6jCJZEFIto
fefafefa
 
Mensajes: 2
Registrado: Jue Mar 08, 2012 10:23 pm

Re: Syscall hooking en espacio de kernel

por fefafefa Vie Mar 09, 2012 4:33 pm

fefafefa escribió:Tienes un mail.


Hay que joderse...
<mail_address@domainYcom>: host XXXXXXYriseupYnet[XX.XX.XX.XX] said: 550 5.7.1
Virus detected: winnow.malware.11m.malware.700986.UNOFFICIAL
Mi TFM es un virus... :lol:

Te lo vuelvo a enviar.
Avatar de Usuario
newlog
El Eterno Alumno
 
Mensajes: 170
Registrado: Lun Jun 23, 2008 7:28 pm

Re: Syscall hooking en espacio de kernel

por newlog Vie Mar 09, 2012 8:12 pm

Buenas!

Ahora mismo no tengo un Open/LibreOffice con el que abrir tu documento! Pero nos haces un favor si posteas el modo limpio y simple de saber la dirección de la tabla de llamadas al sistema. Ya te digo, a parte de la posteada, sólo sé la del grep al System.map tal y como está en los comentarios del código.

Nos vemos!
Imagen
Avatar de Usuario
vlan7
 
Mensajes: 87
Registrado: Jue Jul 22, 2010 3:45 pm
Ubicación: Mas alla del EIP

Re: Syscall hooking en espacio de kernel

por vlan7 Lun Mar 19, 2012 1:52 pm

Hola,

Pero esto es viejo ya Newlog, como no preguntaste antes...

Aparte de mirar en System.map puedes buscar "sys_call_table" en /proc/kallsyms

Yo lo que hacia era un script en bash que me sacara ambos valores, los comparara por si habian sido manipulados y en caso de ser iguales los escribiera en el .C sustituyendo la palabra /unica) 0xesticbastantquebrat por la direccion de la SCT.

Mas cosas, al codigo que pones para encontrarla, para que funcione, hay que añadirle las definiciones de las estructuras tal y como aparecen en el texto Kernel Hacking que citas.

Y hay mas formas de obtenerla:

Defines un rango de memoria (si la syscalltable esta en el kernel y el kernel en x86 va de 0xc0000000 a 0xd0000000, ya os he chivado el rango valido donde buscar). y haces fuerza bruta ahi. Escaneas ese rango con un puntero con el que puedas hacerlo, como
unsigned long **sct
y cuando detectas que la direccion el valor del campo “__NR_close” de la direccion n del puntero sct es igual a la direccion de sys_close(), entonces con total seguridad el puntero sct esta apuntando a la syscall table.

Y mas cosas, en x64 aparte de la sys_call_table existe otro valor al hacer grep de sys_call_table al System.map. ¿Cual nos sirve? ¿Que hace el otro? Tarea para casa.

Por lo que veo quereis hacer rootkits eh bribones? vale, pues tendreis que esconderos. Un primer paso es esconderos del comando lsmod (es decir, no aparecer en /proc/modules). Para esto podemos aprovecharnos de una funcion llamada list_del_init()

Código: Seleccionar todo
(...)includes includes

static int init(void) {
    list_del_init(&__this_module.list);
    return 0;
}

static void exit(void) {
    return;
}
module_init(init);
module_exit(exit);


¿Aparece en lsmod? ¿Aparece en /proc/modules ? ¿Y asi?...

grep sys_write /proc/kallsyms
grep hook_idt /proc/kallsyms

A experimentar.

Tambien es posible hacer syscall hooking _sin_ modificar la syscall table... A buscar syscall table en VX Heavens, a ver que andan haciendo los autores de virus...

Tambien habia un documento mitico sobre esto de los folks del THC, pero como no lo hayan actualizado estara totalmente desactualizado. Creo que cubira kernels 2.4 como mucho, y creo que ni eso. Pero era el mejor documento que habia, y si eso lo actualizo el jefe van hauser, la bomba sera...

Y esto es todo por ahora...

Be safe,
int *p = new int[7];
p = p + 7;
*p = 42;

int a[7];
a[7] = 42; /* ESC[2;9y */
Avatar de Usuario
newlog
El Eterno Alumno
 
Mensajes: 170
Registrado: Lun Jun 23, 2008 7:28 pm

Re: Syscall hooking en espacio de kernel

por newlog Lun Mar 19, 2012 4:08 pm

Maldito vlan!!

Me acuerdo que pregunté sobre este tema en wadalbertia hace un porrón de tiempo, y no encontré la respuesta, y eso que TuXeD también estaba por el tema! Ahora he buscado el tema y no lo he encontrado.

A ver, la idea era hacerlo leyendo de memoria, ya que tengo entendido que si se lee del sistema (system.map) no siempre es fiable. No se si esto aplica a lo que comentas de /proc/kallsyms.

Sobre:

y cuando detectas que la direccion el valor del campo “__NR_close” de la direccion n del puntero sct es igual a la direccion de sys_close(), entonces con total seguridad el puntero sct esta apuntando a la syscall table.


No lo he acabado de entender. Te refieres a exportar la dirección de sys_close() y restarle el valor de "_NR_close" para obtener la dirección de la sct? Si es eso, cómo sabes la dirección de sys_close()? Imagino que no te refieres a eso, porque como has visto no te he comentado lo del rango de direcciones. Así que veo que no he entendido algo. Tu dirás ;)


Sobre los rootkits... Yo sólo he intentado explicar la base, para todo lo demás, se requiere un poco más de lectura, pero al fin y al cabo, es lo que se trata en la mayoría de los sitios. Y sino, esperar a que Nighterman (rootedcon) libere el código de lsnake (que es un rootkit en userland jodidamente bueno).


Saludos!

P.D.: Sí, me tendría que poner con x64, no? Voy con un poco de retraso :P
P.D2.: Acabo de corregir el código y añadirle la definición de las estructuras. Se me pasó, gracias.
Imagen
Avatar de Usuario
vlan7
 
Mensajes: 87
Registrado: Jue Jul 22, 2010 3:45 pm
Ubicación: Mas alla del EIP

Re: Syscall hooking en espacio de kernel

por vlan7 Lun Mar 19, 2012 10:27 pm

Newlog escribió:Te refieres a exportar la dirección de sys_close() y restarle el valor de "_NR_close" para obtener la dirección de la sct? Si es eso, cómo sabes la dirección de sys_close()?


La sabes porque esta exportada.

Aunque dejaran de exportar la SCT en si a partir de 2.6, los LKMs tienen acceso a la memoria en kernel-land, por lo tanto es posible saber la direccion de la sys_call_table comparando algunos campos clave de syscalls que siguen exportadas, por exjemplo sys_close(). Pero tambien sys_read() o sys_write(). Mira (aviso, es lento):

Código: Seleccionar todo
#define START_MEM   0xc0000000
#define END_MEM   0xd0000000

unsigned long **find() {

   unsigned long **sctable;
   unsigned long int i = START_MEM;

   while ( i < END_MEM) {
      sctable = (unsigned long **)i;
      if ( sctable[__NR_close] == (unsigned long *) sys_close) {
         return &sctable[0];
      }
      i += sizeof(void *);
   }
   return NULL;
}


Y lo cierto es qye hay bastante info al respecto, una pagina donde comentan algo similar es esta, donde dicen cosas como are still exported and available to LKMs. To demonstrate how to get access to sys_call_table in the 2.6 kernels, we will write a simple LKM that intercepts sys_open( ) and prevents anyone from opening the /tmp/test file..

Y hay codigo similar al que puse pero con otras syscalls y en un bucle infinito (vamos, que no acota entre START_MEM y END_MEM).

Newlog escribió:P.D.: Sí, me tendría que poner con x64, no? Voy con un poco de retraso :P

Yo estuve dandole hoy, y apenas hay info y cambia mas de lo que pensaba. En lo mas basico tienes que andar con PLTs e historias. Yo estuve siguiendo un texto de enye security, busca por "enye x64 exploit" o similar, esta en castellano.

Pero no lograba gran cosa en mi Debian Squeeze x64, asi que me puse con la tercera entrega de las ASM/Shellcoding Series. Asi que...

fefafefa escribió:Tienes un mail.
jajaja

Y todo esto... joder, todo esto me recuerda a cuando capturabamos INTs en MSDOS. Esto y las ISR del DOS son casi lo mismo. De hecho en DOS tenias una tabla con punteros tambien, y tenias que tocarla si querias capturar una interrupcion y meter la tuya... El concepto en si es exacto.

Los virus, los virus hacian eso para sus fines. Los juegos capturaban la INT de teclado para poder pulsar simultaneamente cursor arriba+cursor izquierda, algunas protecciones engañaban al DOS haciendole ver Bad-clusters (pobre DOS engañado jaja) en los diskettes, y el diskette original cargaba el juego porque incluia su propia rutina de carga, que era eso, capturar la INT de Disco, cambiar punteros en la tabla de interrupciones y apuntar a tu ISR... etc etc etc
int *p = new int[7];
p = p + 7;
*p = 42;

int a[7];
a[7] = 42; /* ESC[2;9y */
Avatar de Usuario
newlog
El Eterno Alumno
 
Mensajes: 170
Registrado: Lun Jun 23, 2008 7:28 pm

Re: Syscall hooking en espacio de kernel

por newlog Lun Mar 19, 2012 11:18 pm

Buenas de nuevo!

Voy a revisar ese código, porque imagino que faltan las librerías y tal. Cuando lo pruebe digo algo.

Tienes un mail.


Joder, ojalá tu intranquilidad mental siempre desemboque en geniales papers :roll:

Qué pasa si te digo que también nos tendríamos que mirar la versión del kernel 3.0+? Jajajaja, a ver si tengo algo de tiempo para ver si algo cambia con las llamadas al sistema. Así ya habremos llegado al nivel del estado del arte.

Imagino que en cuanto a lo de x64 exploiting te refieres a esto, no? Tiene mérito escribir sobre este tema en 2007. Durante un tiempo seguí a enye-sec, hasta creo que tengo el código de su rootkit, pero creo recordar que en el momento en el que intenté hookear las syscalls, no me funcionó. En fin...

Sobre los hooks en DOS... Disfruté leyendo tus anécdotas a Keyser, pero por desgracia yo no pude disfrutar con eso!

Saludos!
Imagen
Avatar de Usuario
vlan7
 
Mensajes: 87
Registrado: Jue Jul 22, 2010 3:45 pm
Ubicación: Mas alla del EIP

Re: Syscall hooking en espacio de kernel

por vlan7 Mar Mar 27, 2012 10:41 pm

Ey Newlog, que se me fue la olla, creia que habia respondido a este hilo pero veo que no.

Si que segui ese texto de enye-sec, parece bastante decente pero yo no logre nada mas que ver desensamblados similares por lo que algo haria mal seguramente asi que ya lo retomare que mi experiencia en x64 empezo esa tarde.

Sobre el codigo que puse, puedes guiarte del blog de donde lo saque que sal en google al buscar alguna linea de codigo clave como por ejemplo esta:

if ( sctable[__NR_close] == (unsigned long *) sys_close) {

Y sobre lsnake lei en la lista de correo de la rooted que unhide no lo detecta. Tiene buena pinta, a ver si se libera algo.

Un saludo,
int *p = new int[7];
p = p + 7;
*p = 42;

int a[7];
a[7] = 42; /* ESC[2;9y */
blackngel
 
Mensajes: 1
Registrado: Lun Sep 02, 2013 9:06 pm

Re: Syscall hooking en espacio de kernel

por blackngel Lun Sep 02, 2013 9:33 pm

Hace unos días estaba realizando unas pruebas sobre derreferencia de punteros nulos en espacio de kernel,
y en vez de crear un nuevo dispositivo en /dev para ofrecer comunicación con los procesos de usuario, decidí
hookear una entrada en la sys_call_table (__NR_MKDIR por ejemplo) y comprobar por el UID del usuario que
invocaba la llamada en cuestión. (lo que hacía dentro la llamada interceptada ya son cosas más sucias :P).
La escasa portabilidad de los métodos de búsqueda de la sys_call_table es conocida, las direcciones de referencia
siempre varían entre distintas versiones del kernel. Después de realizar algunas pruebas conseguí realizar
una ligera variación del método común que he comprobado funciona en Ubuntu 9.04 (kernel 2.6.28) y en Ubuntu
12.04 (3.5.0-39). Lo que abarca un buén rango de versiones y parece bastante estable. Aquí el código:

Código: Seleccionar todo
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");

void **sc_table;

void get_syscall_table(void)
{
    int i = 0;
    unsigned long *ptr;
    unsigned long dir = (unsigned long)&loops_per_jiffy;

    printk(KERN_ALERT "[+] Searching from address 0x%lX...\n", dir);

    while ( 1 ) {
        ptr = (unsigned long *)dir;
        if ( ptr[6] == (unsigned long) sys_close ) {
            sc_table = (void **)ptr;
            i++;
            if ( i == 3 )
                break;
        }
        dir -= sizeof(unsigned long);
    }
}

static int __init inicio(void)
{
    printk(KERN_ALERT "[+] Hack Mod Started.\n");

    get_syscall_table();

    printk(KERN_ALERT "[+] Syscall Table at 0x%lX\n", (unsigned long)&sc_table[0]);

    return 0;
}

static void __exit fin(void)
{
    printk(KERN_ALERT "[+] Exiting...\n");
}

module_init(inicio);
module_exit(fin);


El algoritmo es simple, desde la dirección del símbolo loops_per_jiffy accesible desde el kernel se recorre
la memoria hacia atrás en múltiplos de un unsigned long, luego, como de costumbre, se compara si la entrada
a sys_close se encuentra donde debe. Lo curioso es que "de ser el caso", todavía no habremos encontrado
la sys_call_table, el proceso se repite por lo tanto hasta la tercera ocurrencia, momento en el que dispondremos
de la dirección real que buscábamos.

blackngel@bbc:~$ sudo insmod hackmod.ko
[sudo] password for blackngel:
blackngel@bbc:~$ dmesg | tail -n 3
[ 1208.117574] [+] Hack Mod Started.
[ 1208.117577] [+] Searching from address 0xC1862EE0...
[ 1208.118503] [+] Syscall Table at 0xC15ED0A0
blackngel@bbc:~$ sudo cat /proc/kallsyms | grep sys_call_table
c15ed0a0 R sys_call_table
blackngel@bbc:~$

El espacio de memoria recorrido es de unos 2,5mb / sizeof(unsigned long) = 0,625mb (32 bits).
Solo una aproximación, la obtención de la sys_call_table es inmediata. Luego ya se puede jugar
a ser dios con el sistema... ;P

Saludos!
Volver a C/C++

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado