Перехват системных вызовов в Kali Linux (ядро версии 5)

Я пытаюсь настроить перехватчик для системного вызова bind() на Kali Linux 2021-W1 (ядро Linux версии 5), но по какой-то причине вызов исходного системного вызова не выполняется и возникает ошибка.

Вот мой код:

/* includes, license, author... */

void **sys_call_table_addr = (void **) 0xffffffff9e0002c0;

int enable_page_rw(void *ptr){
        unsigned int level;
        pte_t *pte = lookup_address((unsigned long) ptr, &level);
        if(pte->pte &~_PAGE_RW){
            pte->pte |=_PAGE_RW;
        }
        return 0;
}

int disable_page_rw(void *ptr){
        unsigned int level;
        pte_t *pte = lookup_address((unsigned long) ptr, &level);
        pte->pte = pte->pte &~_PAGE_RW;
        return 0;
}

asmlinkage int (*original_bind) (int, const struct sockaddr *, int);
asmlinkage int log_bind(int sockfd, const struct sockaddr *addr, int addrlen) {
        int ret;
        printk(KERN_INFO SOCKETLOG "bind was called");
        return (*original_bind)(sockfd, addr, addrlen);
}

static int __init socketlog_init(void) {
        printk(KERN_INFO SOCKETLOG "socketlog module has been loaded\n");

        enable_page_rw(sys_call_table_addr);
        original_bind = sys_call_table_addr[__NR_bind];
        if (!original_bind) return -1;
        sys_call_table_addr[__NR_bind] = log_bind;
        disable_page_rw(sys_call_table_addr);

        printk(KERN_INFO SOCKETLOG "original_bind = %p", original_bind);
        return 0;
}

static void __exit socketlog_exit(void) {
        printk(KERN_INFO SOCKETLOG "socketlog module has been unloaded\n");

        enable_page_rw(sys_call_table_addr);
        sys_call_table_addr[__NR_bind] = original_bind;
        disable_page_rw(sys_call_table_addr);
}

module_init(socketlog_init);
module_exit(socketlog_exit);

После выполнения sudo insmod socketlog.ko я вижу ожидаемый результат:

[  +0.000488] [SOCKETLOG] socketlog module has been loaded
[  +0.000002] [SOCKETLOG] original_bind = 00000000bbf288f1

Но каждый раз, когда вызывается bind(), я получаю странное поведение:

[  +0.000488] [SOCKETLOG] bind was called
[  +0.000005] BUG: unable to handle page fault for address: 0000000040697fb8 
[  +0.000002] #PF: supervisor read access in kernel mode
[  +0.000001] #PF: error_code(0x0000) - not-present page

Как и ожидалось, 0x0000000040697fb8 — это адрес, на который указывает 0x00000000bbf288f1: содержимое исходного системного вызова. Что мне не хватает?


person Omer Lubin    schedule 09.01.2021    source источник
comment
На какой архитектуре вы работаете? х86_64 ?   -  person Rachid K.    schedule 09.01.2021
comment
@РашидК. Да, х86_64   -  person Omer Lubin    schedule 09.01.2021


Ответы (1)


Возможно, то, как вы оборачиваете системный вызов, не работает. Например, в общей архитектуре x86_64 Linux 5.4.0-59 системный вызов в ядре вызывается через общую оболочку с именем do_syscall_64(). Он передает параметры через структуру pt_regs в запись в sys_call_table[]:

__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
    struct thread_info *ti;

    enter_from_user_mode();
    local_irq_enable();
    ti = current_thread_info();
    if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
        nr = syscall_trace_enter(regs);

    if (likely(nr < NR_syscalls)) {
        nr = array_index_nospec(nr, NR_syscalls);
        regs->ax = sys_call_table[nr](regs); <-------- Call to the entry with pt_regs structure
#ifdef CONFIG_X86_X32_ABI
    } else if (likely((nr & __X32_SYSCALL_BIT) &&
              (nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
        nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
                    X32_NR_syscalls);
        regs->ax = x32_sys_call_table[nr](regs);
#endif
    }

    syscall_return_slowpath(regs);
}

Структура pt_regs включает параметры, переданные системному вызову пользователем. Таким образом, это может объяснить, почему у вас происходит сбой: printk(...bind was call) работает, поскольку он не обращается к параметрам, но после вызова исходной записи системного вызова не соответствует ожидаемому параметры.

Если вы посмотрите на исходный код системного вызова bind() в net/socket.c, он определяется как:

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
    return __sys_bind(fd, umyaddr, addrlen);
}

Приведенный выше макрос SYSCALL_DEFINE3() расширяется в некоторые оболочки, которые извлекают параметры из структуры pt_regs.

Итак, вот в качестве примера некоторые исправления в вашем модуле, который работает на моей универсальной версии 5.4.0-60 Ubuntu x86_64:

#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/ptrace.h>
#include <linux/socket.h>
#include <linux/kallsyms.h>


MODULE_LICENSE("Dual BSD/GPL");

typedef int (* syscall_wrapper)(struct pt_regs *);

unsigned long sys_call_table_addr;

#define SOCKETLOG "[SOCKETLOG]"


