C语言 Linux 内核:系统调用挂钩示例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2103315/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 04:12:06  来源:igfitidea点击:

Linux Kernel: System call hooking example

clinux-kernelhook

提问by Stephen

I'm trying to write some simple test code as a demonstration of hooking the system call table.

我正在尝试编写一些简单的测试代码作为挂钩系统调用表的演示。

"sys_call_table" is no longer exported in 2.6, so I'm just grabbing the address from the System.map file, and I can see it is correct (Looking through the memory at the address I found, I can see the pointers to the system calls).

“sys_call_table”在 2.6 中不再导出,所以我只是从 System.map 文件中获取地址,我可以看到它是正确的(在我找到的地址处查看内存,我可以看到指向系统调用)。

However, when I try to modify this table, the kernel gives an "Oops" with "unable to handle kernel paging request at virtual address c061e4f4" and the machine reboots.

但是,当我尝试修改这个表时,内核给出了一个“糟糕”,“无法处理虚拟地址 c061e4f4 上的内核分页请求”,并且机器重新启动。

This is CentOS 5.4 running 2.6.18-164.10.1.el5. Is there some sort of protection or do I just have a bug? I know it comes with SELinux, and I've tried putting it in to permissive mode, but it doesn't make a difference

这是运行 2.6.18-164.10.1.el5 的 CentOS 5.4。是否有某种保护或我只是有一个错误?我知道它随 SELinux 一起提供,并且我已尝试将其置于许可模式,但这并没有什么区别

Here's my code:

这是我的代码:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>

void **sys_call_table;

asmlinkage int (*original_call) (const char*, int, int);

asmlinkage int our_sys_open(const char* file, int flags, int mode)
{
   printk("A file was opened\n");
   return original_call(file, flags, mode);
}

int init_module()
{
    // sys_call_table address in System.map
    sys_call_table = (void*)0xc061e4e0;
    original_call = sys_call_table[__NR_open];

    // Hook: Crashes here
    sys_call_table[__NR_open] = our_sys_open;
}

void cleanup_module()
{
   // Restore the original call
   sys_call_table[__NR_open] = original_call;
}

采纳答案by Stephen

I finally found the answer myself.

我终于自己找到了答案。

http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html

http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html

The kernel was changed at some point so that the system call table is read only.

内核在某个时候发生了变化,因此系统调用表是只读的。

cypherpunk:

密码朋克:

Even if it is late but the Solution may interest others too: In the entry.S file you will find: Code:

.section .rodata,"a"
#include "syscall_table_32.S"

sys_call_table -> ReadOnly You have to compile the Kernel new if you want to "hack" around with sys_call_table...

即使已经晚了,但解决方案也可能引起其他人的兴趣:在 entry.S 文件中,您会发现: 代码:

.section .rodata,"a"
#include "syscall_table_32.S"

sys_call_table -> ReadOnly 如果你想用 sys_call_table 来“破解”,你必须编译新的内核...

The link also has an example of changing the memory to be writable.

该链接还有一个将内存更改为可写的示例。

nasekomoe:

nasekomoe:

Hi everybody. Thanks for replies. I solved the problem long ago by modifying access to memory pages. I have implemented two functions that do it for my upper level code:

#include <asm/cacheflush.h>
#ifdef KERN_2_6_24
#include <asm/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ | VM_WRITE;
    return change_page_attr(pg, 1, prot);
}

int set_page_ro(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ;
    return change_page_attr(pg, 1, prot);
}

#else
#include <linux/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
    return set_memory_rw(_addr, 1);
}

int set_page_ro(long unsigned int _addr)
{
    return set_memory_ro(_addr, 1);
}

#endif // KERN_2_6_24

大家好。感谢您的回复。我很久以前通过修改对内存页面的访问解决了这个问题。我已经为我的上层代码实现了两个函数:

#include <asm/cacheflush.h>
#ifdef KERN_2_6_24
#include <asm/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ | VM_WRITE;
    return change_page_attr(pg, 1, prot);
}

int set_page_ro(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ;
    return change_page_attr(pg, 1, prot);
}

#else
#include <linux/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
    return set_memory_rw(_addr, 1);
}

int set_page_ro(long unsigned int _addr)
{
    return set_memory_ro(_addr, 1);
}

#endif // KERN_2_6_24

Here's a modified version of the original code that works for me.

这是对我有用的原始代码的修改版本。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
#include <asm/semaphore.h>
#include <asm/cacheflush.h>

void **sys_call_table;

asmlinkage int (*original_call) (const char*, int, int);

asmlinkage int our_sys_open(const char* file, int flags, int mode)
{
   printk("A file was opened\n");
   return original_call(file, flags, mode);
}

int set_page_rw(long unsigned int _addr)
{
   struct page *pg;
   pgprot_t prot;
   pg = virt_to_page(_addr);
   prot.pgprot = VM_READ | VM_WRITE;
   return change_page_attr(pg, 1, prot);
}

