Зонды ядра показывают неправильные возвращаемые данные

К сожалению, название может не лучшим образом описать проблему.

Я изучаю программирование ядра Linux, и когда я был взволнован, когда дело дошло до kprobe и kretprobe, но вскоре я начал испытывать какое-то странное (на мой очень низкий уровень знаний в этом вопросе) поведение. Позволь мне объяснить...

Я установил kretprobe на __x64_sys_getdents64, моя конечная цель — скопировать часть кода, который я видел в Интернете, для сокрытия файлов и каталогов в Linux, но с использованием тестов ядра.

Прототип getdents:

long syscall(SYS_getdents, unsigned int fd, struct linux_dirent *dirp,
                    unsigned int count);

и в случае успеха возвращается количество прочитанных байтов.

Забавные факты

  • В обработчике возврата kretprobe у нас нет доступа к аргументам функции, только к возвращаемому значению, и мы также можем изменить возвращаемое значение.
  • В обработчике записи kretprobe у нас есть аргументы функции доступа

Итак, в обработчике сообщений kprobe я сохраняю некоторую информацию, такую ​​​​как указатель dirp, выделяю немного памяти и читаю dirp, который на данный момент должен быть списком файлов для выделенного буфера в куче.

// Global variables
struct linux_dirent64 *g_dirp = NULL;
unsigned long g_count = GETDENTS_COUNT_DUMMY;
char* g_kdirp_buf = NULL;
int g_is_copied = 0;

Часть обработчика записи kretprobe,

    g_dirp = dirp;
    g_count = count;
    g_kdirp_buf = kzalloc(count, GFP_KERNEL);
    if (g_kdirp_buf == NULL)
    {
        printk("ERR kmalloc() failed!\n");
        return 0;
    }

    if (copy_from_user(g_kdirp_buf, dirp, count))
    {
        printk("ERR copy_from_user() failed!\n");
        kfree(g_kdirp_buf);
        return 0;
    }

В обработчике возврата kretprobe я проверяю, больше ли возвращаемое значение 0, то есть функция вернула X байт, затем я читаю данные, которые уже сохранены для нас в обработчике сообщений kprobe.

    long ret;
    ret = regs_return_value(regs);
    // Technically none-zero return value means there should be some data in dirp
    if (ret != 0)
    {
        unsigned long offset = 0;
        struct linux_dirent64 *d;
        d = (struct linux_dirent64 *)(g_kdirp_buf + offset);
    }

Но когда я пытаюсь прочитать файл из данных, которые мы храним в обработчике сообщений kprobe, файл недоступен! Я наблюдаю следующее:

  • Если возвращаемое значение не равно нулю, в каталоге нет файла.
  • Когда возвращаемое значение равно 0, я получаю список файлов в каталоге.

Ниже приведен полный код модуля ядра (следующий код беспорядочен и нарушает все лучшие практики в мире программирования ядра, извините за это),

модуль ядра,

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/unistd.h>
#include <linux/slab.h>

#define ROOTKIT_PATTERN                 "example.txt"
#define GETDENTS_COUNT_DUMMY            9999888221

struct linux_dirent
{
    unsigned long   d_ino;
    unsigned long   d_off;
    unsigned short  d_reclen;
    char            d_name[];
};

struct linux_dirent64
{
    uint64_t        d_ino;
    int64_t         d_off;
    unsigned short  d_reclen;
    unsigned char   d_type;
    char            d_name[];
};

spinlock_t supp_len_lock;

struct linux_dirent64 *g_dirp = NULL;
unsigned long g_count = GETDENTS_COUNT_DUMMY;
char* g_kdirp_buf = NULL;
int g_is_copied = 0;

static int handler_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
#if IS_ENABLED(CONFIG_X86_64)
    // int fd = ((struct pt_regs*)regs->di)->di;
    void *dirv = (void *)((struct pt_regs*)regs->di)->si;
    struct linux_dirent64 *dirp = (struct linux_dirent64 *)dirv;
    unsigned long count = ((struct pt_regs*)regs->di)->dx;
