无法在ARM64的Linux内核中使用set_memory_rw [英] Cannot use set_memory_rw in Linux kernel on ARM64

查看:71
本文介绍了无法在ARM64的Linux内核中使用set_memory_rw的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试开发一个挂接 read()系统调用的内核模块.由于某些原因, set_memory_rw()函数似乎不起作用.

I am trying to develop a kernel module that hooks the read() system call. for some reason the set_memory_rw() function does not seem to work.

我看到了另一个类似的问题,但我真的不知道该怎么办.

I saw another question of this sort but I didn't really understand what to do.

我正在使用Raspberry-pi 4开发Kali 4.19.93

I am working on Kali 4.19.93 with Raspberry-pi 4

我的代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/slab.h>
#include <linux/kern_levels.h>
#include <asm/unistd.h>
#include <asm/cacheflush.h>
#include <linux/semaphore.h>
#include <asm/set_memory.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Omri Ben David");
MODULE_DESCRIPTION("Hooking Linux System calls");
MODULE_VERSION("1.0");

unsigned long** SYS_CALL_TABLE = (unsigned long**) 0xc02011c4;

asmlinkage ssize_t (*original_read) (int fd, char *buf, size_t count);
asmlinkage ssize_t HookRead(unsigned int fd, char * buf, size_t count)
{
    printk(KERN_INFO "Rootkit_Debug: Yay you entered my function!!\n Now you can read\n");
    return (*original_read)(fd,buf,count);
}   

void (*seek)(unsigned long, int);
void (*hide)(unsigned long, int);

static int __init SetHooks(void)
{
    printk(KERN_INFO "Hooks Will now be set, hold on tight\n");
    printk(KERN_INFO "System calls table is at address %p\n",SYS_CALL_TABLE);

    original_read = (void*) SYS_CALL_TABLE[__NR_read];
    
    seek = (void*) kallsyms_lookup_name("set_memory_rw");
    hide = (void*) kallsyms_lookup_name("set_memory_ro");

    (*seek)((unsigned long)SYS_CALL_TABLE, 1);
    SYS_CALL_TABLE[__NR_read] = (unsigned long*)HookRead;
    (*hide)((unsigned long)SYS_CALL_TABLE, 1);
    printk(KERN_INFO "System calls hooked successfully\n");

    return 0;
}

static void __exit HookCleanup(void)
{
    printk(KERN_INFO "System calls restore initiated\n");

    (*seek)((unsigned long)SYS_CALL_TABLE, 1);
    SYS_CALL_TABLE[__NR_read] = (unsigned long*) original_read;
    (*hide)((unsigned long)SYS_CALL_TABLE, 1);

    printk(KERN_INFO "System successfully restored. hope you had fun");
}

module_init(SetHooks);
module_exit(HookCleanup);

如何使 set_memory_rw()函数起作用以覆盖syscall表?还是应该使用其他方法?

How can I make the set_memory_rw() function work in order to overwrite the syscall table? Or should I use another method?

推荐答案

因此,正如我在上面的评论中所述,函数

So, as I said in the comments above, it appears that the function change_memory_common() (which is used by set_memory_ro/rw()) does a check before applying the requested permissions. This is documented with a comment:

/*
 * Kernel VA mappings are always live, and splitting live section
 * mappings into page mappings may cause TLB conflicts. This means
 * we have to ensure that changing the permission bits of the range
 * we are operating on does not result in such splitting.
 *
 * Let's restrict ourselves to mappings created by vmalloc (or vmap).
 * Those are guaranteed to consist entirely of page mappings, and
 * splitting is never needed.
 *
 * So check whether the [addr, addr + size) interval is entirely
 * covered by precisely one VM area that has the VM_ALLOC flag set.
 */
area = find_vm_area((void *)addr);
if (!area ||
    end > (unsigned long)area->addr + area->size ||
    !(area->flags & VM_ALLOC))
    return -EINVAL;

该函数似乎仅适用于通过 vmalloc() vmap()创建的映射,并且 sys_call_table 并不驻留在这种映射.

The function seems to only work for mappings created through vmalloc() or vmap(), and the sys_call_table does not reside in a mapping of such kind.

问题似乎与TLB冲突有关.我真的不确定为什么会导致TLB冲突,因为函数似乎可以正确调用 flush_tlb_kernel_range() ,但是我不是ARM专家,所以我可能会缺少一些东西.我想可以打电话给 flush_tlb_all() ,但似乎是不必要的.欢迎任何其他见解!

The concern seems to be around TLB conflicts. I'm really not sure why this would cause TLB conflicts since the function seems to properly call flush_tlb_kernel_range(), but I am no ARM expert, so I might be missing something. I suppose one could call flush_tlb_all(), but it seems unnecessary. Any additional insight is welcome!

在任何情况下,出于练习syscall劫持的目的,您可以重新编写自己的 set_memory_common() set_memory_rw/ro()版本这张支票.一种更简单的方法是只为所需的地址获取适当的PTE,然后更改权限,但是我并没有浏览所有不计其数的宏.

