Cannot syscall hooking / write to protected memory page syscall_table in Linux kernel 4.19 on ARM

0

I am trying to develop a kernel module that hooks system call. I'm testing on Raspberry Pi 3B, running raspbian buster Linux 4.19.97-v7+ armv7l.

So typically on x86 we can overwrite CR0 register but there is no similar register on ARM architecture. I tried to do it via set_memory_rw and then enabling it before exiting using set_memory_ro like one answer to a similar question at Cannot use set_memory_rw in Linux kernel on ARM64 but it doesn't work.

My code:

// 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 void* *syscall_table;

#define MAGIC "mamaliga"
#define SIZEOF_MAGIC 8
#define ROOTKIT_SYS_CALL_TABLE 0x801011c4
int pos; 

// static void* original_read;
asmlinkage long (*original_read)(unsigned int fd, char __user *buf, size_t count);
/********** HELPERS **********/

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

static int change_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
            void *data)
{
    struct page_change_data *cdata = data;
    pte_t pte = *ptep;

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

    set_pte_ext(ptep, pte, 0);
    return 0;
}

void (*flush)(unsigned long start, unsigned long end);
// 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 = (void*)kallsyms_lookup_name("flush_tlb_kernel_range");
    flush(start, start + size);
    return ret;
}

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

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

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

asmlinkage long myread(unsigned int fd, char __user *buf, size_t count)
{
    long ret;

    /* Call original read_syscall */
    ret = original_read(fd, buf, count);

    pr_info("Hooked!\n");

    return ret;
}

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 = (void* *)kallsyms_lookup_name("sys_call_table");
    
    printk(KERN_INFO "syscall_table: 0xx%llx\n", syscall_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;
    }
    else {
        pr_info("set_page_rw() OK");
    }

    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;
    }
    else {
        pr_info("set_page_ro() OK");
    }

    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");

Dmesg trace errors as follow

