wazuh 原理分析之Syscollector 系統信息收集工作流程

 

wazuh是從ossec-hids衍生過來的,部分架構設計有所不同, 多進程多線程模式。本機的進程之間通過Unix domain socket 進行通信的。

今天簡單介紹一下數據蒐集的相關功能的實現(Linux系統)。

注意由於篇幅所限, 在函數中我們只列出相關代碼。

可以先看一下官網的架構設計

https://documentation.wazuh.com/3.9/getting-started/architecture.html?highlight=architecture

agent的wazuh-modulesd端處理流程:

  • 數據蒐集的代碼主要在src/wazuh_modules/下面。wazuh-modules進程是一個多線程模式,每個線程負責一個模塊的功能。
  • 所有模塊都放在wmodule *wmodules(單向鏈表)全局變量中。

主要數據結構

// Main module structure

typedef struct wmodule {
    pthread_t thread;                   // Thread ID
    const wm_context *context;          // Context (common structure) 比如 WM_SYS_CONTEXT
    char *tag;                          // Module tag 這裏是 "syscollector"
    void *data;                         // Data (module-dependent structure) 是 wm_sys_t 實例
    struct wmodule *next;               // Pointer to next module
} wmodule;

typedef struct wm_context {
    const char *name;                   // Name for module
    wm_routine start;                   // Main function 比如 wm_sys_main(信息蒐集主入口)
    wm_routine destroy;                 // Destructor 資源回收
    cJSON *(* dump)(const void *);
} wm_context;

/* wm_sys_main 輸入參數, 可以由配置參數填充*/
typedef struct wm_sys_t {
    unsigned int interval;                  // Time interval between cycles (seconds) 定時間隔
    wm_sys_flags_t flags;                   // Flag bitfield 每一個字段都是用來控制收集的方式、是否收集
    wm_sys_state_t state;                   // Running state
} wm_sys_t;

/* 如果想增加收集信息,可通過在該結構中增加一個字段(1 bit)來控制是否收集 */
typedef struct wm_sys_flags_t {
    unsigned int enabled:1;                 // Main switch 如果這個關閉, 該線程退出, 見wm_sys_check實現
    unsigned int scan_on_start:1;           // Scan always on start 如果沒設置,延遲1秒
    unsigned int hwinfo:1;                  // Hardware inventory 這些默認都是收集的
    unsigned int netinfo:1;                 // Network inventory
    unsigned int osinfo:1;                  // OS inventory
    unsigned int programinfo:1;             // Installed packages inventory
    unsigned int portsinfo:1;               // Opened ports inventory
    unsigned int allports:1;                // Scan only listening ports or all
    unsigned int procinfo:1;                // Running processes inventory
} wm_sys_flags_t;

默認所有信息是必須收集的比 os version 、Hardware等等。可以通過修改配置文件不蒐集。通過定義配置名稱例如"processes",在wmodules_syscollector.c中實現相關代碼。

下面的代碼是講解如何收集os信息的,因爲這類數據是靜態的,一般不會變化,所以發送的記錄字段type 沒有類似xxx_end

的信息,比如port_end; 這種信息會在manager端將歷史數據刪除掉。

ossec.conf中關於syscollector的默認配置如下:默認開啓,1小時收集一次等。

  <!-- System inventory -->
  <wodle name="syscollector">
    <disabled>no</disabled>
    <interval>1h</interval>
    <scan_on_start>yes</scan_on_start>
    <hardware>yes</hardware>
    <os>yes</os>
    <network>yes</network>
    <packages>yes</packages>
    <ports all="no">yes</ports>
    <processes>yes</processes>
  </wodle>

wazuh-modulesd的啓動流程:

  • 1:讀取配置文件,創建需要的數據結構, 創建模塊鏈表;

  • 2:爲每個模塊創建線程,每個線程負責收集自己的信息,並轉發到/var/ossec/queue/ossec/queue unix socket中;

  • 3: 等待線程退出;

 

在src/wazuh_modules/main.c 中

  • 啓動時讀取配置的流程:main =>wm_setup=>wm_config=>ReadConfig=>read_main_elements=>Read_WModule=>wm_sys_read,在Read_WModule函數中建立wmodules鏈表。

下面列出wm_sys_read相關代碼:

// 這個函數先設置初值,然後再通過配置文件去覆蓋初值
int wm_sys_read(XML_NODE node, wmodule *module) {
    wm_sys_t *syscollector;
    int i;

    /* 創建對象 並設置初值*/
    if(!module->data) {
        os_calloc(1, sizeof(wm_sys_t), syscollector);
        syscollector->flags.enabled = 1;
        syscollector->flags.scan_on_start = 1;
        syscollector->flags.netinfo = 1;
        syscollector->flags.osinfo = 1;
        syscollector->flags.hwinfo = 1;
        syscollector->flags.programinfo = 1;
        syscollector->flags.portsinfo = 1;
        syscollector->flags.allports = 0;
        syscollector->flags.procinfo = 1;
        module->context = &WM_SYS_CONTEXT;
        module->tag = strdup(module->context->name);
        module->data = syscollector;
    }

    syscollector = module->data;

    if (!node)
        return 0;

    // Iterate over elements

    /* 讀取配置的信息,進行覆蓋初值、設置循環蒐集信息的時間間隔*/
    for (i = 0; node[i]; i++) {
        if (!node[i]->element) {
            merror(XML_ELEMNULL);
            return OS_INVALID;
        } else if (!strcmp(node[i]->element, XML_INTERVAL)) {
            char *endptr;
            syscollector->interval = strtoul(node[i]->content, &endptr, 0);

            if (syscollector->interval == 0 || syscollector->interval == UINT_MAX) {
                merror("Invalid interval at module '%s'", WM_SYS_CONTEXT.name);
                return OS_INVALID;
            }

            switch (*endptr) {
            case 'd':
                syscollector->interval *= 86400;
                break;
            case 'h':
                syscollector->interval *= 3600;
                break;
            case 'm':
                syscollector->interval *= 60;
                break;
            case 's':
            case '\0':
                break;
            default:
                merror("Invalid interval at module '%s'", WM_SYS_CONTEXT.name);
                return OS_INVALID;
            }
        } else if (!strcmp(node[i]->element, XML_SCAN_ON_START)) {
            if (!strcmp(node[i]->content, "yes"))
                syscollector->flags.scan_on_start = 1;
            else if (!strcmp(node[i]->content, "no"))
                syscollector->flags.scan_on_start = 0;
            else {
                merror("Invalid content for tag '%s' at module '%s'.", XML_SCAN_ON_START, WM_SYS_CONTEXT.name);
                return OS_INVALID;
            }
        } else if (!strcmp(node[i]->element, XML_DISABLED)) {
            if (!strcmp(node[i]->content, "yes"))
                syscollector->flags.enabled = 0;
            else if (!strcmp(node[i]->content, "no"))
                syscollector->flags.enabled = 1;
            else {
                merror("Invalid content for tag '%s' at module '%s'.", XML_DISABLED, WM_SYS_CONTEXT.name);
                return OS_INVALID;
            }
        }
        ...
        else {
            merror("No such tag '%s' at module '%s'.", node[i]->element, WM_SYS_CONTEXT.name);
            return OS_INVALID;
        }
    }

    return 0;
}

