/* * heartbeatusb.c -- flash a Lock LED (keyboard) in an hearthbeat fashion and monitor * the existence of the keyboard at Bus, Device number and ID (Keylogger detection). * The freqenzcy increases with the load, to display the load and if the computer works * even without login. * This is the USB version of heartbeat++.c, which uses the PS/2 controller and the * PS/2 timeout flag for PS/2 keyboards. * * The blinking frequency is: if (load < 1) than f = 1+ load/2 else f = 1/2 +sqrt(load), * e. g. 1 Hz at zero load, 1.25 Hz at load 0.5, 1.5 Hz at load 1, 16.5 Hz at load 256. * This function is continuously differentiable. * On multi-processor system, the load is relative to the number of processor cores available, * so here is no scaling with the number of cores. * Compile, e. g. via gcc -Wall -O3 -Wno-unused-result -o heartbeatusb heartbeatusb.c -lm The program should be executed by root or via upstart and should be copied to /sbin. The usual usage is via updstart, with the file /etc/init/heartbeatusb.conf and this content, without leading whitespaces: # heartbeatusb, usb keyboard monitoring description "simple keyboard monitoring" start on stopped rc RUNLEVEL=[123456] # stop on runlevel [!123456] #task # monitor ID 05af:0802 at Bus 3 exec /sbin/heartbeatusb 05af:0802 3 After that you can start manually via "start heartbeatusb", stop with stop instead of start etc. and e. g. via webmin you can configure to start or not start the service at booting (at System - Bootup and Shutdown). On older systems you have to use inittab, a line in the /etc/inittab like (without leading whitespaces): 13:S123456:respawn:/sbin/heartbeatusb 05af:0802 After that you can start manually via "telinit q". Copyright Dr. Rolf Freitag (rolf dot freitag at email dot de), 2014 - ... * ---------------------------------------------------------------------------- * "THE BEERWARE LICENSE" (Revision 44): * Dr. Rolf Freitag (rolf dot freitag at email dot de) wrote this file. * As long as you retain this notice you can do whatever * the GPL (GNU Public License version 3) allows with this stuff. * If you think this stuff is worth it, you can send me money via * paypal, and get a contribution receipt if you wish, or if we met some day * you can buy me a beer in return. * ---------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include /* or */ #include #include #include #include // simple version (date) #define SOFTWARE_VERSION "2014-06-08" // debugging (comment if not needed) //#define DEBUG #define MAX_LINE_LENGTH 1234 #define DEFAULT_DELAY_us 100000 // 1/10 s = 100 ms default delay // Initialize an array, struct or union variable via memset with value uc. #define mc_INIT_AR(varptr, uc) memset((void *)(varptr), (unsigned char)(uc), sizeof((varptr))) // For registering the signal handler void sig_handler (int sig). This macro saves 32 bytes ;-) #define mc_register_sig_handler \ {\ int i;\ for(i=0; i<=UCHAR_MAX; i++)\ signal (i, sig_handler);\ } void sig_handler (const int sig) // signal handler { if ((SIGINT == sig) or (SIGILL == sig) or (SIGKILL == sig) or (SIGSEGV == sig) or (SIGTERM == sig)) { (void) printf ("\a\a\a\a\a\a\a Signal %d, program exiting... \r\n\a\a\a", sig); exit (0); } return; } int main (int argc, char **argv) { FILE *fp = NULL; unsigned char uc_led = 0; unsigned char uc_chosenled = 4; // default LED: Caps (4), alternatives: Scroll (1), Num (2) or combinations e. g. 3 = 1 (Scroll) + 2 (Num) unsigned char a_heartbp[10] = { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; // heartbeat pattern, blinking pattern for a loop (of 2 s at load 0), // default pattern: 1110000000, because for systems with only one LED, there should be a difference between the pattern and the inverted pattern. unsigned int ui_delay = DEFAULT_DELAY_us; // time step for blinking/one heartbeat part int i_load = 0, i_detected = 1, i_ret = 0; char *prgname = argv[0]; FILE *f = NULL; int i_old_status = 1; // Keyboard status at the last round, default 1 for found uint64_t u64_lc = 0; // loop counter uint64_t u64_dc = 0; // disconnect counter long double ld1 = 0.0, ld3 = 0.0; long int li_bus = 0, li_devnum = 0; char *ca_id = NULL; char ac_line[MAX_LINE_LENGTH + 1] = // command line { '\0' }; // first: Check the number of parameters (command line options) if ((argc > 4) or (argc < 2)) { (void) fprintf (stderr, "%s version %s\n", argv[0], SOFTWARE_VERSION); (void) fprintf (stderr, " Usage: %s \n", argv[0]); (void) fprintf (stderr, " Example: %s 05af:0802 3\n", argv[0]); (void) fprintf (stderr, " You can find the Parameters via \n lsusb\n or\n lsusb -v > lsusb.txt\n"); (void) fprintf (stderr, " and searching for the device with your favorite text editor.\n"); (void) fprintf (stderr, " If you want only the heartbeat, monitor an alway present device like an\n"); (void) fprintf (stderr, " internal root hub.\n"); (void) fprintf (stderr, " Only monitoring the ID is generally not a good idea because an attcker can\n"); (void) fprintf (stderr, " connect a second device with the same ID and the program checks only if at\n"); (void) fprintf (stderr, " least one specified device exists.\n"); (void) fprintf (stderr, " Specifying the device number is generally not a good idea because after\n"); (void) fprintf (stderr, " disconnect and reconnect the number is increased\n"); (void) fprintf (stderr, " Got wrong number of options (%d instead of 1 ... 3), exiting.\n", argc - 1); exit (-1); } ca_id = argv[1]; if (argc > 2) // at least two arguments (parameters) { li_bus = strtol (argv[2], (char **) NULL, 0); if (argc > 3) { li_devnum = strtol (argv[3], (char **) NULL, 0); } } mc_register_sig_handler; // register signal handler before critical sections (void) nice (19); // be nice // main loop, "endless" for (u64_lc = 0;; u64_lc++) { int consolefd = open ("/dev/tty0", O_RDONLY); // interface to the keyboard int i = 0; // first part: Get the load average ////////////////////////////////////////////////////////////////////////////// f = fopen ("/proc/loadavg", "r"); // load average if (f) { (void) fscanf (f, "%d.%d", &i_load, &i); (void) fclose (f); } else { i_load = 0; i = 0; } // scale i_load by 100 to get the value in % and add the two parts of the load i_load = i_load * 100 + i; #ifdef DEBUG (void) printf ("argc: %d, i_load [%%]: %d, bus: %ld, devnum: %ld, ID: %s\n", argc, i_load, li_bus, li_devnum, ca_id); #endif // delay calculation in double, without percentage ld1 = ((long double) i_load) * 0.01; // Frequency: 1 Hz + (load < 1) ? 1 + load/2 : 0.5 + sqrt(load), so the delay for one (of 10) steps is 100ms/(1+load/2) or 100 ms/(0.5+sqrt(load)) ld3 = ((long double) DEFAULT_DELAY_us) / ( (ld1 < 1.0) ? (1.0 +ld1/2) : (0.5 +sqrtl(ld1)) ); ui_delay = (unsigned int) (ld3 + 0.5); // rounding to the next integer #ifdef DEBUG (void) printf ("waiting intervall %d microseconds (%d default), ld1: %Lf, ld3: %Lf\n", ui_delay, DEFAULT_DELAY_us, ld1, ld3); #endif // second part: Heartbeat loop, with the heartbeat pattern stored in a_heartbp ////////////////////////////////////////////////////////////////////// for (i = 0; i < sizeof (a_heartbp) / sizeof (a_heartbp[0]); i++) { // heartbeat output via the LEDs: Read the pattern (uc_led), modify it (set bit mask, set bits) and set the new pattern if (u64_dc) // maybe compromised system: Alternate blinking pattern (inverted heartbeat pattern and all LEDs) { if (ioctl (consolefd, KDGETLED, &uc_led) || ioctl (consolefd, KDSETLED, (uc_led & 0xf8) | (a_heartbp[i] ? 0 : 7))) // blink with Scroll (1) + Num (2) + Caps(4) = 7 { (void) fprintf (stderr, "%s: ioctl(): %s\n", prgname, strerror (errno)); exit (-2); } } else { if (ioctl (consolefd, KDGETLED, &uc_led) || ioctl (consolefd, KDSETLED, (uc_led & ~uc_chosenled) | (a_heartbp[i] ? uc_chosenled : 0))) // blink with the chosen LED (uc_chos { (void) fprintf (stderr, "%s: ioctl(): %s\n", prgname, strerror (errno)); exit (-1); } } #ifdef DEBUG (void) printf ("led: %i, a_heartbp[i]: %i, (uc_led & ~uc_chosenled) | (uc_chosenled * a_heartbp[i]): %i, (uc_led & uc_chosenled) | (a_heartbp[i] ? 2 : 4): %i\n", (int) uc_led, (int) a_heartbp[i], (int) ((uc_led & ~uc_chosenled) | (uc_chosenled * a_heartbp[i])), (int) ((uc_led & uc_chosenled) | (a_heartbp[i] ? 2 : 4))); #endif usleep (ui_delay); // wait till the delay ends } // for(i // third part: The usb device detection ////////////////////////////////////////////////////////////////////////////// mc_INIT_AR (ac_line, 0); // set every Byte to 0x00 // lsusb command line with 1, 2 or 3 arguments switch (argc) { case 4: (void) snprintf (ac_line, MAX_LINE_LENGTH, "lsusb -s %ld:%ld -d %s", li_bus, li_devnum, ca_id); break; case 3: (void) snprintf (ac_line, MAX_LINE_LENGTH, "lsusb -s %ld: -d %s", li_bus, ca_id); break; default: case 2: (void) snprintf (ac_line, MAX_LINE_LENGTH, "lsusb -d %s", ca_id); break; } fp = popen (ac_line, "r"); if (NULL == fp) { (void) fprintf (stderr, "Could not open Pipe, ignoring. Command string: %s\n", ac_line); //err_sys(" popen error "); //return (-1); } else { while (fgets (ac_line, sizeof (ac_line) - 1, fp)) // read the output { // nothing to do here now ... } i_ret = pclose (fp); } // The return value of the child process is in the top 16 bits; only in the first 8. i_detected = (i_ret >> 8) ? 0 : 1; // 0 for not present and not ok, 1 for present and ok #ifdef DEBUG printf ("i_detected = %d\n", i_detected); #endif if (0 == i_detected) // not detected { // usually \a does not work; it's only a one-line try (void) printf ("\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"); // beep via the speaker (install beep first) //(void) system (" beep - e / dev / input / by - path / platform - pcspkr - event - spkr - l 432 "); (void) system ("beep -l 50"); // beep via the (first) soundcard (void) system ("dd if=/dev/urandom of=/dev/dsp bs=1 count=321"); } else // detected { // ok, nothing to do } if (i_detected != i_old_status) // status change { if (i_detected) // keyboard has gone online { mc_INIT_AR (ac_line, 0); // set every Byte to 0x00 (void) snprintf (ac_line, MAX_LINE_LENGTH, "date --rfc-3339=ns | mail -s \" The Keyboard (USB %s) removal has been ended!\" root@localhost", ca_id); (void) system (ac_line); (void) system ("echo -n \"+\" >> /root/heartbeatusb.log; date --rfc-3339=ns >> /root/heartbeatusb.log"); (void) printf ("Keyboard status change: The (USB) Keyboard removal has been ended! loop counter %llu\n", (long long unsigned int) u64_lc); } else // keyboard disconnected { u64_dc++; // increase disconnect counter mc_INIT_AR (ac_line, 0); // set every Byte to 0x00 (void) snprintf (ac_line, MAX_LINE_LENGTH, "date --rfc-3339=ns | mail -s \" The Keyboard (USB %s) has been removed!\" root@localhost", ca_id); (void) system (ac_line); (void) system ("echo -n \"-\" >> /root/heartbeatusb.log; date --rfc-3339=ns >> /root/heartbeatusb.log"); (void) printf ("Keyboard status change: The Keyboard (USB %s) has been removed! loop counter %llu\n", ca_id, (long long unsigned int) u64_lc); // usually \a does not work; it's only a one-line try (void) printf ("\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"); // beep via the speaker (install beep first) (void) system ("beep -e /dev/input/by-path/platform-pcspkr-event-spkr -l 100"); // beep via the (first) soundcard (void) system ("dd if=/dev/urandom of=/dev/dsp bs=1 count=4321"); } i_old_status = i_detected; // store the status of this round } i = close (consolefd); } // main loop, endless exit (0); // never happen } // main