I have been working on part of an assignment which I am having trouble fixing. The requirement was to intercept the system call open and to replace it with a new system open call only for regular users and would print out the user id and filename in the system log. Otherwise it would just execute the standard system open call. Here is part of what I have that is troubling me:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/syscalls.h>
unsigned long **sys_call_table;
asmlinkage long (*ref_sys_open)(const char *filename, int flags, umode_t mode);
asmlinkage long (*ref_sys_close)(unsigned int fd);
asmlinkage long new_sys_open(const char *filename, int flags, umode_t mode) {
if (current_uid() >= 1000) {
printk(KERN_INFO "User %d is opening file: %s\n", current_uid(), filename);
} else {
(*ref_sys_open)(filename, flags, mode);
}
return 0;
}
The problem I am getting is that the returning value of current_uid() is a struct with type kuid_t. I looked into it further and found that the struct looks like this:
typedef struct {
uid_t val;
} kuid_t;
I was wondering how do I compare int 1000 to type uid_t val?
On a side note, did I call the old versions of the system call correctly?
from here, uid_t is just a typedef of __kernel_uid32_t, which is unsigned int according to here
Related
#include <linux/kernel.h>
#include <linux/uaccess.h>
unsigned long long cnt = 0;
asmlinkage long sys_customcall(unsigned long long __user *output)
{
unsigned long err;
err = copy_to_user(output, &cnt, sizeof(unsigned long long));
return err;
}
I'm implementing a simple system call. I would like to copy a value from the kernel (cnt) to user pointer (output). However, when I run the code err = 8 which is sizeof(unsigned long long) - user side values are not changing. What have I done wrong?
I'm using linux-kernel version 5.4.59.
The user-level code is as following.
#include <stdio.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
int main()
{
unsigned long long cnt=0;
long int err = syscall(436, &cnt);
printf("System call returned %lu\n", err);
printf("System call returned %llu\n", cnt);
return 0;
}
Problem got solved when I changed asmlinkage to SYSCALL_DEFINEx macro.
It's not certain but I believe it had something to do with x86_64 system call wrapper.
This link was helpful.
Thank you all.
I wrote an example of system call hooking from our Linux Kernel module.
Updated open system call in system call table to use my entry point instead of the default.
#include <linux/module.h>
#include <linux/kallsyms.h>
MODULE_LICENSE("GPL");
char *sym_name = "sys_call_table";
typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *sys_call_table;
typedef asmlinkage long (*custom_open) (const char __user *filename, int flags, umode_t mode);
custom_open old_open;
static asmlinkage long my_open(const char __user *filename, int flags, umode_t mode)
{
char user_msg[256];
pr_info("%s\n",__func__);
memset(user_msg, 0, sizeof(user_msg));
long copied = strncpy_from_user(user_msg, filename, sizeof(user_msg));
pr_info("copied:%ld\n", copied);
pr_info("%s\n",user_msg);
return old_open(filename, flags, mode);
}
static int __init hello_init(void)
{
sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name(sym_name);
old_open = (custom_open)sys_call_table[__NR_open];
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
return 0;
}
static void __exit hello_exit(void)
{
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = (sys_call_ptr_t)old_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
}
module_init(hello_init);
module_exit(hello_exit);
I wrote a simple user program to verify.
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd = syscall(__NR_open, "hello.txt", O_RDWR|O_CREAT, 0777);
exit(EXIT_SUCCESS);
}
File gets created in my folder, but strncpy_user fails with bad address
[ 927.415905] my_open
[ 927.415906] copied:-14
What is the mistake in the above code?
OP is probably using a kernel/architecture that uses "syscall wrappers" where the system call table contains a wrapper function that calls the real syscall function (possibly as an inline function call). The x86_64 architecture has used syscall wrappers since kernel version 4.17.
For x86_64 on kernel 4.17 or later, sys_call_table[__NR_open] points to __x64_sys_open (with prototype asmlinkage long __x64_sys_open(const struct pt_regs *regs)), which calls static function __se_sys_open (with prototype static long __se_sys_open(const __user *filename, int flags, umode_t mode)), which calls inline function __do_sys_open (with prototype static inline long __do_sys_open(const __user *filename, int flags, umode_t mode). Those will all be defined by the SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) macro call in "fs/open.c" and the function body that follows the macro call.
SYSCALL_DEFINE3 is defined in "include/linux/syscalls.h" and uses the SYSCALL_DEFINEx macro in the same file, which uses the __SYSCALL_DEFINEx macro. Since x86_64 defines CONFIG_ARCH_HAS_SYSCALL_WRAPPER, the __SYSCALL_DEFINEx macro is defined by #include <asm/syscall_wrapper.h>, which maps to "arch/x86/include/asm/syscall_wrapper.h".
For background on this change, see
LWN: use struct pt_regs based syscall calling for x86-64
LKML: [PATCH 000/109] remove in-kernel calls to syscalls https://lkml.org/lkml/2018/3/29/409
It seems the motivation is to only pass a pointer to pt_regs, instead of having a bunch of user-space values in registers down the call chain. (Perhaps to increase resistance to Spectre attacks by making gadgets less useful?)
Why open still worked, even though the wrapper didn't:
If OP is indeed using x86_64 kernel 4.17 or later, and replacing the sys_call_table[__NR_open] entry with a pointer to a function that uses a different prototype and calls the original function (pointed to by old_open) with the same parameters, that explains why the call to strncpy_from_user(user_msg, filename, sizeof(user_msg)) failed. Although declared as const char * __user filename, the filename pointer is actually pointing to the original struct pt_regs in kernel space.
In the subsequent call to old_open(filename, flags, mode), the first parameter filename is still pointing to the original struct pt_regs so the old function (which expects a single parameter of type struct pt_regs *) still works as expected.
i.e. the function passed on its first pointer arg unchanged, despite calling it a different type.
Update: Below is working code, Thanks everyone for providing inputs
#include <linux/module.h>
#include <linux/kallsyms.h>
MODULE_LICENSE("GPL");
char *sym_name = "sys_call_table";
typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *sys_call_table;
sys_call_ptr_t old_open;
static asmlinkage long my_open(const struct pt_regs *regs)
{
char __user *filename = (char *)regs->di;
char user_filename[256] = {0};
long copied = strncpy_from_user(user_filename, filename, sizeof(user_filename));
if (copied > 0)
pr_info("%s filename:%s\n",__func__, user_filename);
return old_open(regs);
}
static int __init hello_init(void)
{
sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name(sym_name);
old_open = sys_call_table[__NR_open];
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = my_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
return 0;
}
static void __exit hello_exit(void)
{
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = old_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
}
module_init(hello_init);
module_exit(hello_exit);
So i've been adding my custom syscall to kernel. This is my syscall's source code:
//sys_procmem.c. This syscall helps users to show the memory layout of a specific process.
#include <linux/linkage.h>
#include <linux/sched.h>
struct proc_segs {
unsigned long my_id;
unsigned long start_code;
unsigned long end_code;
unsigned long start_data;
unsigned long end_data;
unsigned long start_heap;
unsigned long end_heap;
unsigned long start_stack;
};
asmlinkage long sys_procmem( int pid, struct proc_segs * info) {
struct task_struct *task;
info->my_id = 1512853;
for_each_process(task){
if((int) task->pid==pid){
info->start_code=task->mm->start_code;
info->end_code=task->mm->end_code;
info->start_data=task->mm->start_data;
info->end_data=task->mm->end_data;
info->start_heap=task->mm->start_brk;
info->end_heap=task->mm->brk;
info->start_stack=task->mm->start_stack;
return 0;
}
}
return -1;
}
Everything seems fine until when i write a testing program after i compile my kernel, source code:
//sys.c
#include <sys/syscall.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 100
int main() {
long sysvalue;
unsigned long info[SIZE];
sysvalue = syscall([number_32], 1, info)
printf("My ID: %lu\n", info[0]);
}
I compiled the program with:
gcc sys.c -o sys
Then i ran it with:
./sys
I got this error:
Please help me, thank you guys for reading :)
okay, ive searched a solution for like two days now but i couldnt find whats going wrong with my code. ;(
The task is simple: define a new type using typedef and have a function read out lines of this new type from a file into an array of again this new type. so my typedef inside the headerfile looks like this right now (ive tried several variants of writing this)
// filename: entries.h
#ifndef ENTRIES_H_
#define ENTRIES_H_
#include<time.h>
typedef struct{
char Loginname[25];
time_t RegDate;
unsigned long Highscore;
time_t Hdate;
}typePlayerEntry;
int readPlayerList(char *name, typePlayerEntry *feld);
#endif /* ENTRIES_H_ */
the main.c:
//filename: main.c
#include <stdio.h>
#include "entries.h"
int main(void) {
char name[13]="numbers.txt";
typePlayerEntry *pep;
readPlayerList(name, pep);
return 0;
}
my function file looks like this (and heres where the error is shown)
//filename: readPlayerList.c
int readPlayerList(char *name, typePlayerEntry *feld) {
return 0;
}
irrelevant code is completely left out. The problem is reproducable with the code posted.
the program wont compile because the type of the second argument in the function file could not be recognized,
- which is odd, because its defined in the header file and also usable in the main function.
And this error is somehow connected to the declaration of (in this case) a pointer of type playerEntry in my main.c. So if i do not declare it, theres no error, though i have to declare it to actually give it to the function. how come that the solution so far is to include the entries.h into the readPlayerList.c, which wasnt neccesary for previous functions?
im using eclipse kepler with MinGW, in case thats a compiler issue.
corrected the missing include of time.h and adjusted the code a little.
You are missing #include <time.h> in entries.h.
// filename: entries.h
#ifndef ENTRIES_H_
#define ENTRIES_H_
typedef struct {
char Loginname[25];
time_t RegDate; /* from <time.h> */
unsigned long Highscore;
time_t Hdate; /* from <time.h> */
} playerEntry;
int readPlayerList(char *name, playerEntry *feld);
#endif /* ENTRIES_H_ */
And you need to #include "entries.h" in readPlayerList.c
//filename: readPlayerList.c
int readPlayerList(char *name, typePlayerEntry *feld) {
/* ^^^^^^^^^^^^^^^ from entries.h */
return 0;
}
part of the problem is the compiler is seeing (at least) two different meanings/definitions for the 'playerEntry' name.
Suggest:
1) eliminate the 'typedef' statement
(it is just cluttering the code and confusing the compiler)
2) properly reference the struct via:
'struct playerEntry' instead of 'playerEntry'
in TheHeader.h file:
struct playerEntry
{
char Loginname[25];
time_t RegDate;
unsigned long Highscore;
time_t Hdate;
};
int readPlayerList(char *name, struct playerEntry *feld);
in the source file:
#include "TheHeader.h"
int readPlayerList(char *name, struct playerEntry *feld)
{
return 0;
}
I'm trying to write some simple test code as a demonstration of hooking the system call table.
"sys_call_table" is no longer exported in 2.6, so I'm just grabbing the address from the System.map file, and I can see it is correct (Looking through the memory at the address I found, I can see the pointers to the system calls).
However, when I try to modify this table, the kernel gives an "Oops" with "unable to handle kernel paging request at virtual address c061e4f4" and the machine reboots.
This is CentOS 5.4 running 2.6.18-164.10.1.el5. Is there some sort of protection or do I just have a bug? I know it comes with SELinux, and I've tried putting it in to permissive mode, but it doesn't make a difference
Here's my code:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
void **sys_call_table;
asmlinkage int (*original_call) (const char*, int, int);
asmlinkage int our_sys_open(const char* file, int flags, int mode)
{
printk("A file was opened\n");
return original_call(file, flags, mode);
}
int init_module()
{
// sys_call_table address in System.map
sys_call_table = (void*)0xc061e4e0;
original_call = sys_call_table[__NR_open];
// Hook: Crashes here
sys_call_table[__NR_open] = our_sys_open;
}
void cleanup_module()
{
// Restore the original call
sys_call_table[__NR_open] = original_call;
}
I finally found the answer myself.
http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html
The kernel was changed at some point so that the system call table is read only.
cypherpunk:
Even if it is late but the Solution
may interest others too: In the
entry.S file you will find: Code:
.section .rodata,"a"
#include "syscall_table_32.S"
sys_call_table -> ReadOnly You have to
compile the Kernel new if you want to
"hack" around with sys_call_table...
The link also has an example of changing the memory to be writable.
nasekomoe:
Hi everybody. Thanks for replies. I
solved the problem long ago by
modifying access to memory pages. I
have implemented two functions that do
it for my upper level code:
#include <asm/cacheflush.h>
#ifdef KERN_2_6_24
#include <asm/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
struct page *pg;
pgprot_t prot;
pg = virt_to_page(_addr);
prot.pgprot = VM_READ | VM_WRITE;
return change_page_attr(pg, 1, prot);
}
int set_page_ro(long unsigned int _addr)
{
struct page *pg;
pgprot_t prot;
pg = virt_to_page(_addr);
prot.pgprot = VM_READ;
return change_page_attr(pg, 1, prot);
}
#else
#include <linux/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
return set_memory_rw(_addr, 1);
}
int set_page_ro(long unsigned int _addr)
{
return set_memory_ro(_addr, 1);
}
#endif // KERN_2_6_24
Here's a modified version of the original code that works for me.
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
#include <asm/semaphore.h>
#include <asm/cacheflush.h>
void **sys_call_table;
asmlinkage int (*original_call) (const char*, int, int);
asmlinkage int our_sys_open(const char* file, int flags, int mode)
{
printk("A file was opened\n");
return original_call(file, flags, mode);
}
int set_page_rw(long unsigned int _addr)
{
struct page *pg;
pgprot_t prot;
pg = virt_to_page(_addr);
prot.pgprot = VM_READ | VM_WRITE;
return change_page_attr(pg, 1, prot);
}
int init_module()
{
// sys_call_table address in System.map
sys_call_table = (void*)0xc061e4e0;
original_call = sys_call_table[__NR_open];
set_page_rw(sys_call_table);
sys_call_table[__NR_open] = our_sys_open;
}
void cleanup_module()
{
// Restore the original call
sys_call_table[__NR_open] = original_call;
}
Thanks Stephen, your research here was helpful to me. I had a few problems, though, as I was trying this on a 2.6.32 kernel, and getting WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted) followed by a kernel OOPS about not being able to write to the memory address.
The comment above the mentioned line states:
// People should not be passing in unaligned addresses
The following modified code works:
int set_page_rw(long unsigned int _addr)
{
return set_memory_rw(PAGE_ALIGN(_addr) - PAGE_SIZE, 1);
}
int set_page_ro(long unsigned int _addr)
{
return set_memory_ro(PAGE_ALIGN(_addr) - PAGE_SIZE, 1);
}
Note that this still doesn't actually set the page as read/write in some situations. The static_protections() function, which is called inside of set_memory_rw(), removes the _PAGE_RW flag if:
It's in the BIOS area
The address is inside .rodata
CONFIG_DEBUG_RODATA is set and the kernel is set to read-only
I found this out after debugging why I still got "unable to handle kernel paging request" when trying to modify the address of kernel functions. I was eventually able to solve that problem by finding the page table entry for the address myself and manually setting it to writable. Thankfully, the lookup_address() function is exported in version 2.6.26+. Here is the code I wrote to do that:
void set_addr_rw(unsigned long addr) {
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;
}
void set_addr_ro(unsigned long addr) {
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
pte->pte = pte->pte &~_PAGE_RW;
}
Finally, while Mark's answer is technically correct, it'll case problem when ran inside Xen. If you want to disable write-protect, use the read/write cr0 functions. I macro them like this:
#define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000))
#define GPF_ENABLE write_cr0(read_cr0() | 0x10000)
Hope this helps anyone else who stumbles upon this question.
Note that the following will also work instead of using change_page_attr and cannot be depreciated:
static void disable_page_protection(void) {
unsigned long value;
asm volatile("mov %%cr0,%0" : "=r" (value));
if (value & 0x00010000) {
value &= ~0x00010000;
asm volatile("mov %0,%%cr0": : "r" (value));
}
}
static void enable_page_protection(void) {
unsigned long value;
asm volatile("mov %%cr0,%0" : "=r" (value));
if (!(value & 0x00010000)) {
value |= 0x00010000;
asm volatile("mov %0,%%cr0": : "r" (value));
}
}
If you are dealing with kernel 3.4 and later (it can also work with earlier kernels, I didn't test it) I would recommend a smarter way to acquire the system callы table location.
For example
#include <linux/module.h>
#include <linux/kallsyms.h>
static unsigned long **p_sys_call_table;
/* Aquire system calls table address */
p_sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");
That's it. No addresses, it works fine with every kernel I've tested.
The same way you can use a not exported Kernel function from your module:
static int (*ref_access_remote_vm)(struct mm_struct *mm, unsigned long addr,
void *buf, int len, int write);
ref_access_remote_vm = (void *)kallsyms_lookup_name("access_remote_vm");
Enjoy!
As others have hinted, the whole story is a bit different now on modern kernels. I'll be covering x86-64 here, for syscall hijacking on modern arm64 refer to this other answer of mine. Also NOTE: this is plain and simple syscall hijacking. Non-invasive hooking can be done in a much nicer way using kprobes.
Since Linux v4.17, x86 (both 64 and 32 bit) now uses syscall wrappers that take a struct pt_regs * as the only argument (see commit 1, commit 2). You can see arch/x86/include/asm/syscall.h for the definitions.
Additionally, as others have described already in different answers, the simplest way to modify sys_call_table is to temporarily disable CR0 WP (Write-Protect) bit, which could be done using read_cr0() and write_cr0(). However, since Linux v5.3, [native_]write_cr0 will check sensitive bits that should never change (like WP) and refuse to change them (commit). In order to work around this, we need to write CR0 manually using inline assembly.
Here is a working kernel module (tested on Linux 5.10 and 5.18) that does syscall hijacking on modern Linux x86-64 considering the above caveats and assuming that you already know the address of sys_call_table (if you also want to find that in the module, see Proper way of getting the address of non-exported kernel symbols in a Linux kernel module):
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/**
* Test syscall table hijacking on x86-64. This module will replace the `read`
* syscall with a simple wrapper which logs every invocation of `read` using
* printk().
*
* Tested on Linux x86-64 v5.10, v5.18.
*
* Usage:
*
* sudo cat /proc/kallsyms | grep sys_call_table # grab address
* sudo insmod syscall_hijack.ko sys_call_table_addr=0x<address_here>
*/
#include <linux/init.h> // module_{init,exit}()
#include <linux/module.h> // THIS_MODULE, MODULE_VERSION, ...
#include <linux/kernel.h> // printk(), pr_*()
#include <asm/special_insns.h> // {read,write}_cr0()
#include <asm/processor-flags.h> // X86_CR0_WP
#include <asm/unistd.h> // __NR_*
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
typedef long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *real_sys_call_table;
static sys_call_ptr_t original_read;
static unsigned long sys_call_table_addr;
module_param(sys_call_table_addr, ulong, 0);
MODULE_PARM_DESC(sys_call_table_addr, "Address of sys_call_table");
// Since Linux v5.3 [native_]write_cr0 won't change "sensitive" CR0 bits, need
// to re-implement this ourselves.
static void write_cr0_unsafe(unsigned long val)
{
asm volatile("mov %0,%%cr0": "+r" (val) : : "memory");
}
static long myread(const struct pt_regs *regs)
{
pr_info("read(%ld, 0x%lx, %lx)\n", regs->di, regs->si, regs->dx);
return original_read(regs);
}
static int __init modinit(void)
{
unsigned long old_cr0;
real_sys_call_table = (typeof(real_sys_call_table))sys_call_table_addr;
pr_info("init\n");
// Temporarily disable CR0 WP to be able to write to read-only pages
old_cr0 = read_cr0();
write_cr0_unsafe(old_cr0 & ~(X86_CR0_WP));
// Overwrite syscall and save original to be restored later
original_read = real_sys_call_table[__NR_read];
real_sys_call_table[__NR_read] = myread;
// Restore CR0 WP
write_cr0_unsafe(old_cr0);
pr_info("init done\n");
return 0;
}
static void __exit modexit(void)
{
unsigned long old_cr0;
pr_info("exit\n");
old_cr0 = read_cr0();
write_cr0_unsafe(old_cr0 & ~(X86_CR0_WP));
// Restore original syscall
real_sys_call_table[__NR_read] = original_read;
write_cr0_unsafe(old_cr0);
pr_info("goodbye\n");
}
module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Test syscall table hijacking on x86-64.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("Dual MIT/GPL");