1 /* Copyright (C) 2009 Trend Micro Inc.
4 * This program is a free software; you can redistribute it
5 * and/or modify it under the terms of the GNU General Public
6 * License (version 2) as published by the FSF - Free Software
11 #include "rootcheck.h"
14 static int read_sys_file(const char *file_name, int do_read);
15 static int read_sys_dir(const char *dir_name, int do_read);
17 /* Global variables */
18 static int _sys_errors;
19 static int _sys_total;
26 static int read_sys_file(const char *file_name, int do_read)
33 /* Check for NTFS ADS on Windows */
34 os_check_ads(file_name);
36 if (lstat(file_name, &statbuf) < 0) {
38 char op_msg[OS_SIZE_1024 + 1];
39 snprintf(op_msg, OS_SIZE_1024, "Anomaly detected in file '%s'. "
40 "Hidden from stats, but showing up on readdir. "
41 "Possible kernel level rootkit.",
43 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
49 /* If directory, read the directory */
50 else if (S_ISDIR(statbuf.st_mode)) {
51 /* Make Darwin happy. For some reason,
52 * when I read /dev/fd, it goes forever on
53 * /dev/fd5, /dev/fd6, etc.. weird
55 if (strstr(file_name, "/dev/fd") != NULL) {
59 /* Ignore the /proc directory (it has size 0) */
60 if (statbuf.st_size == 0) {
64 return (read_sys_dir(file_name, do_read));
67 /* Check if the size from stats is the same as when we read the file */
68 if (S_ISREG(statbuf.st_mode) && do_read) {
69 char buf[OS_SIZE_1024];
74 fd = open(file_name, O_RDONLY, 0);
76 /* It may not necessarily open */
78 while ((nr = read(fd, buf, sizeof(buf))) > 0) {
83 if (strcmp(file_name, "/dev/bus/usb/.usbfs/devices") == 0) {
84 /* Ignore .usbfs/devices */
85 } else if (total != statbuf.st_size) {
88 if ((lstat(file_name, &statbuf2) == 0) &&
89 (total != statbuf2.st_size) &&
90 (statbuf.st_size == statbuf2.st_size)) {
91 char op_msg[OS_SIZE_1024 + 1];
92 snprintf(op_msg, OS_SIZE_1024, "Anomaly detected in file "
93 "'%s'. File size doesn't match what we found. "
94 "Possible kernel level rootkit.",
96 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
103 /* If has OTHER write and exec permission, alert */
105 if ((statbuf.st_mode & S_IWOTH) == S_IWOTH && S_ISREG(statbuf.st_mode)) {
106 if ((statbuf.st_mode & S_IXUSR) == S_IXUSR) {
108 fprintf(_wx, "%s\n", file_name);
114 fprintf(_ww, "%s\n", file_name);
118 if (statbuf.st_uid == 0) {
119 char op_msg[OS_SIZE_1024 + 1];
121 snprintf(op_msg, OS_SIZE_1024, "File '%s' is owned by root "
122 "and has write permissions to anyone.", file_name);
124 snprintf(op_msg, OS_SIZE_1024, "File '%s' is: \n"
125 " - owned by root,\n"
126 " - has write permissions to anyone.",
129 notify_rk(ALERT_SYSTEM_CRIT, op_msg);
133 } else if ((statbuf.st_mode & S_ISUID) == S_ISUID) {
135 fprintf(_suid, "%s\n", file_name);
142 static int read_sys_dir(const char *dir_name, int do_read)
145 unsigned int entry_count = 0;
148 struct dirent *entry;
154 const char *(dirs_to_doread[]) = { "/bin", "/sbin", "/usr/bin",
155 "/usr/sbin", "/dev", "/etc",
160 if ((dir_name == NULL) || (strlen(dir_name) > PATH_MAX)) {
161 merror("%s: Invalid directory given.", ARGV0);
165 /* Ignore user-supplied list */
166 if (rootcheck.ignore) {
167 while (rootcheck.ignore[i]) {
168 if (strcmp(dir_name, rootcheck.ignore[i]) == 0) {
176 /* Should we check for NFS? */
177 if(rootcheck.skip_nfs)
179 is_nfs = IsNFS(dir_name);
182 // Error will be -1, and 1 means skipped
187 /* Getting the number of nodes. The total number on opendir
190 if(lstat(dir_name, &statbuf) < 0)
195 /* Current device id */
196 if (did != statbuf.st_dev) {
200 did = statbuf.st_dev;
203 if (!S_ISDIR(statbuf.st_mode)) {
208 /* Check if the do_read is valid for this directory */
209 while (dirs_to_doread[i]) {
210 if (strcmp(dir_name, dirs_to_doread[i]) == 0) {
220 /* Open the directory */
221 dp = opendir(dir_name);
223 if ((strcmp(dir_name, "") == 0) &&
224 (dp = opendir("/"))) {
231 /* Read every entry in the directory */
232 while ((entry = readdir(dp)) != NULL) {
233 char f_name[PATH_MAX + 2];
234 struct stat statbuf_local;
236 /* Ignore . and .. */
237 if ((strcmp(entry->d_name, ".") == 0) ||
238 (strcmp(entry->d_name, "..") == 0)) {
243 /* Create new file + path string */
244 if (strcmp(dir_name, "/") == 0) {
245 snprintf(f_name, PATH_MAX + 1, "/%s", entry->d_name);
247 snprintf(f_name, PATH_MAX + 1, "%s/%s", dir_name, entry->d_name);
250 /* Check if file is a directory */
251 if (lstat(f_name, &statbuf_local) == 0) {
252 /* On all the systems except Darwin, the
253 * link count is only increased on directories
256 if (S_ISDIR(statbuf_local.st_mode))
258 if (S_ISDIR(statbuf_local.st_mode) ||
259 S_ISREG(statbuf_local.st_mode)
260 /* No S_ISLNK on Windows */
262 || S_ISLNK(statbuf_local.st_mode)
271 /* Check every file against the rootkit database */
272 for (i = 0; i <= rk_sys_count; i++) {
273 if (!rk_sys_file[i]) {
277 if (strcmp(rk_sys_file[i], entry->d_name) == 0) {
278 char op_msg[OS_SIZE_1024 + 1];
281 snprintf(op_msg, OS_SIZE_1024, "Rootkit '%s' detected "
282 "by the presence of file '%s/%s'.",
283 rk_sys_name[i], dir_name, rk_sys_file[i]);
285 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
289 /* Ignore the /proc and /sys filesystems */
290 if ((strcmp(f_name, "/proc") == 0) || (strcmp(f_name, "/sys") == 0)) {
294 read_sys_file(f_name, do_read);
297 /* skip further test because the FS cant deliver the stats (btrfs link count always is 1) */
298 skip_fs = skipFS(dir_name);
301 // Error will be -1, and 1 means skipped
306 /* Entry count for directory different than the actual
307 * link count from stats
309 if ((entry_count != (unsigned) statbuf.st_nlink) &&
310 ((did_changed == 0) || ((entry_count + 1) != (unsigned) statbuf.st_nlink))) {
312 struct stat statbuf2;
313 char op_msg[OS_SIZE_1024 + 1];
315 if ((lstat(dir_name, &statbuf2) == 0) &&
316 (statbuf2.st_nlink != entry_count)) {
317 snprintf(op_msg, OS_SIZE_1024, "Files hidden inside directory "
318 "'%s'. Link count does not match number of files "
320 dir_name, entry_count, (int)statbuf.st_nlink);
322 /* Solaris /boot is terrible :) */
324 if (strncmp(dir_name, "/boot", strlen("/boot")) != 0) {
325 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
328 #elif defined(Darwin) || defined(FreeBSD)
329 if (strncmp(dir_name, "/dev", strlen("/dev")) != 0) {
330 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
334 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
346 /* Scan the whole filesystem looking for possible issues */
347 void check_rc_sys(const char *basedir)
349 char file_path[OS_SIZE_1024 + 1];
351 debug1("%s: DEBUG: Starting on check_rc_sys", ARGV0);
355 did = 0; /* device id */
357 snprintf(file_path, OS_SIZE_1024, "%s", basedir);
359 /* Open output files */
360 if (rootcheck.notify != QUEUE) {
361 _wx = fopen("rootcheck-rw-rw-rw-.txt", "w");
362 _ww = fopen("rootcheck-rwxrwxrwx.txt", "w");
363 _suid = fopen("rootcheck-suid-files.txt", "w");
370 if (rootcheck.scanall) {
371 /* Scan the whole file system -- may be slow */
373 snprintf(file_path, 3, "%s", "/");
375 read_sys_dir(file_path, rootcheck.readall);
377 /* Scan only specific directories */
380 const char *(dirs_to_scan[]) = {"/bin", "/sbin", "/usr/bin",
381 "/usr/sbin", "/dev", "/lib",
382 "/etc", "/root", "/var/log",
383 "/var/mail", "/var/lib", "/var/www",
384 "/usr/lib", "/usr/include",
385 "/tmp", "/boot", "/usr/local",
386 "/var/tmp", "/sys", NULL
390 const char *(dirs_to_scan[]) = {"C:\\WINDOWS", "C:\\Program Files", NULL};
394 while (dirs_to_scan[_i] != NULL) {
396 snprintf(file_path, OS_SIZE_1024, "%s%s",
399 read_sys_dir(file_path, rootcheck.readall);
402 read_sys_dir(dirs_to_scan[_i], rootcheck.readall);
409 if (_sys_errors == 0) {
410 char op_msg[OS_SIZE_1024 + 1];
411 snprintf(op_msg, OS_SIZE_1024, "No problem found on the system."
412 " Analyzed %d files.", _sys_total);
413 notify_rk(ALERT_OK, op_msg);
416 else if (_wx && _ww && _suid) {
417 char op_msg[OS_SIZE_1024 + 1];
418 snprintf(op_msg, OS_SIZE_1024, "Check the following files for more "
419 "information:\n%s%s%s",
420 (ftell(_wx) == 0) ? "" :
421 " rootcheck-rw-rw-rw-.txt (list of world writable files)\n",
422 (ftell(_ww) == 0) ? "" :
423 " rootcheck-rwxrwxrwx.txt (list of world writtable/executable files)\n",
424 (ftell(_suid) == 0) ? "" :
425 " rootcheck-suid-files.txt (list of suid files)");
427 notify_rk(ALERT_SYSTEM_ERR, op_msg);
431 if (ftell(_wx) == 0) {
432 unlink("rootcheck-rw-rw-rw-.txt");
438 if (ftell(_ww) == 0) {
439 unlink("rootcheck-rwxrwxrwx.txt");
445 if (ftell(_suid) == 0) {
446 unlink("rootcheck-suid-files.txt");