Having discovered the rootkit on a machine here is some code to beat
versions of ps that "hide" process for system V machines. It works by
running ps and checking with /proc. Patches to allow the enabling of
GET_PARENT welcome. This is alpha and not tested yet, so YMMV.
However I though you might like it anyway. Lots of anti-DoS paranioa
is included. (Just look at the source to see stuff like detection of
SIGSTOP in parent and immediate restart with SIGCONT, time limits,
maximum number of process numbers limits, auto restart code... For
maximum obscurity call it lpd or something inocuous). versions of ps
that add extra processes are also detected.
This program is dedicated to the honesty of ps so sends SIGKILL to all
hidden processes, unless the number is itself (apollogies to crackers
that thought they would avoid this program by hiding it, it does not
work).
To kill, kill the lower process number first or the code will just
restart itself. Sending SIGSTOP to the main job is equally useless (the
backup job sends SIGCONT immediately, restarting everything). I will be
deploying it myself on that rootkit enabled-machine (no loger enabled
or present, of course). Comments welcomed.
Duncan (-:
aka. dps@io.stargate.co.uk, dps@duncan.telstar.net.
for PGP key email pgp@duncan.telstar.net and confirm signature by
email with dps@io.stargate.co.uk (someone might have changed my
autoresponse message given root to play with).
#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 09/16/1997 23:06 UTC by dps96r@feynman
# Source directory /tmp_mnt/home/dps96r/src/check_ps
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 8540 -rw-r--r-- check_ps.c
#
# ============= check_ps.c ==============
if test -f 'check_ps.c' -a X"$1" != X"-c"; then
echo 'x - skipping check_ps.c (File already exists)'
else
echo 'x - extracting check_ps.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'check_ps.c' &&
/* Spiked ps detector */
/* Just runs this in the background and detects nasty version of ps */
/* (with sepcial anti-kill code to help crackers have problems with */
/* avoiding detection... all (c) D.P.Simpson */
X
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
X
/* Tunable */
#define MAXX_NUMS 10000
#define INITIAL_NUMS 200
#define INC 50
X
/* Process nuker, also nukes the child (hopefully the crackers connection) */
void nuke(int pid)
{
#ifdef GET_PARENT
X pid_t parent;
#endif
X
X /* Check for me lest crackers try and hide me so I kill myself */
X if (pid==getpid())
X {
X syslog(LOG_NOTICE, "Actually process %d is me, not nuking");
X return;
X }
X
#ifdef GET_PARENT
X /* read the parent id */
X
X /* Freeze the processes so they can not monitor each other and
X fork something off to keep going. */
X if (parent!=1)
X {
X syslog("parent of %d is %d, nulking it too", pid, parent_id);
X nice(-10); /* Get high nice value to limit interuption */
X kill (parent, SIGSTOP); /* Freeze parent, disables my device */
X kill (pid, SIGSTOP); /* Freeze child, so it can not notice */
X kill (parent, SIGKILL); /* Nuke parent */
X }
X else
X {
X syslog("parent of %d is init, not nuking parent", pid);
X }
#endif
X kill (pid, SIGKILL); /* Nuke child */
X nice(10); /* Back to normal niceness */
}
X
X
/* Child catcher */
void catch_child(int sig)
{
X sig=sig;
X int *status;
X pid_t pid;
X
X pid=wait(&status);
X if (WIFSTOPPED(status))
X kill(pid, SIGCONT); /* Stop crackers stoping the ps process */
X /* (major paranoia) */
X return;
}
X
/* ps output analyser, mainly just toiler roll */
int *run_ps(void)
{
X static int *nchunk, *nnchunk;
X int max, pos;
X int fd[2], null;
X FILE *in;
X int c, num;
X pid_t pid;
X enum {SKIP_WS, GET_NUM, SKIP_END} line;
X
X singal(SIG_CHLD, catch_chld);
X
X if ((nchunk=malloc(INITAL_NUMS))==NULL)
X {
X syslog("Inital number buffer allocation failure");
X return NULL;
X }
X
X if ((null=open("/dev/null", O_RDWR))==-1)
X {
X syslog("Could not open /dev/null (%s)", strerror(errno));
X return NULL;
X }
X
X if (pipe(fd))
X {
X syslog("Could not create pipe");
X free(nchunk);
X close(null);
X return NULL;
X }
X
X if ((in=fdopen(fd[0],"r"))==NULL)
X {
X syslog("fdopen failure");
X free(nchunk);
X close(null);
X close(fd[0]);
X close(fd[1]);
X return NULL;
X }
X
X switch(pid=fork())
X {
X case -1:
X syslog("Fork failed");
X free(nchunk);
X close(null);
X close(fd[0]);
X close(fd[1]);
X return NULL;
X
X case 0: /* child */
X signal(SIG_TSTP, SIG_IGN); /* Stop TSTP */
X close(fd[0]);
X close(0);
X close(1);
X close(2);
X dup2(null,0);
X dup2(fd[1],1);
X dup2(null,2);
X exec("/usr/bin/ps","ax");
X exit(0); /* Should never get here */
X
X default:
X close(fd[1]);
X close(null);
X break;
X }
X max=INITIAL_NUMS; pos=0;
X
X line=SKIP_END; /* Skip 1st line */
X while((c=fgetc(in))!=EOF)
X {
X switch(line)
X {
X case SKIP_WS:
X if (iswhite(c))
X continue;
X num=0;
X sline=GET_NUM;
X /* FALL THRU */
X case GET_NUM:
X if (isdigit(c))
X {
X num=num*10+(c-'0');
X continue;
X }
X if (num!=pid)
X {
X /* Check for overflows, I am paranoid, MAXX_NUMS is to stop
X simple DoS attacks with a fixed version of ps. */
X if (pos==max)
X {
X if(max>MAXX_NUMS ||
X (nnchunk=malloc(sizeof(int)*(max+INC)))==NULL)
X {
X syslog(LOG_NOTICE,
X "Expansion failure (%d procs)", max);
X free(nchunk);
X fclose(in);
X return NULL;
X }
X for (pos=0; pos<max; pos++)
X nnchunk[pos]=nchunk[pos];
X free(nchunk);
X nchunk=nnchunk;
X max+=INC;
X }
X nums[pos++]=num;
X }
X line=SKIP_END;
X /* FALL THRU */
X
X case SKIP_END:
X if (c=='\n')
X line=SKIP_WS;
X break;
X
X default:
X syslog("Imposible state");
X free(nchunk);
X fclose(in);
X return NULL;
X }
X }
X sleep(180); /* Wait 3 mins */
X kill(pid, SIGKILL); /* Kill ps (stop ps DoS attack) */
X
X /* No need to wait, SIGCHLD handler does that */
X
X /* Add -1 to end, might need to expand buffer ... */
X if (pos==max)
X {
X if ((nnchunk=malloc(sizeof(int)*(max+1)))==NULL)as
X {
X syslog(LOG_NOTICE, "Expansion failure (%d procs)", max);
X free(nchunk);
X fclose(in);
X return NULL;
X }
X for (pos=0; pos<max; pos++)
X nnchunk[pos]=nchunk[pos];
X free(nchunk);
X nch