Невозможно использовать set_memory_rw в ядре Linux на ARM64
Я пытаюсь разработать модуль ядра, который подключает read()
системный вызов. по какой-то причинеset_memory_rw()
функция не работает.
Я видел еще один вопрос такого рода, но не совсем понимал, что делать.
Я работаю над Kali 4.19.93 с 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? Или мне следует использовать другой метод?
1 ответ
Итак, как я сказал в комментариях выше, похоже, что функция change_memory_common()
(который используется set_memory_ro/rw()
) выполняет проверку перед применением запрошенных разрешений. Это задокументировано с комментарием:
/*
* 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
не находится в отображении такого рода.
Беспокойство, похоже, вызывает конфликты TLB. Я действительно не уверен, почему это может вызвать конфликты TLB, поскольку функция, похоже, правильно вызываетflush_tlb_kernel_range()
, но я не эксперт по ARM, так что, возможно, я что-то упускаю. Я полагаю, можно было бы позвонить flush_tlb_all()
, но это кажется ненужным. Любая дополнительная информация приветствуется!
В любом случае, для упражнения по перехвату системных вызовов, вы можете переписать свою собственную версию set_memory_common()
а также set_memory_rw/ro()
избегая этой проверки. Более простой способ - просто получить соответствующий PTE для желаемого адреса и затем изменить разрешения, но я не просматривал все бесчисленные макросы для этого.
И последнее, но не менее важное: sys_call_table
может закончиться пересечением границы страницы, лучше использовать syscall_table + __NR_read
вместо просто sys_call_table
при применении изменений к странице.
Вот рабочий пример:
// 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");