以下内容转载于
https://www.cnblogs.com/hello-underworld/p/14589102.html
https://blog.csdn.net/thugkd/article/details/50117125
https://www.pianshen.com/article/3605500050/
推荐
https://www.cnblogs.com/wangzahngjun/p/4992045.html

1,为什么要使用内核模块的方式添加系统调用?

1.1,编译内核的方式费时间,一般的PC机都要两三个小时。
1.2,不方便调试,一旦出现问题前面的工作都前功尽弃。

2,首先要获取系统调用表sys_call_table的地址(虚拟地址)
因为sys_call_table在内核中没有导出,可以使用如下命令查看。

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

注意点:当我把模块在一个机子上运行成功后,如果移植到另外一个机子上马上就会出现错误,为什么呢?因为每个机子sys_call_table的地址可能不一样。

3,需要查看预留的系统调用号。
可以到arch/x86/include/asm/unistd.h文件中查看预留的系统调用号。
可以看出223就是一个预留的系统调用号。
先定位到自己Linux内核放到的位置



4.创建模块对应的源文件hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <linux/sched.h>#define SYS_CALL_TABLE_ADDRESS  0xffffffff816002c0//sys_call_table对应的地址
#define NUM 223//系统调用号为223int orig_cr0;//用来存储cr0寄存器原来的值
unsigned long *sys_call_table_my=0;
static int(*anything_saved)(void);//定义一个函数指针,用来保存一个系统调用
static int clear_cr0(void)//使cr0寄存器的第17位设置为0(内核空间可写)
{unsigned int cr0=0;unsigned int ret;asm volatile("movq %%cr0,%%rax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中ret=cr0;cr0&=0xfffffffffffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器asm volatile("movq %%rax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中,同时移动到寄存器cr0中return ret;}static void setback_cr0(int val)//使cr0寄存器设置为内核不可写
{asm volatile("movq %%rax,%%cr0"::"a"(val));
}asmlinkage long sys_mycall(void)//定义自己的系统调用
{   printk("system call:当前pid:%d,当前comm:%s\n",current->pid,current->comm);printk("hello, underworld!\n");return current->pid;
}int simple_init(void)
{printk(KERN_INFO "Loading Module\n");sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系统调用表中的NUM位置上的系统调用orig_cr0=clear_cr0();//使内核地址空间可写sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系统调用替换NUM位置上的系统调用setback_cr0(orig_cr0);//使内核地址空间不可写return 0;
}void simple_exit(void) {printk(KERN_INFO "Removing Module\n");orig_cr0=clear_cr0();sys_call_table_my[NUM]=(unsigned long)anything_saved;//将系统调用恢复setback_cr0(orig_cr0);
}module_init( simple_init );
module_exit( simple_exit );
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Module");
MODULE_AUTHOR("SGG");

该模块功能主要是返回调用该模块的进程的pid及运行的可执行文件的文件名。同时调用系统调用printk输出pid并打印“hello,underworld”(可用dmesg查看)。
同时,该模块定义了自己的系统调用,并将定义的系统调用指向了系统的系统调用表,这样可以使用syscall(int num)函数调用模块中定义的系统调用。

5、编写对应的makefile文件

obj-m := hello.o
KERNELDIR := /usr/src/linux-$(shell uname -r)PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_installclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

6、执行make指令

make

7、载入模块
通过insmod指令,可以将可链接的模块载入系统内核。
这里即是使用 sudo insmod hello.ko(需要管理员权限)
通过lsmod指令可以在终端输出当前内核载入的模块,可以看到定义的simple模块成功载入。

8、编写测试函数test.c

#include<stdio.h>
#include<stdlib.h>
#include<linux/kernel.h>
#include<sys/syscall.h>
#include<unistd.h>
int main()
{unsigned long x = 0;x = syscall(223);printf("systemcall result is %ld\n", x);return 0;
}



以下内容转载于https://www.pianshen.com/article/3605500050/
没验证

拦截系统调用总结:
系统调用的概念:系统调用是内核和用户应用程序之间的沟通桥梁,是用户应用程序访问内核的入口点。用户程序调用响应的API函数,每一个C库中的API都对应内核中提供的一个系统调用函数。如用户程序中getpid()为得到当前进程的pid,其与内核中sys_getpid()对应。

拦截系统调用:个人理解为,每一个用户程序的API都会对应一个系统调用,当用户程序执行当前API时,最终会执行其内核的系统调用函数。而拦截系统调用,所做的事情就是:将当前API对应的系统调用替换为其他系统调用函数。当用户程序执行该API时,得到的结果为另一个错误结果。
实现一个系统调用的原理:用户程序调用syscall()执行一个系统调用。如:
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
long x = 0;
x = syscall(__NR_getpid);
printf(“syscall result is %ld\n”,x);
return 0;
}
其中__NR_getpid是一个宏定义,其值为20,即系统调用号。为什么为20?
查看方法:查看系统调用号,可以到linux/arch/x86/include/asm/unistd.h文件中查看预留的系统调用号,在unistd.h文件中,对应到unistd_32.h和unistd_64.h文件中查找,前者为32位系统,后者为64位系统(内核版本不同方法不同,2.6版本直接在unistd.h中即为系统调用号),如:
#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H

