File: chkproc.c

package info (click to toggle)
chkrootkit 0.58b-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,796 kB
  • sloc: sh: 187,089; ansic: 3,779; makefile: 103
file content (563 lines) | stat: -rw-r--r-- 16,769 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
/*
  (C) Nelson Murilo - 2004/09/13
  Version 0.10
  C port from chkproc.pl code from Klaus Steding-Jessen <jessen@nic.br>
  and Cristine Hoepers <cristine@nic.br> +little output changes.

  2002/03/02 - Segmentation fault in ps for non ASCII user name, by RainbowHat

  2002/06/13 Updated by Kostya Kortchinsky <kostya.kortchinsky@renater.fr>
  - corrected the program return value ;
  - added a verbose mode displaying information about the hidden process.

  2002/08/08 - Value of MAX_PROCESSES was increased to 99999 (new versions
    of FreeBSD, HP-UX and others), reported by Morohoshi Akihiko, Paul
    and others.

  2002/09/03 - Eliminate (?) false-positives. Original idea from Aaron Sherman.

  2002/11/15 - Updated by Kostya Kortchinsky <kostya.kortchinsky@renater.fr>
  - ported to SunOS.

  2003/01/19 - Another Adore based lkm test. Original idea from Junichi Murakami

  2003/02/02 - More little fixes - Nelson Murilo

  2003/02/23 - Use of kill to eliminate false-positives abandonated, It is
  preferable false-positives that false-negatives. Uncomment kill() functions
  if you like  it.

  2003/06/07 - Fix for NPTL threading mechanisms - patch by Mike Griego

  2003/09/01 - Fix for ps mode detect, patch by Bill Dupree and others

  2004/04/03 - More fix for linux's threads - Nelson Murilo

  2004/09/13 - More and more fix for linux's threads - Nelson Murilo

  2005/02/23 - More and more and more fix for linux's threads - Nelson Murilo

  2005/10/28 - Bug fix for FreeBSD: chkproc was sending a SIGXFSZ (kill -25)
               to init, causing a reboot.  Patch by Nelson Murilo.
               Thanks to Luiz E. R. Cordeiro.

  2005/11/15 - Add check for Enye LKM - Nelson Murilo

  2005/11/25 - Fix for long lines in PS output - patch by Lantz Moore

  2006/01/05 - Add getpriority to identify LKMs, ideas from Yjesus(unhide) and
               Slider/Flimbo (skdet)

  2006/01/11 - Fix signal 25 on parisc linux and return of kill() -
               Thanks to Lantz Moore

  2014/07/16 -  MAX_PROCESSES now is 999999 -
   Thanks to Nico Koenrades

  2017/04/13 -  MAX_PROCESSES now is 4194384 if linux64
   Thanks to DS Store
*/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
/*#include <sys/stat.h>*/
#if defined(__sun)
#include <procfs.h>
#endif
#include <fcntl.h>
#include <sys/resource.h>

#define ENYELKM "/proc/12345"
// #define ENYELKM "/tmp/12345"

#if defined(__sun) || defined(__NetBSD__)
#define FIRST_PROCESS 0
#else
#define FIRST_PROCESS 1
#endif

#if (defined (__x86_64) > 0 || defined (__amd64) > 0)
#define MAX_PROCESSES 4194304
#else
#define MAX_PROCESSES 9999999
#endif

#define MAX_BUF 1024

#if !defined (SIGXFSZ)
#define SIGXFSZ 25
#endif


#define PS_SUN 0
#define PS_COM 2
#define PS_NetBSD 4
#define PS_LINUX 3
#define PS_MAX 4
const char *ps_cmds[] = {
  "ps -edf", // SUN
  "ps auxw", // fallback
  "ps mauxw 2>&1 ", // "COM" - FreeBSD, etc
  "ps -eTo pid= -o spid=", // linux
  "ps -axo pid=" // NetBSD
};

#define PROCDIR "/proc"

