操作系统实验系列一: 编译内核与系统调用
编写查看系统信息的程序
添加系统调用(先进行测试)
添加自己的标识
编译内核
重启
相关链接:linux 内核编译及添加系统调用
_存在一个问题,除了重新编译内核还有其他方法来测试调用函数吗…(已解决)_歪门邪道:使用内核模块添加系统调用 :
使用 linux 内核模块添加系统调用 1
使用 linux 内核模块添加系统调用 2
可加载模块利用修改系统调用表运行木马(逃…
修改 sys_call_table 相关
1. 编写查看系统信息的程序
1.1 内核态读写文件
proc 文件系统提供了内核运行时的配置和状态信息
从 proc 文件夹中的文件读取相关信息即可
但是在内核态中无法调用标准 c 函数库进行读写
1 | asmlinkage long sys_pkernel(char *a,char *b,char *c,char *d,int n) |
1.2 实现步骤
- 打开文件
- 保存当前内存地址
- 将当前内存地址设置为内核空间
- 读取文件
- 将文件内容拷贝到用户空间
- 关闭文件
- 设置内存地址为用户空间
2. 添加系统调用
- 修改系统调用表文件
- 申明系统调用服务例程原型(函数声明)
- 实现系统调用服务(实现函数并将其放入内核代码中)
2.1 修改系统调用表文件
查看系统调用表(./arch/x86/entry/syscalls/syscall_64.tbl)
选择一个未使用过的系统调用号进行分配
2.2 申明系统调用服务例程原型
在系统调用头文件中添加声明(./include/linux/syscalls.h).每个系统调用都对应一个内核服务例程来实现该系统调用的具体功能,其命名格式都是以“sys_”开头,但是新实现的调用直接命名为 print_info 了.其中“asmlinkage”是一个必须的限定词,用于通知编译器仅从堆栈中提取该函数的参数,而不是从寄存器中,因为在执行服务例程之前系统已经将通过寄存器传递过来的参数值压入内核堆栈了。
2.3 实现系统调用服务
实现函数并放入(./kernel/sys.c )中
2.4 不使用编译内核的方法测试系统调用函数
主要思路是通过建立动态加载模块来进入内核态
在动态模块中测试系统调用的一些函数
相关链接:建立动态加载模块
测试的时候可能会出现由于一些原因模块引用计数器不为 0 导致无法正常卸载模块,可以通过一些办法强行卸载:
相关链接:强行卸载模块 1 强行卸载模块 2
(这个动作很危险…电脑鼠标都动不了了…)
常用命令:
指令 | 作用 |
---|---|
insmod | 加载模块 |
rmmod | 卸载模块 |
效果:
不知道为啥立即被杀死了,但是好像有效果.应该是调用代码出问题了.
果然是函数写错了…用了大佬的函数就对了
挖坑
目前只在模块初始化函数里执行系统调用函数 无法传参.
准备看看能不能用模块实现更多的东西,以后可能会有用.
3. 添加自己的标示
在配置内核的时候(make menuconfig)添加:
General setup -> Local version –append to kernel release 中为本地内核的 版本号添加一个标志,要求标志为版本号-学号.
4. 编译内核
- make mrproper(清除残余不必要文件)
- make menuconfig(对编译进行配置)
- make(编译内核)(-jn 指定使用的核心数)
- make modules(编译模块)
- make modules_install(安装模块)
- make install(安装内核)
- update_grub2(配置引导程序)
- sudo reboot(重启)
- uname -r(查看内核版本)
5. 用新内核进行启动
安装玩内核之后运行
1 | sudo update-grub |
在启动的时候选择高级选项,选择不同版本的内核进行启动
6. 番外篇=>尝试用模块修改系统调用函数(高度危险…)
链接:使用内核模块添加系统调用
6.1 准备工作
首先从相关博客上找到修改的代码,思路如下:
- 获取系统的调用表地址
- 找到自己的未使用的调用号(或者可能没用的…)
- 修改 cr0 寄存器,取消系统的内存写保护.
- 将调用号对应的地址指向自己的调用代码
- 恢复 cr0 寄存器
6.2 遇到的问题
- 相关博客都是好多年前的,使用的是 32 位系统,首先要将代码中的 32 位汇编代码修改成 64 位的
- 需要把相关头文件换成现在对应的版本
- 调试的时候无法打断点(废话…),将调试的相关信息通过 printf 打印到内核代码中
- 现在好像是在获取系统调用号对应的地址的时候就受到了系统的阻挠…
- 测试了一下,cr0 的 16 位确实被置 0 关闭了写保护,说明不是写保护导致的写入错误,接下来看看是不是因为应该把内存地址改小一点(去掉 ffffffff)
- 依然出错了,说明不是地址的原因
- 看了一些其他修改 sys_call_table 的博客,他们的数据类型有些不太一样,决定修改下
- 无意中发现 292 不是没用的系统调用….(系统调用表路径:/usr/include/x86_64-linux-gnu/asm),但是同时也发现一个应该用不到的系统调用:) ===> #define __NR_restart_syscall 219
- 好吧这个也有用…用来重新启动另一个被迫中断的系统调用…
#define \_\_NR_renameat2 316
这个应该可以了,重命名文件应该作用不大- 发现了一个奇怪的点,如果强行把模块加载,会提示报错,但是这个时候无法再对一个文件重命名了,这个操作刚好就是我们覆盖了的系统调用,那么感觉可以得出一个结论:系统报错并不是权限不够,而是写入的方法错了,可能是数据类型不匹配? (分两种情况:在 vscode 中改会发现改完后名字不变,在文件管理系统中改系统崩了….死机了…)但是可喜可贺确实已经把 sys_call_table 中的内容给修改了,但是为什么没有按照我们预期的走….emmmm 头大
- 这个时候我发现我装的搜狗输入法崩了…重启后变成了系统原本的输入法….先这么用这吧…
- 先弄到这样吧,暂时先用在加载模块的时候调用函数代替更换系统调用.
- 又尝试了一个大佬写的 linux 文件系统木马(监视文件活动),原理也是通过替换系统调用实现的.但是 5.0 的内核还是报错了,开始怀疑可能是内核版本高引起的问题,决定安装一个低版本的内核重新来一次.
- 内核降级相关链接:ubuntu 内核降级
- 内核降级后重新安装模块安全开发包:重新安装模块安全开发包
安装开发模块安装模块ubuntu 中安装 kernel-devel 命令: sudo apt-get install linux-kernel-headers kernel-package- 接着又遇到一个问题:module verification failed: signature and/or required key missing - tainting kernel(签名问题,这个简单,改一下就好了)
- 添加模块签名
- linux 头文件引用规则(用于寻找对应的头文件):头文件路径规则
- 低版本的 linux 有没用的系统调用!
- 解决搜狗输入法乱码:重新启动 fcitx 框架 (pidof fcitx|xargs kill)
获取系统内存失败
写入系统内存失败
出错代码:
1 | static int (*anything_saved)(void); |
图片来源:kernel oops (Unable to handle kernel paging request at virtual address )三种内存访问异常
系统内存分布
大佬的运行结果
我的运行结果
成了!!!!!!!!!
纪念一下正式成功