Linux用户态Hook DNS方案


作者:老路(Dean)  
微信号:dean2029  (欢迎来撩)
文章来源:主机安全 (知识星球)

一、方案原理

NSSwitch(名称服务开关)是类Unix操作系统中的一项功能,它为通用配置数据库和名称解析机制提供了多种来源。这些源包括本地操作系统文件,域名系统,网络信息服务和LDAP。

通过修改/etc/nsswitch.conf文件中hosts项加载顺序,加载我们自定义的nss插件[参考6],即可hook到dns query.

/etc/nsswitch.conf文件内容如下:

#
# /etc/nsswitch.conf
#
# An example Name Service Switch config file. This file should be
# sorted with the most-used services at the beginning.
#
# The entry '[NOTFOUND=return]' means that the search for an
# entry should stop if the search in the previous entry turned
# up nothing. Note that if the search failed due to some other reason
# (like no NIS server responding) then the search continues with the
# next entry.
#
# Valid entries include:
#
#       nisplus                 Use NIS+ (NIS version 3)
#       nis                     Use NIS (NIS version 2), also called YP
#       dns                     Use DNS (Domain Name Service)
#       files                   Use the local files
#       db                      Use the local database (.db) files
#       compat                  Use NIS on compat mode
#       hesiod                  Use Hesiod for user lookups
#       [NOTFOUND=return]       Stop searching if not found so far
#

# To use db, put the "db" in front of "files" for entries you want to be
# looked up first in the databases
#
# Example:
#passwd:    db files nisplus nis
#shadow:    db files nisplus nis
#group:     db files nisplus nis

passwd:     files sss
shadow:     files sss
group:      files sss
#initgroups: files sss

#hosts:     db files nisplus nis dns
hosts:     files dns myhostname

# Example - obey only what nisplus tells us...
#services:   nisplus [NOTFOUND=return] files
#networks:   nisplus [NOTFOUND=return] files
#protocols:  nisplus [NOTFOUND=return] files
#rpc:        nisplus [NOTFOUND=return] files
#ethers:     nisplus [NOTFOUND=return] files
#netmasks:   nisplus [NOTFOUND=return] files     

bootparams: nisplus [NOTFOUND=return] files

ethers:     files
netmasks:   files
networks:   files
protocols:  files
rpc:        files
services:   files sss

我们只关注hosts项

hosts:     files dns myhostname

这里的files、dns、myhostname分别代表如下图所示,每一项都是一个nss插件. image.png 当我们执行如下请求时:

ping www.baidu.com

首先会根据hosts项中配置的插件,从左向右调用插件的中的回调函数.

调用顺序:  libnss_files.so -> libnss_dns.so -> libnss_myhostname.so

具体nss插件如何开发[参考6]

二、插件代码

 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
#include "hs.h"


#include <stddef.h>
#include <nss.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <zconf.h>


enum nss_status _nss_hs_gethostbyname4_r(
        const char *name,
        struct gaih_addrtuple **pat,
        char *buffer, size_t buflen,
        int *errnop, int *h_errnop,
        int32_t *ttlp) {

    syslog(LOG_ERR, "_nss_hs_gethostbyname4_r ns hook domain:%s  pid:%d\n", name, getpid());

    return NSS_STATUS_NOTFOUND;
}

enum nss_status _nss_hs_gethostbyname3_r(
        const char *name,
        int af,
        struct hostent *host,
        char *buffer, size_t buflen,
        int *errnop, int *h_errnop,
        int32_t *ttlp,
        char **canonp) {

    syslog(LOG_ERR, "_nss_hs_gethostbyname3_r ns hook domain:%s  pid:%d\n", name, getpid());

    return NSS_STATUS_NOTFOUND;
}

enum nss_status _nss_hs_gethostbyname2_r(
        const char *name,
        int af,
        struct hostent *host,
        char *buffer, size_t buflen,
        int *errnop, int *h_errnop) {

    return _nss_hs_gethostbyname3_r(
            name,
            af,
            host,
            buffer, buflen,
            errnop, h_errnop,
            NULL,
            NULL);
}

enum nss_status _nss_hs_gethostbyname_r(
        const char *name,
        struct hostent *host,
        char *buffer, size_t buflen,
        int *errnop, int *h_errnop) {

    return _nss_hs_gethostbyname3_r(
            name,
            AF_UNSPEC,
            host,
            buffer, buflen,
            errnop, h_errnop,
            NULL,
            NULL);
}

void _nss_hs_init(void (*cb)(size_t, struct traced_file*)) {


    syslog(LOG_ERR, "_nss_hs_init...");
}

三、编译/安装

  1. 编译插件

    gcc -shared -fPIC -o libnss_hs.so.2 -Wl,-soname,libnss_hs.so.2 hs.c
    
  2. 安装插件

    cp libnss_hs.so.2 /lib64/libnss_hs.so
    ldconfig
    
  3. 修改配置文件,加载插件

vim /etc/nsswitch.conf 

hosts:     hs files dns myhostname

四、测试

  1. 窗口1、执行 sudo service nscd stop
  2. 窗口1 、执行tail -f /var/log/messages
  3. 窗口2、执行 ping foo

在message里会看到如下内容: image.png

五、注意事项

  1. 如果开启着nscd服务(DNS缓存服务)[参考7],第二次请求就捕获不到了,因为缓存了(默认缓存时间貌似是60s)
    1. 解决方案:无,只要第一次请求能捕获到,第二次请求的重要性就不太大了,毕竟缓存会过期
  2. nscd服务会定时更新缓存,你会发现这台机器不断在dns query
    1. 解决方案:过滤掉nscd进程即可,虽然不够优雅
  3. nscd服务器开启的情况下,所有dns查询进程都走nscd进程,获取的进程pid也是nscd进程的pid[参考7]
    1. 解决方案:目前没有更好的解决方案
  4. 如上遇到的大部分问题都是由于nscd服务所造成,那么使用dnsmasq服务的机器又会如何?

    1. 解决方案:目前没有更好的解决方案

      六、参考

  5. https://www.cnblogs.com/yanghehe/p/12294134.html

  6. https://blog.csdn.net/water_cow/article/details/7190880

  7. https://github.com/tingar/libnss_etcd

  8. https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/

  9. http://log.or.cz/?p=77

  10. http://www.gnu.org/software/libc/manual/html_node/NSS-Module-Function-Internals.html

  11. https://yuerblog.cc/2020/05/23/nscd-dns%E7%BC%93%E5%AD%98%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E4%B8%8E%E5%85%B3%E9%94%AE%E5%8F%82%E6%95%B0/

  12. https://busybox.net/~vda/unscd/nscd-0.51.c