int enable_page_rw(void *ptr){
  unsigned int level;
  pte_t *pte = lookup_address((unsigned long) ptr, &level);
  if(pte->pte &~_PAGE_RW){
    pte->pte |=_PAGE_RW;
  }
  return 0;
}

int disable_page_rw(void *ptr){
  unsigned int level;
  pte_t *pte = lookup_address((unsigned long) ptr, &level);
  pte->pte = pte->pte &~_PAGE_RW;
  return 0;
}

syscall_wrapper original_bind;

//asmlinkage int log_bind(int sockfd, const struct sockaddr *addr, int addrlen) {
int log_bind(struct pt_regs *regs) {
  printk(KERN_INFO SOCKETLOG "bind was called");
  return (*original_bind)(regs);
}

static int __init socketlog_init(void) {

  printk(KERN_INFO SOCKETLOG "socketlog module has been loaded\n");

  sys_call_table_addr = kallsyms_lookup_name("sys_call_table");

  printk(KERN_INFO SOCKETLOG "sys_call_table@%lx\n", sys_call_table_addr);

  enable_page_rw((void *)sys_call_table_addr);
  original_bind = ((syscall_wrapper *)sys_call_table_addr)[__NR_bind];
  if (!original_bind) return -1;
  ((syscall_wrapper *)sys_call_table_addr)[__NR_bind] = log_bind;
  disable_page_rw((void *)sys_call_table_addr);

  printk(KERN_INFO SOCKETLOG "original_bind = %p", original_bind);
  return 0;
}

static void __exit socketlog_exit(void) {
  printk(KERN_INFO SOCKETLOG "socketlog module has been unloaded\n");

  enable_page_rw((void *)sys_call_table_addr);
  ((syscall_wrapper *)sys_call_table_addr)[__NR_bind] = original_bind;
  disable_page_rw((void *)sys_call_table_addr);
}

module_init(socketlog_init);
module_exit(socketlog_exit);

С тестом:

$ sudo insmod ./bind_ovl.ko
$ dmesg
[ 2253.201888] [SOCKETLOG]socketlog module has been loaded
[ 2253.209486] [SOCKETLOG]sys_call_table@ffffffff88c013a0
[ 2253.209489] [SOCKETLOG]original_bind = 00000000f54304a9

Например, после перезагрузки веб-страницы я получаю:

$ dmesg
[ 2136.946042] [SOCKETLOG]socketlog module has been unloaded
[ 2253.201888] [SOCKETLOG]socketlog module has been loaded
[ 2253.209486] [SOCKETLOG]sys_call_table@ffffffff88c013a0
[ 2253.209489] [SOCKETLOG]original_bind = 00000000f54304a9
[ 2281.716581] [SOCKETLOG]bind was called
[ 2295.607476] [SOCKETLOG]bind was called
[ 2301.947866] [SOCKETLOG]bind was called
[ 2304.088116] [SOCKETLOG]bind was called
[ 2309.599634] [SOCKETLOG]bind was called
[ 2310.946833] [SOCKETLOG]bind was called

После выгрузки модуля:

$ sudo rmmod bind_ovl
$ dmesg
[...]
[ 2390.908456] [SOCKETLOG]bind was called
[ 2398.921475] [SOCKETLOG]bind was called
[ 2398.928855] [SOCKETLOG]socketlog module has been unloaded

Конечно, вы можете улучшить перегрузку, отобразив параметры, переданные системному вызову. На x86_64 системные вызовы передаются не более 6 параметров через регистры процессора. Мы можем получить их в структуре pt_regs. Последний определен в arch/x86/include/asm/ptrace.h как:

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 */
};

Соглашение о передаче параметров для системного вызова: параметры от 0 до 5 соответственно передаются в RDI, RSI, RDX, < strong>R10, R8 и R9.

Согласно этому правилу, для системного вызова bind() параметры находятся в следующих регистрах:

  • RDI = int (дескриптор сокета)
  • RSI = структура sockaddr *addr
  • RDX = socklen_t адрес

Затем вы можете улучшить функцию журнала с помощью чего-то вроде:

int log_bind(struct pt_regs *regs) {
  printk(KERN_INFO SOCKETLOG "bind was called(%d, %p, %u)", (int)(regs->di), (void *)(regs->si), (unsigned int)(regs->dx));
  return (*original_bind)(regs);
}

Трассы от модуля становятся более детальными:

[ 3259.589915] [SOCKETLOG]socketlog module has been loaded
[ 3259.594631] [SOCKETLOG]sys_call_table@ffffffff88c013a0
[ 3259.594634] [SOCKETLOG]original_bind = 00000000f54304a9
[ 3274.368906] [SOCKETLOG]bind was called(149, 0000000091c163d5, 12)
[ 3276.040330] [SOCKETLOG]bind was called(149, 0000000075b17cb4, 12)
[ 3278.203942] [SOCKETLOG]bind was called(188, 0000000091c163d5, 12)
[ 3287.014980] [SOCKETLOG]bind was called(214, 0000000075b17cb4, 12)
[ 3287.021167] [SOCKETLOG]bind was called(214, 0000000091c163d5, 12)
[ 3298.395713] [SOCKETLOG]bind was called(3, 000000008c2a9103, 12)
[ 3298.403249] [SOCKETLOG]socketlog module has been unloaded
person Rachid K.    schedule 09.01.2021