/*

  • This file contains the system call numbers.
    */

#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3

#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
此处解释了__NR_getpid的值,从系统调用号表中得到。
现在知道了__NR_getpid为20,再次回到syscall()函数,执行该函数时,会有什么操作?
当用户程序需要系统提供服务时,执行syscall()会通过系统调用产生int 0x80软中断,该中断会进入到系统调用的入口函数,位于linux/arch/x86/kernel/entry_32.S(在linux内核2.6为该地址,3.2版本后未在该地址)。即在main函数中,执行函数syscall(20),此时产生0x80软中断,进入系统调用。
系统调用入口函数,由汇编代码实现
ENTRY(system_call)
518 RING0_INT_FRAME # can’t unwind into user space anyway
519 pushl %eax # save orig_eax
#将系统调用号压入堆栈
520 CFI_ADJUST_CFA_OFFSET 4
521 SAVE_ALL
522 GET_THREAD_INFO(%ebp)
523 # system call tracing in operation / emulation
524 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
525 jnz syscall_trace_entry
526 cmpl $(nr_syscalls), %eax
527 jae syscall_badsys
528 syscall_call:
529 call sys_call_table(,%eax,4)
530 movl %eax,PT_EAX(%esp) # store the return value
现对上述代码做出解释:
519行,将当前应用程序的系统调用号20送入寄存器eax中。
521行,将寄存器eax的值压入到堆栈中,因为系统地址的取得不通过寄存器来传递参数,而是通过栈来传递。
525行,jnz syscall_trace_entry比较结果不为0进行跳转。对用户进程传递的系统调用号进行合法检查,如果不合法则跳转到syscall_badsys命令
526行,比较结果,合法则跳转响应系统调用号对应的服务例程。
528行,526合法执行后,需要在系统调用表,即sys_call_table中找到对应的系统调用例程函数的入口地址。其获得方式为,sys_call_table表的基址加上该函数再sys_call_table表中的偏移量。而sys_call_table表中的每个表项占4个字节,所以对应的将系统调用号eax乘以4加上sys_call_table基址才可得到对应的系统调用函数例程的地址。
sys_call_table(linux/arch/x86/kernel/syscall_table_32.S):
1 ENTRY(sys_call_table)
2 .long sys_restart_syscall /
0 - old “setup()” system call, used for restarting /
3 .long sys_exit
4 .long ptregs_fork
5 .long sys_read
6 .long sys_write
7 .long sys_open /
5 /
8 .long sys_close
9 .long sys_waitpid
10 .long sys_creat
11 .long sys_link
12 .long sys_unlink /
10
……
13 .long sys_getpid /* 20 */

