After the release of RHEL 8.0, the era of RHEL 8.x began, and centos and Oracle Linux also released 8.x based on RHEL. I installed centos8.0 some time ago, but when I compiled and ran the code of the hook kernel written before, I found that the previous hook method did not work.

[PATCH 000/109] Remove in-kernel calls to Syscalls

On 64-bit x86 platforms, kernels 4.17 and up have adopted a new calling convention that uses only one parameter, the struct pT_regs structure pointer, to parse out the required parameters in real time and pass them to the actual system call handler.

Note: This article is based on the RHEL series of Linux version experiments, also applicable to Ubuntu, SUSE, Debian, Kylin and other systems, readers can try their own.

Below we through the kernel source to explore in centos8.0(based on 4.18.0-80 kernel source) system, how to hook the system call.

Experimental environment of this paper:

[root@yglocalhost ~]# uname -r
4.18.0-80.el8.x86_64
[root@yglocalhost ~]# cat /etc/redhat-release
CentOS Linux release 8.0.1905 (Core)
Copy the code

This article takes The Hook of OpenAT system call as an example. For the detailed analysis of the definition of kernel system call, see the Linux system call kernel source code analysis (based on the kernel 4.18.0-87 version).

The source code inside

Include/Linux /syscalls. H

asmlinkage long sys_openat(int dfd, const char __user *filename, int flags,         
      umode_t mode);
Copy the code

01 System call prototype

The definition of system call openat was found in the fs\open.c file:

SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, 
    umode_t, mode){
     if (force_o_largefile())
            flags |= O_LARGEFILE;  

    return do_sys_open(dfd, filename, flags, mode);
} 
Copy the code

The SYSCALL_DEFINE4(openAT, XXX) macro is expanded as follows:

