本文将介绍Linux使用内核模块添加系统调用的方法(无需编译内核),思路就是修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数,如果是已使用的表项,甚至可以实现系统调用劫持。

1、查看预留系统调用号

不同内核版本,文件位置有所不同,我们可以直接查找unistd_64.h文件,命令如下:

sudo find / -name unistd_64.h

sudo cat /usr/src/linux-headers-5.3.0-40-generic/arch/x86/include/generated/uapi/asm/unistd_64.h


其中找到系统调用号335空闲,我们选择此系统调用号来编写内核模块。

2、获取系统调用表 sys_call_table 的虚拟地址

方法一:使用/proc/kallsyms

kallsyms包含了kernel image和动态加载模块的符号表,包括内核中的函数符号(包括没有EXPORT_SYMBOL导出的符号)、全局变量(用EXPORT_SYMBOL导出的全局变量),函数如果被编译器内联(inline)或优化掉,则它在/proc/kallsyms有可能找不到。此方法由变量名获取虚拟地址使用如下命令:

sudo cat /proc/kallsyms | grep sys_call_table


可以看到系统调用表 sys_call_table 的虚拟地址为ffffffffac4002a0,通过R标志可以看出它是只读的。在代码中可以使用kallsyms_lookup_name()函数实现,具体使用方法请看内核模块代码。

方法二:使用System.map

System.map是一份内核符号表kernel symbol table,包含了内核中的变量名和函数名地址,在每次编译内核时,自动生成。由变量名获取虚拟地址使用如下命令:

sudo cat /boot/System.map-`uname -r` | grep sys_call_table


可以看到系统调用表 sys_call_table 的虚拟地址为ffffffff820002a0,与方法一获得的虚拟地址不同,这是因为正在运行的内核可能和System.map不匹配,出现System.map does not match actual kernel/proc/kallsyms中增加的函数符号是后来安装程序中引入的,而system.map仅仅是kenrel编译时生成的符号表,所以/proc/kallsyms才是参考的主要来源,一般通过/proc/kallsyms获得符号的地址。

3、内核模块代码

为了修改内存中的表项,还要修改寄存器写保护位。内核模块代码和详细注释如下,:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kallsyms.h>#define __NR_syscall 335   /* 系统调用号335 */
unsigned long * sys_call_table;unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
static int sys_mycall(void);int orig_cr0;   /* 用来存储cr0寄存器原来的值 */
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void); /*定义一个函数指针,用来保存一个系统调用*/
/** 设置cr0寄存器的第17位为0*/
unsigned int clear_and_return_cr0(void)
{unsigned int cr0 = 0;unsigned int ret;/* 前者用在32位系统。后者用在64位系统,本系统64位 *///asm volatile ("movl %%cr0, %%eax" : "=a"(cr0));   asm volatile ("movq %%cr0, %%rax" : "=a"(cr0));    /* 将cr0寄存器的值移动到rax寄存器中,同时输出到cr0变量中 */ret = cr0;cr0 &= 0xfffeffff;  /* 将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器 *///asm volatile ("movl %%eax, %%cr0" :: "a"(cr0));asm volatile ("movq %%rax, %%cr0" :: "a"(cr0)); /* 读取cr0的值到rax寄存器,再将rax寄存器的值放入cr0中 */return ret;
}/* 读取val的值到rax寄存器,再将rax寄存器的值放入cr0中 */
void setback_cr0(unsigned int val)
{   //asm volatile ("movl %%eax, %%cr0" :: "a"(val));asm volatile ("movq %%rax, %%cr0" :: "a"(val));
}/* 添加自己的系统调用函数 */
static int sys_mycall(void)
{int ret = 12345;printk("My syscall is successful!\n");return ret;
}/*模块的初始化函数,模块的入口函数,加载模块*/
static int __init init_addsyscall(void)
{printk("My syscall is starting。。。\n");sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");   /* 获取系统调用服务首地址 */printk("sys_call_table: 0x%p\n", sys_call_table);anything_saved = (int(*)(void))(sys_call_table[__NR_syscall]); /* 保存原始系统调用 */orig_cr0 = clear_and_return_cr0();   /* 设置cr0可更改 */sys_call_table[__NR_syscall] = (unsigned long)&sys_mycall;   /* 更改原始的系统调用服务地址 */setback_cr0(orig_cr0);   /* 设置为原始的只读cr0 */return 0;
}/*出口函数,卸载模块*/
static void __exit exit_addsyscall(void)
{orig_cr0 = clear_and_return_cr0();    /* 设置cr0中对sys_call_table的更改权限 */sys_call_table[__NR_syscall] = (unsigned long)anything_saved;  /* 设置cr0可更改 */setback_cr0(orig_cr0);    /* 恢复原有的中断向量表中的函数指针的值 */printk("My syscall exit....\n");  /* 恢复原有的cr0的值 */
}module_init(init_addsyscall);
module_exit(exit_addsyscall);
MODULE_LICENSE("GPL");

要注意,不同版本系统,所用的汇编语句不同,现在将用到的汇编语句总结如下:
常见寄存器:

寄存器 16位 32位 64位
累加寄存器 AX EAX RAX
基址寄存器 BX EBX RBX
计数寄存器 CX ECX RCX
数据寄存器 DX EDX RDX
堆栈基指针 BP EBP RBP
变址寄存器 SI ESI RSI
堆栈顶指针 SP ESP RSP
指令寄存器 IP EIP RIP

mov用法: movb(8位)、movw(16位)、movl(32位)、movq(64位)