接下来,通过上一步找到的函数例程地址(eax4+sys_call_table表基址),在sys_call_table查找系统调用服务程序入口函数的地址,再进行跳转执行函数,即sys_getpid。
而sys_getpid函数的具体定义,在linux/include/linux/syscalls.h中,sys_getpid函数的具体实现在linux/fs中(自己查阅资料得到,但并未找到该地址)。
至此给出了整个sys_getpid的实现过程,从用户程序syscall()中得到系统调用号,在系统调用入口函数中,将系统调用号赋值eax,将eax值保存在栈中。而在sys_call_table中找到对应的系统调用服务例程地址,其地址通过eax
4+基址得到,转而执行系统调用服务例程。
asmlinkage long sys_gettid(void);
asmlinkage long sys_nanosleep(struct timespec __user *rqtp, struct timespec __user *rmtp);
asmlinkage long sys_alarm(unsigned int seconds);
asmlinkage long sys_getpid(void);
asmlinkage long sys_getppid(void);
asmlinkage long sys_getuid(void);
asmlinkage long sys_geteuid(void);
asmlinkage long sys_getgid(void);
asmlinkage long sys_getegid(void);
asmlinkage long sys_getresuid(uid_t __user *ruid, uid_t __user *euid, uid_t _user *suid);
附带提一下,这里提到了宏asmlinkage,其定义为:
宏asmlinkage定义:linux/arch/x86/include/asm/linkage.h,从下面第一个代码_attribute
((regparm(0)))表示不通过寄存器传递参数,而是通过栈来传递参数,所以系统调用入口函数里面汇编指令SAVE_ALL将eax寄存器中的系统调用号压入栈。从下图第二个代码可以看出,系统调用最多可以为6个参数,可以传值给eax,ebx,ecx,edx……
代码一
#ifdef CONFIG_X86_32
#define asmlinkage CPP_ASMLINKAGE attribute((regparm(0)))
代码二
#define __asmlinkage_protect0(ret)
__asmlinkage_protect_n(ret)
#define __asmlinkage_protect1(ret, arg1)
__asmlinkage_protect_n(ret, “g” (arg1))
#define __asmlinkage_protect2(ret, arg1, arg2)
__asmlinkage_protect_n(ret, “g” (arg1), “g” (arg2))
#define __asmlinkage_protect3(ret, arg1, arg2, arg3)
__asmlinkage_protect_n(ret, “g” (arg1), “g” (arg2), “g” (arg3))
#define __asmlinkage_protect4(ret, arg1, arg2, arg3, arg4)
__asmlinkage_protect_n(ret, “g” (arg1), “g” (arg2), “g” (arg3),
“g” (arg4))
#define __asmlinkage_protect5(ret, arg1, arg2, arg3, arg4, arg5)
__asmlinkage_protect_n(ret, “g” (arg1), “g” (arg2), “g” (arg3),
“g” (arg4), “g” (arg5))
#define __asmlinkage_protect6(ret, arg1, arg2, arg3, arg4, arg5, arg6)
__asmlinkage_protect_n(ret, “g” (arg1), “g” (arg2), “g” (arg3),
“g” (arg4), “g” (arg5), “g” (arg6))
以上过程为操作系统根据用户程序API实现一个系统调用的原理,下述拦截系统调用原理:
通过上述分析可知,得到系统调用服务例程地址为sys_call_table地址加上eax,eax由用户程序给定,sys_call_table地址由内存决定,不能改变。但如果将sys_call_table表中调用号为20的函数替换为修改函数,通过sys_call_table+eax得到的就不再为sys_getpid,而是为sys_mycall(自己定义的一个函数)

获取内核符号表中的内容:
可以通过在/boot下的System.map中查找对应值,即grep sys_call_table System.map……具体参看该目录下名称。
也可以在/proc/kallsyms中获得。但是普通用户在kallsyms中看到的值全都为0,只能通过命令行
sudo cat /proc/kallsyms | grep "sys_call_table"得到sys_call_table的地址

从上图可以得到sys_call_table的基址为0xc15bb020,而且sys_call_table属性为R,是只读的,要想在sys_call_table表中添加或者删除一个系统调用,必须要改变sys_call_table的属性。
查阅资料得到,控制寄存器cr0的第16位是写保护位若该位清零了则允许超级权限,这里超级权限当然包括往内核空间写的权限。这样,就可以在写入之前,把那一位清零,使我们可以写入。写完后,再将那一位复原。

具体实现拦截系统调用的源代码,依然以getpid这个API函数举例。
首先是内核模块函数,用于实现拦截系统调用:

#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/unistd.h>
#include<linux/time.h>
#include<asm/uaccess.h>
#include<linux/sched.h>#define __NR_syscall 20
#define sys_call_table_address 0xc15bb020unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);int orig_cr0;
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void);unsigned int clear_and_return_cr0(void) //将cr0第16位清零
{unsigned int cr0 = 0;unsigned int ret;asm volatile ("movl %%cr0, %%eax": "=a"(cr0));ret = cr0;cr0 &= 0xfffeffff;asm volatile ("movl %%eax, %%cr0":: "a"(cr0));return ret;
}void setback_cr0(unsigned int val)  //将cr0第16位置位
{asm volatile ("movl %%eax, %%cr0":: "a"(val));
}asmlinkage long sys_mycall(void)  //拦截后的函数
{printk("the system call num.20 has changed!!\n");return 19940208;
}int init_addsyscall(void)
{printk("system call begin\n");sys_call_table = (unsigned long *)sys_call_table_address;anything_saved = (int(*)(void))(sys_call_table[__NR_syscall]);orig_cr0 = clear_and_return_cr0();sys_call_table[__NR_syscall] = (unsigned long)&sys_mycall;setback_cr0(orig_cr0);return 0;
}void exit_addsyscall(void)
{orig_cr0 = clear_and_return_cr0();sys_call_table[__NR_syscall] = (unsigned long)anything_saved;setback_cr0(orig_cr0);printk("call exit...\n");
}module_init(init_addsyscall);
module_exit(exit_addsyscall);MODULE_LICENSE("GPL");
然后是用户程序的测试程序:
#include<unistd.h>
#include<pwd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>int main(void)
{pid_t my_pid;my_pid = getpid();  //调用getpid得到进程的pidprintf("process ID:%ld\n",my_pid);   //输出pidreturn 0;
}