然後介紹一下os_version 這些信息怎麼讀的:wm_sys_main=>sys_os_unix=>getunameJSON=>get_unix_version


// Module main function. It won't return

void* wm_sys_main(wm_sys_t *sys) {

    time_t time_start = 0;
    time_t time_sleep = 0;

    // Check configuration and show debug information
    /* 連接unix socket等, 如果是agent端則是和Agent Daemon建立socket*/
    wm_sys_setup(sys);
    mtinfo(WM_SYS_LOGTAG, "Module started.");

    // First sleeping

    if (!sys->flags.scan_on_start) {
        time_start = time(NULL);

        // On first run, take into account the interval of time specified
        if (sys->state.next_time == 0) {
            sys->state.next_time = time_start + sys->interval;
        }

        if (sys->state.next_time > time_start) {
            mtinfo(WM_SYS_LOGTAG, "Waiting for turn to evaluate.");
            wm_delay(1000 * (sys->state.next_time - time_start));
        }
    } else {
        // Wait for Wazuh DB start
        wm_delay(1000);
    }

    // Main loop

    while (1) {

        mtinfo(WM_SYS_LOGTAG, "Starting evaluation.");

        // Get time and execute
        time_start = time(NULL);

        ...

        /* 獲取操作系統信息 */
        if (sys->flags.osinfo){
            #ifdef WIN32
                sys_os_windows(WM_SYS_LOCATION);
            #else
                sys_os_unix(queue_fd, WM_SYS_LOCATION);
            #endif
        }

        ...

        // 這塊實現定時掃描
        time_sleep = time(NULL) - time_start;

        mtinfo(WM_SYS_LOGTAG, "Evaluation finished.");

        if ((time_t)sys->interval >= time_sleep) {
            time_sleep = sys->interval - time_sleep;
            sys->state.next_time = sys->interval + time_start;
        } else {
            mterror(WM_SYS_LOGTAG, "Interval overtaken.");
            time_sleep = sys->state.next_time = 0;
        }

        if (wm_state_io(WM_SYS_CONTEXT.name, WM_IO_WRITE, &sys->state, sizeof(sys->state)) < 0)
            mterror(WM_SYS_LOGTAG, "Couldn't save running state: %s (%d)", strerror(errno), errno);

        // If time_sleep=0, yield CPU
        wm_delay(1000 * time_sleep);
    }

    return NULL;
}
/* 將獲取的數據通cJSON 構造json字符串發送給其他進程*/
void sys_os_unix(int queue_fd, const char* LOCATION){

    char *string;
    int random_id = os_random();
    char *timestamp;
    time_t now;
    struct tm localtm;

    now = time(NULL);
    localtime_r(&now, &localtm);

    os_calloc(TIME_LENGTH, sizeof(char), timestamp);

    snprintf(timestamp,TIME_LENGTH-1,"%d/%02d/%02d %02d:%02d:%02d",
            localtm.tm_year + 1900, localtm.tm_mon + 1,
            localtm.tm_mday, localtm.tm_hour, localtm.tm_min, localtm.tm_sec);

    if (random_id < 0)
        random_id = -random_id;

    mtdebug1(WM_SYS_LOGTAG, "Starting Operating System inventory.");

    /*每條蒐集都包含type 、ID、timestamp ,用於唯一定位一條記錄,方便增刪改查 */
    cJSON *object = cJSON_CreateObject();
    cJSON_AddStringToObject(object, "type", "OS");
    cJSON_AddNumberToObject(object, "ID", random_id);
    cJSON_AddStringToObject(object, "timestamp", timestamp);

    /* 真正去獲取os 信息的地方*/
    cJSON *os_inventory = getunameJSON();

    if (os_inventory != NULL)
        cJSON_AddItemToObject(object, "inventory", os_inventory);

    /* 將json數據格式化成字符串,都是這套路, 通過SendMSG發送給其他進程進行轉發處理。通過 SYSCOLLECTOR_MQ進行區分信息蒐集*/
    string = cJSON_PrintUnformatted(object);
    mtdebug2(WM_SYS_LOGTAG, "sys_os_unix() sending '%s'", string);
    SendMSG(queue_fd, string, LOCATION, SYSCOLLECTOR_MQ);
    cJSON_Delete(object);
    free(timestamp);
    free(string);
}
cJSON* getunameJSON()
{
    os_info *read_info;
    cJSON* root=cJSON_CreateObject();

#ifndef WIN32
    /* 通過讀取一些相關文件獲取os信息*/
    if (read_info = get_unix_version(), read_info) {
#else
    if (read_info = get_win_version(), read_info) {
#endif
    ...

    }

}

下面看看初始化的調用過程:

  • 從 src/wazuh_modules/main.c中開始   main=>wm_setup=>wm_config=>ReadConfig=>read_main_elements=>Read_WModule=>wm_sys_read;
int main(int argc, char **argv)
{
    ...

    // Setup daemon
    /* 讀取配置,初始化的入口*/
    wm_setup();

    if (test_config)
        exit(EXIT_SUCCESS);

    minfo("Process started.");

    // Run modules
    /* 每種模塊都創建一個線程來處理, 例如信息蒐集線程設置wm_sys_main回調函數*/
    for (cur_module = wmodules; cur_module; cur_module = cur_module->next) {
        if (CreateThreadJoinable(&cur_module->thread, cur_module->context->start, cur_module->data) < 0) {
            merror_exit("CreateThreadJoinable() for '%s': %s", cur_module->tag, strerror(errno));
        }
        mdebug2("Created new thread for the '%s' module.", cur_module->tag);
    }

    // Start com request thread
    w_create_thread(wmcom_main, NULL);

    // Wait for threads

    for (cur_module = wmodules; cur_module; cur_module = cur_module->next) {
        pthread_join(cur_module->thread, NULL);
    }

    return EXIT_SUCCESS;
}
  • 看一下 wm_sys_setup函數的實現, 介紹如何關聯到Agent Daemon進程,文件路徑爲/var/ossec/queue/ossec/queue
#ifndef DEFAULTDIR
#define DEFAULTDIR      "/var/ossec"
#endif
#define DEFAULTQUEUE    "/queue/ossec/queue"

#define DEFAULTQPATH    DEFAULTDIR DEFAULTQUEUE


static void wm_sys_setup(wm_sys_t *_sys) {

    sys = _sys;
    ...
    //  DEFAULTQPATH宏定位文件路徑爲/var/ossec/queue/ossec/queue
    for (i = 0; (queue_fd = StartMQ(DEFAULTQPATH, WRITE)) < 0 && i < WM_MAX_ATTEMPTS; i++)
        wm_delay(1000 * WM_MAX_WAIT);

    if (i == WM_MAX_ATTEMPTS) {
        mterror(WM_SYS_LOGTAG, "Can't connect to queue.");
        pthread_exit(NULL);
    }
    ...
}

ossec-agentd的執行流程

  • 1:初始化:創建unix socket,負責收集本地各個模塊收集到的數據;

  • 2:轉發: 將數據發送到manager端:先壓縮再加密;

 

  • Agent Daemon進程創建unix socket的代碼, src/client_agent/main.c
#ifndef DEFAULTDIR
#define DEFAULTDIR      "/var/ossec"
#endif

int main(int argc, char **argv)
{
    int c = 0;
    int test_config = 0;
    int debug_level = 0;
    agent_debug_level = getDefine_Int("agent", "debug", 0, 2);

    const char *dir = DEFAULTDIR;/* 用來切換工作目錄 可通過命令行修改*/
    const char *user = USER;
    const char *group = GROUPGLOBAL;
    const char *cfg = DEFAULTCPATH;

    uid_t uid;
    gid_t gid;

    run_foreground = 0;

    /* Set the name */
    OS_SetName(ARGV0);

    while ((c = getopt(argc, argv, "Vtdfhu:g:D:c:")) != -1) {
        switch (c) {
            case 'V':
                print_version();
                break;
            case 'h':
                help_agentd();
                break;
            case 'd':
                nowDebug();
                debug_level = 1;
                break;
            case 'f':
                run_foreground = 1;
                break;
            case 'u':
                if (!optarg) {
                    merror_exit("-u needs an argument");
                }
                user = optarg;
                break;
            case 'g':
                if (!optarg) {
                    merror_exit("-g needs an argument");
                }
                group = optarg;
                break;
            case 't':
                test_config = 1;
                break;
            case 'D'://這個參數可以修改工作目錄
                if (!optarg) {
                    merror_exit("-D needs an argument");
                }
                dir = optarg;
                break;
            case 'c':
                if (!optarg) {
                    merror_exit("-c needs an argument.");
                }
                cfg = optarg;
                break;
            default:
                help_agentd();
                break;
        }
    }
    ...
    /* 主要入口函數,初始化工作目錄、創建unix socket、 連接到manager、創建狀態統計線程、接收其他進程發送過來的信息,並轉發到manager端(先使用zip壓縮再使用對稱加密算法加密)*/
    AgentdStart(dir, uid, gid, user, group);

    return (0);
}
  • agent的主入口函數 :初始化、建立連接、發送心跳、轉發數據等

/* Start the agent daemon */
void AgentdStart(const char *dir, int uid, int gid, const char *user, const char *group)
{
    int rc = 0;
    int maxfd = 0;
    fd_set fdset;
    struct timeval fdtimeout;

    available_server = 0;

    /* Initial random numbers must happen before chroot */
    srandom_init();

    /* 後臺運行 */
    if (!run_foreground) {
        nowDaemon();
        goDaemon();
    }
    ...

    /* 改變工作路徑 默認是/var/ossec*/
    if (Privsep_Chroot(dir) < 0) {
        merror_exit(CHROOT_ERROR, dir, errno, strerror(errno));
    }
    nowChroot();

    if (Privsep_SetUser(uid) < 0) {
        merror_exit(SETUID_ERROR, user, errno, strerror(errno));
    }

    /* Try to connect to server */
    os_setwait();

    /* 創建消息隊列 unix socket 路徑是 /var/ossec/ + DEFAULTQUEUE = /var/ossec//queue/ossec/queue.剛好與前面的信息蒐集的socket一致,他們相互通信轉發數據 */
    if ((agt->m_queue = StartMQ(DEFAULTQUEUE, READ)) < 0) {
        merror_exit(QUEUE_ERROR, DEFAULTQUEUE, strerror(errno));
    }

    ...
    maxfd = agt->m_queue;
    agt->sock = -1;

    /* Create PID file */
    if (CreatePID(ARGV0, getpid()) < 0) {
        merror_exit(PID_ERROR);
    }

    /* Read private keys  */
    minfo(ENC_READ);

    OS_StartCounter(&keys);

    /*讀取agent的操作系統信息  */
    os_write_agent_info(keys.keyentries[0]->name, NULL, keys.keyentries[0]->id,
                        agt->profile);

    /*設置與manager通信使用的加密算法, 對稱加密  */
    os_set_agent_crypto_method(&keys,agt->crypto_method);

    switch (agt->crypto_method) {
        case W_METH_AES:
            minfo("Using AES as encryption method.");
            break;
        case W_METH_BLOWFISH:
            minfo("Using Blowfish as encryption method.");
            break;
        default:
            merror("Invalid encryption method.");
    }

    /* Start up message */
    minfo(STARTUP_MSG, (int)getpid());

    os_random();

    /* Ignore SIGPIPE, it will be detected on recv */
    signal(SIGPIPE, SIG_IGN);

    /* Launch rotation thread */

    rotate_log = getDefine_Int("monitord", "rotate_log", 0, 1);
    if (rotate_log && CreateThread(w_rotate_log_thread, (void *)NULL) != 0) {
        merror_exit(THREAD_ERROR);
    }

    /* 啓動轉發數據線程, 通過buffer_append觸發, 如果該函數返回0,正常處理:將數據放入buffer數組中,該線程調用dispatch_buffer處理轉發數據, 如果返回-1,  數據不保存在buffer中,在調用dispatch_buffer函數時發送告警信息給manager,不再讀取本地數據,等待下一次循環*/
    if (agt->buffer){

        buffer_init();

        if (CreateThread(dispatch_buffer, (void *)NULL) != 0) {
            merror_exit(THREAD_ERROR);
        }
    }else{
        minfo(DISABLED_BUFFER);
    }
    /* Connect remote */
    rc = 0;
    while (rc < agt->rip_id) {
        minfo("Server IP Address: %s", agt->server[rc].rip);
        rc++;
    }

    w_create_thread(state_main, NULL);

    /* 連接到manager端 */
    if (!connect_server(0)) {
        merror_exit(UNABLE_CONN);
    }

    /* Set max fd for select */
    if (agt->sock > maxfd) {
        maxfd = agt->sock;
    }

    /* Connect to the execd queue */
    if (agt->execdq == 0) {
        if ((agt->execdq = StartMQ(EXECQUEUE, WRITE)) < 0) {
            minfo("Unable to connect to the active response "
                   "queue (disabled).");
            agt->execdq = -1;
        }
    }

    start_agent(1);

    os_delwait();
    update_status(GA_STATUS_ACTIVE);

    // Ignore SIGPIPE signal to prevent the process from crashing
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &act, NULL);

    /* Send integrity message for agent configs */
    intcheck_file(OSSECCONF, dir);
    intcheck_file(OSSEC_DEFINES, dir);

    // Start request module
    req_init();
    w_create_thread(req_receiver, NULL);

    /* 發送心跳數據 */
    run_notify();

    /* Maxfd must be higher socket +1 */
    maxfd++;

    /* 死循環, 處理本地和manager的數據 */
    while (1) {

        /* 類似心跳數據  keep alive */
        run_notify();

        if (agt->sock > maxfd - 1) {
            maxfd = agt->sock + 1;
        }

        /* Monitor all available sockets from here */
        FD_ZERO(&fdset);
        FD_SET(agt->sock, &fdset);
        FD_SET(agt->m_queue, &fdset);

        fdtimeout.tv_sec = 1;
        fdtimeout.tv_usec = 0;

        /* Wait with a timeout for any descriptor */
        rc = select(maxfd, &fdset, NULL, NULL, &fdtimeout);
        if (rc == -1) {
            merror_exit(SELECT_ERROR, errno, strerror(errno));
        } else if (rc == 0) {
            continue;
        }

        /* 接收數據,判定manager是否在線 數據格式"Received message: '#!-agent ack '", 可通過將agent進程的日誌級別調成 agent.debug=2, 重啓就行了*/
        if (FD_ISSET(agt->sock, &fdset)) {
            if (receive_msg() < 0) {
                update_status(GA_STATUS_NACTIVE);
                merror(LOST_ERROR);
                os_setwait();
                start_agent(0);
                minfo(SERVER_UP);
                os_delwait();
                update_status(GA_STATUS_ACTIVE);
            }
        }

        /* 將信息蒐集的數據進行轉發到manager,其他類似日誌也是從這裏轉發的 */
        if (FD_ISSET(agt->m_queue, &fdset)) {
            EventForward();
        }
    }
}
/* Receive a message locally on the agent and forward it to the manager */
void *EventForward()
{

    ssize_t recv_b;
    char msg[OS_MAXSTR + 1];

    /* Initialize variables */
    msg[0] = '\0';
    msg[OS_MAXSTR] = '\0';
    /* 讀取數據 */
    while ((recv_b = recv(agt->m_queue, msg, OS_MAXSTR, MSG_DONTWAIT)) > 0) {
        msg[recv_b] = '\0';
        if (agt->buffer){//默認走這個分支,進行控制處理數據的速率
            if (buffer_append(msg) < 0) {
                break;
            }
        }else{
            agent_state.msg_count++;
            /*轉發到manager端*/
            if (send_msg(msg, -1) < 0) {
                break;
            }
        }

    }

    return (NULL);
}