SYSCALL_METADATA(_openat, 4, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
asmlinkage long __x64_sys_openat(const struct pt_regs *regs);
ALLOW_ERROR_INJECTION(__x64_sys_openat, ERRNO);
static long __se_sys_openat(__MAP(4,__SC_LONG,__VA_ARGS__));
static inline long __do_sys_openat(__MAP(4,__SC_DECL,__VA_ARGS__));

asmlinkage long __x64_sys_openat(const struct pt_regs *regs)
{
    return __se_sys_openat(SC_X86_64_REGS_TO_ARGS(4,__VA_ARGS__));
}

//__IA32_SYS_STUBx(x, name, __VA_ARGS__)
static long __se_sys_openat(__MAP(4,__SC_LONG,__VA_ARGS__))
{
    long ret = __do_sys_openat(__MAP(4,__SC_CAST,__VA_ARGS__));     
    __MAP(4,__SC_TEST,__VA_ARGS__); 
    __PROTECT(4, ret,__MAP(4,__SC_ARGS,__VA_ARGS__));
    return ret;
}

static inline long __do_sys_openat(int dfd, const char __user *filename, int flags, 
    umode_t mode){
    if (force_o_largefile())
        flags |= O_LARGEFILE;

    return do_sys_open(AT_FDCWD, filename, flags, mode);
}
Copy the code

So, an openAT system call from the application layer to the kernel is essentially:

asmlinkage long __x64_sys_openat(const struct pt_regs *regs);
Copy the code

We can see that by command

[root@yglocal /]# grep __x64_sys_openat /proc/kallsyms 
ffffffffbb0b60e0 T __x64_sys_openat
ffffffffbc796c50 t _eil_addr___x64_sys_openat
Copy the code

For x86-64, in kernel versions 4.17 and above, the system call name is prefixed with “_x64”. So the kernel address of the system call table for the __NR_xxx subscript is pointed to here (__x64_sys_openat).

It is now clear that the prototype for a hook system call looks like this:

asmlinkage long __x64_sys_xxxx(const struct pt_regs *);
Copy the code

02 Parameter Analysis

Arch \x86\include\asm\ptrace.h struct pt_regs (arch\x86\include\asm\ptrace.

struct pt_regs {
/*
 * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
 * unless syscall needs a complete, fully filled "struct pt_regs".
 */
	unsigned long r15;
	unsigned long r14;
	unsigned long r13;
	unsigned long r12;
	unsigned long bp;
	unsigned long bx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
	unsigned long r11;
	unsigned long r10;
	unsigned long r9;
	unsigned long r8;
	unsigned long ax;
	unsigned long cx;
	unsigned long dx;
	unsigned long si;
	unsigned long di;
/*
 * On syscall entry, this is syscall#. On CPU exception, this is error code.
 * On hw interrupt, it's IRQ number:
 */
	unsigned long orig_ax;
/* Return frame for iretq */
	unsigned long ip;
	unsigned long cs;
	unsigned long flags;
	unsigned long sp;
	unsigned long ss;
/* top of stack page */
};
Copy the code

Struct pt_regs (struct pt_regs, struct pt_regs, struct pt_regs, struct pt_regs, struct pt_regs)

/* Mapping of registers to parameters for syscalls on x86-64 and x32 */
#define SC_X86_64_REGS_TO_ARGS(x, ...)					\
	__MAP(x,__SC_ARGS						\
		,,regs->di,,regs->si,,regs->dx				\
		,,regs->r10,,regs->r8,,regs->r9)			\
Copy the code

The system call parameters should be regs->di,regs->si,regs->dx,regs-> R10, regS -> R8, regS -> R9.

Arch \x86\entry\entry_64.S: arch\x86\entry\entry_64.It can be seen that the six parameters are in RDI, RSI, RDX, R10, R8 and R9 respectively.

Since then, the problem is solved, hook system call prototype and parameters are clear.

coded

1 System call prototype to hook

typedef asmlinkage long(*sys_call_ptr_t)(const struct pt_regs *);
Copy the code

For openAT, save the system’s old OpenAT address and declare it as follows:

sys_call_ptr_t old_openat;
old_openat = sys_call_table[__NR_openat];
Copy the code

2 Customize hook functions

Define our own my_openAT handler to replace the old OpenAT

static asmlinkage long my_openat(const struct pt_regs *regs)
Copy the code

3 Parameter Processing

From the previous analysis, we know that at most 6 parameters of system call correspond to 6 registers in struct pt_regs *regs respectively, which are di, SI, dx, R10, R8 and R9 in sequence.

Back to the declaration of the system call Openat:

asmlinkage long sys_openat(int dfd,const char__user *filename,int flags,umode_t mode);
Copy the code

So, the first argument DFD is in regs->di the second argument filename is in regs->si the third argument flags is in regs->dx the fourth argument mode is in regS -> R10

4 Complete code implementation

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *sys_call_table;
sys_call_ptr_t old_openat; //

void disable_write_protect(void)
{
    write_cr0(read_cr0() & (~0x10000));
}

void enable_write_protect(void)
{
    write_cr0(read_cr0() | 0x10000);   
}

static asmlinkage long my_openat(const struct pt_regs *regs)
{
    int dfd = regs->di;
    char __user *filename = (char *)regs->si;
    char user_filename[256] = {0};
    int ret = raw_copy_from_user(user_filename, filename, sizeof(user_filename));

    printk("%s. proc:%s, pid:%d, dfd:%d, filename:[%s], copy ret:%d\n", __func__,
        current->group_leader->comm, current->tgid, dfd, user_filename, ret);

    return old_openat(regs);
}

static int __init test_init(void)
{
    sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name("sys_call_table");
    old_openat = sys_call_table[__NR_openat];

    printk("[info] %s. old_openat:0x%llx\n", __func__, old_openat);

    disable_write_protect();
    sys_call_table[__NR_openat] = my_openat;
    enable_write_protect();

    printk("%s inserted.\n",__func__);

    return 0;
}

static void __exit test_exit(void)
{
    disable_write_protect();
    sys_call_table[__NR_openat] = old_openat;
    enable_write_protect();

    printk("%s removed.\n",__func__);
}

module_init(test_init);
module_exit(test_exit);
Copy the code

After compiling to KO, Insmod is loaded into the kernel and the test results are shown below:

This article is also available on ubuntu 4.17 and up, so you can try it out on the latest Ubuntu OS.

For a detailed description of the hook system call, see the complete hook call example. You can follow my wechat public account Big fat chat programming, contact me through the public account, you can add friends to exchange and learn together.