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!
User contributions licensed under CC BY-SA 3.0