[   89.767769] interceptor: loading out-of-tree module taints kernel.
[   89.771442] interceptor: init
[   89.806435] syscall_table: 0xx75c5dda175c5dda1
[   89.812886] interceptor: set_page_rw() OK
[   89.812898] Unable to handle kernel paging request at virtual address 801011d0
[   89.822616] pgd = c9de9ec3
[   89.826529] [801011d0] *pgd=0001141e(bad)
[   89.831760] Internal error: Oops: 80d [#1] SMP ARM
[   89.837763] Modules linked in: interceptor(O+) cmac bnep hci_uart btbcm serdev bluetooth ecdh_generic binfmt_misc evdev brcmfmac brcmutil sha256_generic cfg80211 raspberrypi_hwmon rfkill hwmon bcm2835_codec(C) bcm2835_v4l2(C) snd_bcm2835(C) v4l2_mem2mem snd_pcm bcm2835_mmal_vchiq(C) v4l2_common videobuf2_dma_contig snd_timer videobuf2_vmalloc videobuf2_memops videobuf2_v4l2 videobuf2_common snd videodev media vc_sm_cma(C) fixed uio_pdrv_genirq uio ip_tables x_tables ipv6
[   89.887532] CPU: 1 PID: 981 Comm: insmod Tainted: G         C O      4.19.97-v7+ #1293
[   89.898046] Hardware name: BCM2835
[   89.902693] PC is at modinit+0xb0/0x1000 [interceptor]
[   89.909084] LR is at   (null)
[   89.913214] pc : [<7f7430b0>]    lr : [<00000000>]    psr: 60000013
[   89.920704] sp : b20e5d80  ip : 80d0517c  fp : b20e5d9c
[   89.927176] r10: b5ab3340  r9 : 00000002  r8 : b5ab3300
[   89.933650] r7 : 00000000  r6 : fffff000  r5 : 00000000  r4 : 801011c4
[   89.941469] r3 : 7f73e068  r2 : 75c5dda1  r1 : 00000000  r0 : 0000001d
[   89.949256] Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
[   89.957653] Control: 10c5383d  Table: 320ec06a  DAC: 00000055
[   89.964666] Process insmod (pid: 981, stack limit = 0x322dc319)
[   89.971873] Stack: (0xb20e5d80 to 0xb20e6000)
[   89.977495] 5d80: 7f740000 7f743000 80d04d48 00000000 b20e5e14 b20e5da0 8010312c 7f74300c
[   89.988192] 5da0: 802821d4 8085dffc 00000000 006000c0 b20e5dcc b20e5dc0 8085dffc 802ba274
[   89.998889] 5dc0: b20e5e14 b20e5dd0 802ba274 802c7118 802bb6cc 802bab54 00000001 00003c76
[   90.009578] 5de0: 00000000 a0000013 bccc8000 75c5dda1 7f740000 7f740000 7f740000 b7c04880
[   90.020281] 5e00: 80d04d48 b5ab3300 b20e5e3c b20e5e18 801ba19c 801030e8 b20e5e3c b20e5e28
[   90.031099] 5e20: 802a8250 b20e5f30 7f740000 00000002 b20e5f0c b20e5e40 801b9114 801ba134
[   90.042029] 5e40: 7f74000c 00007fff 7f740000 801b6100 00000000 80ae4f00 7f7401fc 7f740114
[   90.053063] 5e60: 7f740130 00000000 b5ab3308 7f740048 b20e5e94 80afd2d0 802d061c 802d0488
[   90.064243] 5e80: b20e5ea0 b21029c0 00000000 00000000 00000000 00000000 00000000 00000000
[   90.075471] 5ea0: 6e72656b 00006c65 00000000 00000000 00000000 00000000 00000000 00000000
[   90.086778] 5ec0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 75c5dda1
[   90.098119] 5ee0: 7fffffff 80d04d48 00000000 00000003 0002d064 7fffffff 00000000 0000017b
[   90.109451] 5f00: b20e5fa4 b20e5f10 801b9974 801b7360 7fffffff 00000000 00000003 00000000
[   90.120790] 5f20: 00000000 bccc8000 0000209c 00000000 bccc84e2 bccc89c0 bccc8000 0000209c
[   90.132121] 5f40: bccc9a5c bccc98a8 bccc92a0 00003000 000032c0 00000000 00000000 00000000
[   90.143457] 5f60: 000018d8 00000025 00000026 0000001d 0000001b 00000017 00000000 75c5dda1
[   90.154784] 5f80: 010d6c00 7e9317d4 0003fce8 0000017b 801011c4 b20e4000 00000000 b20e5fa8
[   90.166119] 5fa0: 80101000 801b98c4 010d6c00 7e9317d4 00000003 0002d064 00000000 00000004
[   90.177457] 5fc0: 010d6c00 7e9317d4 0003fce8 0000017b 01355818 00000000 00000002 00000000
[   90.188783] 5fe0: 7e931608 7e9315f8 00022cb8 76cadaf0 60000010 00000003 00000000 00000000
[   90.200155] [<7f7430b0>] (modinit [interceptor]) from [<8010312c>] (do_one_initcall+0x50/0x218)
[   90.212033] [<8010312c>] (do_one_initcall) from [<801ba19c>] (do_init_module+0x74/0x220)
[   90.223280] [<801ba19c>] (do_init_module) from [<801b9114>] (load_module+0x1dc0/0x2404)
[   90.234455] [<801b9114>] (load_module) from [<801b9974>] (sys_finit_module+0xbc/0xcc)
[   90.245459] [<801b9974>] (sys_finit_module) from [<80101000>] (ret_fast_syscall+0x0/0x28)
[   90.256813] Exception stack(0xb20e5fa8 to 0xb20e5ff0)
[   90.263464] 5fa0:                   010d6c00 7e9317d4 00000003 0002d064 00000000 00000004
[   90.274748] 5fc0: 010d6c00 7e9317d4 0003fce8 0000017b 01355818 00000000 00000002 00000000
[   90.286023] 5fe0: 7e931608 7e9315f8 00022cb8 76cadaf0
[   90.292627] Code: eb28f040 e594400c e30e3068 e3473f73 (e584300c)
[   90.300277] ---[ end trace 9daed852fe9a568f ]---

I also tried some other suggestions from ARM64 - Linux Memory Write protection won't disable which disable the Memory Write protection via the corresponding PTE to an virtual address by using the Linux Kernel Functions. it doesn't work either.

#include <linux/module.h>   /* Needed by all modules */
#include <linux/unistd.h>   /* Needed for __NR_read */
#include <linux/reboot.h>   /* Needed for kernel_restart() */
#include <linux/slab.h> /* Needed for kmalloc() */
#include <linux/mm.h>
#include <asm/cacheflush.h> /* Needed for cache flush */
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>

#define CR0_WRITE_PROTECT_MASK (1 << 16)
#define MAGIC "mamaliga"
#define SIZEOF_MAGIC 8
#define ROOTKIT_SYS_CALL_TABLE 0x801011c4
// ARM
#define HIJACK_SIZE 12
void **sys_call_table;
asmlinkage long (*read_syscall_ref)(unsigned int fd, char __user *buf, size_t count);
int pos;    /* Size of MAGIC matched so far */

/* Function that replaces the original read_syscall.*/
asmlinkage long my_read_syscall_ref(unsigned int fd, char __user *buf, size_t count)
{
    long ret;
    int i;

    /* Call original read_syscall */
    ret = read_syscall_ref(fd, buf, count);
    return ret;
}
void mem_text_write_kernel_word(unsigned long *addr, unsigned long word)
{
    *addr = word;
    flush_icache_range((unsigned long)addr,
               ((unsigned long)addr + sizeof(long)));
}
void cacheflush ( void *begin, unsigned long size )
{
    flush_icache_range((unsigned long)begin, (unsigned long)begin + size);
}
static pgd_t *get_global_pgd (void)
{
        pgd_t *pgd;
        unsigned int ttb_reg;

    asm volatile (
    "       mrc     p15, 0, %0, c2, c0, 1"
    : "=r" (ttb_reg));
    printk(KERN_INFO "1st try: %08x", ttb_reg);
    asm volatile (
    "mrrc   p15, 1, %Q0, %R0, c2"
    : "=r"(ttb_reg));
    printk(KERN_INFO "2nd try: %08x", ttb_reg);

    if (PAGE_OFFSET == 0x80000000) ttb_reg -= (1 << 4); else if (PAGE_OFFSET == 0xc0000000) ttb_reg -= (16 << 10);
    printk(KERN_INFO "3rd try: %08x", ttb_reg);
    ttb_reg &= ~(PTRS_PER_PGD*sizeof(pgd_t)-1);
    printk(KERN_INFO "4th try: %08x", ttb_reg);
    pgd = __va(ttb_reg);
    
    printk(KERN_INFO "Global virt pgd: %08x", pgd);
    return pgd;
    }

static pte_t *lookup_address (unsigned long addr, unsigned int *level)
{
        pgd_t *pgd;
        pud_t *pud;
        pmd_t *pmd;
    
    printk(KERN_INFO "lookup_address %08x", addr);
    pgd = get_global_pgd() + pgd_index(addr);
    printk(KERN_INFO "pgd 0x%0x= %p",pgd_val(*pgd), pgd);
    pud = pud_offset (pgd, addr);
    printk(KERN_INFO "pud 0x%0x= %p", pud_val(*pud), pud);
    pmd = pmd_offset (pud, addr);
    printk(KERN_INFO "pmd 0x%0x= %p", pmd_val(*pmd), pmd);
    return pte_offset_kernel (pmd, addr);
}

    
int make_rw(unsigned long address)
{
    unsigned int level;
    pte_t *ptep, pte;
    ptep = lookup_address(address, &level);
    pte = *ptep;
    printk(KERN_INFO "pte = %08x", pte);
    
    printk(KERN_INFO "PTE before 0x%lx\n", pte);
    
    *ptep = pte_mkwrite(*ptep);
    *ptep = clear_pte_bit(*ptep, __pgprot((_AT(pteval_t, 1) << 7)));
    __flush_tlb_all();

    printk(KERN_INFO "PTE after 0x%lx\n", pte);
    return 0;

}

static int __init interceptor_start(void)
{
    unsigned long original_cr0;

    /* Reading contents of control register cr0. The cr0 register has various
       control flags that modify the basic operation of the processor. */

    /* Disable `write-protect` mode. Do so by setting the WP (Write protect)
       bit to 0. When set to 1, the CPU can't write to read-only pages */

    // /* Store original read() syscall */
    static void **sys_call_table;
    void *swi_table_addr = (long *)0xffff0008; // Known address of Software Interrupt handler
    unsigned long offset_from_swi_vector_adr = 0;
    unsigned long *swi_vector_adr = 0;

    offset_from_swi_vector_adr = ((*(long *)swi_table_addr) & 0xfff) + 8;
    swi_vector_adr = *(unsigned long *)(swi_table_addr + offset_from_swi_vector_adr);

    while (swi_vector_adr++)
    {
        if (((*(unsigned long *)swi_vector_adr) & 0xfffff000) == 0xe28f8000)
        {                                                                                  // Copy the entire sys_call_table from the offset_from_swi_vector_adr starting the hardware interrupt table
            offset_from_swi_vector_adr = ((*(unsigned long *)swi_vector_adr) & 0xfff) + 8; // 0xe28f8000 is end of interrupt space. Hence we stop.
            sys_call_table = (void *)swi_vector_adr + offset_from_swi_vector_adr;
            break;
        }
    }
    // sys_call_table = (void *) ROOTKIT_SYS_CALL_TABLE;
    printk(KERN_INFO "ROOTKIT_SYS_CALL_TABLE: 0x%08x\n", sys_call_table);
    printk(KERN_INFO "__NR_read: 0x%d\n", __NR_read);
    read_syscall_ref = (void *)sys_call_table[__NR_read];
    printk(KERN_INFO "read_syscall_ref: 0x%p\n", read_syscall_ref);
    printk("func: %pF at address: %p\n", read_syscall_ref, read_syscall_ref);
    void *func = &my_read_syscall_ref;
    printk("Func: %pF at address: %p\n", func, func);

    printk(KERN_INFO "my_read_syscall_ref: 0x%p\n", my_read_syscall_ref);
    /* Replace in the system call table the original
       read() syscall with our intercepting function */
    make_rw(&read_syscall_ref);
    // sys_call_table[__NR_read] = (unsigned long *) my_read_syscall_ref;
    //hijack_start(read_syscall_ref, &my_read_syscall_ref);

    printk(KERN_INFO "sys_call_table[__NR_read]: 0x%p\n", sys_call_table[__NR_read]);


    printk(KERN_INFO "%s\n", "Hello");

    /* A non 0 return value means init_module failed; module can't be loaded */
    return 0;
}


/* Cleanup function which is called just before module
   is rmmoded. It restores the original read() syscall. */
static void __exit interceptor_end(void)
{
    
    /* Restore original read() syscall */
     sys_call_table[__NR_read] = (unsigned long *) read_syscall_ref;

    printk(KERN_INFO "%s\n", "Bye bye");

    return;
}

module_init(interceptor_start);
module_exit(interceptor_end);

MODULE_LICENSE("GPL");

I'm really stuck at the moment and your suggestions would be very much appreciated!

linux-kernel
arm
hook
system-calls
rootkit
asked on Stack Overflow Sep 16, 2020 by lephuc

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0