/* 發送數據到server端 */
int send_msg(const char *msg, ssize_t msg_length)
{
    ssize_t msg_size;
    char crypt_msg[OS_MAXSTR + 1];
    int retval;
    int error;
    merror("send_msg '%.*s'.",
                       msg_length, msg);
    /* 構造消息頭, 壓縮 加密*/    
    msg_size = CreateSecMSG(&keys, msg, msg_length < 0 ? strlen(msg) : (size_t)msg_length, crypt_msg, 0);
    if (msg_size <= 0) {
        merror(SEC_ERROR);
        return (-1);
    }

    /* Send msg_size of crypt_msg */
    if (agt->server[agt->rip_id].protocol == UDP_PROTO) {
        retval = OS_SendUDPbySize(agt->sock, msg_size, crypt_msg);
#ifndef WIN32
        error = errno;
#endif
    } else {
        w_mutex_lock(&send_mutex);
        /* 轉發數據到manager*/
        retval = OS_SendSecureTCP(agt->sock, msg_size, crypt_msg);
#ifndef WIN32
        error = errno;
#endif
        w_mutex_unlock(&send_mutex);
    }
    ...
        sleep(1);
    }

    return retval;
}

manager端處理流程:

ossec-remoted進程:

  • 1:初始化:切換工作路徑(/var/ossec),創建 網絡連接(udp )和 unix socket

  • 2: 通過網絡連接接收到agent的數據,解析先解密再解壓;

  • 3:通過 unix socket進行轉發給ossec-analysisd進程;