// nb: we rely on global vars being initialized to 0
int psproc [MAX_PROCESSES+1]; // pids in ps(1) output
int dirproc[MAX_PROCESSES+1]; // pids in /proc/ dir
int recheck[MAX_PROCESSES+1]; // list of pids to recheck

#if !defined(__FreeBSD__) // however, getpriority does exist on FreeBSD?
// getpriority(2) may reveal hidden processes
#define CHECK_PRIORITY 1
#else
#define CHECK_PRIORITY 0
#endif

#ifdef __GNUC__
__attribute__((__noreturn__))
#endif
void usage(void){
  fprintf(stderr, "Usage: chkproc [-v] [-p ps-syntax] [-N max-pid-to-check]\n");
  exit(1);
}

/* read a line from stream into s. the line is capped at size chars.
 * read at most (size-1) chars from stream into s and terminate with a
 * '\0'.  stops reading after a newline (which is retained )or EOF. If
 * no newline was read, then also keep reading from stream discarding
 * chars until a newline is found or EOF.
 */
char *readline(char *s, size_t size, FILE *stream)
{
  char *rv = fgets(s, size, stream);

  if (strlen(s) == (size-1) && s[size-1] != '\n')
    {
      char buf[MAX_BUF];
      (void) !fgets(buf, MAX_BUF, stream);
      while (strlen(buf) == (MAX_BUF-1) && buf[MAX_BUF-1] != '\n')
        {
          (void) !fgets(buf, MAX_BUF, stream);
        }
    }

  return rv;
}

// show target of symlink link (at most MAX_BUF-1 chars). The other
// args construct the output text. (eg link is relative to dir)
void showlink(const char * link, const char * prefix, const int i, const char * buf){
  char path[MAX_BUF];
  ssize_t ret;

  ret = readlink(link, path, sizeof(path)-1);
  if (ret < 0)
    printf("PID %d: %s: cannot read %s/%s: %s\n",
           i, prefix, buf, link,  strerror(errno));
  else
    {
      path[ret] = 0;
      printf("PID %d: %s: '%s'\n", i, prefix, path);
    }
}

// show contents of file (at most MAX_BUF-1 chars). The other
// args construct the output text. (eg link is relative to dir)
void showfile(const char * file, const char * prefix, const int i, const char * buf){
  int fd;
  char contents[MAX_BUF];
  ssize_t ret;

  if ((fd = open(file, O_RDONLY)) < 0)
    printf("PID %d: %s: cannot open file %s%s: %s\n", i, prefix, buf, file, strerror(errno));
  else
    {
      if ((ret = read(fd, contents, sizeof(contents)-1)) < 0)
        printf("PID %d: %s: cannot read file %s%s: %s\n", i, prefix, buf, file, strerror(errno));
      else
        {
          while (ret >=1 && ( contents[ret-1]==0 || contents[ret-1]=='\n' || contents[ret-1]==' '))
            --ret; // remove trailing cruft)
          for (ssize_t j = 0; j < ret; ++j)
            // replace embedded \0 and \n (especially present in cmdline)
            if (contents[j]==0 || contents[j]=='\n') contents[j]=' ';
          contents[ret]=0; // ensure a terminator
          printf("PID %d: %s: '%s'\n", i, prefix, contents);
        }
      close(fd);
    }
}

/* see if pid i (which has a /proc/i directory) is missing from
   readdir or ps output. if so, increment retdir and/or retps as
   appropriate andntry and read info from proc. the string buf holds
   /proc/i. You need to have run populate_psdir and populate_psproc first to set the global vars dirproc and psproc */
