当前位置: 首页 > news >正文

Linux 2.6 中导出sys_call_table表修改系统调用函数

         Linux中实现系统调用时是利用了i386体系结构中的软中断,通过产生0x80中断,使程序由用户态进入内核态执行系统调用函数。当系统调用发生时,产生0x80中断,CPU被切换到内核态执行中断向量表IDT对应的0x80中断处理函数,即跳转到了system_call()的入口,system_call()函数检查系统调用号,到系统调用表sys_call_table中找到该系统调用(号)对应的内核函数入口,接着调用这个内核函数,然后返回。

                                                

     

   那么要修改系统调,只需要在sys_call_table中将原有的系统调用替换为自己写的新系统调用就可以了。但是这对用户来说无疑是个巨大的安全隐患。在2.4内核中sys_call_table是直接导出的,使改变系统调用函数变得很容易,出现了很多安全问题(后门程序)。在2.6内核中已经没有将sys_call_table导出了。所以我们第一件要做的事情就要想办法“重新”导出sys_call_table,找到其地址。

(一) 导出sys_call_table

 1、首先我们在Sysmap中查看sys_call_table 地址。

[root@sun ~]# grep sys_call_table /boot/System.map-2.6.35.6-45.fc14.i686 
c07ae328 R sys_call_table
其中 R 直接显示了这段内存是只读的性质,这就引出了第二步骤“取消页读写保护”。


2、可以直接修改内核源码,自己导出sys_call_table,然后再编译内核。这个办法耗时,不可移植。

修改过程分为三步:

1、修改arch/x86/kernel/entry_32.S
将语句
-.section .rodata,"a"
替换为
+.section .data,"aw"
将sys_call_table设置为可读可写

2、修改kernel/kallsyms.c,到处sys_call_table符号
+extern void *sys_call_table;
+EXPORT_SYMBOL(sys_call_table);

3、在自己的模块程序中声明sys_call_table并使用
extern void *sys_call_table[];
old_entry = sys_call_table[285];
sys_call_table[285] = sys_storeint;

3、从中断向量表获取系统调用符号表sys_call_table。(采用)

unsigned long* find_sys_call_table(void)
{
		// 中断描述符表寄存器结构
        struct {
                unsigned short  limit;
                unsigned int    base;
        } __attribute__ ( ( packed ) ) idtr;
 
       // 中断描述符表结构
        struct {
                unsigned short  offset_low;
                unsigned short  segment_select;
                unsigned char   reserved,   flags;
                unsigned short  offset_high;
        } __attribute__ ( ( packed ) ) * idt;
 
        unsigned long system_call = 0;        // x80中断处理程序system_call 地址
        char *call_hex = "\xff\x14\x85";        // call 指令
        char *code_ptr = NULL;
        char *p = NULL;
        unsigned long sct = 0x0;
        int i = 0;
	
        //通过sidt指令获得中断描述表寄存器内容放入idtr,通过idtr.base即可得到idt的基地址
        __asm__ ( "sidt %0": "=m" ( idtr ) );
        idt = ( void * ) ( idtr.base + 8 * 0x80 );
        system_call = ( idt->offset_high << 16 ) | idt->offset_low;
 
        code_ptr = (char *)system_call;
        for(i = 0;i < ( 100 - 2 ); i++) {
                if(code_ptr[i] == call_hex[0]
                                && code_ptr[i+1] == call_hex[1]
                                && code_ptr[i+2] == call_hex[2] ) {
                        p = &code_ptr[i] + 3;
                        break;
                }
        }
        if ( p ){
                sct = *(unsigned long*)p;
        }
        return (unsigned long*)sct;
}

实现原理:

      在linux中使用0x80异常实现系统调用,因此,主要通过先获取中断向量表,然后或许0x80中断处理函数(系统调用处理函数system_call())地址,最后根据system_call()编码特点找到 sys_call_table。

(1)中断向量表的获取

   在x86中,idtr寄存器使得中断向量表可以存放在内存的任何位置,idtr寄存器有一个基地址和一个段限地址组成,高4字节为基地址,低2字节为段限地址。可以通过sidt指令获得idtr的内容。

// 中断描述符表寄存器结构
struct {
unsigned short limit;
unsigned int base;
} __attribute__ ( ( packed ) ) idtr;

__asm__ ( "sidt %0": "=m" ( idtr ) );

通过sidt指令获得中断描述表寄存器内容放入idtr,通过idtr.base即可得到idt的基地址

(2)系统调用处理函数地址的获取

    IDT基地址存放的是中断门,每个门8个字节,门描述符的格式参考Intel开发手册,其中,中断门是最低两个字节和最高两个字节构成了中断处理程序的地址。

   // 中断描述符表结构
        struct {
                unsigned short  offset_low;
                unsigned short  segment_select;
                unsigned char   reserved,   flags;
                unsigned short  offset_high;
        } __attribute__ ( ( packed ) ) * idt;

