前言
在开发HIDS的时候内核监控是必不可少的环节,但是具体有哪些东西需要监控呢,本文就会对此进行一些梳理。我们知道新的进程、新的文件这些最基础的监控是必须有的。还有哪些
hook技术简介
简单来讲hook技术,基本上就是劫持和监控某个系统调用。hook的技术有许多,不同的hids有不同的hook策略。比如驭龙系统就是从内核态hook syscall,AgentSmith则使用kprobe
机制来进行hook。各种hook的技术各有优缺点,本文不详细讲,具体的比较可以参考笔者之前文章。
到底需要hook哪些?
hook当然是了更好的发现入侵行为。以入侵行为角度分析,结合AgentSmith,来详细介绍下具体需要hook的函数。
Execve hook
execve
是用来监控命令执行的,入侵必然需要执行命令获取权限或者数据。作为最基础的策略,execve hook
是最简单也是最必要的监控。execve hook
主要通过execve/execveat/compat_execve/compat_execveat
监控这4个函数实现。关键代码如下:
1 | if (EXECVE_HOOK == 1) { |
2 | ret = execve_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] execve register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | |
8 |
|
9 | ret = execveat_register_kprobe(); |
10 | if (ret < 0) { |
11 | printk(KERN_INFO "[SMITH] execveat register_kprobe failed, returned %d\n", ret); |
12 | } |
13 |
|
14 | |
15 |
|
16 | ret = compat_execve_register_kprobe(); |
17 | if (ret < 0) { |
18 | printk(KERN_INFO "[SMITH] compat_sys_execve register_kprobe failed, returned %d\n", ret); |
19 | } |
20 | |
21 |
|
22 | ret = compat_execveat_register_kprobe(); |
23 | if (ret < 0) { |
24 | printk(KERN_INFO "[SMITH] compat_sys_execveat register_kprobe failed, returned %d\n", ret); |
25 | } |
26 |
|
27 | |
28 |
|
29 | } |
生成的数据样例如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"59", |
4 | "run_path":"/usr/bin/bash", |
5 | "exe":"/usr/bin/bash", |
6 | "argv":"bash -i ", |
7 | "pid":"6364", |
8 | "ppid":"2549", |
9 | "pgid":"6364", |
10 | "tgid":"6364", |
11 | "comm":"bash", |
12 | "nodename":"test", |
13 | "stdin":"socket:[80649]", |
14 | "stdout":"socket:[80649]", |
15 | "sessionid":"3", |
16 | "dip":"127.0.0.1", |
17 | "dport":"233", |
18 | "sip":"127.0.0.1", |
19 | "sport":"60620", |
20 | "sa_family":"2", |
21 | "pid_tree":"1(systemd)->1565(sshd)->2093(sshd)->2096(bash)->2147(fish)->2549(bash)->6364(bash)", |
22 | "tty_name":"pts0", |
23 | "socket_process_pid":"6364", |
24 | "socket_process_exe":"/usr/bin/bash", |
25 | "SSH_CONNECTION":"192.168.165.1 50422 192.168.165.152 22", |
26 | "LD_PRELOAD":"", |
27 | "user":"root", |
28 | "time":"1580104472249", |
29 | "local_ip":"192.168.165.152", |
30 | "hostname":"test", |
31 | "exe_md5":"f926bedd777fa0f4f71dd2d28155862a", |
32 | "socket_process_exe_md5":"f926bedd777fa0f4f71dd2d28155862a" |
33 | } |
用这个数据就可以做反弹shell监控策略,比如判断exe是bash,stdin和stdout是socket,则是反弹shell导致的命令执行。
connect hook和bind hook
监控connect
和bind
,关键代码如下:
1 | if (CONNECT_HOOK == 1) { |
2 | ret = tcp_v4_connect_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] connect register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | |
8 | ret = ip4_datagram_connect_register_kprobe(); |
9 | if (ret < 0) { |
10 | printk(KERN_INFO |
11 | "[SMITH] ip4_datagram_connect register_kprobe failed, returned %d\n", ret); |
12 | } |
13 | |
14 |
|
15 | ret = tcp_v6_connect_register_kprobe(); |
16 | if (ret < 0) { |
17 | printk(KERN_INFO |
18 | "[SMITH] tcp_v6_connect register_kprobe failed, returned %d\n", ret); |
19 | } |
20 | |
21 | ret = ip6_datagram_connect_register_kprobe(); |
22 | if (ret < 0) { |
23 | printk(KERN_INFO |
24 | "[SMITH] ip6_datagram_connect register_kprobe failed, returned %d\n", ret); |
25 | } |
26 |
|
27 | } |
28 | |
29 | int ip6_datagram_connect_register_kprobe(void) { |
30 | int ret; |
31 | ret = register_kretprobe(&ip6_datagram_connect_kretprobe); |
32 | |
33 | if (ret == 0) |
34 | ip6_datagram_connect_kprobe_state = 0x1; |
35 | |
36 | return ret; |
37 | } |
38 | |
39 | void unregister_kprobe_ip6_datagram_connect(void) { |
40 | unregister_kretprobe(&ip6_datagram_connect_kretprobe); |
41 | } |
42 | |
43 | int tcp_v6_connect_register_kprobe(void) { |
44 | int ret; |
45 | ret = register_kretprobe(&tcp_v6_connect_kretprobe); |
46 | |
47 | if (ret == 0) |
48 | tcp_v6_connect_kprobe_state = 0x1; |
49 | |
50 | return ret; |
51 | } |
52 | |
53 | void unregister_kprobe_tcp_v6_connect(void) { |
54 | unregister_kretprobe(&tcp_v6_connect_kretprobe); |
55 | } |
56 |
|
57 | |
58 | int ip4_datagram_connect_register_kprobe(void) { |
59 | int ret; |
60 | ret = register_kretprobe(&ip4_datagram_connect_kretprobe); |
61 | |
62 | if (ret == 0) |
63 | ip4_datagram_connect_kprobe_state = 0x1; |
64 | |
65 | return ret; |
66 | } |
67 | |
68 | void unregister_kprobe_ip4_datagram_connect(void) { |
69 | unregister_kretprobe(&ip4_datagram_connect_kretprobe); |
70 | } |
71 | |
72 | int tcp_v4_connect_register_kprobe(void) { |
73 | int ret; |
74 | ret = register_kretprobe(&tcp_v4_connect_kretprobe); |
75 | |
76 | if (ret == 0) |
77 | tcp_v4_connect_kprobe_state = 0x1; |
78 | |
79 | return ret; |
80 | } |
81 | |
82 | void unregister_kprobe_tcp_v4_connect(void) { |
83 | unregister_kretprobe(&tcp_v4_connect_kretprobe); |
84 | } |
1 | if (BIND_HOOK == 1) { |
2 | ret = bind_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] bind register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | } |
8 | |
9 | struct kretprobe bind_kretprobe = { |
10 | .kp.symbol_name = P_GET_SYSCALL_NAME(bind), |
11 | .data_size = sizeof(struct bind_data), |
12 | .handler = bind_handler, |
13 | .entry_handler = bind_entry_handler, |
14 | .maxactive = MAXACTIVE, |
15 | }; |
样例数据如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"42", |
4 | "sa_family":"2", |
5 | "fd":"4", |
6 | "dport":"1025", |
7 | "dip":"180.101.49.11", |
8 | "exe":"/usr/bin/ping", |
9 | "pid":"6294", |
10 | "ppid":"1941", |
11 | "pgid":"6294", |
12 | "tgid":"6294", |
13 | "comm":"ping", |
14 | "nodename":"test", |
15 | "sip":"192.168.165.153", |
16 | "sport":"45524", |
17 | "res":"0", |
18 | "sessionid":"1", |
19 | "user":"root", |
20 | "time":"1575721921240", |
21 | "local_ip":"192.168.165.153", |
22 | "hostname":"test", |
23 | "exe_md5":"735ae70b4ceb8707acc40bc5a3d06e04" |
24 | } |
这些数据可以用来制定恶意软件连接等监控策略。
DNS hook
监控udp_recvmsg
,关键代码如下:
1 | if (DNS_HOOK == 1) { |
2 | ret = udp_recvmsg_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] udp_recvmsg register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | |
8 | struct kretprobe udp_recvmsg_kretprobe = { |
9 | .kp.symbol_name = "udp_recvmsg", |
10 | .data_size = sizeof(struct udp_recvmsg_data), |
11 | .handler = udp_recvmsg_handler, |
12 | .entry_handler = udp_recvmsg_entry_handler, |
13 | .maxactive = MAXACTIVE, |
14 | }; |
样例数据如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"601", |
4 | "sa_family":"2", |
5 | "fd":"4", |
6 | "dport":"53", |
7 | "dip":"192.168.165.2", |
8 | "exe":"/usr/bin/ping", |
9 | "pid":"6294", |
10 | "ppid":"1941", |
11 | "pgid":"6294", |
12 | "tgid":"6294", |
13 | "comm":"ping", |
14 | "nodename":"test", |
15 | "sip":"192.168.165.153", |
16 | "sport":"53178", |
17 | "qr":"1", |
18 | "opcode":"0", |
19 | "rcode":"0", |
20 | "query":"www.baidu.com", |
21 | "sessionid":"1", |
22 | "user":"root", |
23 | "time":"1575721921240", |
24 | "local_ip":"192.168.165.153", |
25 | "hostname":"test", |
26 | "exe_md5":"39c45487a85e26ce5755a893f7e88293" |
27 | } |
可以通过该数据判断木马恶意的c2地址等。
create file hook
通过hook security_inode_create
,关键代码如下:
1 | if (CREATE_FILE_HOOK == 1) { |
2 | ret = create_file_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] create_file register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | } |
8 | |
9 | struct kretprobe security_inode_create_kretprobe = { |
10 | .kp.symbol_name = "security_inode_create", |
11 | .entry_handler = security_inode_create_entry_handler, |
12 | .maxactive = MAXACTIVE, |
13 | }; |
数据样例如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"602", |
4 | "exe":"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.232.b09-0.el7_7.x86_64/jre/bin/java", |
5 | "file_path":"/tmp/kafka-logs/replication-offset-checkpoint.tmp", |
6 | "pid":"3341", |
7 | "ppid":"1", |
8 | "pgid":"2657", |
9 | "tgid":"2659", |
10 | "comm":"kafka-scheduler", |
11 | "nodename":"test", |
12 | "sessionid":"3", |
13 | "user":"root", |
14 | "time":"1575721984257", |
15 | "local_ip":"192.168.165.153", |
16 | "hostname":"test", |
17 | "exe_md5":"215be70a38c3a2e14e09d637c85d5311", |
18 | "create_file_md5":"d41d8cd98f00b204e9800998ecf8427e" |
19 | } |
监控新文件,可以用于判断是否是恶意文件或者webshell等。
ptrace hook
通过hook ptrace
实现,关键代码如下:
1 | if (PTRACE_HOOK == 1) { |
2 | ret = ptrace_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] ptrace register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | } |
8 | |
9 | struct kprobe ptrace_kprobe = { |
10 | .symbol_name = P_GET_SYSCALL_NAME(ptrace), |
11 | .post_handler = ptrace_post_handler, |
12 | }; |
数据样例如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"101", |
4 | "ptrace_request":"4", |
5 | "target_pid":"7402", |
6 | "addr":"00007ffe13011ee6", |
7 | "data":"-a", |
8 | "exe":"/root/ptrace/ptrace", |
9 | "pid":"7401", |
10 | "ppid":"1941", |
11 | "pgid":"7401", |
12 | "tgid":"7401", |
13 | "comm":"ptrace", |
14 | "nodename":"test", |
15 | "sessionid":"1", |
16 | "user":"root", |
17 | "time":"1575722717065", |
18 | "local_ip":"192.168.165.153", |
19 | "hostname":"test", |
20 | "exe_md5":"863293f9fcf1af7afe5797a4b6b7aa0a" |
21 | } |
ptrace监控对于ptrace进程注入的检测是非常有效的,正式环境中一般不会有gdb的调试,所以如果有ptrace时间,很大概率就是被入侵了。
LKM hook
通过hook load_module
实现,关键代码如下:
1 | if (LOAD_MODULE_HOOK == 1) { |
2 | ret = load_module_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] load_module register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | } |
8 | |
9 | struct kprobe load_module_kprobe = { |
10 | .symbol_name = "load_module", |
11 | .post_handler = load_module_post_handler, |
12 | }; |
样例数据如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"603", |
4 | "exe":"/usr/bin/kmod", |
5 | "lkm_file":"/root/ptrace/ptrace", |
6 | "pid":"29461", |
7 | "ppid":"9766", |
8 | "pgid":"29461", |
9 | "tgid":"29461", |
10 | "comm":"insmod", |
11 | "nodename":"test", |
12 | "sessionid":"13", |
13 | "user":"root", |
14 | "time":"1577212873791", |
15 | "local_ip":"192.168.165.152", |
16 | "hostname":"test", |
17 | "exe_md5":"0010433ab9105d666b044779f36d6d1e", |
18 | "load_file_md5":"863293f9fcf1af7afe5797a4b6b7aa0a" |
19 | } |
用于监控恶意的lkm模块的加载。
Cred hook
通过hook commit_creds
实现,关键代码如下:
1 | if (UPDATE_CRED_HOOK == 1) { |
2 | ret = update_cred_register_kprobe(); |
3 | if (ret < 0) { |
4 | printk(KERN_INFO |
5 | "[SMITH] update_cred register_kprobe failed, returned %d\n", ret); |
6 | } |
7 | } |
8 | |
9 | struct kretprobe update_cred_kretprobe = { |
10 | .kp.symbol_name = "commit_creds", |
11 | .data_size = sizeof(struct update_cred_data), |
12 | .handler = update_cred_handler, |
13 | .entry_handler = update_cred_entry_handler, |
14 | .maxactive = MAXACTIVE, |
15 | }; |
样例数据如下:
1 | { |
2 | "uid":"0", |
3 | "data_type":"604", |
4 | "exe":"/tmp/tt", |
5 | "pid":"27737", |
6 | "ppid":"26865", |
7 | "pgid":"27737", |
8 | "tgid":"27737", |
9 | "comm":"tt", |
10 | "old_uid":"1000", |
11 | "nodename":"test", |
12 | "sessionid":"42", |
13 | "user":"root", |
14 | "time":"1578396197131", |
15 | "local_ip":"192.168.165.152", |
16 | "hostname":"test", |
17 | "exe_md5":"d99a695d2dc4b5099383f30964689c55" |
18 | } |
通过uid的变化可以判断提权行为。
新进程 hook
AgentSmith缺少新建进程的监控。
新建进程用的是fork
、vfork
、clone
这三个函数,我们在内核中监控三个对应的系统调用即可。
fork
fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容vfork
vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行clone
Linux上创建线程一般使用的是pthread库 实际上linux也给我们提供了创建线程的系统调用,就是clone
总结
攻防总是相互促进的,当你对防御手段有较深的理解,对学习反入侵,制定反入侵策略有很好的借鉴意义。