Unfortunately the title might not be the best for describing the problem.
I'm learning Linux kernel programming and when I was excited when it came to kprobe and kretprobe, but I start to experience some strange(to my very low knowledge of the matter) behavior soon. Let me explain...
I installed kretprobe on __x64_sys_getdents64
, my final goal is to replicate some of the code I saw online for hiding files and directories in Linux but using kernel probes.
The getdents
prototype is,
long syscall(SYS_getdents, unsigned int fd, struct linux_dirent *dirp,
unsigned int count);
and on success it returns the number of bytes read is returned.
Fun Facts
So, kprobe post handler, I keep some information like dirp pointer, allocate some memory and read dirp which at this point supposed to be the list of files to allocated buffer on heap.
// 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;
Part of kretprobe entry handler,
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;
}
In kretprobe return handler I check if the return value is bigger than 0, meaning the function returned X number of bytes, then I read the data that is already kept for us in kprobe post handler.
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);
}
But when I try to read a file from the data we kept in kprobe post handler, there is not file available! What I observe is as follow,
Below is the full code for the kernel module(the following code is messy and violates every best practice in kernel programming world, sorry for that),
Kernel module,
#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");
The 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
To my understanding, I should get a file name in dmesg, but I just get an empty entry! When the ret value is equal to 0 then dirp is populated with file names!
Any clue is welcome.
Thanks, Jelal
Thanks to @Tsyvarev I could find the solution. I moved the reading to the ret handler, so when I copy_from_user
the actual syscall is being called and data is available.
Cheers