К сожалению, название может не лучшим образом описать проблему.
Я изучаю программирование ядра Linux, и когда я был взволнован, когда дело дошло до kprobe и kretprobe, но вскоре я начал испытывать какое-то странное (на мой очень низкий уровень знаний в этом вопросе) поведение. Позволь мне объяснить...
Я установил kretprobe на __x64_sys_getdents64, моя конечная цель — скопировать часть кода, который я видел в Интернете, для сокрытия файлов и каталогов в Linux, но с использованием тестов ядра.
Прототип getdents:
long syscall(SYS_getdents, unsigned int fd, struct linux_dirent *dirp,
unsigned int count);
и в случае успеха возвращается количество прочитанных байтов.
Забавные факты
Итак, в обработчике сообщений 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, файл недоступен! Я наблюдаю следующее:
Ниже приведен полный код модуля ядра (следующий код беспорядочен и нарушает все лучшие практики в мире программирования ядра, извините за это),
модуль ядра,
#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 заполняется именами файлов!
Любая подсказка приветствуется.
Спасибо, Джелал
Итак, я просто сохраняю указатель из обработчика записи и выполняю copy_from_user() в обработчике возврата? Могу ли я вообще вызывать copy_from_user() или copy_to_user() изнутри обработчика возврата kretprobe?
«В обработчике возврата kretprobe у нас нет доступа к аргументам функции» — это не совсем так. У вас есть доступ к аргументам функции через параметр regs: stackoverflow.com/questions/22686393/…. Кроме того, для передачи данных из .entry_handler в .handler вместо глобальных переменных лучше использовать структуру kretprobe_instance. Посмотрите, как .data_size используется в другом коде.
«Мне вообще разрешено вызывать copy_from_user() или copy_to_user() изнутри обработчика возврата kretprobe?» - Я не вижу никаких ограничений в использовании функций сна внутри kprobes.
собираюсь попробовать это прямо сейчас! Спасибо
@Цыварев, это сработало. Если вы опубликуете ответ, я отмечу его как принятый. Спасибо.





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