Syscall Hooking on Kali Linux (kernel version 5)
Perhaps the way you are wrapping the system call does not work. For example, on Linux 5.4.0-59-generic x86_64 architecture, a system call in the kernel is called through a common wrapper called do_syscall_64(). It passes the parameters through the pt_regs structure to the entry in sys_call_table[]:
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
struct thread_info *ti;
enter_from_user_mode();
local_irq_enable();
ti = current_thread_info();
if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
nr = syscall_trace_enter(regs);
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs); <-------- Call to the entry with pt_regs structure
#ifdef CONFIG_X86_X32_ABI
} else if (likely((nr & __X32_SYSCALL_BIT) &&
(nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
X32_NR_syscalls);
regs->ax = x32_sys_call_table[nr](regs);
#endif
}
syscall_return_slowpath(regs);
}
The pt_regs structure embeds the parameters passed to the system call by the user. So this may explain why you are crashing : the printk(..."bind was called") works as it does not access the parameters but after the call to the original system call entry does not comply with the expected parameters.
If you look at the source code of bind() system call in net/socket.c, it is defined as:
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
return __sys_bind(fd, umyaddr, addrlen);
}
The above macro SYSCALL_DEFINE3() expands into some wrappers which extract the parameters from the pt_regs structure.
So, here is as an example, some fixes in your module which works on my 5.4.0-60-generic Ubuntu x86_64:
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/ptrace.h>
#include <linux/socket.h>
#include <linux/kallsyms.h>
MODULE_LICENSE("Dual BSD/GPL");
typedef int (* syscall_wrapper)(struct pt_regs *);
unsigned long sys_call_table_addr;
#define SOCKETLOG "[SOCKETLOG]"
int enable_page_rw(void *ptr){
unsigned int level;
pte_t *pte = lookup_address((unsigned long) ptr, &level);
if(pte->pte &~_PAGE_RW){
pte->pte |=_PAGE_RW;
}
return 0;
}
int disable_page_rw(void *ptr){
unsigned int level;
pte_t *pte = lookup_address((unsigned long) ptr, &level);
pte->pte = pte->pte &~_PAGE_RW;
return 0;
}
syscall_wrapper original_bind;
//asmlinkage int log_bind(int sockfd, const struct sockaddr *addr, int addrlen) {
int log_bind(struct pt_regs *regs) {
printk(KERN_INFO SOCKETLOG "bind was called");
return (*original_bind)(regs);
}
static int __init socketlog_init(void) {
printk(KERN_INFO SOCKETLOG "socketlog module has been loaded\n");
sys_call_table_addr = kallsyms_lookup_name("sys_call_table");
printk(KERN_INFO SOCKETLOG "sys_call_table@%lx\n", sys_call_table_addr);
enable_page_rw((void *)sys_call_table_addr);
original_bind = ((syscall_wrapper *)sys_call_table_addr)[__NR_bind];
if (!original_bind) return -1;
((syscall_wrapper *)sys_call_table_addr)[__NR_bind] = log_bind;
disable_page_rw((void *)sys_call_table_addr);
printk(KERN_INFO SOCKETLOG "original_bind = %p", original_bind);
return 0;
}
static void __exit socketlog_exit(void) {
printk(KERN_INFO SOCKETLOG "socketlog module has been unloaded\n");
enable_page_rw((void *)sys_call_table_addr);
((syscall_wrapper *)sys_call_table_addr)[__NR_bind] = original_bind;
disable_page_rw((void *)sys_call_table_addr);
}
module_init(socketlog_init);
module_exit(socketlog_exit);
With a test:
$ sudo insmod ./bind_ovl.ko
$ dmesg
[ 2253.201888] [SOCKETLOG]socketlog module has been loaded
[ 2253.209486] [SOCKETLOG]sys_call_table@ffffffff88c013a0
[ 2253.209489] [SOCKETLOG]original_bind = 00000000f54304a9
After a reload of a WEB page for example, I get:
$ dmesg
[ 2136.946042] [SOCKETLOG]socketlog module has been unloaded
[ 2253.201888] [SOCKETLOG]socketlog module has been loaded
[ 2253.209486] [SOCKETLOG]sys_call_table@ffffffff88c013a0
[ 2253.209489] [SOCKETLOG]original_bind = 00000000f54304a9
[ 2281.716581] [SOCKETLOG]bind was called
[ 2295.607476] [SOCKETLOG]bind was called
[ 2301.947866] [SOCKETLOG]bind was called
[ 2304.088116] [SOCKETLOG]bind was called
[ 2309.599634] [SOCKETLOG]bind was called
[ 2310.946833] [SOCKETLOG]bind was called
After unloading the module:
$ sudo rmmod bind_ovl
$ dmesg
[...]
[ 2390.908456] [SOCKETLOG]bind was called
[ 2398.921475] [SOCKETLOG]bind was called
[ 2398.928855] [SOCKETLOG]socketlog module has been unloaded
You can of course enhance the overload by displaying the parameters passed to the system call. On x86_64, the system calls are passed at most 6 parameters through the processor registers. We can retrieve them in the pt_regs structure. The latter is defined in arch/x86/include/asm/ptrace.h as:
struct pt_regs {
/*
* C ABI says these regs are callee-preserved. They aren't saved on kernel entry
* unless syscall needs a complete, fully filled "struct pt_regs".
*/
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long bp;
unsigned long bx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long ax;
unsigned long cx;
unsigned long dx;
unsigned long si;
unsigned long di;
/*
* On syscall entry, this is syscall#. On CPU exception, this is error code.
* On hw interrupt, it's IRQ number:
*/
unsigned long orig_ax;
/* Return frame for iretq */
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};
The parameter passing convention for a system call is: param#0 to param#5 are respectively passed into the RDI, RSI, RDX, R10, R8 and R9 registers.
According to this rule, for bind() system call, the parameters are in the following registers:
- RDI = int (socket descriptor)
- RSI = struct sockaddr *addr
- RDX = socklen_t addrlen
You can then enhance the log function with something like:
int log_bind(struct pt_regs *regs) {
printk(KERN_INFO SOCKETLOG "bind was called(%d, %p, %u)", (int)(regs->di), (void *)(regs->si), (unsigned int)(regs->dx));
return (*original_bind)(regs);
}
The traces from the module become more detailed:
[ 3259.589915] [SOCKETLOG]socketlog module has been loaded
[ 3259.594631] [SOCKETLOG]sys_call_table@ffffffff88c013a0
[ 3259.594634] [SOCKETLOG]original_bind = 00000000f54304a9
[ 3274.368906] [SOCKETLOG]bind was called(149, 0000000091c163d5, 12)
[ 3276.040330] [SOCKETLOG]bind was called(149, 0000000075b17cb4, 12)
[ 3278.203942] [SOCKETLOG]bind was called(188, 0000000091c163d5, 12)
[ 3287.014980] [SOCKETLOG]bind was called(214, 0000000075b17cb4, 12)
[ 3287.021167] [SOCKETLOG]bind was called(214, 0000000091c163d5, 12)
[ 3298.395713] [SOCKETLOG]bind was called(3, 000000008c2a9103, 12)
[ 3298.403249] [SOCKETLOG]socketlog module has been unloaded
System call hooking example arguments are incorrect
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.
Linux x86-64 syscall hooking, path names garbled
As Tsyvarev alluded to, the manner in which syscall parameters are passed has changed for newer kernel versions (> 4.17) and requires that the intercepting trampoline have the following signature:
asmlinkage __type__ sys_foobar(const struct pt_regs * regs).
Individual parameters are then accessed via the respective registers, i.e (x86_64)
arg1 arg2 arg3 arg4 arg5 arg6
regs-> (di) (si) (dx) (r10) (r8) (r9)
So to get the path string for the access syscall, you would use regs->di
.
asmlinkage int sys_access_trampoline(const struct pt_regs * regs)
{
char buffer[STR_MAX];
strncpy_from_user(buffer, regs->di, strnlen_user(path, PATH_MAX+1));
printk(buffer);
STUB_ORIGIN();
return sys_access(path, mode);
}
I now receive the correct ASCII path name.
Thank you to everyone for helping!
Hooking sys_execve() on Linux 3.x
You can't hook execve
by modifying the system call table in a such a way as on x86_64
the sys_execve
is called from the stub_execve
. So the call chain is sys_call_table[NR_execve] -> stub_execve -> sys_execve -> do_execve
... Take a look at stub_execve on LXR.
Related Topics
Bash Script to Install Postgresql - Not Working
Inconsistent Systemd Startup of Freeswitch
Building Subversion 1.5.4 on Debian: Could Not Find Library Containing Rsa_New
Chef-Server Stuck Ruby_Block[Supervise_Rabbitmq_Sleep] Action Run on Docker Container
How to Enable Spell Checker in Google Colab (Colab Operates on Linux Os)
Xdotool Type Takes Ages and Causes Entire Desktop to Freeze
Would Gcc 4.8 and 4.7 Peacefully Coexist on The Same Machine
Posix: Pipe Syscall in Freebsd Vs Linux
Ftrace: System Crash When Changing Current_Tracer from Function_Graph via Echo
How to Get The Process Id of Command Executed in Bash Script
Compare Two Files of Different Columns and Print Different Columns
Sublime Text 2 Build (Ctrl +B) Intel Fortran Compiler
Extract/See Content of a Specific File Inside a .War File
Having an Issue Passing Variables to Subshell
G++ Error: Expected ; Before "It"
Perl-Mechanize Runs into Limitations - Several Debugging Attempts Started