寻址方式 举例 说明
寄存器寻址 movl %eax, %edx eax -> edx
立即数寻址 movl $0x123, %edx 数字->寄存器
直接寻址 movl 0x123, %edx 直接访问内存地址数据,edx = *(int32_t *)0x123;
间接寻址 movl (%ebx), %edx %ebx 是个内存地址,(%ebx)指的是该地址中的数据,edx = (int32_t)ebx;
变址寻址 movl 4(%ebx), %edx edx = (int32_t)(ebx+4);

4、Makefile文件

obj-m:=syscall.o
PWD:= $(shell pwd)
KERNELDIR:= /lib/modules/$(shell uname -r)/build
EXTRA_CFLAGS= -O0all:make -C $(KERNELDIR)  M=$(PWD) modules
clean:make -C $(KERNELDIR) M=$(PWD) clean

要注意添加EXTRA_CFLAGS= -O0关闭gcc优化选项,避免插入模块出错。

5、测试程序

#include <syscall.h>
#include <stdio.h>
int main(void)
{printf("%d\n",syscall(335));return 0;
}

6、运行结果

编译模块后插入内核,测试结果如下:

运行用户态测试程序,结果如下:

查看系统日志,结果如下:

Linux 系统调用(二)——使用内核模块添加系统调用(无需编译内核)相关推荐

  1. 《精通Linux设备驱动程序开发》——1.7 编译内核

    本节书摘来自异步社区<精通Linux设备驱动程序开发>一书中的第1章,第1.7节,作者:[印]Sreekrishnan Venkateswaran(斯里克里斯汉 温卡特斯瓦兰)著,更多章节 ...

  2. linux dev net tun,使用TUN / Bridge支持编译内核,但我找不到/ dev / net / tun

    来自tuntap的内核文档:http://www.mjmwired.net/kernel/Documentation/networking/tuntap.txt 36 2.配置 37创建设备节点: 3 ...

  3. 杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)

    一 题目介绍 Linux是开源操作系统.在系统中根据需要添加新的系统调用是修改内核的一种常用手段,通过本次实验,我们可以理解Linux系统处理系统调用的流程以及增加系统调用的方法.Linux系统提供了 ...

  4. 【Linux、进程隐藏】在Linux环境下添加系统调用实现进程隐藏

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 [进程隐藏]在Linux环境下添加系统调用实现进程隐藏 前言 一.环境设置: 二.实现方法步骤: 1.思路图 2.利用strace命令 ...

  5. Linux内核2.6.34.14添加系统调用及编译方法(CentOS-6.4-x86_64)

    <?xml version="1.0" encoding="UTF-8"?> //我添加系统调用步骤,仅供参考,尤其是系统调用的实现部分,建议大家自 ...

  6. linux内核添加系统调用(详细)

    linux内核添加系统调用(详细) 说在前面: 这是我第五次编译内核,分别踩了很多坑.中途问过wz佬,佬让我用qemu.我还是最后换ubuntu虚拟机跑了.现在已经有点emo了. 这篇博客是我第五次的 ...

  7. 操作系统实验(linux内核编译,添加系统调用,windows进程创建,脚本程序编写)

    <操作系统原理>实验报告 一.实验目的 (1)理解操作系统生成的概念和过程; (2)理解操作系统两类用户界面(操作界面,系统调用)概念; 二.实验内容 (1)在Unbantu或Fedora ...

  8. Linux内核学习8——添加系统调用

    基于linux 5.0内核添加一个系统调用,但是单纯添加一个系统调用会显得有些单调,所以这里把系统调用,工作队列,修改内核,内核编译,内核模块的编写,插入等结合起来. 要添加的是一个系统调用日志收集系 ...

  9. 【Linux系列】添加系统调用

    [Linux系列]添加系统调用最新教程 编译环境 修改任务 基础任务 进阶任务 修改准备 修改源码 添加系统调用号 修改系统调用头文件 修改系统调用函数定义 编译安装内核 编写主程序 进阶任务 总结 ...

最新文章

  1. usb网络linux系统安装教程,Linux系统入门学习:如何在树莓派上安装USB网络摄像头...
  2. 许式伟:毕业两年成为首席架构师,我的技术学习方法论
  3. yum安装Imagick及扩展
  4. JAVA面试必备的知识宝典(一)
  5. python decimal_【进阶】嫌弃Python慢,试试这几个方法?
  6. ElasticSearch 动态映射与静态映射_08
  7. sort numbers with three stacks
  8. 【CSS】Table样式
  9. appium学习链接记录
  10. 分页加载总页数计算公式
  11. 股票实时行情数据接口编译
  12. 成都Uber优步司机奖励政策(2月25日)
  13. 计算机专业裁合词英语,计算机专业英语的构词方法
  14. 学习搜狗workflow心路历程(1)Windows版本的环境搭建
  15. 【文件】Notepad3下载和配置
  16. 知领·报告 | 车路协同技术发展态势分析报告.md
  17. python3编写http代理服务器_HTTP代理服务器[Python]
  18. python之类与对象
  19. Windows 7 下www服务器构筑设定(四)
  20. 均线粘合突破选股指标公式:寻找启动点,并进行信号过滤

热门文章

  1. leetcode 101 Symmetric Tree
  2. MYSQL触发器记录用户操作的命令
  3. Vue_VueRouter
  4. Zigbee通讯漫谈(初次见面)
  5. 对Linux系统中的时钟和时间的探讨
  6. 下载android的linux内核的方法
  7. 很高兴加入51cto——交朋友
  8. jstack分析cpu占用100%
  9. eclipse安装SVN插件的两种方法
  10. 网络连接错误 failed to handler mux client connection