OSSEC Windows RootKit检测部分源码分析

 

OSSEC简介

OSSEC是一个开源的多平台入侵检测系统,可以运行在Windows,Linux等多个平台之上。一般分为客户端和服务端。客户端用来收集客户机运行时消息,主要以日志为载体传送给服务端。在服务端OSSEC进行消息的解码与告警。除了告警之外,OSSEC还支持用户自定义的自主相应来抵抗入侵。

OSSEC windows客户端用来采集windows客户机运行时信息,进行一定分析后传送给服务端进行进一步的分析告警。OSSEC windows客户端同时支持rootkit检测,本文即从源码角度分析OSSEC对windows rootkit检测的实现。

 

整体框架

OSSEC github开源地址为:https://github.com/ossec/ossec-hids 。下载到源码之后,关于rootkit检测部分的源码位于ossec-hidssrcrootcheck目录下:

下面是OSSEC windows_rootkit检测的整体架构:

下面分部分逐一介绍。

 

rootcheck.c

整体框架:

rootcheck.c是rootcheck功能实现的入口位置。
首先调用WSAStartup()创建socket用于和server进行通信。
然后调用Read_Rootcheck_Config()进行rootcheck功能的配置,主要包括需要检查windows相关的项的配置,这个在rootcheck.config可以配置。
然后StartMQ()用于启动和server的通信,用于传输检查结果。
最后是run_rk_check()进行最后的检查任务。

相关源码和注释:

1.    int main(int argc, char **argv)  
2.    {  
3.        int test_config = 0;  
4.        const char *cfg = "./rootcheck.conf";  
5.      
6.    #else  
7.      
8.    int rootcheck_init(int test_config)  
9.    {  
10.        const char *cfg = DEFAULTCPATH;  
11.      
12.    #endif /* OSSECHIDS */  
13.      
14.        int c;  
15.      
16.        /* Zero the structure, initialize default values */  
17.        rootcheck.workdir = NULL;  
18.        rootcheck.basedir = NULL;  
19.        rootcheck.unixaudit = NULL;  
20.        rootcheck.ignore = NULL;  
21.        rootcheck.rootkit_files = NULL;  
22.        rootcheck.rootkit_trojans = NULL;  
23.        rootcheck.winaudit = NULL;  
24.        rootcheck.winmalware = NULL;  
25.        rootcheck.winapps = NULL;  
26.        rootcheck.daemon = 1;  
27.        rootcheck.notify = QUEUE;  
28.        rootcheck.scanall = 0;  
29.        rootcheck.readall = 0;  
30.        rootcheck.disabled = 0;  
31.        rootcheck.skip_nfs = 0;  
32.        rootcheck.alert_msg = NULL;  
33.        rootcheck.time = ROOTCHECK_WAIT;  
34.      
35.        rootcheck.checks.rc_dev = 1;  
36.        rootcheck.checks.rc_files = 1;  
37.        rootcheck.checks.rc_if = 1;  
38.        rootcheck.checks.rc_pids = 1;  
39.        rootcheck.checks.rc_ports = 1;  
40.        rootcheck.checks.rc_sys = 1;  
41.        rootcheck.checks.rc_trojans = 1;  
42.      
43.    #ifdef OSSECHIDS  
44.        rootcheck.tsleep = (unsigned int) getDefine_Int("rootcheck", "sleep", 0, 64);  
45.    #endif  
46.      
47.    #ifdef WIN32  
48.        rootcheck.checks.rc_winaudit = 1;  
49.        rootcheck.checks.rc_winmalware = 1;  
50.        rootcheck.checks.rc_winapps = 1;  
51.    #else  
52.        rootcheck.checks.rc_unixaudit = 1;  
53.    #endif  
54.      
55.        /* We store up to 255 alerts in there */  
56.        os_calloc(256, sizeof(char *), rootcheck.alert_msg);  
57.        c = 0;  
58.        while (c <= 255) {  
59.            rootcheck.alert_msg[c] = NULL;  
60.            c++;  
61.        }  
62.      
63.    #ifndef OSSECHIDS  
64.        rootcheck.notify = SYSLOG;  
65.        rootcheck.daemon = 0;  
66.        while ((c = getopt(argc, argv, "VstrdhD:c:")) != -1) {  
67.            switch (c) {  
68.                case 'V':  
69.                    print_version();  
70.                    break;  
71.                case 'h':  
72.                    help_rootcheck();  
73.                    break;  
74.                case 'd':  
75.                    nowDebug();  
76.                    break;  
77.                case 'D':  
78.                    if (!optarg) {  
79.                        ErrorExit("%s: -D needs an argument", ARGV0);  
80.                    }  
81.                    rootcheck.workdir = optarg;  
82.                    break;  
83.                case 'c':  
84.                    if (!optarg) {  
85.                        ErrorExit("%s: -c needs an argument", ARGV0);  
86.                    }  
87.                    cfg = optarg;  
88.                    break;  
89.                case 's':  
90.                    rootcheck.scanall = 1;  
91.                    break;  
92.                case 't':  
93.                    test_config = 1;  
94.                    break;  
95.                case 'r':  
96.                    rootcheck.readall = 1;  
97.                    break;  
98.                default:  
99.                    help_rootcheck();  
100.                    break;  
101.            }  
102.        }  
103.    #ifdef WIN32  
104.        /* Start Winsock */  
105.        {  
106.            WSADATA wsaData;  
107.            if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) {  
108.                ErrorExit("%s: WSAStartup() failed", ARGV0);  
109.            }  
110.        }  
111.    #endif /* WIN32 */  
112.      
113.    #endif /* OSSECHIDS */  
114.      
115.        /* Start up message */  
116.        debug1(STARTED_MSG, ARGV0);  
117.      
118.        /* Check if the configuration is present */  
119.        if (File_DateofChange(cfg) < 0) {  
120.            merror("%s: Configuration file '%s' not found", ARGV0, cfg);  
121.            return (-1);  
122.        }  
123.      
124.        /* Read configuration  --function specified twice (check makefile) */  
125.        if (Read_Rootcheck_Config(cfg) < 0) {  
126.            ErrorExit(CONFIG_ERROR, ARGV0, cfg);  
127.        }  
128.      
129.        /* If testing config, exit here */  
130.        if (test_config) {  
131.            return (0);  
132.        }  
133.      
134.        /* Return 1 disables rootcheck */  
135.        if (rootcheck.disabled == 1) {  
136.            verbose("%s: Rootcheck disabled. Exiting.", ARGV0);  
137.            return (1);  
138.        }  
139.      
140.        /* Check if Unix audit file is configured */  
141.        if (!rootcheck.unixaudit) {  
142.    #ifndef WIN32  
143.            log2file("%s: System audit file not configured.", ARGV0);  
144.    #endif  
145.        }  
146.      
147.        /* Set default values */  
148.        if (rootcheck.workdir == NULL) {  
149.            rootcheck.workdir = DEFAULTDIR;  
150.        }  
151.      
152.    #ifdef OSSECHIDS  
153.        /* Start up message */  
154.    #ifdef WIN32  
155.        verbose(STARTUP_MSG, "ossec-rootcheck", getpid());  
156.    #else  
157.      
158.        /* Connect to the queue if configured to do so */  
159.        if (rootcheck.notify == QUEUE) {  
160.            debug1("%s: Starting queue ...", ARGV0);  
161.      
162.            /* Start the queue */  
163.            if ((rootcheck.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {  
164.                merror(QUEUE_ERROR, ARGV0, DEFAULTQPATH, strerror(errno));  
165.      
166.                /* 5 seconds to see if the agent starts */  
167.                sleep(5);  
168.                if ((rootcheck.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {  
169.                    /* Wait 10 more seconds */  
170.                    merror(QUEUE_ERROR, ARGV0, DEFAULTQPATH, strerror(errno));  
171.                    sleep(10);  
172.                    if ((rootcheck.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {  
173.                        ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH);  
174.                    }  
175.                }  
176.            }  
177.        }  
178.      
179.    #endif /* WIN32 */  
180.      
181.    #endif /* OSSECHIDS */  
182.      
183.        /* Initialize rk list */  
184.        rk_sys_name = (char **) calloc(MAX_RK_SYS + 2, sizeof(char *));  
185.        rk_sys_file = (char **) calloc(MAX_RK_SYS + 2, sizeof(char *));  
186.        if (!rk_sys_name || !rk_sys_file) {  
187.            ErrorExit(MEM_ERROR, ARGV0, errno, strerror(errno));  
188.        }  
189.        rk_sys_name[0] = NULL;  
190.        rk_sys_file[0] = NULL;  
191.      
192.    #ifndef OSSECHIDS  
193.    #ifndef WIN32  
194.        /* Start signal handling */  
195.        StartSIG(ARGV0);  
196.    #endif  
197.        debug1("%s: DEBUG: Running run_rk_check", ARGV0);  
198.        run_rk_check();  
199.      
200.        debug1("%s: DEBUG: Leaving...", ARGV0);  
201.    #endif /* OSSECHIDS */  
202.        return (0);  
203.    }

 

run_rk_check.c

整体框架:

run_rk_check()的源码当中包含所有平台的rootcheck检测的代码,这里只分析和windows相关的部分。
首先是check_rc_winaudit(),主要是检测windows的一些审计相关的内容,它的配置文件是在 :

用户可以进行配置来控制它的检测行为。具体如下:

1.    # OSSEC Linux Audit - (C) 2018 OSSEC Project  
2.    #  
3.    # Released under the same license as OSSEC.  
4.    # More details at the LICENSE file included with OSSEC or online  
5.    # at: https://github.com/ossec/ossec-hids/blob/master/LICENSE  
6.    #  
7.    # [Application name] [any or all] [reference]  
8.    # type:<entry name>;  
9.    #  
10.    # Type can be:  
11.    #             - f (for file or directory)  
12.    #             - r (registry entry)  
13.    #             - p (process running)  
14.    #  
15.    # Additional values:  
16.    # For the registry and for directories, use "->" to look for a specific entry and another  
17.    # "->" to look for the value.  
18.    # Also, use " -> r:^. -> ..." to search all files in a directory  
19.    # For files, use "->" to look for a specific value in the file.  
20.    #  
21.    # Values can be preceded by: =: (for equal) - default  
22.    #                             r: (for ossec regexes)  
23.    #                             >: (for strcmp greater)  
24.    #                             <: (for strcmp  lower)  
25.    # Multiple patterns can be specified by using " && " between them.  
26.    # (All of them must match for it to return true).  
27.      
28.    # http://technet2.microsoft.com/windowsserver/en/library/486896ba-dfa1-4850-9875-13764f749bba1033.mspx?mfr=true  
29.    [Disabled Registry tools set {PCI_DSS: 10.6.1}] [any] []  
30.    r:HKCUSoftwareMicrosoftWindowsCurrentVersionPoliciesSystem -> DisableRegistryTools -> 1;  
31.    r:HKLMSoftwareMicrosoftWindowsCurrentVersionPoliciesSystem -> DisableRegistryTools -> 1;  
32.      
33.    # http://support.microsoft.com/kb/825750  
34.    [DCOM disabled {PCI_DSS: 10.6.1}] [any] []  
35.    r:HKEY_LOCAL_MACHINESoftwareMicrosoftOLE -> EnableDCOM -> N;  
36.      
37.    # http://web.mit.edu/is/topics/windows/server/winmitedu/security.html  
38.    [LM authentication allowed (weak passwords) {PCI_DSS: 10.6.1, 11.4}] [any] []  
39.    r:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlLSA -> LMCompatibilityLevel -> 0;  
40.    r:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlLSA -> LMCompatibilityLevel -> 1;  
41.      
42.    # http://research.eeye.com/html/alerts/AL20060813.html  
43.    # Disabled by some Malwares (sometimes by McAfee and Symantec  
44.    # security center too).  
45.    [Firewall/Anti Virus notification disabled {PCI_DSS: 10.6.1}] [any] []  
46.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftSecurity Center -> FirewallDisableNotify -> !0;  
47.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftSecurity Center -> antivirusoverride -> !0;  
48.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftSecurity Center -> firewalldisablenotify -> !0;  
49.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftSecurity Center -> firewalldisableoverride -> !0;  
50.      
51.    # Checking for the microsoft firewall.  
52.    [Microsoft Firewall disabled {PCI_DSS: 10.6.1, 1.4}] [all] []  
53.    r:HKEY_LOCAL_MACHINEsoftwarepoliciesmicrosoftwindowsfirewalldomainprofile -> enablefirewall -> 0;  
54.    r:HKEY_LOCAL_MACHINEsoftwarepoliciesmicrosoftwindowsfirewallstandardprofile -> enablefirewall -> 0;  
55.      
56.    #http://web.mit.edu/is/topics/windows/server/winmitedu/security.html  
57.    [Null sessions allowed {PCI_DSS: 11.4}] [any] []  
58.    r:HKLMSystemCurrentControlSetControlLsa -> RestrictAnonymous -> 0;  
59.      
60.    [Error reporting disabled {PCI_DSS: 10.6.1}] [any] [http://windowsir.blogspot.com/2007/04/something-new-to-look-for.html]  
61.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftPCHealthErrorReporting -> DoReport -> 0;  
62.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftPCHealthErrorReporting -> IncludeKernelFaults -> 0;  
63.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftPCHealthErrorReporting -> IncludeMicrosoftApps -> 0;  
64.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftPCHealthErrorReporting -> IncludeWindowsApps -> 0;  
65.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftPCHealthErrorReporting -> IncludeShutdownErrs -> 0;  
66.    r:HKEY_LOCAL_MACHINESOFTWAREMicrosoftPCHealthErrorReporting -> ShowUI -> 0;  
67.      
68.    # http://support.microsoft.com/default.aspx?scid=315231  
69.    [Automatic Logon enabled {PCI_DSS: 10.6.1}] [any] [http://support.microsoft.com/default.aspx?scid=315231]  
70.    r:HKLMSOFTWAREMicrosoftWindowsNTCurrentVersionWinlogon -> DefaultPassword;  
71.    r:HKLMSOFTWAREMicrosoftWindowsNTCurrentVersionWinlogon -> AutoAdminLogon -> 1;  
72.      
73.    [Winpcap packet filter driver found {PCI_DSS: 10.6.1}] [any] []  
74.    f:%WINDIR%System32driversnpf.sys;

第二个是check_rc_winmalware,用来根据配置文件检查是否存在特定的恶意软件,最后一个是check_rc_winapps,用来检查特定的windows app的特征,检查他们是否被rootkit更改等。

具体代码和注释:(只列出和windows相关的源码注释,其他的源码都已省略)

1.    void run_rk_check()  
2.    {  
3.        time_t time1;  
4.        time_t time2;  
5.        FILE *fp;  
6.        OSList *plist;  
7.      
8.    #ifndef WIN32  
9.        /* On non-Windows, always start at / */  
10.        size_t i;  
11.        char basedir[] = "/";  
12.      
13.        /* Removing the last / from basedir */  
14.        i = strlen(basedir);  
15.        if (i > 0) {  
16.            if (basedir[i - 1] == '/') {  
17.                basedir[i - 1] = '';  
18.            }  
19.        }  
20.    #else  
21.        /* On Windows, always start at C: */  
22.        char basedir[] = "C:\";  
23.      
24.    #endif  
25.      
26.        /* Set basedir */  
27.        if (rootcheck.basedir == NULL) {  
28.            rootcheck.basedir = basedir;  
29.        }  
30.      
31.        time1 = time(0);  
32.      
33.        /* Initial message */  
34.        if (rootcheck.notify != QUEUE) {  
35.            printf("n");  
36.            printf("** Starting Rootcheck v0.9 by Daniel B. Cid        **n");  
37.            printf("** http://www.ossec.net/en/about.html#dev-team     **n");  
38.            printf("** http://www.ossec.net/rootcheck/                 **nn");  
39.            printf("Be patient, it may take a few minutes to complete...n");  
40.            printf("n");  
41.        }  
42.      
43.        /* Clean the global variables */  
44.        rk_sys_count = 0;  
45.        rk_sys_file[rk_sys_count] = NULL;  
46.        rk_sys_name[rk_sys_count] = NULL;  
47.      
48.        /* Send scan start message */  
49.        notify_rk(ALERT_POLICY_VIOLATION, "Starting rootcheck scan.");  
50.        if (rootcheck.notify == QUEUE) {  
51.            merror("%s: INFO: Starting rootcheck scan.", ARGV0);  
52.        }  
53.      
54.        /* Check for Rootkits */  
55.        /* Open rootkit_files and pass the pointer to check_rc_files */  
56.        if (rootcheck.checks.rc_files) {  
57.            if (!rootcheck.rootkit_files) {  
58.    #ifndef WIN32  
59.                merror("%s: No rootcheck_files file configured.", ARGV0);  
60.    #endif  
61.            } else {  
62.                fp = fopen(rootcheck.rootkit_files, "r");  
63.                if (!fp) {  
64.                    merror("%s: No rootcheck_files file: '%s'", ARGV0,  
65.                           rootcheck.rootkit_files);  
66.                }  
67.      
68.                else {  
69.                    check_rc_files(rootcheck.basedir, fp);  
70.      
71.                    fclose(fp);  
72.                }  
73.            }  
74.        }  
75.      
76.        /* Check for trojan entries in common binaries */  
77.        if (rootcheck.checks.rc_trojans) {  
78.            if (!rootcheck.rootkit_trojans) {  
79.    #ifndef WIN32  
80.                merror("%s: No rootcheck_trojans file configured.", ARGV0);  
81.    #endif  
82.            } else {  
83.                fp = fopen(rootcheck.rootkit_trojans, "r");  
84.                if (!fp) {  
85.                    merror("%s: No rootcheck_trojans file: '%s'", ARGV0,  
86.                           rootcheck.rootkit_trojans);  
87.                } else {  
88.    #ifndef HPUX  
89.                    check_rc_trojans(rootcheck.basedir, fp);  
90.    #endif  
91.                    fclose(fp);  
92.                }  
93.            }  
94.        }  
95.      
96.    #ifdef WIN32  
97.        /* Get process list */  
98.        plist = os_get_process_list();//获取每个进程的信息  
99.      
100.        /* Windows audit check */  
101.        if (rootcheck.checks.rc_winaudit) {  
102.            if (!rootcheck.winaudit) {  
103.                merror("%s: No winaudit file configured.", ARGV0);  
104.            } else {  
105.                fp = fopen(rootcheck.winaudit, "r");  
106.                if (!fp) {  
107.                    merror("%s: No winaudit file: '%s'", ARGV0,  
108.                           rootcheck.winaudit);  
109.                } else {  
110.                    check_rc_winaudit(fp, plist);  
111.                    fclose(fp);  
112.                }  
113.            }  
114.        }  
115.      
116.        /* Windows malware */  
117.        if (rootcheck.checks.rc_winmalware) {  
118.            if (!rootcheck.winmalware) {  
119.                merror("%s: No winmalware file configured.", ARGV0);  
120.            } else {  
121.                fp = fopen(rootcheck.winmalware, "r");  
122.                if (!fp) {  
123.                    merror("%s: No winmalware file: '%s'", ARGV0,  
124.                           rootcheck.winmalware);  
125.                } else {  
126.                    check_rc_winmalware(fp, plist);  
127.                    fclose(fp);  
128.                }  
129.            }  
130.        }  
131.      
132.        /* Windows Apps */  
133.        if (rootcheck.checks.rc_winapps) {  
134.            if (!rootcheck.winapps) {  
135.                merror("%s: No winapps file configured.", ARGV0);  
136.            } else {  
137.                fp = fopen(rootcheck.winapps, "r");  
138.                if (!fp) {  
139.                    merror("%s: No winapps file: '%s'", ARGV0,  
140.                           rootcheck.winapps);  
141.                } else {  
142.                    check_rc_winapps(fp, plist);  
143.                    fclose(fp);  
144.                }  
145.            }  
146.        }  
147.      
148.        /* Free the process list */  
149.        del_plist((void *)plist);  
150.      
151.    #else  
152.        /* Checks for other non-Windows */  
153.      
154.        /* Unix audit check ***/  
155.        .....  
156.    #endif /* !WIN32 */  
157.      
158.        /* Check for files in the /dev filesystem */  
159.        if (rootcheck.checks.rc_dev) {  
160.            debug1("%s: DEBUG: Going into check_rc_dev", ARGV0);  
161.            check_rc_dev(rootcheck.basedir);  
162.            debug1("%s: DEBUG: Exiting check_rc_dev", ARGV0);  
163.        }  
164.      
165.        /* Scan the whole system for additional issues */  
166.        if (rootcheck.checks.rc_sys) {  
167.            debug1("%s: DEBUG: Going into check_rc_sys", ARGV0);  
168.            check_rc_sys(rootcheck.basedir);  
169.            debug1("%s: DEBUG: Exiting check_rc_sys", ARGV0);  
170.        }  
171.      
172.        /* Check processes */  
173.        if (rootcheck.checks.rc_pids) {  
174.            debug1("%s: DEBUG: Going into check_rc_pids", ARGV0);  
175.            check_rc_pids();  
176.            debug1("%s: DEBUG: Exiting check_rc_pids", ARGV0);  
177.        }  
178.      
179.        /* Check all ports */  
180.        if (rootcheck.checks.rc_ports) {  
181.            debug1("%s: DEBUG: Going into check_rc_ports", ARGV0);  
182.            check_rc_ports();  
183.            debug1("%s: DEBUG: Exiting check_rc_ports", ARGV0);  
184.      
185.            /* Check open ports */  
186.            debug1("%s: DEBUG: Going into check_open_ports", ARGV0);  
187.            check_open_ports();  
188.            debug1("%s: DEBUG: Exiting check_open_ports", ARGV0);  
189.        }  
190.      
191.        /* Check interfaces */  
192.        if (rootcheck.checks.rc_if) {  
193.            debug1("%s: DEBUG: Going into check_rc_if", ARGV0);  
194.            check_rc_if();  
195.            debug1("%s: DEBUG: Exiting check_rc_if", ARGV0);  
196.        }  
197.      
198.        debug1("%s: DEBUG: Completed with all checks.", ARGV0);  
199.      
200.        /* Clean the global memory */  
201.        {  
202.            int li;  
203.            for (li = 0; li <= rk_sys_count; li++) {  
204.                if (!rk_sys_file[li] ||  
205.                        !rk_sys_name[li]) {  
206.                    break;  
207.                }  
208.      
209.                free(rk_sys_file[li]);  
210.                free(rk_sys_name[li]);  
211.            }  
212.        }  
213.      
214.        /* Final message */  
215.        time2 = time(0);  
216.      
217.        if (rootcheck.notify != QUEUE) {  
218.            printf("n");  
219.            printf("- Scan completed in %d seconds.nn", (int)(time2 - time1));  
220.        } else {  
221.            sleep(5);  
222.        }  
223.      
224.        /* Send scan ending message */  
225.        notify_rk(ALERT_POLICY_VIOLATION, "Ending rootcheck scan.");  
226.        if (rootcheck.notify == QUEUE) {  
227.            merror("%s: INFO: Ending rootcheck scan.", ARGV0);  
228.        }  
229.      
230.        debug1("%s: DEBUG: Leaving run_rk_check", ARGV0);  
231.        return;  
232.    }

 

check_rc_policy.c

整体框架:

check_rc_policy只是具体功能实现过程中的一个转折点。它们在实现时都调用了rkcl_get_entry()。具体如下:

1.    /* Read the file pointer specified 
2.     * and check if the configured file is there 
3.     */  
4.    void check_rc_winaudit(FILE *fp, OSList *p_list)  
5.    {  
6.        debug1("%s: DEBUG: Starting on check_rc_winaudit", ARGV0);  
7.        rkcl_get_entry(fp, "Windows Audit:", p_list);  
8.    }  
9.      
10.    /* Read the file pointer specified 
11.     * and check if the configured file is there 
12.     */  
13.    void check_rc_winmalware(FILE *fp, OSList *p_list)  
14.    {  
15.        debug1("%s: DEBUG: Starting on check_rc_winmalware", ARGV0);  
16.        rkcl_get_entry(fp, "Windows Malware:", p_list);  
17.    }  
18.      
19.    /* Read the file pointer specified 
20.     * and check if the configured file is there 
21.     */  
22.    void check_rc_winapps(FILE *fp, OSList *p_list)  
23.    {  
24.        debug1("%s: DEBUG: Starting on check_rc_winapps", ARGV0);  
25.        rkcl_get_entry(fp, "Application Found:", p_list);  
26.    }

 

common_rcl.c

整体框架:

common_rcl.c中的rkcl_get_entry()函数是rootcheck功能实现的核心函数。
首先调用_rkcl_getrootdir()、_rkcl_getfp()、rkcl_get_vars()等函数进行一些初始化,包括设置环境变量,具体读取配置文件(read_vars)等。
然后就是与windows相关的具体检查操作,包括如下四个方面:

下面分别介绍这四个方面:

rk_check_file

整体框架:

它的功能是检查一个文件是否存在,底层调用fopen来测试,如果打开成功则成功,否则失败:
is_file()源码如下:

1.    /* Check if a file exists */  
2.    int is_file(char *file)  
3.    {  
4.        FILE *fp;  
5.        fp = fopen(file, "r");  
6.        if (fp) {  
7.            fclose(fp);  
8.            return (1);  
9.        }  
10.        return (0);  
11.    }  
rk_check_file源码和注释如下:
1.    int rk_check_file(char *file, char *pattern)  
2.    {  
3.        char *split_file;  
4.        int full_negate = 0;  
5.        int pt_result = 0;  
6.        FILE *fp;  
7.        char buf[OS_SIZE_2048 + 1];  
8.      
9.        if (file == NULL) {  
10.            return (0);  
11.        }  
12.      
13.        /* Check if the file is divided */  
14.        split_file = strchr(file, ',');  
15.        if (split_file) {  
16.            *split_file = '';  
17.            split_file++;  
18.        }  
19.      
20.        /* Get each file */  
21.        do {  
22.            /* If we don't have a pattern, just check if the file/dir is there */  
23.            if (pattern == NULL) {  
24.                if (is_file(file)) {  
25.                    int i = 0;  
26.                    char _b_msg[OS_SIZE_1024 + 1];  
27.      
28.                    _b_msg[OS_SIZE_1024] = '';  
29.                    snprintf(_b_msg, OS_SIZE_1024, " File: %s.",  
30.                             file);  
31.      
32.                    /* Already present */  
33.                    if (_is_str_in_array(rootcheck.alert_msg, _b_msg)) {  
34.                        return (1);  
35.                    }  
36.      
37.                    while (rootcheck.alert_msg[i] && (i < 255)) {  
38.                        i++;  
39.                    }  
40.      
41.                    if (!rootcheck.alert_msg[i]) {  
42.                        os_strdup(_b_msg, rootcheck.alert_msg[i]);  
43.                    }  
44.      
45.                    return (1);  
46.                }  
47.            } else {  
48.                full_negate = pt_check_negate(pattern);  
49.                /* Check for content in the file */  
50.                debug1("checking file: %s", file);  
51.                fp = fopen(file, "r");  
52.                if (fp) {  
53.      
54.                    debug1(" starting new file: %s", file);  
55.                    buf[OS_SIZE_2048] = '';  
56.                    while (fgets(buf, OS_SIZE_2048, fp) != NULL) {  
57.                        char *nbuf;  
58.      
59.                        /* Remove end of line */  
60.                        nbuf = strchr(buf, 'n');  
61.                        if (nbuf) {  
62.                            *nbuf = '';  
63.                        }  
64.    #ifdef WIN32  
65.                        /* Remove end of line */  
66.                        nbuf = strchr(buf, 'r');  
67.                        if (nbuf) {  
68.                            *nbuf = '';  
69.                        }  
70.    #endif  
71.                        /* Matched */  
72.                        pt_result = pt_matches(buf, pattern);  
73.                        debug1("Buf == "%s"", buf);  
74.                        debug1("Pattern == "%s"", pattern);  
75.                        debug1("pt_result == %d and full_negate == %d", pt_result, full_negate);  
76.                        if ((pt_result == 1 && full_negate == 0) ) {  
77.                            debug1("alerting file %s on line %s", file, buf);  
78.                            int i = 0;  
79.                            char _b_msg[OS_SIZE_1024 + 1];  
80.      
81.                            /* Close the file before dealing with the alert */  
82.                            fclose(fp);  
83.      
84.                            /* Generate the alert itself */  
85.                            _b_msg[OS_SIZE_1024] = '';  
86.                            snprintf(_b_msg, OS_SIZE_1024, " File: %s.",  
87.                                     file);  
88.      
89.                            /* Already present */  
90.                            if (_is_str_in_array(rootcheck.alert_msg, _b_msg)) {  
91.                                return (1);  
92.                            }  
93.      
94.                            while (rootcheck.alert_msg[i] && (i < 255)) {  
95.                                i++;  
96.                            }  
97.      
98.                            if (!rootcheck.alert_msg[i]) {  
99.                                os_strdup(_b_msg, rootcheck.alert_msg[i]);  
100.                            }  
101.      
102.                            return (1);  
103.                        } else if ((pt_result == 0 && full_negate == 1) ) {  
104.                            /* Found a full+negate match so no longer need to search 
105.                             * break out of loop and make sure the full negate does 
106.                             * not alert. 
107.                             */  
108.                            debug1("found a complete match for full_negate");  
109.                            full_negate = 0;  
110.                            break;  
111.                        }  
112.                    }  
113.      
114.                    fclose(fp);  
115.      
116.                    if (full_negate == 1) {  
117.                        debug1("full_negate alerting - file %s", file);  
118.                        int i = 0;  
119.                        char _b_msg[OS_SIZE_1024 + 1];  
120.      
121.                        /* Generate the alert itself */  
122.                        _b_msg[OS_SIZE_1024] = '';  
123.                        snprintf(_b_msg, OS_SIZE_1024, " File: %s.",  
124.                                 file);  
125.      
126.                        /* Already present */  
127.                        if (_is_str_in_array(rootcheck.alert_msg, _b_msg)) {  
128.                            return (1);  
129.                        }  
130.      
131.                        while (rootcheck.alert_msg[i] && (i < 255)) {  
132.                            i++;  
133.                        }  
134.      
135.                        if (!rootcheck.alert_msg[i]) {  
136.                            os_strdup(_b_msg, rootcheck.alert_msg[i]);  
137.                        }  
138.      
139.                        return (1);  
140.                    }  
141.                }  
142.            }  
143.      
144.            if (split_file) {  
145.                file = split_file;  
146.                split_file = strchr(split_file, ',');  
147.                if (split_file) {  
148.                    split_file++;  
149.                }  
150.            }  
151.      
152.      
153.        } while (split_file);  
154.      
155.        return (0);  
156.    }
is_registry

整体框架:


is_registry函数是用来检查相应注册表是否存在的,也可以进行配置。
底层是调用RegOpenKeyEx(),RegQueryInfoKey()来进行测试:

1.    int is_registry(char *entry_name, char *reg_option, char *reg_value)  
2.    {  
3.        char *rk;  
4.      
5.        rk = __os_winreg_getkey(entry_name);  
6.        if (rk_sub_tree == NULL || rk == NULL) {  
7.            merror(SK_INV_REG, ARGV0, entry_name);  
8.            return (0);  
9.        }  
10.      
11.        if (__os_winreg_open_key(rk, entry_name, reg_option, reg_value) == 0) {  
12.            return (0);  
13.        }  
14.      
15.        return (1);  
16.    }

__os_winreg_open_key的具体实现如下:

1.    int __os_winreg_open_key(char *subkey, char *full_key_name,  
2.                             char *reg_option, char *reg_value)  
3.    {  
4.        int ret = 1;  
5.        HKEY oshkey;  
6.      
7.        int REG64MASK = (KEY_READ | KEY_WOW64_64KEY);  
8.        int REG32MASK = (KEY_READ | KEY_WOW64_32KEY);  
9.      
10.        if((RegOpenKeyEx(rk_sub_tree, subkey, 0, REG64MASK, &oshkey) ||  
11.            (RegOpenKeyEx(rk_sub_tree, subkey, 0, REG32MASK, &oshkey))  
12.            ) != ERROR_SUCCESS)  
13.        {  
14.            return(0);  
15.        }  
16.      
17.        /* If option is set, return the value of query key */  
18.        if (reg_option) {  
19.            ret = __os_winreg_querykey(oshkey, subkey, full_key_name,  
20.                                       reg_option, reg_value);  
21.        }  
22.      
23.        RegCloseKey(oshkey);  
24.        return (ret);  
25.    }

__os_winreg_querykey()具体源码和注释如下:

1.    /* Query the key and get the value of a specific entry */  
2.    int __os_winreg_querykey(HKEY hKey,  
3.            __attribute__((unused))char *p_key,  
4.            __attribute__((unused)) char *full_key_name,  
5.                             char *reg_option, char *reg_value)  
6.    {  
7.        int rc;  
8.        DWORD i, j;  
9.      
10.        /* QueryInfo and EnumKey variables */  
11.        TCHAR class_name_b[MAX_PATH + 1];  
12.        DWORD class_name_s = MAX_PATH;  
13.      
14.        /* Number of sub keys */  
15.        DWORD subkey_count = 0;  
16.      
17.        /* Number of values */  
18.        DWORD value_count;  
19.      
20.        /* Variables for RegEnumValue */  
21.        TCHAR value_buffer[MAX_VALUE_NAME + 1];  
22.        TCHAR data_buffer[MAX_VALUE_NAME + 1];  
23.        DWORD value_size;  
24.        DWORD data_size;  
25.      
26.        /* Data type for RegEnumValue */  
27.        DWORD data_type = 0;  
28.      
29.        /* Storage var */  
30.        char var_storage[MAX_VALUE_NAME + 1];  
31.      
32.        /* Initialize the memory for some variables */  
33.        class_name_b[0] = '';  
34.        class_name_b[MAX_PATH] = '';  
35.      
36.        /* We use the class_name, subkey_count and the value count */  
37.        rc = RegQueryInfoKey(hKey, class_name_b, &class_name_s, NULL,  
38.                             &subkey_count, NULL, NULL, &value_count,  
39.                             NULL, NULL, NULL, NULL);  
40.        if (rc != ERROR_SUCCESS) {  
41.            return (0);  
42.        }  
43.      
44.        /* Get values (if available) */  
45.        if (value_count) {  
46.            char *mt_data;  
47.      
48.            /* Clear the values for value_size and data_size */  
49.            value_buffer[MAX_VALUE_NAME] = '';  
50.            data_buffer[MAX_VALUE_NAME] = '';  
51.            var_storage[MAX_VALUE_NAME] = '';  
52.      
53.            /* Get each value */  
54.            for (i = 0; i < value_count; i++) {  
55.                value_size = MAX_VALUE_NAME;  
56.                data_size = MAX_VALUE_NAME;  
57.      
58.                value_buffer[0] = '';  
59.                data_buffer[0] = '';  
60.                var_storage[0] = '';  
61.      
62.                rc = RegEnumValue(hKey, i, value_buffer, &value_size,  
63.                                  NULL, &data_type, (LPBYTE)data_buffer, &data_size);  
64.      
65.                /* No more values available */  
66.                if (rc != ERROR_SUCCESS) {  
67.                    break;  
68.                }  
69.      
70.                /* Check if no value name is specified */  
71.                if (value_buffer[0] == '') {  
72.                    value_buffer[0] = '@';  
73.                    value_buffer[1] = '';  
74.                }  
75.      
76.                /* Check if the entry name matches the reg_option */  
77.                if (strcasecmp(value_buffer, reg_option) != 0) {  
78.                    continue;  
79.                }  
80.      
81.                /* If a value is not present and the option matches, 
82.                 * we can return ok 
83.                 */  
84.                if (!reg_value) {  
85.                    return (1);  
86.                }  
87.      
88.                /* Write value into a string */  
89.                switch (data_type) {  
90.                        int size_available;  
91.      
92.                    case REG_SZ:  
93.                    case REG_EXPAND_SZ:  
94.                        snprintf(var_storage, MAX_VALUE_NAME, "%s", data_buffer);  
95.                        break;  
96.                    case REG_MULTI_SZ:  
97.                        /* Printing multiple strings */  
98.                        size_available = MAX_VALUE_NAME - 3;  
99.                        mt_data = data_buffer;  
100.      
101.                        while (*mt_data) {  
102.                            if (size_available > 2) {  
103.                                strncat(var_storage, mt_data, size_available);  
104.                                strncat(var_storage, " ", 2);  
105.                                size_available = MAX_VALUE_NAME -  
106.                                                 (strlen(var_storage) + 2);  
107.                            }  
108.                            mt_data += strlen(mt_data) + 1;  
109.                        }  
110.      
111.                        break;  
112.                    case REG_DWORD:  
113.                        snprintf(var_storage, MAX_VALUE_NAME,  
114.                                 "%x", (unsigned int)*data_buffer);  
115.                        break;  
116.                    default:  
117.                        size_available = MAX_VALUE_NAME - 2;  
118.                        for (j = 0; j < data_size; j++) {  
119.                            char tmp_c[12];  
120.      
121.                            snprintf(tmp_c, 12, "%02x",  
122.                                     (unsigned int)data_buffer[j]);  
123.      
124.                            if (size_available > 2) {  
125.                                strncat(var_storage, tmp_c, size_available);  
126.                                size_available = MAX_VALUE_NAME -  
127.                                                 (strlen(var_storage) + 2);  
128.                            }  
129.                        }  
130.                        break;  
131.                }  
132.      
133.                /* Check if value matches */  
134.                if (pt_matches(var_storage, reg_value)) {  
135.                    return (1);  
136.                }  
137.      
138.                return (0);  
139.            }  
140.        }  
141.      
142.        return (0);  
143.    }
rk_check_dir

整体框架:

递归遍历目录,具体调用rk_check_file()来检查文件目录是否存在。
具体代码和注释如下:

1.    int rk_check_dir(const char *dir, const char *file, char *pattern)  
2.    {  
3.        int ret_code = 0;  
4.        char f_name[PATH_MAX + 2];  
5.        struct dirent *entry;  
6.        struct stat statbuf_local;  
7.        DIR *dp = NULL;  
8.      
9.        f_name[PATH_MAX + 1] = '';  
10.      
11.        dp = opendir(dir);  
12.        if (!dp) {  
13.            return (0);  
14.        }  
15.      
16.        while ((entry = readdir(dp)) != NULL) {  
17.            /* Ignore . and ..  */  
18.            if ((strcmp(entry->d_name, ".") == 0) ||  
19.                    (strcmp(entry->d_name, "..") == 0)) {  
20.                continue;  
21.            }  
22.      
23.            /* Create new file + path string */  
24.            snprintf(f_name, PATH_MAX + 1, "%s/%s", dir, entry->d_name);  
25.      
26.            /* Check if the read entry matches the provided file name */  
27.            if (strncasecmp(file, "r:", 2) == 0) {  
28.                if (OS_Regex(file + 2, entry->d_name)) {  
29.                    if (rk_check_file(f_name, pattern)) {  
30.                        ret_code = 1;  
31.                    }  
32.                }  
33.            } else {  
34.                /* ... otherwise try without regex */  
35.                if (OS_Match2(file, entry->d_name)) {  
36.                    if (rk_check_file(f_name, pattern)) {  
37.                        ret_code = 1;  
38.                    }  
39.                }  
40.            }  
41.      
42.            /* Check if file is a directory */  
43.            if (lstat(f_name, &statbuf_local) == 0) {  
44.                if (S_ISDIR(statbuf_local.st_mode)) {  
45.                    if (rk_check_dir(f_name, file, pattern)) {  
46.                        ret_code = 1;  
47.                    }  
48.                }  
49.            }  
50.        }  
51.      
52.        closedir(dp);  
53.        return (ret_code);  
54.      
55.    }
is_process

整体框架:

这个函数是用来检查相应的进程是否存在的。
具体是先调用OSList_GetFirstNode()获取第一个进程的信息,进程是以链表的形式进行链接的,所以循环进行检查即可,中间调用pt_matches()进行匹配。
具体源码和注释如下:

1.    int is_process(char *value, OSList *p_list)  
2.    {  
3.        OSListNode *l_node;  
4.        if (p_list == NULL) {  
5.            return (0);  
6.        }  
7.        if (!value) {  
8.            return (0);  
9.        }  
10.      
11.        l_node = OSList_GetFirstNode(p_list);  
12.        while (l_node) {  
13.            Proc_Info *pinfo;  
14.      
15.            pinfo = (Proc_Info *)l_node->data;  
16.      
17.            /* Check if value matches */  
18.            if (pt_matches(pinfo->p_path, value)) {  
19.                int i = 0;  
20.                char _b_msg[OS_SIZE_1024 + 1];  
21.      
22.                _b_msg[OS_SIZE_1024] = '';  
23.      
24.                snprintf(_b_msg, OS_SIZE_1024, " Process: %s.",  
25.                         pinfo->p_path);  
26.      
27.                /* Already present */  
28.                if (_is_str_in_array(rootcheck.alert_msg, _b_msg)) {  
29.                    return (1);  
30.                }  
31.      
32.                while (rootcheck.alert_msg[i] && (i < 255)) {  
33.                    i++;  
34.                }  
35.      
36.                if (!rootcheck.alert_msg[i]) {  
37.                    os_strdup(_b_msg, rootcheck.alert_msg[i]);  
38.                }  
39.      
40.                return (1);  
41.            }  
42.      
43.            l_node = OSList_GetNextNode(p_list);  
44.        }  
45.      
46.        return (0);  
47.    }

 

功能测试

首先windows客户端和用户端建立连接:

然后为了测试,我们故意在C:Program Files文件夹下放一个sb32mon.exe文件(伪造的rootkit文件),然后运行rootkit_check进行检测。

可以看到成功检测到root_kit文件。

同时在server端也可以看到成功检测的日志:

 

总结

本文从源码角度分析OSSEC windows rootkit的实现。在具体使用OSSEC进行入侵检测时,知其然,知其所以然才可以在出现故障时更快更好的进行修复与响应。

(完)