前言

OS:Kylin Linux Advanced Server
内核版本:4.19.90
处理器:aarch64

一、hook原理简介

Linux hook系统调用就是修改系统调用表,对于ARM64:

const syscall_fn_t sys_call_table[__NR_syscalls] = {
	[0 ... __NR_syscalls - 1] = __arm64_sys_ni_syscall,

修改 sys_call_table[__NR_syscalls] 系统调用表对应的成员,比如 hook mkdir 就是修改sys_call_table[__NR_mkdirat] 成员。

其中 __NR_syscalls 是系统调用号,__NR_mkdirat的系统调用可通过一下查看:
用户态文件中:

// /usr/include/asm-generic/unistd.h

#define __NR_mkdirat 34
__SYSCALL(__NR_mkdirat, sys_mkdirat)

内核源码文件中:

// include/uapi/asm-generic/unistd.h

#define __NR_mkdirat 34
__SYSCALL(__NR_mkdirat, sys_mkdirat)

sys_call_table 是只读变量,位于内核镜像中的 .rodata segment .

内核镜像文件段分布:

名称 区间范围 描述
代码段 _text ~ _etext 内核的代码段
只读数据段 __start_rodata ~ __init_begin 内核的只读数据,包括 sys_call_table
init段 __init_begin ~ __init_end 内核初始化是所需要的代码和数据
数据段 _sdata ~ _edata 内核中可读可写的数据
bss段 __bss_start ~ __bss_stop 内核中初始化为0的数据以及没有初始化的全局变量和静态变量

sys_call_table 位于__start_rodata ~ __init_begin区间,由于是rodata ,因此要将其内核虚拟地址的内存属性修改为可写,将其对应的页表项 pte 改为可写即可。

// arch/arm64/include/asm/pgtable-prot.h

#define PAGE_KERNEL			__pgprot(PROT_NORMAL)
#define PAGE_KERNEL_RO		__pgprot((PROT_NORMAL & ~PTE_WRITE) | PTE_RDONLY)

我在这里调用 update_mapping_prot 函数来将整个.rodata segment改为可写,该函数可参考:Linux ARM64 update_mapping_prot函数

二、代码演示

(1)

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kallsyms.h> 
#include <linux/syscalls.h>
#include <asm/unistd.h>
#include <asm/ptrace.h> 

void (*update_mapping_prot)(phys_addr_t phys, unsigned long virt, phys_addr_t size, pgprot_t prot);

// .rodata segment 区间
unsigned long start_rodata;
unsigned long init_begin;

#define section_size init_begin - start_rodata

static unsigned long *__sys_call_table;

typedef long (*syscall_fn_t)(const struct pt_regs *regs);

#ifndef __NR_mkdirat
#define __NR_mkdirat 34
#endif

//用于保存原始的 mkdir 系统调用
static syscall_fn_t orig_mkdir;

//mkdir hook 函数
asmlinkage long mkdir_hook(const struct pt_regs *regs)
{
    printk("hook mkdir sys_call\n");
    
    return 0;
}

//修改指定内核地址范围的内存属性为只读
static inline void protect_memory(void)
{
	update_mapping_prot(__pa_symbol(start_rodata), (unsigned long)start_rodata,
			section_size, PAGE_KERNEL_RO);
}

//修改指定内核地址范围的内存属性为可读可写等
static inline void unprotect_memory(void)
{
	update_mapping_prot(__pa_symbol(start_rodata), (unsigned long)start_rodata,
			section_size, PAGE_KERNEL);
}
	
static int __init lkm_init(void)
{
    update_mapping_prot = (void *)kallsyms_lookup_name("update_mapping_prot");

	start_rodata = (unsigned long)kallsyms_lookup_name("__start_rodata");
	init_begin = (unsigned long)kallsyms_lookup_name("__init_begin");

    __sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");
    if (!__sys_call_table)
		return -1;

    printk("__sys_call_table = %lx\n", __sys_call_table);
    
    //保存原始的系统调用:mkdir
	orig_mkdir = (syscall_fn_t)__sys_call_table[__NR_mkdirat];

	//hook 系统调用表表项:sys_call_table[__NR_mkdirat]
    unprotect_memory();
    __sys_call_table[__NR_mkdirat] = (unsigned long)mkdir_hook;
    protect_memory();

    printk("lkm_init\n");

	return 0;
}

static void __exit lkm_exit(void)
{
	//模块卸载时恢复原来的mkdir系统调用
	unprotect_memory();
    __sys_call_table[__NR_mkdirat] = (unsigned long)orig_mkdir;
    protect_memory();

    printk("lkm_exit\n");
}

module_init(lkm_init);
module_exit(lkm_exit);

MODULE_LICENSE("GPL");

(2)
hook后返回原始的系统调用:

SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, umode_t, mode)
{
	return do_mkdirat(dfd, pathname, mode);
}