ossec-analysisd進程:

  • 1:初始化 :切換工作目錄,創建 unix socket ,讀取配置的規則: decoder和rules ,在ruleset目錄下;創建decoder 和process event等線程組,和線程之間數據共享的各種隊列。 我們主要關心的是ad_input_main線程、w_decode_syscollector_thread線程組、decode_queue_syscollector_input、decode_queue_event_output 隊列

  • 2:ad_input_main負責從網絡中獲取數據, 將數據存入decode_queue_syscollector_input

  • 3:w_decode_syscollector_thread負責從decode_queue_syscollector_input拿出數據,通過DecodeSyscollector函數進行解析,解析方式按照agent端發過來的標誌進行判定是哪種類型的數據(OS),然後進入decode_osinfo函數中進行解析json數據,然後拼接數據以'|'分隔,例如agent 000 osinfo save 1216023267|2019/11/07 07:56:58|ubuntu|x86_64|Ubuntu|14.04.6 LTS, Trusty Tahr|NULL|14|04|NULL|ubuntu|Linux|4.4.0-142-generic|#168~14.04.1-Ubuntu SMP Sat Jan 19 11:26:28 UTC 2019,

  • 再通過WDB_LOCAL_SOCK unix socket 轉發到wazuh-db進程, 將數據入隊decode_queue_event_output;

  • 4: w_process_event_thread線程從decode_queue_event_output拿出數據,進行後續處理;

wazuh-db進程:

  • 1: 初始化:切換工作路徑,創建線程組run_dealer、run_worker、run_gc、run_up, 創建notify_queue、WDB_LOCAL_SOCK unix socket、open_dbs哈希表;

  • 2:  run_dealer線程負責accept連接,並存入notify_queue中;

  • 3: run_worker負責從notify_queue中取出socket,接收數據,通過wdb_parse函數並根據類型調用對應的解析函數(wdb_parse_osinfo)進行解析,並調用對應的存儲函數wdb_osinfo_save存入sqlite數據庫,如果是新增加的類型需要在schema_agents.sql中增加schema,並在wdb.c文件中的SQL_STMT數組中,定義插入刪除更新sql語句,並增加對應的枚舉變量wdb_stmt的元素;

  • 4:run_up 循環遍歷/var/ossec/queue/db目錄,讀取 db文件,調用wdb_open_agent2函數判定是否已經打開過,如果沒有,則創建wdb_t實例(給run_worker寫入數據庫用), 調用sqlit api 打開對應數據庫文件, 其中open_dbs用於保存打開的sqlite db文件來去重:例如:/var/ossec/queue/db/000.db;

  • 5:run_gc用於提交事務;

代碼分析:

src/remoted/main.c