void dodgy_process(int i, const char * buf, int * retdir, int * retps)
{
#if defined(__sun)
  int fd;
  psinfo_t psbuf;
#endif
  int bad = 0;

  if (!dirproc[i])
    {
      bad = 1;
      (*retdir)++;
      printf("PID %d: hidden from readdir(3) but %s exists\n", i, buf);
    }

  if (!psproc[i])
    {
      bad = 1;
      (*retps)++;
      printf("PID %d: hidden from ps(1) output but %s exists\n", i, buf);
    }

  if (bad)
    {
#if defined(__linux__) || defined(__NetBSD__)
      // symlink to the executable
      showlink("exe", "EXE", i, buf);

      // cmdline holds argv
      showfile("cmdline", "CMDLINE", i, buf);
#if defined(__linux__)
      // comm is (usually) the basename of the command: see https://lwn.net/Articles/999770/
      showfile("comm","COMM", i, buf);
      showfile("cgroup","CGROUP", i, buf);
      // systemd unit that launched the process
#endif
#elif defined(__FreeBSD__)
      showlink("file", "FILE", i, buf);
#elif defined(__sun)
      if ((fd = open("./psinfo", O_RDONLY)) < 0)
        {
          if (read(fd, &psbuf, sizeof(psbuf)) == sizeof(psbuf))
            printf("PSINFO %d: %s\n", i, psbuf.pr_psargs);
          else
            printf("PSINFO %d: unknown\n", i);
          close(fd);
        }
      else
        printf("PSINFO %d: unknown\n", i);
#else
      printf("(Getting info about PID %d not implemented on this OS)\n", i);
#endif
        }
}

#if CHECK_PRIORITY
/* check if we can find PID by checking getpriority.
   returns 1 if we can, 0 if not */
int check_priority(int pid)
{
  errno = 0;
  getpriority(PRIO_PROCESS, pid);
  return(!errno);
}
#endif

/* run ps_cmd and set psproc[pid]=1 for every pid we find (and also
   dirproc[tid]=1 if pv is PS_LINUX). pv determines how we parse the
   output: pv=n means we assume that ps_cmd is output from
   ps_cmds[pv] */
void populate_psproc(const char *ps_cmd, int pv, int verbose)
{
  char buf[MAX_BUF], *p;
  int pid = 0, tid = 0;
  FILE *ps;

  if (!ps_cmd) exit(3); // cannot happen!
  if (verbose) printf("Running ps(1): %s\n", ps_cmd);
  if (!(ps = popen(ps_cmd, "r")))
    {
      perror("ps");
      exit(errno);
    }

  // skip header (if there should be one)
  if (pv != PS_LINUX && pv != PS_NetBSD)
    {
      readline(buf, MAX_BUF, ps); /* should be header (except Sun)*/
      if (verbose) printf("<<%s>>\n", buf);
#if defined(__sun)
      if (!isspace(*buf))
#else
      if (!isalpha(*buf))
#endif
        {
          readline(buf, MAX_BUF, ps); /* Look again for header */
          if (verbose) printf("<<%s>>\n",buf);
          if (!isalpha(*buf))
            {
              fprintf(stderr, "OooPS! failed to parse ps output: '%s'\n", buf);
              exit(2);
            }
        }
    }
  if (!memcmp(buf, "ps:", 3))
    {
      fprintf(stderr, "OooPS! failed to parse ps output: '%s'\n", buf);
      exit(2);
    }

  // main output
  while (readline(buf, MAX_BUF, ps))
    {
      if (verbose) printf("<<%s>>\n", buf);
      p = buf;
#if defined(__sun)
      while (isspace(*p)) /* Skip spaces */
        p++;
#endif
      if (pv != PS_LINUX && pv != PS_NetBSD)
        while (!isspace(*p)) /* Skip username */
          p++;
      while (isspace(*p)) /* Skip spaces */
        p++;
      if (!isdigit(*p))
        fprintf(stderr,"Skipping unexpected pid (not numeric) in ps outout: '%s' while looking at: '%s'\n", buf, p);
      else
        {
          pid = atoi(p);
          if (pid < FIRST_PROCESS || pid > MAX_PROCESSES)
            fprintf(stderr, "Unexpected pid (out of range) in ps outout: expected 'pid' got: '%s' looking at '%s'\n", buf, p);
          else
            {
              if (verbose) printf("ps sees: pid=%d\n",pid);
              psproc[pid] = 1;

              if (pv == PS_LINUX && pid != -1)
                {
                  while (isdigit(*p)) /* skip pid */
                    p++;
                  while (isspace(*p)) /* Skip spaces between pid and tid */
                    p++;
                  if (!isdigit(*p))
                    fprintf(stderr,"Skipping unexpected tid (not numeric) in ps outout (pid=%d): '%s' looking at '%s'\n", pid, buf, p);
                  else
                    {
                      tid = atoi(p);
                      if (tid < FIRST_PROCESS || tid > MAX_PROCESSES)
                        fprintf(stderr, "Skipping unexpected tid (out of range, or non-numeric) in ps output: expected 'pid tid' got: '%s' (pid=%d) lookong at '%s'\n", buf, pid, p);
                      else
                        {
                          if (verbose) printf("ps sees: tid=%d (pid=%d)\n", tid, pid);
                          psproc[tid] = 1;
                          dirproc[tid] = 1;
                        }
                    }
                }
            }
        }
    }
  if (verbose) printf("Done checking ps\n");
  pclose(ps);
}

