wazuh是从ossec-hids衍生过来的,部分架构设计有所不同, 多进程多线程模式。本机的进程之间通过Unix domain socket 进行通信的。
今天简单介绍一下数据搜集的相关功能的实现(Linux系统)。
注意由于篇幅所限, 在函数中我们只列出相关代码。
https://documentation.wazuh.com/3.9/getting-started/architecture.html?highlight=architecture
// 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小时收集一次等。
no
1h
yes
yes
yes
yes
yes
yes
yes
在src/wazuh_modules/main.c 中
下面列出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
...
}
}
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;
}
#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);
}
...
}
#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);
}
/* 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;
}
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();
}
}
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;
}
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
curl -u foo:bar -k -X GET "http://127.0.0.1:55000/syscollector/007/os?pretty"