Network.Security.Tools [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Network.Security.Tools [Electronic resources] - نسخه متنی

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید









7.2. Intercepting System Calls



Processes run in two modes:
user and kernel. Most of the time
processes run under the user mode when they have access to limited
resources. When a process needs to perform a service offered by the
kernel, it invokes a system call. System calls
serve as gates into the kernel. They are software interrupts that the
operating system processes in kernel mode. The sections in the
following paragraphs show how LKMs can perform various tricks by
intercepting system calls.



7.2.1. The System Call Table



The

Linux kernel maintains a
system call table, which is simply a set of
pointers to functions that implement the system calls. To see the
list of system calls implemented by your kernel, see
/usr/include/bits/syscall.h. The kernel stores
the system call table under a structure called
sys_call_table, which you can find in the
arch/i386/kernel/entry.S file.



Linux kernels 2.5 or greater no longer export the
sys_call_table structure. Prior to the 2.5
kernels, an LKM could instantly access the
sys_call_table structure by declaring it as an
extern variable:


extern void *sys_call_table[];


For more details, see the Section 7.2.5 later in this chapter.



7.2.2. strace Is Your Friend



Often


it is necessary
to hook into programs to understand what system calls they invoke.
The strace tool can do this. For example, consider
the following C program, which simply prints the
/etc/passwd file:


#include <stdio.h>
int main(void)
{
FILE *myfile;
char tempstring[1024];
if(!(myfile=fopen("/etc/passwd","r")))
{
fprintf(stderr,"Could not open file");
exit(1);
}
while(!feof(myfile))
{
fscanf(myfile,"%s",tempstring);
fprintf(stdout,"%s",tempstring);
}
exit(0);
}


Assuming you have compiled the preceding code with the
gcc compiler to produce an executable called
a.out, run the following strace
command:


[notroot]$ strace -o strace.out ./a.out > /dev/null


Now the output from strace is stored in
strace.out. Take a look at it to see all the
function calls invoked by a.out. For example,
issue the following grep command to realize that
the fopen( ) library call in
a.out invokes the open( )
system call to open the /etc/passwd file:


[notroot]$ grep "/etc/passwd" strace.out
open("/etc/passwd", O_RDONLY) = 3



7.2.3. Forcing Access to sys_call_table



Because
sys_call_table is no longer exported in the
2.6 kernels, we can access it only by brute force. LKMs have access
to kernel memory, so it is possible to gain access to
sys_call_table by comparing known locations with
exported system calls. Although sys_call_table
itself is not exported, a few system calls such as sys_read() and sys_write( ) are still exported
and available to LKMs. To demonstrate how to get access to
sys_call_table in the 2.6 kernels, we will write a
simple LKM that intercepts sys_open( ) and prevents anyone
from opening the /tmp/test file.



Although we intercept sys_open( ) in this section
to prevent someone from opening a file, it is not completely
foolproof. This is because the root user still has access to the raw
disk device, which determined users can manipulate directly.


We'll walk through the critical bits here, but
you'll find the full source code for
intercept_open.c in the next section. Notice
that the my_init( ) function is called during
initialization. This function attempts to gain access to
sys_call_table by starting at the address of
system_utsname. The
system_utsname structure contains a list of system
information and is known to exist before the system call table.
Therefore, the function starts at the location of
system_utsname and iterates 1,024
(MAX_TRY) times. It advances a byte every time and
compares the current location with that of sys_read(), whose address is assumed to be available to the LKM.
Once a match is found, the loop breaks and we have access to
sys_call_table:


while(i)
{
if(sys_table[__NR_read] == (unsigned long)sys_read)
{
sys_call_table=sys_table;
flag=1;
break;
}
i--;
sys_table++;
}


The LKM invokes xchg( ) to alter the system call
table to point sys_call_table[_ _NR_open] to
our_fake_open_function( ):


original_sys_open =(void * )xchg(&sys_call_table[_ _NR_open],
our_fake_open_function);


This causes our_fake_open_function( ) to be
invoked instead of the original sys_open( ) call.
The xchg( ) function also returns
original_sys_open, which contains a pointer to the
original sys_open( ). We use this pointer to reset
the system call table to point to the original sys_open() when the LKM is unloaded:


xchg(&sys_call_table[_ _NR_open], original_sys_open);


The our_fake_open_function( ) function checks to
see if the *filename parameter is set to the file
we are trying to prevent from being opened, which in our case is
assumed to be /tmp/test. However, it is not
sufficient to compare /tmp/test with the value
of filename because if a
process's current directory is
/tmp, for example, it might invoke
sys_open( ) with test as the
parameter. The surest way to check if filename is
indeed referring to /tmp/test is to compare the
inode of /tmp/test with the inode of the file
corresponding to filename.
Inodes are data structures that contain
information about files in the system. Because every file has a
unique inode, we can be certain of our results. To obtain the inode,
our_fake_open_function( ) invokes
user_path_walk( ) and passes it
filename and a structure of type
nameidata as required by the function. However,
before user_path_walk( ) is called with
/tmp/test as a parameter, the LKM calls the
following functions:


fs=get_fs( );
set_fs(get_ds( ));


The user_path_walk( ) function expects the
location of filename to be present in memory in
user space. However, because we are writing a kernel module, our code
will be in kernel space and user_path_walk( ) will
fail because it expects to be run in user mode. Therefore, before we
invoke user_path_walk( ), we will need to invoke
the get_fs( ) function, which reads the value of
the highest segment of kernel memory, and then invoke
set_fs( ) along with get_ds( )
as a parameter. This changes the kernel virtual memory limit for user
space memory so that user_path_walk( ) can
succeed. Once the module is done calling user_path_walk(
)
, it restores the limit:


set_fs(fs);


If the files' inodes are equal, we know the user is
attempting to open /tmp/test and the module
returns -EACCES:


if(inode==inode_t)
return -EACCES;


Otherwise, the module invokes the original sys_open(
)
:


return original_sys_open(filename,flags,mode);



7.2.3.1 intercept_open.c



Following is the full source code of our
intercept_open LKM:


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/namei.h>
int flag=0;
#define MAX_TRY 1024;
MODULE_LICENSE ("GPL");
unsigned long *sys_call_table;
asmlinkage long (*original_sys_open) (const char __user * filename, int
flags, int mode);
asmlinkage int our_fake_open_function(const char __user *filename, int
flags, int mode)
{
int error;
struct nameidata nd,nd_t;
struct inode *inode,*inode_t;
mm_segment_t fs;
error=user_path_walk(filename,&nd);
if(!error)
{
inode=nd.dentry->d_inode;
/*Have to do this before calling user_path_walk( )
from kernel space:*/
fs=get_fs( );
set_fs(get_ds( ));
/*Protect /tmp/test. Change this to whatever file you
want to protect*/
error=user_path_walk("/tmp/test",&nd_t);
set_fs(fs);
if(!error)
{
inode_t=nd_t.dentry->d_inode;
if(inode==inode_t)
return -EACCES;
}
}
return original_sys_open(filename,flags,mode);
}
static int __init my_init (void)
{
int i=MAX_TRY;
unsigned long *sys_table;
sys_table = (unsigned long *)&system_utsname;
while(i)
{
if(sys_table[__NR_read] == (unsigned long)sys_read)
{
sys_call_table=sys_table;
flag=1;
break;
}
i--;
sys_table++;
}
if(flag)
{
original_sys_open =(void * )xchg(&sys_call_table[__NR_open],
our_fake_open_function);
}
return 0;
}
static void my_exit (void)
{
xchg(&sys_call_table[__NR_open], original_sys_open);
}
module_init(my_init);
module_exit(my_exit);



7.2.3.2 Compiling and testing intercept_open



To compile intercept_open.c, use the following
makefile:


obj-m += intercept_open.o


Compile using the following make command:


[notroot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules


Create /tmp/test:


[notroot]$ echo hi > /tmp/test


Load insert_open.ko:


[root]# insmod ./intercept_open.ko


Try to open /tmp/test:


[root]# cat /tmp/test
cat: /tmp/test: Permission denied


Unload the module:


[root]# rmmod intercept_open


Try to open /tmp/test
again:


[root]# cat /tmp/test
hi



7.2.4. Intercepting sys_unlink( ) Using System.map



In the previous section, we looked at how
to obtain the address of sys_call_table by
searching kernel memory. However, if the kernel's
System.map file is available, you can use it to
obtain the location of sys_call_table, and this
location can be hardcoded into the LKM. An LKM that denies the
deletion of files by intercepting sys_unlink( ) is
a good illustration. First, find the location of
sys_call_table from
System.map:


[notroot]$ grep sys_call_table /boot/System.map
c044fd00 D sys_call_table


The module's source code hardcodes the address to
obtain sys_call_table:


*(long *)&sys_call_table=0xc044fd00;


The module alters the system call table to point _
_NR_unlink
to hacked_sys_unlink, and
stores the original location of sys_unlink( ):


original_sys_unlink =(void * )xchg(&sys_call_table[_ _NR_unlink],
hacked_sys_unlink);


The hacked_sys_unlink( ) function returns
-1 whenever it is called. It never invokes the
original sys_unlink( ):


asmlinkage long hacked_sys_unlink(const char *pathname)
{
return -1;
}


This prevents any process from being able to delete any file on the
system.



7.2.4.1 intercept_unlink.c



Following is the full source code of our
intercept_unlink LKM:


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
MODULE_LICENSE ("GPL");
unsigned long *sys_call_table;
asmlinkage long (*original_sys_unlink) (const char *pathname);
/*return -1. this will prevent any process from unlinking any file*/
asmlinkage long hacked_sys_unlink(const char *pathname)
{
return -1;
}
static int _ _init my_init (void)
{
/*obtain sys_call_table from hardcoded value
we found in System.map*/
*(long *)&sys_call_table=0xc044fd00;
/*store original location of sys_unlink. Alter sys_call_table
to point _ _NR_unlink to our hacked_sys_unlink*/
original_sys_unlink =(void * )xchg(&sys_call_table[_ _NR_unlink],
hacked_sys_unlink);
return 0;
}
static void my_exit (void)
/*restore original sys_unlink in sys_call_table*/
xchg(&sys_call_table[_ _NR_unlink], original_sys_unlink);
}
module_init(my_init);
module_exit(my_exit);



7.2.4.2 Compiling and testing intercept_unlink



To test the module, use the following makefile:


obj-m += intercept_unlink.o


Compile using the following make command:


[notroot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules


Create a test file:


[notroot]$ touch /tmp/testfile


Load the module:


[root]# insmod ./intercept_unlink.ko


Attempt to delete the file:


[root]# rm -rf /tmp/testfile
rm: cannot remove `/tmp/testfile': Operation not permitted


Unload the module:


[root]# rmmod intercept_unlink


Now, you should be able to delete the file:


[root]# rm -rf /tmp/testfile



7.2.5. Intercepting sys_exit( ) in 2.4 Kernels



The 2.4 kernels export the
sys_call_table symbol. Many people still use the
2.4 kernels, so this section quickly shows you how to write an LKM
for the 2.4 kernel to intercept sys_exit( ). This
example is very simple and straightforward, and once you understand
how intercept_exit.c works,
you'll be able to port the other examples in this
chapter to 2.4 kernels.



The 2.4 kernels distributed by Red Hat are back-ported and do not
export sys_call_table. In this case, use the
techniques presented in the earlier sections to grab
sys_call_table by brute force or by using
System.map.


The intercept_exit module intercepts
sys_exit( ) and prints the value of
error_code passed to sys_exit() onto the console. The init_module( )
function is called when the LKM is loaded. This function stores a
reference to the original sys_exit( ) call, and it
points sys_call_table[_ _NR_exit] to
our_fake_exit_function:


original_sys_exit = sys_call_table[_ _NR_exit];
sys_call_table[_ _NR_exit]=our_fake_exit_function;


The our_fake_exit_function( ) call prints the
value of error_code and then calls the original
sys_exit( ):


asmlinkage int our_fake_exit_function(int error_code)
{
printk("HEY! sys_exit called with error_code=%d\n",error_code);
return original_sys_exit(error_code);
}


The LKM restores sys_call_table[_ _NR_exit] to
point to original_sys_exit when it is unloaded:


sys_call_table[_ _NR_exit]=original_sys_exit;



7.2.5.1 intercept_exit.c



Following is the full source code of our
intercept_exit LKM:


#include <linux/module.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
MODULE_LICENSE("GPL");
extern void *sys_call_table[];
asmlinkage int (*original_sys_exit)(int);
asmlinkage int our_fake_exit_function(int error_code)
{
/*print message on console every time we are called*/
printk("HEY! sys_exit called with error_code=%d\n",error_code);
/*call original sys_exit and return its value*/
return original_sys_exit(error_code);
}
int init_module(void)
{
/*store reference to the original sys_exit call*/
original_sys_exit = sys_call_table[__NR_exit];
/*manipulate sys_call_table to call our fake exit
function instead*/
sys_call_table[__NR_exit]=our_fake_exit_function;
return 0;
}
void cleanup_module(void)
{
/*restore original sys_exit*/
sys_call_table[__NR_exit]=original_sys_exit;
}



7.2.5.2 Compiling and testing intercept_exit



Compile intercept_exit.c:


[notroot]$ gcc -D__KERNEL_  _ -DMODULE -I/usr/src/linux/include -c intercept_exit.c


Insert it into the kernel:


[root]# insmod ./intercept_exit.o


Ask ls to list a nonexistent file. This will cause
ls to exit with a nonzero value, and our LKM will
print this value:


 [notroot]$ ls /tmp/nonexistent
ls: /tmp/nonexistent: No such file or directory
HEY! sys_exit called with error_code=1


Remove the module when done:


[root]# rmmod intercept_exit



/ 85