前言

在开发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
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
9
        ret = execveat_register_kprobe();
10
        if (ret < 0) {
11
            printk(KERN_INFO "[SMITH] execveat register_kprobe failed, returned %d\n", ret);
12
        }
13
#endif
14
15
#ifdef CONFIG_COMPAT
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
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
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
#endif
27
28
#endif
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

监控connectbind,关键代码如下:

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
#if IS_ENABLED(CONFIG_IPV6)
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
#endif
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
#endif
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缺少新建进程的监控。

新建进程用的是forkvforkclone这三个函数,我们在内核中监控三个对应的系统调用即可。

  • fork fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
  • vfork vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
  • clone Linux上创建线程一般使用的是pthread库 实际上linux也给我们提供了创建线程的系统调用,就是clone

总结

攻防总是相互促进的,当你对防御手段有较深的理解,对学习反入侵,制定反入侵策略有很好的借鉴意义。