Seções iMasters
Desenvolvimento + Linux

Sequestro de chamadas de sistema Linux – Parte 01

Alguma vez você já tentou pesquisar no Google por “fragmentando tabela da chamada de sistema Linux”? Existem centenas, senão milhares, de mensagens em relação a esse problema. A maioria delas está desatualizada, pois se refere a kernels mais antigos (aqueles que ainda exportam sys_call_table), outras são sobre a adição de chamada de sistema personalizado e recompilar o kernel. Há algumas que abrangem kernels modernos, mas são sucintas e, principalmente, te dão apenas uma ideia geral de como ele funciona. Decidi fazer uma descrição profunda do processo e fornecer um exemplo que funciona.

Essa descrição consiste em três partes: Módulos, Miscellaneous Character Drivers e System Call Table. Apesar de a segunda parte ser opcional, ela tornaria o seu espaço de código do kernel mais flexível e utilizável a partir da perspectiva do espaço do usuário.

  • Módulos – essa parte segue imediatamente este preâmbulo e aborda os conceitos básicos dos módulos do kernel carregáveis.
  • Miscellaneous Character Drivers – aqui procuro fornecer explicação aprofundada sobre o que é, como funciona e, o mais importante, como ele pode ser usado.
  • System Call Table – essa vai ser a parte mais curta, uma vez que só inclui a estrutura da tabela de chamadas do sistema.

Então, vamos mergulhar no negócio.

Módulos do kernel carregáveis

Módulos de kernel carregáveis (LKMs, abreviação do inglês) são simplesmente extensões para o kernel básico em seu sistema operacional que pode ser carregado/descarregado em tempo real, sem a necessidade de recompilar o kernel ou de reiniciar o sistema. Esse recurso existe em todos os principais sistemas operacionais (Windows, Linux, MacOS), mas vamos nos concentrar apenas no Linux, de preferência com o kernel 2.6.38 (já que este é o único que eu testei em exemplos) e superior.

Vá em frente, abra seu editor de código preferido e comece adicionando os arquivos de include necessários:

#include <linux/version.h>
#include <linux/module.h>

Basicamente, isso é tudo que você precisa para construir um módulo simples do kernel que pode ser carregado e descarregado. Iremos, no entanto, adicionar alguns inclusões de módulos mais tarde.

Há mais duas coisas que nós, incondicionalmente, precisamos implementar – Inicialização e rotinas de limpeza. Aqui vamos nós:

static int __init init_module(void) /* You may use any name other than
init_module */
{
/* your initialization code goes here */
/* Once you are done, return 0 to tell the OS that your module
has been loaded successfully or return relevant error code
(which must be a negative integer) */
printk(KERN_INFO "We are in kernel space\n");
return 0;
}

Essa é a nossa rotina de inicialização. Se você precisa configurar quaisquer variáveis ou fazer outros arranjos que são cruciais para o módulo, você deve fazer isso aqui.

static void __exit cleanup_module(void) /* Same here - you may use
any name instead of
cleanup_module */
{
printk(KERN_INFO "Elvis has left the building\n");
return;
}

Por outro lado, a rotina acima é usada para limpar a bagunça que nós produzimos com o nosso módulo. Ela é chamada antes de o módulo ser descarregado.

Como você observou, usamos a função printk aqui – uma das funções mais robustas exportadas pelo kernel do Linux, geralmente usada para mensagens de saída de log/diagnóstico. Você pode obter a sua saída com o comando dmesg.

Use macros module_init e module_exit para traçar essas rotinas:

module_init(init_module);
module_exit(cleanup_module);

E a última coisa – adicionar mais algumas informações ao módulo usando os seguintes macros:

   /* Beware, that some of  kernel functions may not be available 
to your code if you use license other then GPL */
MODULE_LICENSE("GPL");
/* Your name and email goes here */
MODULE_AUTHOR("your name goes here");
/* Version of your module */
MODULE_VERSION("this string is up to you");
/* Write a line about what this module is */
MODULE_DESCRIPTION("describe this module here");

É isso aí. Acabamos de construir um skeleton kernel module. Agora temos que compilá-lo. Mas o problema é que você não pode compilar os módulos do kernel da forma que faria com seus aplicativos. Simplesmente porque eles não são aplicativos simples. Você tem que criar um arquivo makefile especial, destacando que é um makefile para um módulo do kernel:

#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 := name_of_the_module.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

Execute o comando make e você terá o módulo compilado pronto para ser conectado. Tudo que você precisa fazer agora é tentar carregar o módulo no kernel, e nós temos o comando insmod para esse fim. Use um dos seguintes, dependendo se sua distro é baseada em debian ou em Red Hat, respectivamente:

sudo insmod ./your_module_name.ko

or

su -c "insmod ./your_module_name.ko"

Será solicitada a sua senha ou a senha do root (depende do comando emitido); depois disso, você deve obter a janela de comandos. Se você não receber nenhuma notificação de erro, significa que o módulo foi carregado com êxito, caso contrário, como de costume, verifique o seu código e verifique-o contra suas fontes de kernel. Use o seguinte código para descarregar o módulo:

sudo rmmod your_module_name

or

su -c "rmmod your_module_name"

Você não precisa da extensão “. ko” aqui. Se não receber qualquer notificação de erro, está tudo bem. Mas se receber, isso significaria que, por algumas razões, o sistema é incapaz de descarregar o módulo. A mensagem mais frequente que eu costumava obter era sobre o meu módulo estando ocupado. Às vezes, mesmo rmmod -f não pode resolver esse problema, e você tem que reiniciar a máquina a fim de tirar o seu módulo das mãos de kernel. Claro, você tem que verificar o seu código depois disso, por razões possíveis.

Agora digite “dmesg | tail” para obter o final do log do kernel. As últimas duas linhas devem conter as strings que passamos para função printk ou a razão de não ser capaz de descarregar o módulo. É importante executar esse comando antes de reiniciar em caso de erro a fim de ver a razão, caso contrário, você não encontrará essa entrada.

Conclusão

Então acabamos de construir nosso módulo mais simples. Na prática, isso não é verdade, já que o módulo mais simples não deve conter printk :). Somos (espero) capazes de carregar e descarregá-lo.

No próximo artigo, vamos adicionar um dispositivo virtual, que seria responsável pela interação com um processo de usuário e executaria todas as operações de aplicação de patches/correção.

Espero que este artigo tenha sido útil. Vejo vocês na próxima!

?

Texto original disponível em http://syprog.blogspot.com.br/2011/10/hijack-linux-system-calls-part-i.html

Mensagem do anunciante:

Torne-se um Parceiro de Software Intel®. Filie-se ao Intel® Developer Zone. Intel®Developer Zone

Qual a sua opinião?