b7b34ef014babb17591b28ba46edfbbebff20526
[ossec-hids.git] / src / rootcheck / check_rc_sys.c
1 /* @(#) $Id$ */
2
3 /* Copyright (C) 2009 Trend Micro Inc.
4  * All right reserved.
5  *
6  * This program is a free software; you can redistribute it
7  * and/or modify it under the terms of the GNU General Public
8  * License (version 2) as published by the FSF - Free Software
9  * Foundation
10  */
11
12
13 #include "shared.h" 
14 #include "rootcheck.h"
15
16 int _sys_errors;
17 int _sys_total;
18 dev_t did;
19
20 FILE *_wx;
21 FILE *_ww;
22 FILE *_suid;
23
24 /** Prototypes **/
25 int read_sys_dir(char *dir_name, int do_read);
26
27 int read_sys_file(char *file_name, int do_read)
28 {
29     struct stat statbuf;
30    
31     _sys_total++;
32
33
34     #ifdef WIN32
35     /* Check for NTFS ADS on Windows */
36     os_check_ads(file_name);
37     #endif
38
39
40     if(lstat(file_name, &statbuf) < 0)
41     {
42         #ifndef WIN32
43         char op_msg[OS_SIZE_1024 +1];
44         snprintf(op_msg, OS_SIZE_1024, "Anomaly detected in file '%s'. "
45                 "Hidden from stats, but showing up on readdir. "
46                 "Possible kernel level rootkit.",
47                 file_name);
48         notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
49         _sys_errors++;
50
51         #endif
52         return(-1);
53     }
54    
55     /* If directory, read the directory */
56     else if(S_ISDIR(statbuf.st_mode))
57     {
58         /* Making Darwin happy. for some reason,
59          * when I read /dev/fd, it goes forever on
60          * /dev/fd5, /dev/fd6, etc.. weird
61          */
62         if(strstr(file_name, "/dev/fd") != NULL)
63             return(0);
64
65         /* Ignoring /proc directory (it has the size 0). */
66         if(statbuf.st_size == 0)
67             return(0);
68
69         return(read_sys_dir(file_name, do_read));
70     }
71
72     /* Check if the size from stats is the same as when we
73      * read the file
74      */
75     if(S_ISREG(statbuf.st_mode) && do_read)
76     {
77         char buf[OS_SIZE_1024];
78         int fd;
79         int nr;
80         unsigned long int total = 0;
81
82         fd = open(file_name, O_RDONLY, 0);
83
84         /* It may not necessarily open */
85         if(fd >= 0)
86         {
87             while ((nr = read(fd, buf, sizeof(buf))) > 0)
88             {
89                 total += nr;
90             }
91             close(fd);
92
93             if(strcmp(file_name, "/dev/bus/usb/.usbfs/devices") == 0)
94             {
95                 /* Ignore .usbfs/devices. */
96             }
97
98             else if(total != statbuf.st_size)
99             {
100                 struct stat statbuf2;
101
102                 if((lstat(file_name, &statbuf2) == 0) &&
103                    (total != statbuf2.st_size) &&
104                    (statbuf.st_size == statbuf2.st_size))
105                 {
106                     char op_msg[OS_SIZE_1024 +1];
107                     snprintf(op_msg, OS_SIZE_1024, "Anomaly detected in file "
108                             "'%s'. File size doesn't match what we found. "
109                             "Possible kernel level rootkit.",
110                             file_name);
111                     notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
112                     _sys_errors++;
113                 }
114             }
115         }
116     }
117      
118     
119     /* If has OTHER write and exec permission, alert */
120     #ifndef WIN32
121     if(((statbuf.st_mode & S_IWOTH) == S_IWOTH) && 
122          (S_ISREG(statbuf.st_mode)))
123     {
124         if((statbuf.st_mode & S_IXUSR) == S_IXUSR)
125         {
126             if(_wx)
127                 fprintf(_wx, "%s\n",file_name);
128                 
129             _sys_errors++;    
130         }
131         else
132         {
133             if(_ww)
134                 fprintf(_ww, "%s\n", file_name);
135         }
136
137         if(statbuf.st_uid == 0)
138         {
139             char op_msg[OS_SIZE_1024 +1];
140             #ifdef OSSECHIDS
141             snprintf(op_msg, OS_SIZE_1024, "File '%s' is owned by root "
142                              "and has written permissions to anyone.",
143                              file_name);
144             #else
145             snprintf(op_msg, OS_SIZE_1024, "File '%s' is: \n"
146                              "          - owned by root,\n"
147                              "          - has written permissions to anyone.",
148                              file_name);
149             #endif
150             notify_rk(ALERT_SYSTEM_CRIT, op_msg);
151
152         }
153         _sys_errors++;
154     }
155
156     else if((statbuf.st_mode & S_ISUID) == S_ISUID)
157     {
158         if(_suid)
159             fprintf(_suid,"%s\n", file_name);
160     }
161     #endif
162
163     return(0);
164 }
165
166 /* read_dir v0.1
167  *
168  */
169 int read_sys_dir(char *dir_name, int do_read)
170 {
171     int i = 0;
172     unsigned int entry_count = 0;
173     int did_changed = 0;
174     DIR *dp;
175     
176         struct dirent *entry;
177     struct stat statbuf;        
178    
179     #ifndef WIN32
180     char *(dirs_to_doread[]) = { "/bin", "/sbin", "/usr/bin",
181                                  "/usr/sbin", "/dev", "/etc", 
182                                  "/boot", NULL };
183     #endif
184     
185     if((dir_name == NULL)||(strlen(dir_name) > PATH_MAX))
186     {
187         merror("%s: Invalid directory given.",ARGV0);
188         return(-1);
189     }
190
191
192     /* Ignoring user-supplied list. */
193     if(rootcheck.ignore)
194     {
195         while(rootcheck.ignore[i])
196         {
197             if(strcmp(dir_name, rootcheck.ignore[i]) == 0)
198             {
199                 return(1);
200             }
201             i++;
202         }
203         i = 0;
204     }
205
206     
207     
208     /* Getting the number of nodes. The total number on opendir
209      * must be the same
210      */
211     if(lstat(dir_name, &statbuf) < 0)
212     {
213         return(-1);
214     }
215     
216     
217     /* Currently device id */
218     if(did != statbuf.st_dev)
219     {
220         if(did != 0)
221             did_changed = 1;
222         did = statbuf.st_dev;
223     }
224     
225     
226     if(!S_ISDIR(statbuf.st_mode))
227     {
228         return(-1);
229     }
230    
231
232     #ifndef WIN32
233     /* Check if the do_read is valid for this directory */
234     while(dirs_to_doread[i])
235     {
236         if(strcmp(dir_name, dirs_to_doread[i]) == 0)
237         {
238             do_read = 1;
239             break;
240         }
241         i++;
242     }
243     #else
244     do_read = 0;
245     #endif
246      
247      
248     /* Opening the directory given */
249     dp = opendir(dir_name);
250         if(!dp)
251     {
252         if((strcmp(dir_name, "") == 0)&&
253            (dp = opendir("/"))) 
254         {
255             /* ok */
256         }
257         else
258         {
259             return(-1);
260         }
261     }
262
263
264     /* Reading every entry in the directory */
265     while((entry = readdir(dp)) != NULL)
266     {
267         char f_name[PATH_MAX +2];
268         struct stat statbuf_local;
269
270         /* Just ignore . and ..  */
271         if((strcmp(entry->d_name,".") == 0) ||
272            (strcmp(entry->d_name,"..") == 0))  
273         {
274             entry_count++;
275             continue;
276         }
277
278         /* Creating new file + path string */
279         if(strcmp(dir_name, "/") == 0)
280         {
281             snprintf(f_name, PATH_MAX +1, "/%s", entry->d_name);
282         }
283         else
284         {
285             snprintf(f_name, PATH_MAX +1, "%s/%s",dir_name, entry->d_name);
286         }
287
288         /* Checking if file is a directory */
289         if(lstat(f_name, &statbuf_local) == 0)
290         {
291             /* On all the systems, except darwin, the
292              * link count is only increased on directories.
293              */
294                 #ifndef Darwin
295             if(S_ISDIR(statbuf_local.st_mode))
296                 #else
297                 if(S_ISDIR(statbuf_local.st_mode) || 
298                    S_ISREG(statbuf_local.st_mode) ||
299                    S_ISLNK(statbuf_local.st_mode))
300                 #endif
301             {
302                 entry_count++;
303             }
304         }
305
306         
307         /* Checking every file against the rootkit database */
308         for(i = 0; i<= rk_sys_count; i++)
309         {
310             if(!rk_sys_file[i])
311                 break;
312
313             if(strcmp(rk_sys_file[i], entry->d_name) == 0)
314             {
315                 char op_msg[OS_SIZE_1024 +1];
316
317                 _sys_errors++;
318                 snprintf(op_msg, OS_SIZE_1024, "Rootkit '%s' detected "
319                         "by the presence of file '%s/%s'.",
320                         rk_sys_name[i], dir_name, rk_sys_file[i]);
321
322                 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
323             }
324         }
325
326         /* Ignoring /proc */
327         if((strcmp(f_name, "/proc") == 0) || (strcmp(f_name, "/sys") == 0))
328             continue;
329
330         read_sys_file(f_name, do_read);
331     }
332
333     /* Entry count for directory different than the actual
334      * link count from stats.
335      */
336     if((entry_count != statbuf.st_nlink) && 
337        ((did_changed == 0) || ((entry_count + 1) != statbuf.st_nlink)))
338     {
339         #ifndef WIN32
340         struct stat statbuf2;
341         char op_msg[OS_SIZE_1024 +1];
342         
343
344         if((lstat(dir_name, &statbuf2) == 0) && 
345             (statbuf2.st_nlink != entry_count))
346         {
347             snprintf(op_msg, OS_SIZE_1024, "Files hidden inside directory "
348                     "'%s'. Link count does not match number of files "
349                     "(%d,%d).",
350                     dir_name, entry_count, (int)statbuf.st_nlink);
351
352             /* Solaris /boot is terrible :) */
353             #ifdef SOLARIS
354             if(strncmp(dir_name, "/boot", strlen("/boot")) != 0)
355             {
356                 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
357                 _sys_errors++;
358             }
359             #elif Darwin
360             if(strncmp(dir_name, "/dev", strlen("/dev")) != 0)
361             {
362                 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
363                 _sys_errors++;
364             } 
365             #else
366             notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
367
368             _sys_errors++;
369             #endif
370         }
371
372         #endif
373     }
374     
375     closedir(dp);
376     
377     return(0);
378 }
379
380
381 /*  check_rc_sys: v0.1
382  *  Scan the whole filesystem looking for possible issues
383  */
384 void check_rc_sys(char *basedir)
385 {
386     char file_path[OS_SIZE_1024 +1];
387
388     debug1("%s: DEBUG: Starting on check_rc_sys", ARGV0);
389
390     _sys_errors = 0;
391     _sys_total = 0;
392     did = 0; /* device id */
393     
394     snprintf(file_path, OS_SIZE_1024, "%s", basedir);
395
396
397     /* Opening output files */
398     if(rootcheck.notify != QUEUE)
399     {
400         _wx = fopen("rootcheck-rw-rw-rw-.txt", "w");
401         _ww = fopen("rootcheck-rwxrwxrwx.txt", "w");
402         _suid=fopen("rootcheck-suid-files.txt", "w");
403     }
404     else
405     {
406         _wx = NULL;
407         _ww = NULL;
408         _suid = NULL;
409     }
410
411
412         
413     /* Scan the whole file system -- may be slow */
414     if(rootcheck.scanall)    
415     {
416         #ifndef WIN32
417         snprintf(file_path, 3, "%s", "/");
418         #endif
419
420         read_sys_dir(file_path, rootcheck.readall);
421     }
422
423     
424     /* Scan only specific directories */
425     else
426     {
427         int _i = 0;
428         
429         #ifndef WIN32
430         char *(dirs_to_scan[]) = {"/bin", "/sbin", "/usr/bin",
431                                   "/usr/sbin", "/dev", "/lib",
432                                   "/etc", "/root", "/var/log",
433                                   "/var/mail", "/var/lib", "/var/www",
434                                   "/usr/lib", "/usr/include",
435                                   "/tmp", "/boot", "/usr/local", 
436                                   "/var/tmp", "/sys", NULL};
437
438         #else
439         char *(dirs_to_scan[]) = {"C:\\WINDOWS", "C:\\Program Files", NULL};
440         #endif
441         
442         for(_i = 0; _i <= 24; _i++)
443         {
444             if(dirs_to_scan[_i] == NULL)
445                 break;
446                 
447             #ifndef WIN32    
448             snprintf(file_path, OS_SIZE_1024, "%s%s", 
449                                             basedir, 
450                                             dirs_to_scan[_i]);
451             read_sys_dir(file_path, rootcheck.readall);
452
453             #else
454             read_sys_dir(dirs_to_scan[_i], rootcheck.readall);
455             #endif
456             
457         }
458     }
459     
460     if(_sys_errors == 0)
461     {
462         char op_msg[OS_SIZE_1024 +1];
463         snprintf(op_msg, OS_SIZE_1024, "No problem found on the system."
464                                     " Analyzed %d files.", _sys_total);
465         notify_rk(ALERT_OK, op_msg);
466     }
467
468     else if(_wx && _ww && _suid)
469     {
470         char op_msg[OS_SIZE_1024 +1];
471         snprintf(op_msg, OS_SIZE_1024, "Check the following files for more "
472             "information:\n%s%s%s",
473             (ftell(_wx) == 0)?"":       
474             "       rootcheck-rw-rw-rw-.txt (list of world writable files)\n",
475             (ftell(_ww) == 0)?"":
476             "       rootcheck-rwxrwxrwx.txt (list of world writtable/executable files)\n",
477             (ftell(_suid) == 0)?"":        
478             "       rootcheck-suid-files.txt (list of suid files)");
479         
480         notify_rk(ALERT_SYSTEM_ERROR, op_msg);
481     }
482
483     if(_wx)
484     {
485         if(ftell(_wx) == 0)
486             unlink("rootcheck-rw-rw-rw-.txt");
487         fclose(_wx);
488     }
489     
490     if(_ww)
491     {
492         if(ftell(_ww) == 0)
493             unlink("rootcheck-rwxrwxrwx.txt");
494         fclose(_ww);
495     }
496     
497     if(_suid)
498     {
499         if(ftell(_suid) == 0)
500             unlink("rootcheck-suid-files.txt");
501         fclose(_suid); 
502     }
503                
504     return;
505 }
506
507 /* EOF */