int main(int argc, char **argv)
{
    ...
    /* 切換工作目錄 */
    if (Privsep_Chroot(dir) < 0) {
        merror_exit(CHROOT_ERROR, dir, errno, strerror(errno));
    }
    nowChroot();    
    ...
    /* Really start the program */
    i = 0;
    /* 一個子進程處理一種類型的網絡連接, 默認udp*/
    while (logr.conn[i] != 0) {
        /* Fork for each connection handler */
        if (fork() == 0) {
            /* On the child */
            mdebug1("Forking remoted: '%d'.", i);
            logr.position = i;
            /* 內部處理agent發送過來的數據,並轉發到消息隊列中,給後續處理*/
            HandleRemote(uid);
        } else {
            i++;
            continue;
        }
    }    
}
void HandleRemote(int uid)
{
    int position = logr.position;
    int recv_timeout;    //timeout in seconds waiting for a client reply
    int send_timeout;

    /*獲取配置文件信息或者默認值*/
    recv_timeout = getDefine_Int("remoted", "recv_timeout", 1, 60);
    send_timeout = getDefine_Int("remoted", "send_timeout", 1, 60);

    tcp_keepidle = getDefine_Int("remoted", "tcp_keepidle", 1, 7200);
    tcp_keepintvl = getDefine_Int("remoted", "tcp_keepintvl", 1, 100);
    tcp_keepcnt = getDefine_Int("remoted", "tcp_keepcnt", 1, 50);

    /* If syslog connection and allowips is not defined, exit */
    if (logr.conn[position] == SYSLOG_CONN) {
        if (logr.allowips == NULL) {
            exit(0);
        } else {
            os_ip **tmp_ips;

            tmp_ips = logr.allowips;
            while (*tmp_ips) {
                minfo("Remote syslog allowed from: '%s'", (*tmp_ips)->ip);
                tmp_ips++;
            }
        }
    }

    // Set resource limit for file descriptors

    {
        nofile = getDefine_Int("remoted", "rlimit_nofile", 1024, 1048576);
        struct rlimit rlimit = { nofile, nofile };

        if (setrlimit(RLIMIT_NOFILE, &rlimit) < 0) {
            merror("Could not set resource limit for file descriptors to %d: %s (%d)", (int)nofile, strerror(errno), errno);
        }
    }

    /* 綁定tcp 連接 IP PORT,socket fd 保存在logr.sock中,後面accept時會用到 */
    if (logr.proto[position] == TCP_PROTO) {
        if ((logr.sock = OS_Bindporttcp(logr.port[position], logr.lip[position], logr.ipv6[position])) < 0) {
            merror_exit(BIND_ERROR, logr.port[position], errno, strerror(errno));
        } else if (logr.conn[position] == SECURE_CONN) {

            if (OS_SetKeepalive(logr.sock) < 0){
                merror("OS_SetKeepalive failed with error '%s'", strerror(errno));
            }
#ifndef CLIENT
            else {
                OS_SetKeepalive_Options(logr.sock, tcp_keepidle, tcp_keepintvl, tcp_keepcnt);
            }
#endif
            if (OS_SetRecvTimeout(logr.sock, recv_timeout, 0) < 0){
                merror("OS_SetRecvTimeout failed with error '%s'", strerror(errno));
            }
            if (OS_SetSendTimeout(logr.sock, send_timeout) < 0){
                merror("OS_SetSendTimeout failed with error '%s'", strerror(errno));
            }
        }
    } else {
        /* UDP相關設置 */
        if ((logr.sock =
                    OS_Bindportudp(logr.port[position], logr.lip[position], logr.ipv6[position])) < 0) {
            merror_exit(BIND_ERROR, logr.port[position], errno, strerror(errno));
        }
    }

    /* Revoke privileges */
    if (Privsep_SetUser(uid) < 0) {
        merror_exit(SETUID_ERROR, REMUSER, errno, strerror(errno));
    }

    /* Create PID */
    if (CreatePID(ARGV0, getpid()) < 0) {
        merror_exit(PID_ERROR);
    }

    /* 啓動的相關日誌 */
    minfo(STARTUP_MSG " Listening on port %d/%s (%s).",
    (int)getpid(),
    logr.port[position],
    logr.proto[position] == TCP_PROTO ? "TCP" : "UDP",
    logr.conn[position] == SECURE_CONN ? "secure" : "syslog");

    /* 如果是安全網絡連接, 調用 HandleSecure 處理*/
    if (logr.conn[position] == SECURE_CONN) {
        HandleSecure();
    }

    else if (logr.proto[position] == TCP_PROTO) {
        HandleSyslogTCP();
    }

    /* If not, deal with syslog */
    else {
        HandleSyslog();
    }
}

/* 處理安全連接主入口 */
void HandleSecure()
{
    const int protocol = logr.proto[logr.position];
    int sock_client;
    int n_events = 0;
    char buffer[OS_MAXSTR + 1];
    ssize_t recv_b;
    struct sockaddr_in peer_info;
    wnotify_t * notify = NULL;

    /* manager端的初始化 */
    manager_init();

    // 初始化消息隊列,在接收到數據包的時候,經過相關處理後,入隊
    rem_msginit(logr.queue_size);

    ...
    /* 創建等待消息線程池,還沒分析其作用*/

    {
        int i;
        sender_pool = getDefine_Int("remoted", "sender_pool", 1, 64);

        mdebug2("Creating %d sender threads.", sender_pool);

        for (i = 0; i < sender_pool; i++) {
            w_create_thread(wait_for_msgs, NULL);
        }
    }

    //創建消息處理線程池
    {
        int worker_pool = getDefine_Int("remoted", "worker_pool", 1, 16);

        while (worker_pool > 0) {
            w_create_thread(rem_handler_main, NULL);
            worker_pool--;
        }
    }

    /* Connect to the message queue
     * 連接到消息隊列, unix socket 路徑/var/ossec/queue/ossec/queue.
     */
    if ((logr.m_queue = StartMQ(DEFAULTQUEUE, WRITE)) < 0) {
        merror_exit(QUEUE_FATAL, DEFAULTQUEUE);
    }
    ...
    /* Set up peer size */
    logr.peer_size = sizeof(peer_info);

    /* Initialize some variables */
    memset(buffer, '\0', OS_MAXSTR + 1);
    /* 如果走TCP協議, 初始化 epoll,並將監聽的fd添加進epoll*/
    if (protocol == TCP_PROTO) {
        if (notify = wnotify_init(MAX_EVENTS), !notify) {
            merror_exit("wnotify_init(): %s (%d)", strerror(errno), errno);
        }

        if (wnotify_add(notify, logr.sock) < 0) {
            merror_exit("wnotify_add(%d): %s (%d)", logr.sock, strerror(errno), errno);
        }
    }

    while (1) {
        /* 死循環接收消息 */
        if (protocol == TCP_PROTO) {
            /* 獲取活躍的event*/
            if (n_events = wnotify_wait(notify, EPOLL_MILLIS), n_events < 0) {
                if (errno != EINTR) {
                    merror("Waiting for connection: %s (%d)", strerror(errno), errno);
                    sleep(1);
                }
                continue;
            }

            /* 遍歷活躍的event */
            int i;
            for (i = 0; i < n_events; i++) {
                int fd = wnotify_get(notify, i);
                /* 如果是監聽的fd,說明是agent連上來了, accept它*/
                if (fd == logr.sock) {
                    sock_client = accept(logr.sock, (struct sockaddr *)&peer_info, &logr.peer_size);
                    if (sock_client < 0) {
                        merror_exit(ACCEPT_ERROR);
                    }
                    /* 初始化新的socket fd */
                    nb_open(&netbuffer, sock_client, &peer_info);
                    rem_inc_tcp();
                    
                    /* 將新的fd加入epoll進行監控*/
                    if (wnotify_add(notify, sock_client) < 0) {
                        merror("wnotify_add(%d, %d): %s (%d)", notify->fd, sock_client, strerror(errno), errno);
                        _close_sock(&keys, sock_client);
                    }
                } else {
                    /* 如果是已經accept的socket ,直接接收數據,並放入消息隊列中*/
                    sock_client = fd;
                    /*nb_recv內部最終還是調用rem_msgpush進行轉發到消息隊列*/
                    switch (recv_b = nb_recv(&netbuffer, sock_client), recv_b) {
                    case -2:
                       ...
                        continue;

                    case -1:
                    ...
                    case 0:
                        if (wnotify_delete(notify, sock_client) < 0) {
                            merror("wnotify_delete(%d): %s (%d)", sock_client, strerror(errno), errno);
                        }

                        _close_sock(&keys, sock_client);
                        continue;

                    default:
                        rem_add_recv((unsigned long)recv_b);
                    }
                }
            }
        } else {
            /* udp 直接接收數據*/
            recv_b = recvfrom(logr.sock, buffer, OS_MAXSTR, 0, (struct sockaddr *)&peer_info, &logr.peer_size);

            /* Nothing received */
            if (recv_b <= 0) {
                continue;
            } else {
                /* 數據不爲空,放入消息隊列,等待後續處理*/
                rem_msgpush(buffer, recv_b, &peer_info, -1);
                rem_add_recv((unsigned long)recv_b);
            }
        }
    }
}