In any case, for the purpose of your exercise on syscall hijacking, you can re-write your own version of set_memory_common() and set_memory_rw/ro() avoiding this check. An easier way would be to just get the appropriate PTE for the desired address and then change the permissions, but I didn't look through all the countless macros for that.

最后但并非最不重要的一点是,由于 sys_call_table 最终可能会跨越页面边界,因此最好使用 syscall_table + __NR_read 而不是仅仅使用 sys_call_table 将更改应用于页面.

Last, but not least, since the sys_call_table could end up crossing a page boundary, it's better to use syscall_table + __NR_read instead of just sys_call_table when applying changes to the page.

这是一个可行的示例:

// SPDX-License-Identifier: GPL-3.0
#include <linux/init.h>     // module_{init,exit}()
#include <linux/module.h>   // THIS_MODULE, MODULE_VERSION, ...
#include <linux/kernel.h>   // printk(), pr_*()
#include <linux/kallsyms.h> // kallsyms_lookup_name()
#include <asm/syscall.h>    // syscall_fn_t, __NR_*
#include <asm/ptrace.h>     // struct pt_regs
#include <asm/tlbflush.h>   // flush_tlb_kernel_range()
#include <asm/pgtable.h>    // {clear,set}_pte_bit(), set_pte()
#include <linux/vmalloc.h>  // vm_unmap_aliases()
#include <linux/mm.h>       // struct mm_struct, apply_to_page_range()
#include <linux/kconfig.h>  // IS_ENABLED()

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

static struct mm_struct *init_mm_ptr;
static syscall_fn_t *syscall_table;
static syscall_fn_t original_read;

/********** HELPERS **********/

// From arch/arm64/mm/pageattr.c.
struct page_change_data {
    pgprot_t set_mask;
    pgprot_t clear_mask;
};

// From arch/arm64/mm/pageattr.c.
static int change_page_range(pte_t *ptep, unsigned long addr, void *data)
{
    struct page_change_data *cdata = data;
    pte_t pte = READ_ONCE(*ptep);

    pte = clear_pte_bit(pte, cdata->clear_mask);
    pte = set_pte_bit(pte, cdata->set_mask);

    set_pte(ptep, pte);
    return 0;
}

// From arch/arm64/mm/pageattr.c.
static int __change_memory_common(unsigned long start, unsigned long size,
                  pgprot_t set_mask, pgprot_t clear_mask)
{
    struct page_change_data data;
    int ret;

    data.set_mask = set_mask;
    data.clear_mask = clear_mask;

    ret = apply_to_page_range(init_mm_ptr, start, size, change_page_range, &data);

    flush_tlb_kernel_range(start, start + size);
    return ret;
}

// Simplified set_memory_rw() from arch/arm64/mm/pageattr.c.
static int set_page_rw(unsigned long addr)
{
    vm_unmap_aliases();    
    return __change_memory_common(addr, PAGE_SIZE, __pgprot(PTE_WRITE), __pgprot(PTE_RDONLY));
}

// Simplified set_memory_ro() from arch/arm64/mm/pageattr.c.
static int set_page_ro(unsigned long addr)
{
    vm_unmap_aliases();
    return __change_memory_common(addr, PAGE_SIZE, __pgprot(PTE_RDONLY), __pgprot(PTE_WRITE));
}

/********** ACTUAL MODULE **********/

static long myread(const struct pt_regs *regs)
{
    pr_info("read() called\n");
    return original_read(regs);
}

static int __init modinit(void)
{
    int res;

    pr_info("init\n");

    // Shouldn't fail.
    init_mm_ptr = (struct mm_struct *)kallsyms_lookup_name("init_mm");
    syscall_table = (syscall_fn_t *)kallsyms_lookup_name("sys_call_table");

    original_read = syscall_table[__NR_read];

    res = set_page_rw((unsigned long)(syscall_table + __NR_read) & PAGE_MASK);
    if (res != 0) {
        pr_err("set_page_rw() failed: %d\n", res);
        return res;
    }

    syscall_table[__NR_read] = myread;

    res = set_page_ro((unsigned long)(syscall_table + __NR_read) & PAGE_MASK);
    if (res != 0) {
        pr_err("set_page_ro() failed: %d\n", res);
        return res;
    }

    pr_info("init done\n");

    return 0;
}

static void __exit modexit(void)
{
    int res;

    pr_info("exit\n");

    res = set_page_rw((unsigned long)(syscall_table + __NR_read) & PAGE_MASK);
    if (res != 0) {
        pr_err("set_page_rw() failed: %d\n", res);
        return;
    }

    syscall_table[__NR_read] = original_read;

    res = set_page_ro((unsigned long)(syscall_table + __NR_read) & PAGE_MASK);
    if (res != 0)
        pr_err("set_page_ro() failed: %d\n", res);

    pr_info("goodbye\n");
}

module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Syscall hijack on arm64.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");

这篇关于无法在ARM64的Linux内核中使用set_memory_rw的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