// see which directories /proc/i exist using readdir(3). Set dirproc[i] to 1 if /proc/i is found.
// found.
void populate_psdir(const char * proc_dir, int verbose)
{
  char *d_name;
  DIR *proc;
  struct dirent *dir;
  int pid;

  if (!proc_dir) exit(3); // cannot happen!
  if ((proc = opendir(proc_dir)) == NULL)
    {
      perror(proc_dir);
      exit(1);
    }
  if (verbose) printf("Reading directory: %s\n", proc_dir);
  while ((dir = readdir(proc)))
    {
      d_name = dir->d_name;
      if (!isdigit(*d_name))
        {
          if (verbose) printf("Skipping non-pid: %s\n", d_name);
          continue;
        }

      pid = atoi(d_name);
      if (pid < FIRST_PROCESS || pid > MAX_PROCESSES)
        fprintf(stderr, "Ignoring pid %s/%s (out of bounds)\n", proc_dir, d_name);
      else
        {
          if (verbose) printf("Entry exists for pid %d: %s/%s\n",pid, proc_dir, d_name);
          dirproc[pid] = 1;
        }
    }
  closedir(proc);
}

int main(int argc, char **argv)
{
  char buf[MAX_BUF];
  int i, retps=0, retdir=0, retpri=0, ret=0;

  int verbose=0;

#if defined(__sun)
  int pv = PS_SUN;
#elif defined(__linux__)
  int pv = PS_LINUX;
#elif defined(__NetBSD__)
  int pv = PS_NetBSD;
#else
  int pv = PS_COM;
#endif
  const char *ps_cmd=0; // for testing
  const char *proc_dir=PROCDIR; // for testing
  int get_priority=1; // for testing: 0=disable, 1=enable (supported platforms only)
  int len; // usually 5 - we add pid to buf at this position

  int max_pid=MAX_PROCESSES;
  int to_recheck=-1; // incremented each time we find a pid to recheck

  for (i = 1; i < argc; ++i)
    {
      if (!memcmp(argv[i], "-v", 2))
        verbose = 1;
      else if (!memcmp(argv[i], "-p", 2))
        {
          if (i+1 < argc)
            pv = atoi(argv[++i]);
          else
            usage();
        }
      else if (!memcmp(argv[i], "-N", 2))
        {
          if (i+1 < argc)
            max_pid = atoi(argv[++i]);
          else
            usage();
        }
       // Some undocumented options to test the parser
      else if (!memcmp(argv[i], "--ps-cmd", 8))
        {
          /* use custom ps+args.
             -p still determines how to parse the output */
          if (i+1 < argc)
            ps_cmd=argv[++i];
          else
            usage();
        }
      else if (!memcmp(argv[i], "--proc-dir", 10))
        {
          /* treat a custom dir as /proc  */
          if (i+1 < argc)
            proc_dir=argv[++i];
          else
            usage();
        }
      else if (!memcmp(argv[i], "--no-getpriority", 16))
        // skip use of getpriority(2)
        get_priority=0;
      else usage();
    }

  if (max_pid < FIRST_PROCESS || max_pid > MAX_PROCESSES)
    max_pid = MAX_PROCESSES;

  if (pv < 0 || pv > PS_MAX)
    pv = PS_COM;

  if (!ps_cmd) ps_cmd=ps_cmds[pv];
  if (!proc_dir) proc_dir=PROCDIR;

  len=strlen(proc_dir);
  if (len+22 >= MAX_BUF) {
	// unlikely, but avoid possibility for proc_dir+/+PID+\0 to overflow buf
	// nb: the largest 64-bit unsigned integer (2^64-1) has 20 digits+/+\0
	printf("ERROR: %s too long to use as /proc\n", proc_dir);
	exit(2);
  }
  strcpy(buf, proc_dir);

  populate_psproc(ps_cmd, pv, verbose);
  populate_psdir(proc_dir, verbose);

  for (i = FIRST_PROCESS; i <= max_pid; ++i)
    {
      if (verbose && i%100000 == 0)
        {
          printf("Checking for PIDS: %d/%d\n", i, max_pid);
          fflush(stdout);
        }
	  // largest 64-bit unsigned integer (2^64-1) has 20 digits+/+\0
      snprintf(&buf[len], 22, "/%d", i);
      if (!chdir(buf))
        {
          /* cd /proc/i worked: pid i should be in both dirproc and
             psproc (unless it is a linux tid) */
          if (verbose) printf("Looking at %d (ps=%d, dir=%d): ", i, psproc[i], dirproc[i]);
          if (!psproc[i] || !dirproc[i])
            {
              if (verbose) printf("To recheck: %d\n", i);
              recheck[++to_recheck] = i;
            }
          else if (verbose) printf("OK\n");
        }
#if CHECK_PRIORITY
      else if (get_priority && check_priority(i))
        {
          /* no /proc/i so getpriority should fail */
          if (verbose) printf("To recheck (due to getpriority): %d\n", i);
          recheck[++to_recheck] = i;
        }
#endif
    }
  if (verbose) printf("Checked for PIDs up to: %d (done: %d to recheck)\n", max_pid, to_recheck + 1);

  /* second pass - repopulate psproc, psdir and retry */
  if (to_recheck >= 0)
    {
      if (verbose) printf("To recheck: %d PID(s)\n", to_recheck + 1);
      for (i = 0; i <= MAX_PROCESSES; ++i)
        psproc[i] = dirproc[i] = 0;
      populate_psproc(ps_cmd, pv, verbose);
      populate_psdir(proc_dir, verbose);

      for (i = 0; i <= to_recheck; ++i)
        {
          // largest 64-bit unsigned integer (2^64-1) has 20 digits+/+\0
          snprintf(&buf[len], 22, "/%d", recheck[i]);
          if (!chdir(buf))
            dodgy_process(recheck[i], buf, &retdir, &retps);
#if CHECK_PRIORITY
          else if (get_priority && check_priority(recheck[i]))
            {
              retpri++;
              printf("PID %d: is hidden: it has getpriority(2) information but no %s entry\n", recheck[i], buf);
            }
#endif
        }
    }

  if ((ret = retdir + retps + retpri)) printf("\n");

  if (retdir)
    printf("You have %d process(es) hidden from readdir(3)\n", retdir);
  if (retps)
    printf("You have %d process(es) hidden from ps(1)\n", retps);
#if defined(CHECK_PRIORITY)
  if (retpri)
    printf("You have %d hidden process(es) with getpriority(2) information\n", retpri);
#endif

  fflush(stdout);
  return (ret>0);
}