int init_module()
{
    // sys_call_table address in System.map
    sys_call_table = (void*)0xc061e4e0;
    original_call = sys_call_table[__NR_open];

    set_page_rw(sys_call_table);
    sys_call_table[__NR_open] = our_sys_open;
}

void cleanup_module()
{
   // Restore the original call
   sys_call_table[__NR_open] = original_call;
}

回答by Corey Henderson

Thanks Stephen, your research here was helpful to me. I had a few problems, though, as I was trying this on a 2.6.32 kernel, and getting WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted)followed by a kernel OOPS about not being able to write to the memory address.

谢谢斯蒂芬,你在这里的研究对我很有帮助。但是,我遇到了一些问题,因为我在 2.6.32 内核上尝试此操作,并且遇到WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted)内核 OOPS 无法写入内存地址的问题。

The comment above the mentioned line states:

上述行上方的评论指出:

// People should not be passing in unaligned addresses

The following modified code works:

以下修改后的代码有效:

int set_page_rw(long unsigned int _addr)
{
    return set_memory_rw(PAGE_ALIGN(_addr) - PAGE_SIZE, 1);
}

int set_page_ro(long unsigned int _addr)
{
    return set_memory_ro(PAGE_ALIGN(_addr) - PAGE_SIZE, 1);
}

Note that this still doesn't actually set the page as read/write in some situations. The static_protections()function, which is called inside of set_memory_rw(), removes the _PAGE_RWflag if:

请注意,在某些情况下,这实际上并未将页面设置为读/写。在static_protections()内部调用的函数在以下情况下set_memory_rw()删除_PAGE_RW标志:

  • It's in the BIOS area
  • The address is inside .rodata
  • CONFIG_DEBUG_RODATA is set and the kernel is set to read-only
  • 它在 BIOS 区域
  • 地址在 .rodata 里面
  • CONFIG_DEBUG_RODATA 设置,内核设置为只读

I found this out after debugging why I still got "unable to handle kernel paging request" when trying to modify the address of kernel functions. I was eventually able to solve that problem by finding the page table entry for the address myself and manually setting it to writable. Thankfully, the lookup_address()function is exported in version 2.6.26+. Here is the code I wrote to do that:

我在调试后发现为什么在尝试修改内核函数的地址时仍然“无法处理内核分页请求”。我最终能够通过自己找到地址的页表条目并手动将其设置为可写来解决该问题。幸运的是,该lookup_address()函数在 2.6.26+ 版本中导出。这是我编写的代码:

void set_addr_rw(unsigned long addr) {

    unsigned int level;
    pte_t *pte = lookup_address(addr, &level);

    if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;

}

void set_addr_ro(unsigned long addr) {

    unsigned int level;
    pte_t *pte = lookup_address(addr, &level);

    pte->pte = pte->pte &~_PAGE_RW;

}

Finally, while Mark's answer is technically correct, it'll case problem when ran inside Xen. If you want to disable write-protect, use the read/write cr0 functions. I macro them like this:

最后,虽然 Mark 的回答在技术上是正确的,但在 Xen 中运行时会出现问题。如果要禁用写保护,请使用读/写 cr0 函数。我像这样宏它们:

#define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000))
#define GPF_ENABLE write_cr0(read_cr0() | 0x10000)

Hope this helps anyone else who stumbles upon this question.

希望这可以帮助任何偶然发现这个问题的人。

回答by mark

Note that the following will also work instead of using change_page_attr and cannot be depreciated:

请注意,以下内容也可以代替使用 change_page_attr 并且不能折旧:

static void disable_page_protection(void) {

    unsigned long value;
    asm volatile("mov %%cr0,%0" : "=r" (value));
    if (value & 0x00010000) {
            value &= ~0x00010000;
            asm volatile("mov %0,%%cr0": : "r" (value));
    }
}

static void enable_page_protection(void) {

    unsigned long value;
    asm volatile("mov %%cr0,%0" : "=r" (value));
    if (!(value & 0x00010000)) {
            value |= 0x00010000;
            asm volatile("mov %0,%%cr0": : "r" (value));
    }
}

回答by Sebastian Mountaniol

If you are dealing with kernel 3.4 and later (it can also work with earlier kernels, I didn't test it) I would recommend a smarter way to acquire the system callы table location.

如果您正在处理内核 3.4 及更高版本(它也可以与早期内核一起使用,我没有测试过),我会推荐一种更智能的方法来获取系统 callы 表位置。

For example

例如

#include <linux/module.h>
#include <linux/kallsyms.h>

static unsigned long **p_sys_call_table;
/* Aquire system calls table address */
p_sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");

That's it. No addresses, it works fine with every kernel I've tested.

就是这样。没有地址,它适用于我测试过的每个内核。

The same way you can use a not exported Kernel function from your module:

您可以使用模块中未导出的内核函数的相同方式:

static int (*ref_access_remote_vm)(struct mm_struct *mm, unsigned long addr,
                void *buf, int len, int write);
ref_access_remote_vm = (void *)kallsyms_lookup_name("access_remote_vm");

Enjoy!

享受!