I'm trying to complete a hooking sample attachment in a program for my uni assignment. The task requires to get a system call sys_rt_sigaction hooked when initiating a loadable module in Linux kernel (I use Ubuntu 18.04 LTS, kernel version is 5.0.0-23-generic). So, the case I'm struggling originates from an error could not insert module <module name>: Unknown symbol in module once I started sudo insmod <my module name>.ko.
After some googling, I see clear this problem arises due to missing sys_call_table export to run inserting as smoothly as well. Following this post, I want to cope that invoking kallsyms_lookup_name call before kicking off init procedure.
There is .c-file which provides with definitions of operations accessible by module (file name is buffer.c):
#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/unistd.h>
void * sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");// some wrongness here, but what exactly?
MODULE_LICENSE("GPL");
int (*real_rt_sigaction)(const char * path); // true syscall prototype
static int __init buffer_init_module(void);
static void __exit buffer_exit_module(void);
static int device_open(struct inode *, struct file *); // driver file opening
static int device_release(struct inode *, struct file *); // return of system resource control
static ssize_t device_read(struct file *, char *, size_t, loff_t *); // reading from driver file
static ssize_t device_write(struct file *, const char *, size_t, loff_t *); // writing into driver file
#define DEVICE_NAME "buffer"
#define BUF_LEN 80
// to be called instead
int alter_rt_sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact, size_t sigsetsize) {
printk(KERN_INFO "Syscall function hooked - you've lost control of your experience");
return 0;
}
static int Major;
static int Device_Open = 0;
static int total_open = 1;
static char Buf[BUF_LEN + 1] = "Buffer is empty, add some input\n";
static char *Msg_ptr;
static int Buf_Char = 50;
static int Bytes_Read = 0;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
static int __init buffer_init_module(void)
{
printk(KERN_INFO
"Device initializing in progress...");
Major = register_chrdev(0, DEVICE_NAME, &fops);
if(Major < 0) {
printk("Major number hasn't been assigned - Driver registration failed\n");
return Major;
}
printk(KERN_INFO "Registration success - device major number: %d\n", Major);
real_rt_sigaction=sys_call_table[__NR_rt_sigaction];
sys_call_table[__NR_rt_sigaction]=alter_rt_sigaction; // hooking implementation
return 0;
}
static void __exit buffer_exit_module(void)
{
unregister_chrdev(Major, DEVICE_NAME);
printk(KERN_INFO "Outside the module - exit successfully completed\n");
sys_call_table[__NR_rt_sigaction]=real_rt_sigaction; // original call reset
}
static int device_open(struct inode *inode, struct file *file)
{
if(Device_Open)
return -EBUSY;
Device_Open++;
printk(KERN_INFO "Device file has been accessed %d time(s)\n", total_open++);
Msg_ptr = Buf;
try_module_get(THIS_MODULE);
Bytes_Read = 0;
return 0;
}
static int device_release(struct inode * node, struct file * filep)
{
Device_Open--;
module_put(THIS_MODULE);
printk(KERN_INFO "Device file gets close\n");
return 0;
}
static ssize_t device_read(struct file * filep, char * buffer, size_t len, loff_t * offset)
{
int got_read = Bytes_Read;
if(Bytes_Read >= Buf_Char)
return 0;
while(len && (Bytes_Read < Buf_Char)) {
put_user(Msg_ptr[Bytes_Read], buffer+Bytes_Read);
len--;
Bytes_Read++;
}
return Bytes_Read-got_read;
}
static ssize_t device_write(struct file * filep, const char * buffer, size_t len, loff_t * offset)
{
Buf_Char = 0;
if(Buf_Char >= BUF_LEN) {
return 0;
}
while(len && (Buf_Char < BUF_LEN))
{
get_user(Msg_ptr[Buf_Char], buffer+Buf_Char);
len--;
Buf_Char++;
}
return Buf_Char;
}
module_init(buffer_init_module);
module_exit(buffer_exit_module);
Additively, there is code in Makefile:
obj-m += buffer.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
The painful moment here is an error message initializer element is not constant whenever I was trying to build module via sudo make in my project folder. As I follow the beginner's tutorials and need for some basic insight, it might be highly appreciable to see any help with solution or even some ideas how to handle the same problem more effectively, indeed.
I created the device file under /dev folder successfully, but writing to that device file makes kernel to throw following error messages.
kernel:[10090.943733] Internal error: : 1b [#3] PREEMPT SMP ARM
kernel:[10091.049020] Process echo (pid: 3728, stack limit = 0xdc40a218)
kernel:[10091.054880] Stack: (0xdc40be60 to 0xdc40c000)
kernel:[10091.059267] be60: c15491c6 00000022 dc5cb14c bf30430c dc40bedc dc40be88 c075312c c074fe5c
kernel:[10091.067488] be80: c0753018 ffffff04 ffff0a00 c140414c c0d407c8 bf30430c c140414c 40cfbcf3
kernel:[10091.075709] bea0: 00852878 ffffff04 ffff0a00 00040952 c01a7404 c140414c 00852878 00852878
Segmentation fault
I know very basic of Linux Device Drivers
can anyone help me regarding this??
I'm attaching the code for this character device driver
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/device.h>
MODULE_AUTHOR("RUCHA");
MODULE_DESCRIPTION("Character Driver First test");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.1");
static int MajorNum;
static struct class* RetValOfClassRegistration = NULL;
static struct device* RetVal = NULL;
static char message[1024];
static int openDev(struct inode * , struct file *);
static int closeDev(struct inode * , struct file *);
static ssize_t readDev(struct file *, char *, size_t, loff_t *);
static ssize_t writeDev(struct file *, const char *, size_t, loff_t *);
static struct file_operations FileOps = {
.owner = THIS_MODULE,
.open = openDev,
.read = readDev,
.write = writeDev,
.release = closeDev,
};
static int registerCharDev(void){
return register_chrdev(0,"MyDev",&FileOps);
}
static int __init Loaded(void){
// registering device
MajorNum = registerCharDev();
if(MajorNum < 0){
printk("Can not register device\n");
return -1;
}
printk("Driver Loaded with %d \n",MajorNum);
// registering device class
RetValOfClassRegistration = class_create(THIS_MODULE,"MyCharacterDriver");
if(RetValOfClassRegistration < 0){
printk("can not register class for driver number : %d\n",MajorNum);
return 0;
}
// register the driver
RetVal = device_create(RetValOfClassRegistration,NULL,MKDEV(MajorNum,0),NULL,"MyDev");
return 0;
}
static void __exit Removed(void){
device_destroy(RetValOfClassRegistration,MKDEV(MajorNum,0));
class_unregister(RetValOfClassRegistration);
class_destroy(RetValOfClassRegistration);
unregister_chrdev(MajorNum,"MyDev");
printk("Driver Removed\n");
}
module_init(Loaded);
module_exit(Removed);
static int openDev(struct inode *inodep , struct file *filep){
printk("Device is now open to read write operations\n");
return 0;
}
static int closeDev(struct inode *inodep , struct file *filep){
printk("Device Closed\n");
return 0;
}
static ssize_t readDev(struct file *filep, char *c, size_t v, loff_t *lp){
printk("Read From the device\n");
return 0;
}
static ssize_t writeDev(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
sprintf(message, "%s(%zu letters)", buf, len);
return 0;
}
and the user input is
echo '1' > /dev/MyDev
You can't access a user data pointer (buf) directly. You need to use copy_from_user to copy the data into kernel memory first. Also, use snprintf instead of sprintf to avoid buffer overflows.
Also, compile with debugging enabled, especially CONFIG_DEBUG_INFO, to get more helpful error messages in the kernel log.
I am a windows driver programmer, who is also a complete beginner in linux kernel development. im trying out simple linux kernel modules.
I have written a parallel port driver as in the following code and could successfully load this driver.
I have created a char device node using command mknod with same major and minor numbers as in the code. I have tried reading from the file using cat command and i found it was working fine. While i tried writing the same device using command cat in.txt>/dev/parallel_dev the prompt shows a message "killed" and in dmesg, it has shown unable to handle kernel NULL pointer exception.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h> //kprintf
#include<linux/module.h> //kmalloc
#include<linux/types.h>
#include<linux/fs.h> //fileoperations,register_chrdev
#include<linux/slab.h>
#include<linux/uaccess.h> //copy from/to user
#include<linux/ioport.h> //check_region
#include<linux/interrupt.h>
#include<linux/delay.h>
static int parallel_open(struct inode* p_node, struct file* p_file);
static int parallel_release(struct inode* p_node, struct file* p_file);
static ssize_t parallel_read(struct file* p_file, char* buf, size_t count, loff_t* f_pos);
static ssize_t parallel_write(struct file* p_file, const char* buf, size_t count, loff_t* f_pos);
static irqreturn_t parallel_interrupt(int irq,void* dev);
static const int major_no = 709;
static const char* dev_name = "parallel_tryout";
#define par_base 0x378
int port;
int* parallel_buffer;
struct file_operations fops = {
.read = parallel_read,
.write = parallel_write,
.open = parallel_open,
.release = parallel_release,
};
static int parallel_init(void)
{
int reg_result;
int irq_req_result;
unsigned int irq_num = 7;
printk("Entered init");
printk("Initializing parallelport dev with major %d",major_no);
reg_result = register_chrdev(major_no, dev_name, 0);
if(reg_result < 0 )
{
printk("Error registering char dev: %s",dev_name);
}
parallel_buffer = kmalloc(16,GFP_KERNEL);
if(parallel_buffer==NULL)
{
printk("Error allocating memory for: %s",dev_name);
}
printk("Port registration");
request_region(0x378,1,"parallelport");
if(port)
{
printk("Error allocating port");
}
parallel_buffer = inb(par_base);
if(parallel_buffer != NULL)
{
printk("Allocated buffer");
}
irq_req_result = request_irq(irq_num,parallel_interrupt,IRQF_SHARED,dev_name,parallel_buffer);
return 0;
}
static void parallel_exit(void)
{
unregister_chrdev(major_no, dev_name);
release_region(0x378,1);
}
static int parallel_open(struct inode* p_node, struct file* p_file)
{
printk("parallel driver opened from device: %s",dev_name);
return 0;
}
static int parallel_release(struct inode* p_node, struct file* p_file)
{
printk("Memory driver closed from device: %s",dev_name);
return 0;
}
static ssize_t parallel_read(struct file* p_file, char* buf, size_t count, loff_t* f_pos)
{
copy_to_user(buf,parallel_buffer,1);
printk("Copied data:%s ",buf);
printk("Memory driver read from device: %s",dev_name);
return 0;
}
static ssize_t parallel_write(struct file* p_file, const char* buf, size_t count, loff_t* f_pos)
{
copy_from_user(parallel_buffer,buf,1);
printk("Copied data:%s ",parallel_buffer);
printk("Memory driver written from device: %s",dev_name);
return 0;
}
static irqreturn_t parallel_interrupt(int irq,void* dev)
{
irqreturn_t return_value;
printk("parallel interrupt detected!!!!");
return return_value;
}
module_init(parallel_init);
module_exit(parallel_exit);
what could be the possible reason for this, and thanks in advance.
I tried to probe a simple function (e.g. myfunc) which I added in the kernel as following:
I created a file (myfile.c) under ~/source/kernel/ i.e. ~/source/kernel/myfile.c
I added a simple system call mysyscall and a local function myfunc in this file.
mysyscall function calls myfunc function.
I can get the address of the function using
cat /proc/kallsyms | grep myfunc
But the kprobe handler doesn't get called when I call the myfunc.
I can probe the system call 'mysyscall'. But when I try to probe 'myfunc', the handler doesn't get called.
Can anyone please explain why this is the behavior? Thanks.
As asked by Eugene, below is the code for kprobe and, mysyscall & myfunc.
The kprobe handler doesn't get called in the following code. But if i uncomment Line B and comment A in kprobe code given below, then kprobe handler gets called.
I used kernel version 4.8.
I added ~/source/kernel/myfile.c to write mysyscall and myfunc as given below:
#include <linux/linkage.h>
#include <linux/export.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/printk.h>
#include <linux/slab.h>
extern int myfunc(int ax)
{
int x = 6;
return x;
}
asmlinkage int* sys_mysyscall(int bx){
int *retval;
int ret = 0;
printk(KERN_ALERT "Hello World!\n");
ret = myfunc(10);
retval = kmalloc(sizeof(int), GFP_KERNEL);
*retval = 55;
printk("sum: %d\n", *retval);
printk("myfunc return value: %d\n", ret);
return retval;
}
EXPORT_SYMBOL(sys_mysyscall);
kprobe module code is as below:
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/kprobes.h>
//Line A
static const char *probed_func = "myfunc";
//Line B
//static const char *probed_func = "sys_mysyscall";
static unsigned int counter = 0;
int Pre_Handler(struct kprobe *p, struct pt_regs *regs){
printk("Pre_Handler: counter=%u\n",counter++);
return 0;
}
void Post_Handler(struct kprobe *p, struct pt_regs *regs, unsigned long flags){
printk("Post_Handler: counter=%u\n",counter++);
}
static struct kprobe kp;
int myinit(void)
{
int error;
printk("module inserted\n ");
kp.pre_handler = Pre_Handler;
kp.post_handler = Post_Handler;
kp.addr = (kprobe_opcode_t *)kallsyms_lookup_name(probed_func);
error = register_kprobe(&kp);
if(error)
{
pr_err("can't register_kprobe :(\n");
return error;
}
else
{
printk("probe registration successful\n");
}
return 0;
}
void myexit(void)
{
unregister_kprobe(&kp);
printk("module removed\n ");
}
module_init(myinit);
module_exit(myexit);
MODULE_AUTHOR("psin");
MODULE_DESCRIPTION("KPROBE MODULE");
MODULE_LICENSE("GPL");
I use a kernel module to call mysyscall as below:
sys_mysyscall(12);//12 is some random integer as parameter
Goal: Load .so or executable that has been verified to be signed (or verified against an arbitrary algorithm).
I want to be able to verify a .so/executable and then load/execute that .so/executable with dlopen/...
The wrench in this is that there seems to be no programmatic way to check-then-load. One could check the file manually and then load it after.. however there is a window-of-opportunity within which someone could swap out that file for another.
One possible solution that I can think of is to load the binary, check the signature and then dlopen/execvt the /proc/$PID/fd.... however I do not know if that is a viable solution.
Since filesystem locks are advisory in Linux they are not so useful for this purpose... (well, there's mount -o mand ... but this is something for userlevel, not root use).
Many dynamic linkers (including Glibc's) support setting LD_AUDIT environment variable to a colon-separated list of shared libraries. These libraries are allowed to hook into various locations in the dynamic library loading process.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
unsigned int la_version(unsigned int v) { return v; }
unsigned int la_objopen(struct link_map *l, Lmid_t lmid, uintptr_t *cookie) {
if (!some_custom_check_on_name_and_contents(l->l_name, l->l_addr))
abort();
return 0;
}
Compile this with cc -shared -fPIC -o test.so test.c or similar.
You can see glibc/elf/tst-auditmod1.c or latrace for more examples, or read the Linkers and Libraries Guide.
Very very specific to Glibc's internals, but you can still hook into libdl at runtime.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
extern struct dlfcn_hook {
void *(*dlopen)(const char *, int, void *);
int (*dlclose)(void *);
void *(*dlsym)(void *, const char *, void *);
void *(*dlvsym)(void *, const char *, const char *, void *);
char *(*dlerror)(void);
int (*dladdr)(const void *, Dl_info *);
int (*dladdr1)(const void *, Dl_info *, void **, int);
int (*dlinfo)(void *, int, void *, void *);
void *(*dlmopen)(Lmid_t, const char *, int, void *);
void *pad[4];
} *_dlfcn_hook;
static struct dlfcn_hook *old_dlfcn_hook, my_dlfcn_hook;
static int depth;
static void enter(void) { if (!depth++) _dlfcn_hook = old_dlfcn_hook; }
static void leave(void) { if (!--depth) _dlfcn_hook = &my_dlfcn_hook; }
void *my_dlopen(const char *file, int mode, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%s, %d, %p)\n", __func__, file, mode, dl_caller);
enter();
result = dlopen(file, mode);
leave();
return result;
}
int my_dlclose(void *handle) {
int result;
fprintf(stderr, "%s(%p)\n", __func__, handle);
enter();
result = dlclose(handle);
leave();
return result;
}
void *my_dlsym(void *handle, const char *name, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, name, dl_caller);
enter();
result = dlsym(handle, name);
leave();
return result;
}
void *my_dlvsym(void *handle, const char *name, const char *version, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%p, %s, %s, %p)\n", __func__, handle, name, version, dl_caller);
enter();
result = dlvsym(handle, name, version);
leave();
return result;
}
char *my_dlerror(void) {
char *result;
fprintf(stderr, "%s()\n", __func__);
enter();
result = dlerror();
leave();
return result;
}
int my_dladdr(const void *address, Dl_info *info) {
int result;
fprintf(stderr, "%s(%p, %p)\n", __func__, address, info);
enter();
result = dladdr(address, info);
leave();
return result;
}
int my_dladdr1(const void *address, Dl_info *info, void **extra_info, int flags) {
int result;
fprintf(stderr, "%s(%p, %p, %p, %d)\n", __func__, address, info, extra_info, flags);
enter();
result = dladdr1(address, info, extra_info, flags);
leave();
return result;
}
int my_dlinfo(void *handle, int request, void *arg, void *dl_caller) {
int result;
fprintf(stderr, "%s(%p, %d, %p, %p)\n", __func__, handle, request, arg, dl_caller);
enter();
result = dlinfo(handle, request, arg);
leave();
return result;
}
void *my_dlmopen(Lmid_t nsid, const char *file, int mode, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%lu, %s, %d, %p)\n", __func__, nsid, file, mode, dl_caller);
enter();
result = dlmopen(nsid, file, mode);
leave();
return result;
}
static struct dlfcn_hook my_dlfcn_hook = {
.dlopen = my_dlopen,
.dlclose = my_dlclose,
.dlsym = my_dlsym,
.dlvsym = my_dlvsym,
.dlerror = my_dlerror,
.dladdr = my_dladdr,
.dlinfo = my_dlinfo,
.dlmopen = my_dlmopen,
.pad = {0, 0, 0, 0},
};
__attribute__((constructor))
static void init(void) {
old_dlfcn_hook = _dlfcn_hook;
_dlfcn_hook = &my_dlfcn_hook;
}
__attribute__((destructor))
static void fini(void) {
_dlfcn_hook = old_dlfcn_hook;
}
$ cc -shared -fPIC -o hook.so hook.c
$ cat > a.c
#include <dlfcn.h>
int main() { dlopen("./hook.so", RTLD_LAZY); dlopen("libm.so", RTLD_LAZY); }
^D
$ cc -ldl a.c
$ ./a.out
my_dlopen(libm.so, 1, 0x80484bd)
Unfortunately, my investigations are leading me to conclude that even if you could hook into glibc/elf/dl-load.c:open_verify() (which you can't), it's not possible to make this race-free against somebody writing over segments of your library.
The problem is essentially unsolvable in the form you've given, because shared objects are loaded by mmap()ing to process memory space. So even if you could make sure that the file that dlopen() operated on was the one you'd examined and declared OK, anyone who can write to the file can modify the loaded object at any time after you've loaded it. (This is why you don't upgrade running binaries by writing to them - instead you delete-then-install, because writing to them would likely crash any running instances).
Your best bet is to ensure that only the user you are running as can write to the file, then examine it, then dlopen() it. Your user (or root) can still sneak different code in, but processes with those permissions could just ptrace() you to do their bidding anyhow.
This project supposedly solves this on kernel level.
DigSig currently offers:
run time signature verification of ELF binaries and shared libraries.
support for file's signature revocation.
a signature caching mechanism to enhance performances.
I propose the following solution that should work without libraries *)
int memfd = memfd_create("for-debugging.library.so", MFD_CLOEXEC | MFD_ALLOW_SEALING);
assert(memfd != -1);
// Use any way to read the library from disk and copy the content into memfd
// e.g. write(2) or ftruncate(2) and mmap(2)
// Important! if you use mmap, you have to unmap it before the next step
// fcntl( , , F_SEAL_WRITE) will fail if there exists a writeable mapping
int seals_to_set = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL;
int sealing_err = fcntl(memfd, F_ADD_SEALS, seals_to_set);
assert(sealing_err == 0);
// Only now verify the contents of the loaded file
// then you can safely *) dlopen("/proc/self/fd/<memfd>");
*) Not actually tested it against attacks. Do not use in production without further investigation.