获取系统调用中断处理程序sys_call()的地址:

idt = ( void * ) ( idtr.base + 8 * 0x80 );

system_call = ( idt->offset_high << 16 ) | idt->offset_low;

(3)获取系统调用表sys_call_table

        system_call是所有系统调用的处理程序,在进行必要的处理后,统一调用 call sys_call_table(,eax,4)来调用sys_call_table表中的系统调用服务,eax存放的即时系统调用号,因此,获取sys_call_table的地址即可以达到目的。
        通过反汇编sys_call函数,可以得知,只有在调用系统调用处使用了call指令,x86 call指令的二进制格式为\xff\x14\x85,因此,我们可以从sys_call函数开始进行搜索,当出现\xff\x14\x85指令的时候,即为call的地址,从而能得到存放sys_call_table的地址即当前地址+3,而系统调用表即地址的内容,因此,获取系统调用表地址的实现过程就简单了。

code_ptr = (char *)system_call;
        for(i = 0;i < ( 100 - 2 ); i++) {
                if(code_ptr[i] == call_hex[0]
                                && code_ptr[i+1] == call_hex[1]
                                && code_ptr[i+2] == call_hex[2] ) {
                        p = &code_ptr[i] + 3;
                        break;
                }
        }
        if ( p ){
                sct = *(unsigned long*)p;
        }

这是通过简单的搜索的方式来找到call 指令,从而得到sys_call_table的地址的。

(二)取消sys_call_table页表的写保护

我们在上述看到sys_call_table只是可读的,那么我们就没法修改系统调用符号表,用自己写的系统调用函数替换原有函数。所有我们需要取消页的读写保护。

  1、使用change_page_attr()函数。但是经查证在2.6.25以后就取消掉了该函数,故以下方法在本系统(2.6.35.6)中已经失效了,不能编译通过

      static int set_page_rw(long unsigned int _addr)
      {
              struct page* pg;
              pgprot_t prot;
              pg = virt_to_page(_addr);
              prot.pgprot = VM_READ| VM_WRITE;
              return change_page_attr(pg, 1, prot);
      }
     
      static int set_page_ro(long unsigned int _addr)
      {
              struct page* pg;
              pgprot_t prot;
              pg = virt_to_page(_addr);
              prot.pgprot = VM_READ;
              return change_page_attr(pg, 1, prot);
      }

2、设置CR0 的WP位,来取消写保护。 (采用)

原理:对于Intel 80486或以上的CPU,CR0的位16是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当该位复位时则反之。该标志有利于UNIX类操作系统在创建进程时实现写时复制(Copy on Write)技术。

/* FUNCTION TO DISABLE WRITE PROTECT BIT IN CPU */
static void disable_wp(void)
{
        unsigned int cr0_value;
        
        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));
        
        /* Disable WP */
        cr0_value &= ~(1 << 16);
        
        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}
        
/* FUNCTION TO RE-ENABLE WRITE PROTECT BIT IN CPU */
static void enable_wp(void)
{
        unsigned int cr0_value;

        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));

        /* Enable WP */
        cr0_value |= (1 << 16);

        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}


(三)修改系统调用open的实例

        在前面的基础上,写一个自己的open系统调用。自己的系统调用中加入对打开文件的记录,并打印记录,最后在调用原始系统调用open(注意:如果最后不调用原始open函数,那么会带来极大的灾难,例如mkdir等很多命令不能使用)。本模块在2.6.35.6内核中编译、安装通过。

syscall.c

#include <linux/kernel.h>   
#include <linux/module.h>   
#include <linux/moduleparam.h>  
#include <linux/unistd.h>  
#include <linux/init.h>
/* 
 * For the current (process) structure, we need
 * this to know who the current user is. 
 */
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/cacheflush.h>



static  unsigned long **sys_call_table;


unsigned long* find_sys_call_table(void)
{
        struct {
                unsigned short  limit;
                unsigned int    base;
        } __attribute__ ( ( packed ) ) idtr;
 
        struct {
                unsigned short  offset_low;
                unsigned short  segment_select;
                unsigned char   reserved,   flags;
                unsigned short  offset_high;
        } __attribute__ ( ( packed ) ) * idt;
 
        unsigned long system_call = 0;        // x80中断处理程序system_call 地址
        char *call_hex = "\xff\x14\x85";        // call 指令
        char *code_ptr = NULL;
        char *p = NULL;
        unsigned long sct = 0x0;
        int i = 0;
 
        __asm__ ( "sidt %0": "=m" ( idtr ) );
        idt = ( void * ) ( idtr.base + 8 * 0x80 );
        system_call = ( idt->offset_high << 16 ) | idt->offset_low;
 
        code_ptr = (char *)system_call;
        for(i = 0;i < ( 100 - 2 ); i++) {
                if(code_ptr[i] == call_hex[0]
                                && code_ptr[i+1] == call_hex[1]
                                && code_ptr[i+2] == call_hex[2] ) {
                        p = &code_ptr[i] + 3;
                        break;
                }
        }
        if ( p ){
                sct = *(unsigned long*)p;
        }
        return (unsigned long*)sct;
}