前面我提到消息處理線程池,就是在輪詢,消息隊列中是否有消息存在,如果有,就進行解析(解密、解壓)轉發到analysis 進程。

rem_handler_main=>HandleSecureMessage

我們看看HandleSecureMessage的實現


static void HandleSecureMessage(char *buffer, int recv_b, struct sockaddr_in *peer_info, int sock_client) {

    /*解析消息頭 如果agent ID不對關閉socket 返回 代碼比較長, */
    ...
    /* ReadSecMSG內部先解密數據,然後解壓 */
    if (r = ReadSecMSG(&keys, tmp_msg, cleartext_msg, agentid, recv_b - 1, &msg_length, srcip, &tmp_msg), r != KS_VALID) {
        /* If duplicated, a warning was already generated */
        key_unlock();

        if (r == KS_ENCKEY) {
            if (ip_found) {
                push_request(srcip,"ip");
            } else {
                push_request(buffer + 1, "id");
            }
        }

        if (sock_client >= 0)
            _close_sock(&keys, sock_client);

        return;
    }
    ...

    /* 
     *  如果我們不能轉發消息到analysis進程,嘗試重連一次,如果失敗,退出
     */
    if (SendMSG(logr.m_queue, tmp_msg, srcmsg,
                SECURE_MQ) < 0) {
        merror(QUEUE_ERROR, DEFAULTQUEUE, strerror(errno));

        if ((logr.m_queue = StartMQ(DEFAULTQUEUE, WRITE)) < 0) {
            merror_exit(QUEUE_FATAL, DEFAULTQUEUE);
        }
    } else {
        rem_inc_evt();
    }
}

接下來看一下analysis 怎麼處理的:

main函數代碼 (src/analysisd/analysisd.c):

#ifndef TESTRULE
int main(int argc, char **argv)
#else
__attribute__((noreturn))
int main_analysisd(int argc, char **argv)
#endif
{
    int c = 0, m_queue = 0, test_config = 0, run_foreground = 0;
    int debug_level = 0;
    const char *dir = DEFAULTDIR;
    const char *user = USER;
    const char *group = GROUPGLOBAL;
    uid_t uid;
    gid_t gid;
    ...
    /* Set the group */
    if (Privsep_SetGroup(gid) < 0) {
        merror_exit(SETGID_ERROR, group, errno, strerror(errno));
    }

    /* Chroot  切換工作目錄 進行隔離*/
    if (Privsep_Chroot(dir) < 0) {
        merror_exit(CHROOT_ERROR, dir, errno, strerror(errno));
    }
    nowChroot();
    ...
    /* 創建消息隊列從ossec-remoted進程獲取數據 */
    if ((m_queue = StartMQ(DEFAULTQUEUE, READ)) < 0) {
        merror_exit(QUEUE_ERROR, DEFAULTQUEUE, strerror(errno));
    }    
    ...
    /* 主要入口函數 */
    OS_ReadMSG(m_queue);

    exit(0);
}
/* Main function. Receives the messages(events) and analyze them all */
#ifndef TESTRULE
__attribute__((noreturn))
void OS_ReadMSG(int m_queue)
#else
__attribute__((noreturn))
void OS_ReadMSG_analysisd(int m_queue)
#endif
{
   ...
    /* 信息蒐集初始化 */
    SyscollectorInit();

    ...
    /* 創建接收ossec-remoted消息隊列處理線程,並保存到相應隊列中 */
    w_create_thread(ad_input_main, &m_queue);
    ...
    /* 創建 解析 信息蒐集線程池 */
    for(i = 0; i < num_decode_syscollector_threads;i++){
        w_create_thread(w_decode_syscollector_thread,NULL);
    }

}
// 消息處理線程函數
void * ad_input_main(void * args) {
    int m_queue = *(int *)args;
    char buffer[OS_MAXSTR + 1] = "";
    char * copy;
    char *msg;
    int result;
    int recv = 0;

    while (1) {
        //從/var/ossec/queue/ossec/queue socket中讀取數據
        if (recv = OS_RecvUnix(m_queue, OS_MAXSTR, buffer),recv) {
            buffer[recv] = '\0';
            msg = buffer;

            /* Get the time we received the event */
            gettime(&c_timespec);

            /* Check for a valid message */
            if (strlen(msg) < 4) {
                merror(IMSG_ERROR, msg);
                continue;
            }

            s_events_received++;
            // 根據消息類型進行區分 我們需要關注的是SYSCOLLECTOR_MQ
            if (msg[0] == SYSCHECK_MQ) {

                os_strdup(buffer, copy);
                if(queue_full(decode_queue_syscheck_input)){
                    if(!reported_syscheck){
                        reported_syscheck = 1;
                        mwarn("Syscheck decoder queue is full.");
                    }
                    w_inc_dropped_events();
                    free(copy);
                    continue;
                }

                result = queue_push_ex(decode_queue_syscheck_input,copy);

                if(result < 0){
                    if(!reported_syscheck){
                        reported_syscheck = 1;
                        mwarn("Syscheck decoder queue is full.");
                    }
                    w_inc_dropped_events();
                    free(copy);
                    continue;
                }
                hourly_syscheck++;
                /* Increment number of events received */
                hourly_events++;
            }
            ...
             // 信息蒐集處理分支
             else if(msg[0] == SYSCOLLECTOR_MQ){

                os_strdup(buffer, copy);

                if(queue_full(decode_queue_syscollector_input)){
                    if(!reported_syscollector){
                        reported_syscollector = 1;
                        mwarn("Syscollector decoder queue is full.");
                    }
                    w_inc_dropped_events();
                    free(copy);
                    continue;
                }
                //將信息放入decode_queue_syscollector_input隊列中
                result = queue_push_ex(decode_queue_syscollector_input,copy);

                if(result < 0){

                    if(!reported_syscollector){
                        reported_syscollector = 1;
                        mwarn("Syscollector decoder queue is full.");
                    }
                    w_inc_dropped_events();
                    free(copy);
                    continue;
                }
                /* Increment number of events received */
                hourly_events++;
            }
            ...
        }
    }

    return NULL;
}
/* 將數據出隊,然後調用DecodeSyscollector進行解析*/
void * w_decode_syscollector_thread(__attribute__((unused)) void * args){
    Eventinfo *lf = NULL;
    char *msg = NULL;
    int socket = -1;

    while(1){

        /* Receive message from queue */
        if (msg = queue_pop_ex(decode_queue_syscollector_input), msg) {
            ...
            if (!DecodeSyscollector(lf,&socket)) {
                /* We don't process syscollector events further */
                w_free_event_info(lf);
            }
            else{
                if (queue_push_ex_block(decode_queue_event_output,lf) < 0) {
                    w_free_event_info(lf);
                }
            }

            w_inc_syscollector_decoded_events();
        }
    }
}

