将需要inline hook的函数机器码先拷贝到一块内存中, 然后动态修正这块内存的call,jmp对应的offset(相对值), 然后给原始函数做个inline hook, 跳到新函数去执行, 新函数做一些过滤判断后, 调用新分配的内存保存的机器码, 想当于调用原始函数。这种技术在实战中没什么用, 拓展下思路还是不错的。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/string.h>
#include <linux/list.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <asm/cacheflush.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wzt");
#define OPCODE_LEN 38
unsigned char orig_opcode[5];
void (*new_fn)(char *, int n); /// point to opcodes in heap space.
unsigned char *s;
void test(char *str, int n)
{
int i = 0;
for (;i < n ; i++) {
printk("%s\n", str);
}
}
void test1(char *str, int n)
{
int i;
if (n > 3) {
test("aa", 1);
}
test(str, n);
}
void new_test1(char *str, int n)
{
printk("in new_test1.\n");
new_fn(str,n); /// heap space ,function test1.
}
void test2(char *str, int n)
{
test("aaa", 2);
}
void copy_opcode(void)
{
unsigned char *p;
unsigned char buf[4] = "\x00\x00\x00\x00";
unsigned int orig_func_addr = 0;
int orig_offset, new_offset;
int i;
printk("orig func addr: 0x%x\n", test);
p = (unsigned char *)test1; /// print the opcode of function test1.
for (i = 0; i < OPCODE_LEN; i++) {
printk("0x%02x", p[i]);
}
s = kmalloc(100, GFP_KERNEL); /// s point to a heap space.
memset(s, 0x0, 100);
printk("\ns: 0x%x\n", s);
for (i = 0; i < OPCODE_LEN; i++) {
s[i] = p[i];
if (p[i] == 0xe8 || p[i] == 0xe9) { /// opcode of call jmp instruction.
printk("\nfound 0x%x: 0x%x\n", p[i], &p[i]);
buf[0] = p[i + 1];
buf[1] = p[i + 2];
buf[2] = p[i + 3];
buf[3] = p[i + 4];
orig_offset = *(unsigned int *)buf; /// relative address
printk("orig offset: 0x%x\n", orig_offset);
orig_func_addr = orig_offset + (unsigned int)&p[i] + 5; ///absolute address
printk("orig func addr: 0x%x\n", orig_func_addr);
new_offset = orig_func_addr - (unsigned int)&s[i] - 5;
printk("new_offset: 0x%x\n", new_offset);
i++;
s[i++] = (new_offset & 0x000000ff); ///because some calls use relative
s[i++] = (new_offset & 0x0000ff00) >> 8; ///address.But the call instruction's
s[i++] = (new_offset & 0x00ff0000) >> 16; ///absolute address has been changed
s[i++] = (new_offset & 0xff000000) >> 24; ///so the relative address should be
} ///changed too.(very important!)
}
printk("\n");
for (i = 0; i < OPCODE_LEN; i++) {
printk("0x%02x", s[i]);
}
printk("\n");
new_fn = s;
}
static int inline_hook_func(unsigned int old_func, unsigned int new_func,unsigned char *old_opcode)
{
unsigned char *p;
unsigned int offset;
p = (unsigned char *)old_func;
memcpy(old_opcode, p, 5);
offset = (unsigned int)new_func - (unsigned int)old_func - 5;
*p++ = 0xe9;
//memcpy(buf + 1, &p, 4);
*p++ = (offset & 0x000000ff);
*p++ = (offset & 0x0000ff00) >> 8;
*p++ = (offset & 0x00ff0000) >> 16;
*p++ = (offset & 0xff000000) >> 24;
*p++ = 0x90;
return 0;
}
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);
return 0;
}
static int opcode_test_init(void)
{
copy_opcode();
inline_hook_func(test1, new_test1, orig_opcode);
test1("hello,world", 3); ///when to here ,call test1 equals new_test1.
return 0;
}
static void opcode_test_exit(void)
{
printk("unload opcode test module.\n");
}
module_init(opcode_test_init);
module_exit(opcode_test_exit);
上面程序首先调用copy_opcode,将函数test1的机器码拷贝到s指向的堆空间,并修改相对条转指令。
然后在用inline_hook_func 把test1的开始opcode改变为跳向 new_test1的机器码。
当调用test1的时候调用 new_fn ,也就是test1,来回折腾还是那个函数!!!!
源代码意图如上所示,但是根据以前的经验 内核模块中的代码是不可以修改的(内核中的代码才可以修改)。内核模块中代码 即使等完全把内核模块加载上之后再修改也不行!!
因此上面程序中 inline_hook_func 执行到 *p++ = 0xe9; 就会导致内核崩溃。
原文链接:
参考内核调试文章 http://blog.csdn.net/xsckernel/article/details/8159560 对 CONFIG_DEBUG_SET_MODULE_RONX 编译选项的说明
将这个编译选项去掉后可以执行过函数 inline_hook_func(修改可加载模块的代码),但是到 new_func这一行
则执行错误,内核数据不可已被执行(不管这些数据是在全局区还是在堆区)
则执行错误,内核数据不可已被执行(不管这些数据是在全局区还是在堆区)则执行错误,内核数据不可已被执行(不管这些数据是在全局区还是在堆区)则执行错误,内核数据不可已被执行(不管这些数据是在全局区还是在堆区)