结果显示:
首先是未将拦截程序加入内核时测试程序的输出结果

使用内核模块添加系统调用相关推荐

  1. Linux 系统调用(二)——使用内核模块添加系统调用(无需编译内核)

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

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

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

  3. Ubuntu20.04+Linux5.8.8 添加系统调用实现进程隐藏

    Ubuntu20.04+Linux5.8.8 添加系统调用实现进程隐藏 虚拟机版本 主机信息 VMware 15 Thinkpad carbon X1 2020 + win10 目录 Ubuntu20 ...

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

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

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

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

  6. linux 3.5.0-23-generic内核版本系统调用数目,Linux操作系统分析(三)- 更新内核与添加系统调用...

    环境:Ubuntu 12.10     学号:SA****199 1.更新内核: 直接安装的系统内核版本一般不是最新,用 uname -a 查看一下 自己的版本,OK,我的是: Linux chenh ...

  7. MIPS(loongson)linux 中添加系统调用

    在基于MISP(loongson)架构处理器与基于 x86 架构处理器的 linux 内核中添加系统调用时更改的文件是不同的,x86 中需要更改 arch/x86/kernel/syscall_tab ...

  8. 【Linux】Linux添加系统调用以及内核编译过程

    在想要替换原有系统内核或者需要在原来的系统中添加一些系统调用的时候就会涉及到Linux内核的编译.但是内核编译虽然步骤简单,但是需要注意的东西还是太多了.首先一点就是由于Linux的开源性导致的版本问 ...

  9. NeuSoft(2)添加系统调用

    1.下载内核 apt-get install linux-source 在/usr/src下 2.解压内核 cd /usr/src tar -jxvf linux-source-3.2.0.tar.b ...

  10. 【内核驱动】 内核驱动中添加系统调用

    开发环境: Redhat6.5 开发板: Tiny4412 (ARM Cortex A9) 1. 系统调用概述 系统调用请点击 系统调用概述 2. 实现系统调用的步骤 添加一个系统调用比较简单,下面以 ...

最新文章

  1. Spring Cloud(一)服务的注册与发现(Eureka)
  2. 外媒称Windows 10是一辆“广告大巴车”
  3. mysql百万级性能瓶颈-数据库选型
  4. boost::owner_less相关的测试程序
  5. ajax的交互流程有哪几步
  6. 【STM32】FreeRTOS下载和介绍
  7. Enterprise search Callstack in runtime
  8. D. Multiset(树状数组 + 二分)
  9. python正则匹配ip_python实战系列之正则获取IP地址(八)
  10. 聊天类APP的测试点
  11. 人脸识别一体机解决方案
  12. 吞剑!喷火!这种江湖卖艺套路能吸引观众吗?
  13. 保险公司免费赠送保险可信吗?
  14. matplotlib生成没有留白的图片
  15. 二级指针(指向指针的指针)
  16. python你好怎么写_python学习之python入门
  17. xampp 可道云_Windows下用kodexplorer可道云在本地搭建私有云的步骤
  18. WIN10系统中RSLink Classic用RS232连接PLC无法连接
  19. Java基础笔记23-集合练习题
  20. 推荐52个堪称神器的学习网站,每天坚持一小时,让你受益一生【上】

热门文章

  1. app提交到iTunes失败
  2. 倪海厦天纪笔记16_倪海厦《天纪·天机道》笔记
  3. 技术干货 | PACMOO:基于帕累托最优的公平性约束协同过滤算法
  4. 移动端事件touchstart、touchmove、touchend详解
  5. P3369 【模板】普通平衡树(fhq treap)
  6. 创建视图,修改视图,修改视图数据
  7. win10的服务器管理器在哪打开?
  8. matlab恶狼追兔问题,饿狼追兔问题-数学建模.doc
  9. web h5调用微信分享功能
  10. 【冬瓜哥归来】传统存储老矣,新兴存储能当大任否?