/* FUNCTION TO DISABLE WRITE PROTECT BIT IN CPU */
static void disable_wp(void)
{
        unsigned int cr0_value;
        
        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));
        
        /* Disable WP */
        cr0_value &= ~(1 << 16);
        
        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}
        
/* FUNCTION TO RE-ENABLE WRITE PROTECT BIT IN CPU */
static void enable_wp(void)
{
        unsigned int cr0_value;

        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));

        /* Enable WP */
        cr0_value |= (1 << 16);

        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}



/* 
 * UID we want to spy on - will be filled from the
 * command line 
 */
static int uid;
module_param(uid, int, 0644);

asmlinkage int (*original_call) (const char *, int, int);


asmlinkage int our_sys_open(const char *filename, int flags, int mode)
{
    int i = 0;
    char ch;

    /* 
     * Check if this is the user we're spying on 
     */
    if (uid == current->cred->uid) {     //2.6.35 中current->cred->uid
        printk("Opened file by %d: ", uid);
        do {
            get_user(ch, filename + i);
            i++;
            printk("%c", ch);
        } while (ch != 0);
        printk("\n");
    }

    /* 
     * Call the original sys_open - otherwise, we lose
     * the ability to open files 
     */
    return original_call(filename, flags, mode);
}



/* 
 * Initialize the module - replace the system call 
 */


unsigned int cr0;

int init_module()
{
    
    sys_call_table=find_sys_call_table();
    disable_wp();
    
    original_call=sys_call_table[__NR_open];
    sys_call_table[__NR_open] = (long*)our_sys_open;
   

    printk("Spying on UID:%d\n", uid);

    return 0;
}

/* 
 * Cleanup - unregister the appropriate file from /proc 
 */
void cleanup_module()
{
    sys_call_table[__NR_open] = (long *)original_call;
    enable_wp();
}

MODULE_LICENSE("GPL");

Makefile

obj-m:=syscall.o

all:
        make -C /usr/src/kernels/$(shell uname -r)  M=$(shell pwd) modules 
clean:
        make -C /usr/src/kernels/$(shell uname -r) M=$(shell pwd) clean

学习参考:

linux 系统调用中断劫持实现—原理和代码。

http://blog.sina.com.cn/s/blog_596d00a70100jpa7.html

The Linux Kernel Module Programming Guide(chapter 8)

http://www.tldp.org/LDP/lkmpg/2.6/html/

相关文章:

  • [九度 1510 剑指offer]—替换空格 数组插入逆向移动
  • 个人设置随身携带口袋操作系统手到擒来
  • 免费邮箱,谁更可靠?6款常用免费邮箱收信效果对比测试
  • 哪个搜索引擎更聪明?微软必应搜索挑战赛
  • [九度1512 剑指offer7] 用两个栈实现队列
  • 无光驱没光盘 操作系统照样可以安
  • mmap() 实现文件复制
  • [Linux内存管理-分页机制]—把一个虚拟地址转换为物理地址
  • C#也能动态生成Word文档并填充数据
  • Epoll实现服务器高并发
  • Linux中实现线程池
  • 《BREW进阶与精通——3G移动增值业务的运营、定制与开发》连载之18---商业模式...
  • 凤巢能否成功关键还看用户体验
  • 谁能不查字典把这文章念出来我就服谁
  • Android开发指南-框架主题-意图和意图过滤器
  • 2017 年终总结 —— 在路上
  • 2018一半小结一波
  • IP路由与转发
  • Iterator 和 for...of 循环
  • JAVA SE 6 GC调优笔记
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • JS笔记四:作用域、变量(函数)提升
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • python 装饰器(一)
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Sublime text 3 3103 注册码
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 安卓应用性能调试和优化经验分享
  • 关于 Cirru Editor 存储格式
  • 基于web的全景—— Pannellum小试
  • 记录:CentOS7.2配置LNMP环境记录
  • 力扣(LeetCode)21
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 应用生命周期终极 DevOps 工具包
  • puppet连载22:define用法
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (3)(3.5) 遥测无线电区域条例
  • (30)数组元素和与数字和的绝对差
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (NSDate) 时间 (time )比较
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (强烈推荐)移动端音视频从零到上手(上)
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (四) 虚拟摄像头vivi体验
  • (转)shell调试方法
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • *p++,*(p++),*++p,(*p)++区别?
  • .form文件_一篇文章学会文件上传
  • .NET Core 中插件式开发实现
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET 设计模式—简单工厂(Simple Factory Pattern)