Kali 上的系统调用挂钩 Linux(内核版本 5)
Syscall Hooking on Kali Linux (kernel version 5)
我正在尝试为 Kali Linux 2021-W1(Linux 内核版本 5)[=30] 上的 bind()
系统调用设置挂钩=], 但由于某些原因,调用原来的系统调用失败,出现错误。
这是我的代码:
/* includes, license, author... */
void **sys_call_table_addr = (void **) 0xffffffff9e0002c0;
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;
}
asmlinkage int (*original_bind) (int, const struct sockaddr *, int);
asmlinkage int log_bind(int sockfd, const struct sockaddr *addr, int addrlen) {
int ret;
printk(KERN_INFO SOCKETLOG "bind was called");
return (*original_bind)(sockfd, addr, addrlen);
}
static int __init socketlog_init(void) {
printk(KERN_INFO SOCKETLOG "socketlog module has been loaded\n");
enable_page_rw(sys_call_table_addr);
original_bind = sys_call_table_addr[__NR_bind];
if (!original_bind) return -1;
sys_call_table_addr[__NR_bind] = log_bind;
disable_page_rw(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(sys_call_table_addr);
sys_call_table_addr[__NR_bind] = original_bind;
disable_page_rw(sys_call_table_addr);
}
module_init(socketlog_init);
module_exit(socketlog_exit);
执行sudo insmod socketlog.ko
后,可以看到预期的输出:
[ +0.000488] [SOCKETLOG] socketlog module has been loaded
[ +0.000002] [SOCKETLOG] original_bind = 00000000bbf288f1
但是每次调用 bind()
时,我都会遇到奇怪的行为:
[ +0.000488] [SOCKETLOG] bind was called
[ +0.000005] BUG: unable to handle page fault for address: 0000000040697fb8
[ +0.000002] #PF: supervisor read access in kernel mode
[ +0.000001] #PF: error_code(0x0000) - not-present page
果然0x0000000040697fb8
是0x00000000bbf288f1
指向的地址:原来系统调用的内容。我错过了什么?
也许您包装系统调用的方式不起作用。例如,在 Linux 5.4.0-59-generic x86_64 架构上,内核中的系统调用是通过一个名为 do_syscall_64()。它通过 pt_regs 结构将参数传递到 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);
}
pt_regs结构嵌入了用户传递给系统调用的参数。所以这可以解释为什么你崩溃了: printk(..."bind was called") 工作因为它不访问参数但是在调用原始系统调用条目之后不符合预期参数。
如果查看net/socket.c中bind()系统调用的源码,是这样定义的如:
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
return __sys_bind(fd, umyaddr, addrlen);
}
上面的宏 SYSCALL_DEFINE3() 扩展成一些包装器,从 pt_regs 中提取参数结构。
因此,作为示例,您的模块中的一些修复程序适用于我的 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);
有测试:
$ 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
例如,重新加载网页后,我得到:
$ 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
卸载模块后:
$ 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
您当然可以通过显示传递给系统调用的参数来增强重载。在 x86_64,系统调用通过处理器寄存器最多传递 6 个参数。我们可以在 pt_regs 结构中检索它们。后者在arch/x86/include/asm/ptrace.h中定义为:
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 */
};
系统调用的参数传递约定是:param#0到param#5分别传入RDI、RSI , RDX, R10, R8 和 R9 寄存器。
根据这个规则,对于bind()系统调用,参数在如下寄存器中:
- RDI = int(套接字描述符)
- RSI = struct sockaddr *addr
- RDX = socklen_t 地址长度
然后您可以使用以下内容增强日志功能:
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);
}
来自模块的痕迹变得更加详细:
[ 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
我正在尝试为 Kali Linux 2021-W1(Linux 内核版本 5)[=30] 上的 bind()
系统调用设置挂钩=], 但由于某些原因,调用原来的系统调用失败,出现错误。
这是我的代码:
/* includes, license, author... */
void **sys_call_table_addr = (void **) 0xffffffff9e0002c0;
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;
}
asmlinkage int (*original_bind) (int, const struct sockaddr *, int);
asmlinkage int log_bind(int sockfd, const struct sockaddr *addr, int addrlen) {
int ret;
printk(KERN_INFO SOCKETLOG "bind was called");
return (*original_bind)(sockfd, addr, addrlen);
}
static int __init socketlog_init(void) {
printk(KERN_INFO SOCKETLOG "socketlog module has been loaded\n");
enable_page_rw(sys_call_table_addr);
original_bind = sys_call_table_addr[__NR_bind];
if (!original_bind) return -1;
sys_call_table_addr[__NR_bind] = log_bind;
disable_page_rw(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(sys_call_table_addr);
sys_call_table_addr[__NR_bind] = original_bind;
disable_page_rw(sys_call_table_addr);
}
module_init(socketlog_init);
module_exit(socketlog_exit);
执行sudo insmod socketlog.ko
后,可以看到预期的输出:
[ +0.000488] [SOCKETLOG] socketlog module has been loaded
[ +0.000002] [SOCKETLOG] original_bind = 00000000bbf288f1
但是每次调用 bind()
时,我都会遇到奇怪的行为:
[ +0.000488] [SOCKETLOG] bind was called
[ +0.000005] BUG: unable to handle page fault for address: 0000000040697fb8
[ +0.000002] #PF: supervisor read access in kernel mode
[ +0.000001] #PF: error_code(0x0000) - not-present page
果然0x0000000040697fb8
是0x00000000bbf288f1
指向的地址:原来系统调用的内容。我错过了什么?
也许您包装系统调用的方式不起作用。例如,在 Linux 5.4.0-59-generic x86_64 架构上,内核中的系统调用是通过一个名为 do_syscall_64()。它通过 pt_regs 结构将参数传递到 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);
}
pt_regs结构嵌入了用户传递给系统调用的参数。所以这可以解释为什么你崩溃了: printk(..."bind was called") 工作因为它不访问参数但是在调用原始系统调用条目之后不符合预期参数。
如果查看net/socket.c中bind()系统调用的源码,是这样定义的如:
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
return __sys_bind(fd, umyaddr, addrlen);
}
上面的宏 SYSCALL_DEFINE3() 扩展成一些包装器,从 pt_regs 中提取参数结构。
因此,作为示例,您的模块中的一些修复程序适用于我的 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);
有测试:
$ 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
例如,重新加载网页后,我得到:
$ 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
卸载模块后:
$ 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
您当然可以通过显示传递给系统调用的参数来增强重载。在 x86_64,系统调用通过处理器寄存器最多传递 6 个参数。我们可以在 pt_regs 结构中检索它们。后者在arch/x86/include/asm/ptrace.h中定义为:
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 */
};
系统调用的参数传递约定是:param#0到param#5分别传入RDI、RSI , RDX, R10, R8 和 R9 寄存器。
根据这个规则,对于bind()系统调用,参数在如下寄存器中:
- RDI = int(套接字描述符)
- RSI = struct sockaddr *addr
- RDX = socklen_t 地址长度
然后您可以使用以下内容增强日志功能:
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);
}
来自模块的痕迹变得更加详细:
[ 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