はじめに
OSSECのソースコードリーディングの記事です。なお、OSSECのインストール手順にご興味のある方は以下の記事を参照ください。
- Amazon Linux2にOSSECをインストールした(OSSEC-Serverインストール)
- Amazon Linux2にOSSECをインストールした(OSSEC-Agentインストール)
- Amazon Linux2にOSSECをインストールした(OSSEC-Localインストール)
maildデーモン
main()関数
- /ossec-hids-master/src/os_maild/maild.c
- デーモン起動オプションのチェック
- 指定されたユーザー/グループが有効かどうかを確認する
- Config設定の読み込み
- 内部オプション読み込み
- グルーピング取得
- サブジェクトタイプ取得
- ジオIP取得 ←コンパイルオプション有効の場合
- 特権の分離
- chroot変更
- ユーザ変更
- シグナル操作
- PIDファイル生成
- デーモンのメイン処理:OS_Run(&mail)をコール
OS_Run()関数
- 現在時刻取得
- ファイルキュー初期化 os_calloc()
- メールリスト生成
- デフォルトタイムアウト時間設定
- グローバル変数クリア
- 無限ループ while(1)
- 時分秒の時が変わってたら、抑制していたメールを送信。pid = fork()で子プロセスを生成し、子プロセスでOS_Sendmail()をコールして、メールを送信。exit(0)でプログラムを正常終了させる。
- do_not_groupオプションのメッセージをセーブ
- キューからメッセージを取り出して、送信予定メールのリストに保存【OS_AddMailtoList()】。プライオリティを見て処理分岐。DONOTGROUPは低プライオリティ扱いかも。
- 子プロセスが終了するのを待つ。while (childcount)でchildcountがゼロになるまで待つ。子プロセスがメールを送信終わるまでループしているのか。
execdデーモン
main()関数
- /ossec-hids-master/src/os_execd/execd.c
- デーモン起動オプションのチェック
- 指定されたユーザー/グループが有効かどうかを確認する
- 指定されたユーザー/グループが有効かどうかを確認する
- Config設定の読み込み
- シグナル操作 StartSIG2(ARGV0, execd_shutdown)
- PIDファイル生成
- execキュー開始 StartMQ(EXECQUEUEPATH, READ)
- デーモンのメイン処理開始 ExecdStart(m_queue)
ExecdStart()関数
- タイムアウトリスト生成 OSList_Create
- 無限ループ while(1)
- 子プロセスをクリーンアップ
- 現在時刻を取得
- 実行タイムアウトコマンドの存在有無をチェック
- while (timeout_node)タイムアウトリストをループでチェックして、タイムアウトコマンドがあったら、コマンドを実行する
- OSList_GetFirstNode(timeout_list) タイムアウトリストの先頭ノードを取得
- ExecCmd(list_entry->command) コマンド実行
- OSList_DeleteCurrentlyNode(timeout_list) タイムアウトリストから実行したノードを削除
- timeout_node = OSList_GetNextNode(timeout_list) 次のノードをセットして、タイムアウトリストチェックのループの先頭に戻る
- select()システムコールでキューを待ち受け select(q + 1, &fdset, NULL, NULL, &socket_timeout)
- コマンド受信 OS_RecvUnix(q, OS_MAXSTR, buffer)
- 実行コマンドを取り出し GetCommandbyName()
- 実行コマンドの引数を取り出し
- while (timeout_node) 再度、タイムアウトリストをチェックするループ処理
- キューから取り出した実行コマンドをタイムアウトリストに追加する
- 過去に追加したコマンドではない場合は、コマンド実行する ExecCmd(cmd_args)
analysisdデーモン
main()関数
- /ossec-hids-master/src/analysisd/analysisd.c
- デーモン起動オプションのチェック
- 指定されたユーザー/グループが有効かどうかを確認する
- ActiveResponse初期化 AR_Init()
- Config設定の読み込み GlobalConf(cfg)
- ジオIP有効の場合は、ジオIPデータベースの読み込み
- サーバのホストネームを取得 gethostname()
- chroot設定 Privsep_Chroot() nowChroot()
- デコーダーの最大サイズを設定 MAX_DECODER_ORDER_SIZE 20
- すべてのルールとリストの完成
- デコードリスト取得
- リスト取得
- ルール取得
- シグナル操作 StartSIG2(ARGV0, execd_shutdown)
- PIDファイル生成
- execキュー開始 StartMQ(DEFAULTQUEUE, READ)
- デーモンのメイン処理開始 OS_ReadMSG(m_queue)
OS_ReadMSG()
- 各種メモリの初期化 OS_InitLog(),SyscheckInit(),RootcheckInit(),HostinfoInit()
- イベントリスト生成 OS_CreateEventList()
- FTSリスト初期化 FTS_Init()
- アキュームレーター初期化 Accumulate_Init()
- キュー開始 StartMQ()
- デーモンループ開始 while(1)
- キューからメッセージを受信 OS_RecvUnix()
- 受信したメッセージをデコード
- ルールをチェックし、ルールに従って処理を実行
logcollectorデーモン
main()関数
- /ossec-hids-master/src/logcollector/main.c
- キュー開始logr_queue = StartMQ(DEFAULTQPATH, WRITE)
- デーモンのメイン処理 LogCollectorStart()
LogCollectorStart()関数 - /ossec-hids-master/src/logcollector/logcollector.c
- while(1) 無限ループ
- タイムアウトまで待つ select()システムコール if ((r = select(0, NULL, NULL, NULL, &fp_timeout)) < 0)
- 2秒おきにチェック処理を走らせている etc/internal_options.conf:logcollector.loop_timeout=2
- チェック対象のファイル有効性をチェック for (i = 0; i <= max_file; i++)
- keep aliveメッセージを送信 チェックに時間がかかるからか?
- rand_keepalive_str(keepalive, 700)
- SendMSG(logr_queue, keepalive, "ossec-keepalive", LOCALFILE_MQ);
- ファイルタイムスタンプの日付が変わっているか
- ファイルがオープンされている場合、テンポラリファイルを開いている
- ログローテートされたことを通知 SendMSG(logr_queue, msg_alert,"ossec-logcollector", LOCALFILE_MQ)
- ファイルサイズが小さくなっている(reduce)場合、ファイルreduceのアラートを通知SendMSG(logr_queue, msg_alert,"ossec-logcollector", LOCALFILE_MQ)
- 当日のファイル変更はアラート通知対象外にしているようだ
remotedデーモン
main()関数
- /ossec-hids-master/src/remoted/main.c
- それぞれの接続をハンドルするプロセスをfork()して、HandleRemote()
HandleRemote()関数
- /ossec-hids-master/src/remoted/remoted.c
- syslog接続の場合、接続許可IPリストを取得
- ソケットをBind() OS_Bindporttcp(),OS_Bindportudp()
- POSIXスレッド機構を使用して、メッセージ送信(sendto())を行っている。pthread_mutex_init(),pthread_mutex_lock()
syscheckedデーモン
main()関数
- /ossec-hids-master/src/syscheckd/syscheck.c
- キュー書き込みしている syscheck.queue = StartMQ(DEFAULTQPATH, WRITE))
- デーモン開始 start_daemon()
start_daemon()関数
- /ossec-hids-master/src/syscheckd/run_check.c
- 無限ループ while(1)
- リアルタイム監視の処理もある
- MD5とSHA1のチェックサムを比較しているようだ
monitordデーモン
main()関数
- /ossec-hids-master/src/monitord/main.c
- デーモンメイン処理 Monitord()コール
Monitord()関数
- /ossec-hids-master/src/monitord/monitord.c
- 無限ループ while(1)
- 2分間隔でチェックしている sleep(120)
- エージェント監視処理 monitor_agents()
- エージェントからマネージャーに生存通知を投げていて、それをチェックしているのかも
agentdデーモン
- Sender処理とReceiver処理がいる
- Senderはサーバへの生存通知をしているようだ
- ReceiverはサーバからのメッセージをNWソケットで受信して、execd向けのOSキューに登録している
main()関数 - /ossec-hids-master/src/client-agent/main.c
- agentdデーモン開始処理 AgentdStart(dir, uid, gid, user, group)
AgentdStart()関数
- /ossec-hids-master/src/client-agent/agentd.c
- キュー接続 agt->execdq = StartMQ(EXECQUEUE, WRITE)
- サーバへの接続テスト
- os_setwait()
- start_agent(1)
- os_delwait()
- run_notify() 初回通知
- 無限ループ while(1)
- run_notify() サーバへの継続通知をしている。エージェントからサーバーに対する生存通知か。
- select()システムコールによる待ち fdtimeout.tv_sec = 1 1秒停止
- receive_msg()もしている。サーバからのメッセージ受信か。
receive_msg()関数
- /ossec-hids-master/src/client-agent/receiver.c
- ソケット受信処理
- while ((recv_b = recv(agt->sock, buffer, OS_SIZE_1024, MSG_DONTWAIT)) > 0) MSG_DONTWAITなので、メッセージが無ければ抜けるのかも。
- ReadSecMSG()
- execd向けのキューに登録 OS_SendUnix(agt->execdq, tmp_msg, 0) < 0)
おわりに
基本的にデーモン処理なので、while(1)の無限ループ処理のなかで、いろいろ処理を行っていました。デーモン間のやり取りはOSのメッセージキューを使用していて、エージェントとサーバ間はNWソケットでのメッセージ送受信をしていました。
今回の記事は以上です。では、また。