#elif IS_ENABLED(CONFIG_ARM64)
    // int fd = ((struct pt_regs*)regs->regs[1])->regs[0];
    void *dirv = (void *)((struct pt_regs*)regs->regs[1])->regs[1];
    struct linux_dirent64 *dirp = (struct linux_dirent64 *)dirv;
    unsigned long count = ((struct pt_regs*)regs[1])->regs[2];
#endif

    g_dirp = dirp;
    g_count = count;
    g_kdirp_buf = kzalloc(count, GFP_KERNEL);
    if (g_kdirp_buf == NULL)
    {
        printk("ERR kmalloc() failed!\n");
        return 0;
    }

    if (copy_from_user(g_kdirp_buf, dirp, count))
    {
        printk("ERR copy_from_user() failed!\n");
        kfree(g_kdirp_buf);
        return 0;
    }

    return 0;
}

static int handler_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    long ret;

    if ((g_dirp == NULL) || (g_kdirp_buf == NULL) || (g_count == GETDENTS_COUNT_DUMMY))
    {
        printk("[handler_ret]\t\tINITIALIZATION FAILED\n");
        return 0;
    }

    ret = regs_return_value(regs);


    if (ret != 0)
    {
        unsigned long offset = 0;
        struct linux_dirent64 *d;
        d = (struct linux_dirent64 *)(g_kdirp_buf + offset);
        printk("%s\n", d->d_name);
    }

    if (g_kdirp_buf != NULL)
    {
        kfree(g_kdirp_buf);
    }

    return 0;
}

static struct kretprobe kretprobe = {
    .handler = handler_ret,
    .entry_handler = handler_entry,
    .kp.symbol_name = "__x64_sys_getdents64"
};


static int __init kretprobe_init(void)
{
    spin_lock_init(&supp_len_lock);

    register_kretprobe(&kretprobe);
    return 0;
}

static void __exit kretprobe_exit(void)
{

    unregister_kretprobe(&kretprobe);
}

module_init(kretprobe_init)
module_exit(kretprobe_exit)
MODULE_LICENSE("GPL");

Makefile,

obj-m += hidproc.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Насколько я понимаю, я должен получить имя файла в dmesg, но получаю просто пустую запись! Когда значение ret равно 0, dirp заполняется именами файлов!

  • Я делаю что-то не так в своем коде? А может логика неправильная!
  • Почему я не получаю данные, когда возвращаемое значение больше нуля?

Любая подсказка приветствуется.

Спасибо, Джелал

Функция .entry_handler выполняется в начале подключенной функции. Итак, copy_from_user вызывается из handler_entry, копируя содержимое dirp, которое еще не было изменено SYS_getdents. Чтобы получить окончательный контент dirp, вам нужно вместо этого вызвать copy_from_user в handler_ret.

Tsyvarev 14.06.2024 10:32

Итак, я просто сохраняю указатель из обработчика записи и выполняю copy_from_user() в обработчике возврата? Могу ли я вообще вызывать copy_from_user() или copy_to_user() изнутри обработчика возврата kretprobe?

Jelal 14.06.2024 10:34

«В обработчике возврата kretprobe у нас нет доступа к аргументам функции» — это не совсем так. У вас есть доступ к аргументам функции через параметр regs: stackoverflow.com/questions/22686393/…. Кроме того, для передачи данных из .entry_handler в .handler вместо глобальных переменных лучше использовать структуру kretprobe_instance. Посмотрите, как .data_size используется в другом коде.

Tsyvarev 14.06.2024 10:37

«Мне вообще разрешено вызывать copy_from_user() или copy_to_user() изнутри обработчика возврата kretprobe?» - Я не вижу никаких ограничений в использовании функций сна внутри kprobes.

Tsyvarev 14.06.2024 10:40

собираюсь попробовать это прямо сейчас! Спасибо

Jelal 14.06.2024 14:30

@Цыварев, это сработало. Если вы опубликуете ответ, я отмечу его как принятый. Спасибо.

Jelal 17.06.2024 01:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Благодаря @Tsyvarev я смог найти решение. Я переместил чтение в обработчик ret, поэтому, когда я copy_from_user вызывается настоящий системный вызов, данные доступны.

Ваше здоровье

Другие вопросы по теме