/* 信息蒐集數據的解析 */
int DecodeSyscollector(Eventinfo *lf,int *socket)
{
    cJSON *logJSON;
    cJSON *json_type;
    char *msg_type = NULL;

    lf->decoder_info = sysc_decoder;

    // Check location
    if (lf->location[0] == '(') {
        char* search;
        search = strchr(lf->location, '>');
        if (!search) {
            mdebug1("Invalid received event.");
            return (0);
        }
        else if (strcmp(search + 1, "syscollector") != 0) {
            mdebug1("Invalid received event. Not syscollector.");
            return (0);
        }
    } else if (strcmp(lf->location, "syscollector") != 0) {
        mdebug1("Invalid received event. (Location)");
        return (0);
    }

    // Parsing event.
    logJSON = cJSON_Parse(lf->log);
    if (!logJSON) {
        mdebug1("Error parsing JSON event. %s", cJSON_GetErrorPtr());
        return (0);
    }

    // Detect message type
    json_type = cJSON_GetObjectItem(logJSON, "type");
    if (!(json_type && (msg_type = json_type->valuestring))) {
        mdebug1("Invalid message. Type not found.");
        cJSON_Delete (logJSON);
        return (0);
    }

    fillData(lf,"type",msg_type);
    if (strcmp(msg_type, "port") == 0 || strcmp(msg_type, "port_end") == 0) {
        if (decode_port(lf, logJSON,socket) < 0) {
            mdebug1("Unable to send ports information to Wazuh DB.");
            cJSON_Delete (logJSON);
            return (0);
        }
    }
    else if (strcmp(msg_type, "OS") == 0) {
        /* 解析os 相關信息*/
        if (decode_osinfo(lf, logJSON,socket) < 0) {
            mdebug1("Unable to send osinfo message to Wazuh DB.");
            cJSON_Delete (logJSON);
            return (0);
        }
    }
    ...
    else {
        mdebug1("Invalid message type: %s.", msg_type);
        cJSON_Delete (logJSON);
        return (0);
    }

    cJSON_Delete (logJSON);
    return (1);
}
/* 解析os 信息 操作系統的版本 名稱等等, */
int decode_osinfo( Eventinfo *lf, cJSON * logJSON,int *socket) {

    cJSON * inventory;

    if (inventory = cJSON_GetObjectItem(logJSON, "inventory"), inventory) {
        cJSON * scan_id = cJSON_GetObjectItem(logJSON, "ID");
        cJSON * scan_time = cJSON_GetObjectItem(logJSON, "timestamp");
        cJSON * os_name = cJSON_GetObjectItem(inventory, "os_name");
        cJSON * os_version = cJSON_GetObjectItem(inventory, "os_version");
        cJSON * os_codename = cJSON_GetObjectItem(inventory, "os_codename");
        cJSON * hostname = cJSON_GetObjectItem(inventory, "hostname");
        cJSON * architecture = cJSON_GetObjectItem(inventory, "architecture");
        cJSON * os_major = cJSON_GetObjectItem(inventory, "os_major");
        cJSON * os_minor = cJSON_GetObjectItem(inventory, "os_minor");
        cJSON * os_build = cJSON_GetObjectItem(inventory, "os_build");
        cJSON * os_platform = cJSON_GetObjectItem(inventory, "os_platform");
        cJSON * sysname = cJSON_GetObjectItem(inventory, "sysname");
        cJSON * release = cJSON_GetObjectItem(inventory, "release");
        cJSON * version = cJSON_GetObjectItem(inventory, "version");

        char * msg = NULL;
        os_calloc(OS_SIZE_6144, sizeof(char), msg);

        snprintf(msg, OS_SIZE_6144 - 1, "agent %s osinfo save", lf->agent_id);


        if (scan_id) {
            char id[OS_SIZE_1024];
            snprintf(id, OS_SIZE_1024 - 1, "%d", scan_id->valueint);
            wm_strcat(&msg, id, ' ');
        } else {
            wm_strcat(&msg, "NULL", ' ');
        }

        if (scan_time) {
            wm_strcat(&msg, scan_time->valuestring, '|');
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (hostname) {
            wm_strcat(&msg, hostname->valuestring, '|');
            fillData(lf,"os.hostname",hostname->valuestring);
        } else {
                wm_strcat(&msg, "NULL", '|');
        }

        if (architecture) {
            wm_strcat(&msg, architecture->valuestring, '|');
            fillData(lf,"os.architecture",architecture->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (os_name) {
            wm_strcat(&msg, os_name->valuestring, '|');
            fillData(lf,"os.name",os_name->valuestring);
        } else {
                wm_strcat(&msg, "NULL", '|');
        }

        if (os_version) {
            wm_strcat(&msg, os_version->valuestring, '|');
            fillData(lf,"os.version",os_version->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (os_codename) {
            wm_strcat(&msg, os_codename->valuestring, '|');
            fillData(lf,"os.codename",os_codename->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (os_major) {
            wm_strcat(&msg, os_major->valuestring, '|');
            fillData(lf,"os.major",os_major->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (os_minor) {
            wm_strcat(&msg, os_minor->valuestring, '|');
            fillData(lf,"os.minor",os_minor->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (os_build) {
            wm_strcat(&msg, os_build->valuestring, '|');
            fillData(lf,"os.build",os_build->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (os_platform) {
            wm_strcat(&msg, os_platform->valuestring, '|');
            fillData(lf,"os.platform",os_platform->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (sysname) {
            wm_strcat(&msg, sysname->valuestring, '|');
            fillData(lf,"os.sysname",sysname->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (release) {
            wm_strcat(&msg, release->valuestring, '|');
            fillData(lf,"os.release",release->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }

        if (version) {
            wm_strcat(&msg, version->valuestring, '|');
            fillData(lf,"os.release_version",version->valuestring);
        } else {
            wm_strcat(&msg, "NULL", '|');
        }
        /*將解析好的數據發送給 wazuh-db 進行後續處理, 也是通過unix socket實現 /var/ossec/queue/db/wdb*/
        if (sc_send_db(msg,socket) < 0) {
            return -1;
        }

    }

    return 0;
}

wazuh-db進程的處理:

int main(int argc, char ** argv) {
    ...
    // 啓動相關線程, run_dealer 將/var/ossec/queue/db/wdb 的socket 放入notify_queue,等待run_worker 去處理,然後run_worker接收數據並調用wdb_parse解析調用wdb_parse_osinfo=>wdb_osinfo_save=>wdb_osinfo_insert存儲到sqlite中

    if (status = pthread_create(&thread_dealer, NULL, run_dealer, NULL), status != 0) {
        merror("Couldn't create thread: %s", strerror(status));
        return EXIT_FAILURE;
    }

    os_malloc(sizeof(pthread_t) * config.worker_pool_size, worker_pool);

    for (i = 0; i < config.worker_pool_size; i++) {
        if (status = pthread_create(worker_pool + i, NULL, run_worker, NULL), status != 0) {
            merror("Couldn't create thread: %s", strerror(status));
            return EXIT_FAILURE;
        }
    }

    ...
}
void * run_dealer(__attribute__((unused)) void * args) {
    int sock;
    int peer;
    fd_set fdset;
    struct timeval timeout;
    /* 創建/var/ossec/queue/db/wdb socket */
    if (sock = OS_BindUnixDomain(WDB_LOCAL_SOCK, SOCK_STREAM, OS_MAXSTR), sock < 0) {
        merror_exit("Unable to bind to socket '%s': '%s'. Closing local server.",
                WDB_LOCAL_SOCK, strerror(errno));
    }

    while (running) {
        // Wait for socket

        FD_ZERO(&fdset);
        FD_SET(sock, &fdset);
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        switch (select(sock + 1, &fdset, NULL, NULL, &timeout)) {
            ...
        }

        // 接受新的節點

        if (peer = accept(sock, NULL, NULL), peer < 0) {
            ...
            continue;
        }
        /*將對應的socket 添加到notify_queue中,給run_worker來處理*/
        if (wnotify_add(notify_queue, peer) < 0) {
            merror("at run_dealer(): wnotify_add(%d): %s (%d)",
                    peer, strerror(errno), errno);
            goto error;
        }

    }

error:
    close(sock);
    unlink(WDB_LOCAL_SOCK);
    return NULL;
}

void * run_worker(__attribute__((unused)) void * args) {
    char buffer[OS_MAXSTR + 1];
    char response[OS_MAXSTR + 1];
    ssize_t length;
    int terminal;
    int peer;

    while (running) {
        // Dequeue peer
        w_mutex_lock(&queue_mutex);

        // 等待事件
        switch (wnotify_wait(notify_queue, 100)) {
            ...
        }

        // 將給節點從等待隊列中刪除
        peer = wnotify_get(notify_queue, 0);
        if (wnotify_delete(notify_queue, peer) < 0) {
            merror("at run_worker(): wnotify_delete(%d): %s (%d)",
                    peer, strerror(errno), errno);
        }

        w_mutex_unlock(&queue_mutex);

        ssize_t count;
        length = 0;
       // 接收數據
        count = OS_RecvSecureTCP(peer, buffer, OS_MAXSTR);
        ...

        switch (length) {
        ...

        default:
            if (length > 0 && buffer[length - 1] == '\n') {
                buffer[length - 1] = '\0';
                terminal = 1;
            } else {
                buffer[length] = '\0';
                terminal = 0;
            }

            *response = '\0';
            // 開始解析數據
            wdb_parse(buffer, response);
            ... 
            break;
        }

        // 處理完之後重新加入等待隊列中
        if (wnotify_add(notify_queue, peer) < 0) {
            merror("at run_worker(): wnotify_add(%d): %s (%d)",
                    peer, strerror(errno), errno);
        }
    }

    return NULL;
}


int wdb_parse(char * input, char * output) {
    char * actor;
    char * id;
    char * query;
    char * sql;
    char * next;
    int agent_id;
    char sagent_id[64];
    wdb_t * wdb;
    cJSON * data;
    char * out;
    int result = 0;

    ...
    actor = input;
    *next++ = '\0';

    if (strcmp(actor, "agent") == 0) {
        id = next;

        ...

        if (strcmp(query, "syscheck") == 0) {
            if (!next) {
                mdebug1("DB(%s) Invalid FIM query syntax.", sagent_id);
                mdebug2("DB(%s) FIM query error near: %s", sagent_id, query);
                snprintf(output, OS_MAXSTR + 1, "err Invalid Syscheck query syntax, near '%.32s'", query);
                result = -1;
            } else {
                result = wdb_parse_syscheck(wdb, next, output);
            }
        } else if (strcmp(query, "osinfo") == 0) {//處理 os信息
            if (!next) {
                mdebug1("DB(%s) Invalid DB query syntax.", sagent_id);
                mdebug2("DB(%s) query error near: %s", sagent_id, query);
                snprintf(output, OS_MAXSTR + 1, "err Invalid DB query syntax, near '%.32s'", query);
                result = -1;
            } else {
                if (wdb_parse_osinfo(wdb, next, output) == 0){
                    mdebug2("Updated 'sys_osinfo' table for agent '%s'", sagent_id);
                } else {
                    merror("Unable to update 'sys_osinfo' table for agent '%s'", sagent_id);
                }
            }
             ...
        }
        ...
        
        return result;
    } else {
        mdebug1("DB(%s) Invalid DB query actor: %s", sagent_id, actor);
        snprintf(output, OS_MAXSTR + 1, "err Invalid DB query actor: '%.32s'", actor);
        return -1;
    }
}

int wdb_parse_osinfo(wdb_t * wdb, char * input, char * output) {
    char * curr;
    char * next;
    char * scan_id;
    char * scan_time;
    char * hostname;
    char * architecture;
    char * os_name;
    char * os_version;
    char * os_codename;
    char * os_major;
    char * os_minor;
    char * os_build;
    char * os_platform;
    char * sysname;
    char * release;
    char * version;
    int result;

    if (next = strchr(input, ' '), !next) {
        mdebug1("Invalid OS info query syntax.");
        mdebug2("OS info query: %s", input);
        snprintf(output, OS_MAXSTR + 1, "err Invalid OS info query syntax, near '%.32s'", input);
        return -1;
    }

    curr = input;
    *next++ = '\0';

    if (strcmp(curr, "save") == 0) {
        curr = next;
        //解析字段 以'|' 分隔
        if (next = strchr(curr, '|'), !next) {
            mdebug1("Invalid OS info query syntax.");
            mdebug2("OS info query: %s", curr);
            snprintf(output, OS_MAXSTR + 1, "err Invalid OS info query syntax, near '%.32s'", curr);
            return -1;
        }

        scan_id = curr;
        *next++ = '\0';
        curr = next;

        if (!strcmp(scan_id, "NULL"))
            scan_id = NULL;

        ...

        //將各個字段解析出來,並存人sqlite
        if (result = wdb_osinfo_save(wdb, scan_id, scan_time, hostname, architecture, os_name, os_version, os_codename, os_major, os_minor, os_build, os_platform, sysname, release, version), result < 0) {
            mdebug1("Cannot save OS information.");
            snprintf(output, OS_MAXSTR + 1, "err Cannot save OS information.");
        } else {
            snprintf(output, OS_MAXSTR + 1, "ok");
        }

        return result;
    } else {
        mdebug1("Invalid OS info query syntax.");
        mdebug2("DB query error near: %s", curr);
        snprintf(output, OS_MAXSTR + 1, "err Invalid OS info query syntax, near '%.32s'", curr);
        return -1;
    }
}

// 記錄到數據庫中, 插入語句定義在SQL_STMT數組中(src/wazuh_db/wdb.c)
int wdb_osinfo_save(wdb_t * wdb, const char * scan_id, const char * scan_time, const char * hostname, const char * architecture, const char * os_name, const char * os_version, const char * os_codename, const char * os_major, const char * os_minor, const char * os_build, const char * os_platform, const char * sysname, const char * release, const char * version) {

    sqlite3_stmt *stmt = NULL;

    ...
    // 存入數據庫 沒什麼難度
    if (wdb_osinfo_insert(wdb,
        scan_id,
        scan_time,
        hostname,
        architecture,
        os_name,
        os_version,
        os_codename,
        os_major,
        os_minor,
        os_build,
        os_platform,
        sysname,
        release,
        version) < 0) {

        return -1;
    }

    return 0;
}

c代碼就講解完畢了;

接下來可以參照wazuh官網去部署manager agent wazuh-api

展示

manager 端進程

使用curl 調用restfull API 查詢(需要在manager端部署wazuh-api):

curl -u foo:bar -k -X GET "http://127.0.0.1:55000/syscollector/007/os?pretty"

agent 端的數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章