Linux ARM64 hook系统调用
Linux ARM64 hook mkdir 系统调用 简介
文章目录
前言
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
更多推荐
所有评论(0)