mkdirat系统调用的第二参数是pathname,对于arm64来说对应的x1寄存器。

/*
 * This struct defines the way the registers are stored on the stack during an
 * exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for
 * stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.
 */
struct pt_regs {
	union {
		struct user_pt_regs user_regs;
		struct {
			u64 regs[31];
			u64 sp;
			u64 pc;
			u64 pstate;
		};
	};
	......
};

即 struct pt_regs.regs[1]。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kallsyms.h> 
#include <linux/syscalls.h>
#include <asm/unistd.h>
#include <asm/ptrace.h> 

void (*update_mapping_prot)(phys_addr_t phys, unsigned long virt, phys_addr_t size, pgprot_t prot);

// .rodata segment 区间
unsigned long start_rodata;
unsigned long init_begin;

#define section_size init_begin - start_rodata

static unsigned long *__sys_call_table;

typedef long (*syscall_fn_t)(const struct pt_regs *regs);

#ifndef __NR_mkdirat
#define __NR_mkdirat 34
#endif

//用于保存原始的 mkdir 系统调用
static syscall_fn_t orig_mkdir;

asmlinkage long mkdir_hook(const struct pt_regs *regs)
{
    int ret;

    char filename[256] = {0};
    
    //获取第二个参数:const char __user *, pathname
    char __user *pathname = (char*)regs->regs[1];
    
    //返回原始系统调用
    ret = orig_mkdir(regs);
 
    printk("hook mkdir sys_call\n");
 
 	//从用户空间复制数据到内核空间
    if(copy_from_user(filename, pathname, 256))
    return -1;
   
    //打印创建的文件名
    printk("file name = %s\n", filename);

    return ret;
}

static inline void protect_memory(void)
{
	update_mapping_prot(__pa_symbol(start_rodata), (unsigned long)start_rodata,
			section_size, PAGE_KERNEL_RO);
}

static inline void unprotect_memory(void)
{
	update_mapping_prot(__pa_symbol(start_rodata), (unsigned long)start_rodata,
			section_size, PAGE_KERNEL);
}
	
static int __init lkm_init(void)
{
    update_mapping_prot = (void *)kallsyms_lookup_name("update_mapping_prot");

	start_rodata = (unsigned long)kallsyms_lookup_name("__start_rodata");
	init_begin = (unsigned long)kallsyms_lookup_name("__init_begin");

    __sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");
    if (!__sys_call_table)
		return -1;

    printk("__sys_call_table = %lx\n", __sys_call_table);
    
    //保存原始的系统调用:mkdir
	orig_mkdir = (syscall_fn_t)__sys_call_table[__NR_mkdirat];

	//hook 系统调用表表项:sys_call_table[__NR_mkdirat]
    unprotect_memory();
    __sys_call_table[__NR_mkdirat] = (unsigned long)mkdir_hook;
    protect_memory();

    printk("lkm_init\n");

	return 0;
}

static void __exit lkm_exit(void)
{
	//模块卸载时恢复原来的mkdir系统调用
	unprotect_memory();
    __sys_call_table[__NR_mkdirat] = (unsigned long)orig_mkdir;
    protect_memory();

    printk("lkm_exit\n");
}

module_init(lkm_init);
module_exit(lkm_exit);

MODULE_LICENSE("GPL");

三、其他方案

我还写了两种方案来hook系统调用,但是基本原理都一样,修改pte页表属性。

(1)直接遍历页表获取pte,修改其属性,该方案是最直观修改pte页表属性的方案,请参考:
Linux Arm64修改页表项属性

(2)通过set_memory_ro/rw函数来修改pte页表属性,set_memory_ro/rw函数不能直接来修改系统调用表的属性,需要经过一点处理,请参考:
Linux arm64 set_memory_ro/rw函数

参考资料

https://blog.csdn.net/weixin_45030965/article/details/129212657
https://blog.csdn.net/weixin_42915431/article/details/115289115

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