溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Linux HIDS agent 概要和用戶態(tài) HOOK(一)

發(fā)布時間:2020-07-23 09:44:23 來源:網(wǎng)絡 閱讀:219 作者:極客君 欄目:系統(tǒng)運維

作者:u2400@知道創(chuàng)宇404實驗室
時間:2019年12月19日

原文:https://paper.seebug.org/1102/

前言:最近在實現(xiàn)linux的HIDS agent, 搜索資料時發(fā)現(xiàn)雖然資料不少, 但是每一篇文章都各自有側重點, 少有循序漸進, 講的比較全面的中文文章, 在一步步學習中踩了不少坑, 在這里將以進程信息收集作為切入點就如何實現(xiàn)一個HIDS的agent做詳細說明, 希望對各位師傅有所幫助.

1. 什么是HIDS?

主機***檢測, 通常分為agent和server兩個部分

其中agent負責收集信息, 并將相關信息整理后發(fā)送給server.

server通常作為信息中心, 部署由安全人員編寫的規(guī)則(目前HIDS的規(guī)則還沒有一個編寫的規(guī)范),收集從各種安全組件獲取的數(shù)據(jù)(這些數(shù)據(jù)也可能來自waf, NIDS等), 進行分析, 根據(jù)規(guī)則判斷主機行為是否異常, 并對主機的異常行為進行告警和提示.

HIDS存在的目的在于在管理員管理海量IDC時不會被安全事件弄的手忙腳亂, 可以通過信息中心對每一臺主機的健康狀態(tài)進行監(jiān)視.

相關的開源項目有OSSEC, OSquery等, OSSEC是一個已經(jīng)構建完善的HIDS, 有agent端和server端, 有自帶的規(guī)則, 基礎的rootkit檢測, 敏感文件修改提醒等功能, 并且被包含到了一個叫做wazuh的開源項目, OSquery是一個facebook研發(fā)的開源項目, 可以作為一個agent端對主機相關數(shù)據(jù)進行收集, 但是server和規(guī)則需要自己實現(xiàn).

每一個公司的HIDS agent都會根據(jù)自身需要定制, 或多或少的增加一些個性化的功能, 一個基礎的HIDS agent一般需要實現(xiàn)的有:

  • 收集進程信息

  • 收集網(wǎng)絡信息

  • 周期性的收集開放端口

  • 監(jiān)控敏感文件修改

下文將從實現(xiàn)一個agent入手, 圍繞agent討論如何實現(xiàn)一個HIDS agent的進程信息收集模塊

2. agent進程監(jiān)控模塊提要

2.1進程監(jiān)控的目的

在Linxu操作系統(tǒng)中幾乎所有的運維操作和***行為都會體現(xiàn)到執(zhí)行的命令中, 而命令執(zhí)行的本質(zhì)就是啟動進程, 所以對進程的監(jiān)控就是對命令執(zhí)行的監(jiān)控, 這對運維操作升級和***行為分析都有極大的幫助

2.2 進程監(jiān)控模塊應當獲取的數(shù)據(jù)

既然要獲取信息那就先要明確需要什么, 如果不知道需要什么信息, 那實現(xiàn)便無從談起, 即便硬著頭皮先實現(xiàn)一個能獲取pid等基礎信息的HIDS, 后期也會因為缺少規(guī)劃而頻繁改動接口, 白白耗費人力, 這里參考《互聯(lián)網(wǎng)企業(yè)安全高級指南》給出一個獲取信息的基礎列表, 在后面會補全這張表的的獲取方式

數(shù)據(jù)名稱含義
path可執(zhí)行文件的路徑
ppath父進程可執(zhí)行文件路徑
ENV環(huán)境變量
cmdline進程啟動命令
pcmdline父進程啟動命令
pid進程id
ppid父進程id
pgid進程組id
sid進程會話id
uid啟動進程用戶的uid
euid啟動進程用戶的euid
gid啟動進程用戶的用戶組id
egid啟動進程用戶的egid
mode可執(zhí)行文件的權限
owner_uid文件所有者的uid
owner_gid文件所有者的gid
create_time文件創(chuàng)建時間
modify_time最近的文件修改時間
pstart_time進程開始運行的時間
prun_time父進程已經(jīng)運行的時間
sys_time當前系統(tǒng)時間
fd文件描述符

2.3 進程監(jiān)控的方式

進程監(jiān)控, 通常使用hook技術, 而這些hook大概分為兩類:

應用級(工作在r3, 常見的就是劫持libc庫, 通常簡單但是可能被繞過 - 內(nèi)核級(工作在r0或者r1, 內(nèi)核級hook通常和系統(tǒng)調(diào)用VFS有關, 較為復雜, 且在不同的發(fā)行版, 不同的內(nèi)核版本間均可能產(chǎn)生兼容性問題, hook出現(xiàn)嚴重的錯誤時可能導致kenrel panic, 相對的無法從原理上被繞過

首先從簡單的應用級hook說起

3. HIDS 應用級hook

3.1 劫持libc庫

庫用于打包函數(shù), 被打包過后的函數(shù)可以直接使用, 其中l(wèi)inux分為靜態(tài)庫和動態(tài)庫, 其中動態(tài)庫是在加載應用程序時才被加載, 而程序?qū)τ趧討B(tài)庫有加載順序, 可以通過修改?/etc/ld.so.preload?來手動優(yōu)先加載一個動態(tài)鏈接庫, 在這個動態(tài)鏈接庫中可以在程序調(diào)用原函數(shù)之前就把原來的函數(shù)先換掉, 然后在自己的函數(shù)中執(zhí)行了自己的邏輯之后再去調(diào)用原來的函數(shù)返回原來的函數(shù)應當返回的結果.

想要詳細了解的同學, 參考這篇文章

劫持libc庫有以下幾個步驟:

3.1.1 編譯一個動態(tài)鏈接庫

一個簡單的hook execve的動態(tài)鏈接庫如下.
邏輯非常簡單

  1. 自定義一個函數(shù)命名為execve, 接受參數(shù)的類型要和原來的execve相同

  2. 執(zhí)行自己的邏輯

#define?_GNU_SOURCE
#include?<unistd.h>
#include?<dlfcn.h>
typedef?ssize_t?(*execve_func_t)(const?char*?filename,?char*?const?argv[],?char*?const?envp[]);
static?execve_func_t?old_execve?=?NULL;
int?execve(const?char*?filename,?char*?const?argv[],?char*?const?envp[])?{
????????//從這里開始是自己的邏輯,?即進程調(diào)用execve函數(shù)時你要做什么
????printf("Running?hook\n");
????//下面是尋找和調(diào)用原本的execve函數(shù),?并返回調(diào)用結果
????old_execve?=?dlsym(RTLD_NEXT,?"execve");
????return?old_execve(filename,?argv,?envp);
}

通過gcc編譯為so文件.

gcc?-shared?-fPIC?-o?libmodule.so?module.c

3.1.2 修改ld.so.preload

ld.so.preload是LD_PRELOAD環(huán)境變量的配置文件, 通過修改該文件的內(nèi)容為指定的動態(tài)鏈接庫文件路徑,

注意只有root才可以修改ld.so.preload, 除非默認的權限被改動了

自定義一個execve函數(shù)如下:

extern?char?**environ;
int?execve(const?char*?filename,?char*?const?argv[],?char*?const?envp[])?{
????for?(int?i?=?0;?*(environ?+?i)?;?i++)
????{
????????printf("%s\n",?*(environ?+?i));
????}
????printf("PID:%d\n",?getpid());
????old_execve?=?dlsym(RTLD_NEXT,?"execve");
????return?old_execve(filename,?argv,?envp);
}

可以輸出當前進程的Pid和所有的環(huán)境變量, 編譯后修改ld.so.preload, 重啟shell, 運行l(wèi)s命令結果如下
Linux HIDS agent 概要和用戶態(tài) HOOK(一)

3.1.3 libc hook的優(yōu)缺點

優(yōu)點: 性能較好, 比較穩(wěn)定, 相對于LKM更加簡單, 適配性也很高, 通常對抗web層面的***.

缺點: 對于靜態(tài)編譯的程序束手無策, 存在一定被繞過的風險.

3.1.4 hook與信息獲取

設立hook, 是為了建立監(jiān)控點, 獲取進程的相關信息, 但是如果hook的部分寫的過大過多, 會導致影響正常的業(yè)務的運行效率, 這是業(yè)務所不能接受的, 在通常的HIDS中會將可以不在hook處獲取的信息放在agent中獲取, 這樣信息獲取和業(yè)務邏輯并發(fā)執(zhí)行, 降低對業(yè)務的影響.

4 信息補全與獲取

如果對信息的準確性要求不是很高, 同時希望盡一切可能的不影響部署在HIDS主機上的正常業(yè)務那么可以選擇hook只獲取PID和環(huán)境變量等必要的數(shù)據(jù), 然后將這些東西交給agent, 由agent繼續(xù)獲取進程的其他相關信息, 也就是說獲取進程其他信息的同時, 進程就已經(jīng)繼續(xù)運行了, 而不需要等待agent獲取完整的信息表.

/proc/[pid]/stat

/proc是內(nèi)核向用戶態(tài)提供的一組fifo接口, 通過偽文件目錄的形式調(diào)用接口

每一個進程相關的信息, 會被放到以pid命名的文件夾當中, ps等命令也是通過遍歷/proc目錄來獲取進程的相關信息的.

一個stat文件內(nèi)容如下所示, 下面self是/proc目錄提供的一個快捷的查看自己進程信息的接口, 每一個進程訪問/self時看到都是自己的信息.

#cat?/proc/self/stat
3119?(cat)?R?29973?3119?19885?34821?3119?4194304?107?0?0?0?0?0?0?0?20?0?1?0?5794695?5562368?176?18446744073709551615?94309027168256?94309027193225?140731267701520?0?0?0?0?0?0?0?0?0?17?0?0?0?0?0?0?94309027212368?94309027213920?94309053399040?140731267704821?140731267704841?140731267704841?140731267706859?0

會發(fā)現(xiàn)這些數(shù)據(jù)雜亂無章, 使用空格作為每一個數(shù)據(jù)的邊界, 沒有地方說明這些數(shù)據(jù)各自表達什么意思.

一般折騰找到了一篇文章里面給出了一個列表, 這個表里面說明了每一個數(shù)據(jù)的數(shù)據(jù)類型和其表達的含義, 見文章附錄1

最后整理出一個有52個數(shù)據(jù)項每個數(shù)據(jù)項類型各不相同的結構體, 獲取起來還是有點麻煩, 網(wǎng)上沒有找到輪子, 所以自己寫了一個

具體的結構體定義:

struct?proc_stat?{
????int?pid;?//process?ID.????char*?comm;?//可執(zhí)行文件名稱,?會用()包圍????char?state;?//進程狀態(tài)????int?ppid;???//父進程pid????int?pgid;
????int?session;????//sid????int?tty_nr;?????
????int?tpgid;
????unsigned?int?flags;
????long?unsigned?int?minflt;
????long?unsigned?int?cminflt;
????long?unsigned?int?majflt;
????long?unsigned?int?cmajflt;
????long?unsigned?int?utime;
????long?unsigned?int?stime;
????long?int?cutime;
????long?int?cstime;
????long?int?priority;
????long?int?nice;
????long?int?num_threads;
????long?int?itrealvalue;
????long?long?unsigned?int?starttime;
????long?unsigned?int?vsize;
????long?int?rss;
????long?unsigned?int?rsslim;
????long?unsigned?int?startcode;
????long?unsigned?int?endcode;
????long?unsigned?int?startstack;
????long?unsigned?int?kstkesp;
????long?unsigned?int?kstkeip;
????long?unsigned?int?signal;???//The?bitmap?of?pending?signals????long?unsigned?int?blocked;
????long?unsigned?int?sigignore;
????long?unsigned?int?sigcatch;
????long?unsigned?int?wchan;
????long?unsigned?int?nswap;
????long?unsigned?int?cnswap;
????int?exit_signal;
????int?processor;
????unsigned?int?rt_priority;
????unsigned?int?policy;
????long?long?unsigned?int?delayacct_blkio_ticks;
????long?unsigned?int?guest_time;
????long?int?cguest_time;
????long?unsigned?int?start_data;???
????long?unsigned?int?end_data;
????long?unsigned?int?start_brk;????
????long?unsigned?int?arg_start;????//參數(shù)起始地址????long?unsigned?int?arg_end;??????//參數(shù)結束地址????long?unsigned?int?env_start;????//環(huán)境變量在內(nèi)存中的起始地址????long?unsigned?int?env_end;??????//環(huán)境變量的結束地址????int?exit_code;?//退出狀態(tài)碼};

從文件中讀入并格式化為結構體:

struct?proc_stat?get_proc_stat(int?Pid)?{
????FILE?*f?=?NULL;
????struct?proc_stat?stat?=?{0};
????char?tmp[100]?=?"0";
????stat.comm?=?tmp;
????char?stat_path[20];
????char*?pstat_path?=?stat_path;

????if?(Pid?!=?-1)?{
????????sprintf(stat_path,?"/proc/%d/stat",?Pid);
????}?else?{
????????pstat_path?=?"/proc/self/stat";
????}

????if?((f?=?fopen(pstat_path,?"r"))?==?NULL)?{
????????printf("open?file?error");
????????return?stat;
????}

????fscanf(f,?"%d?",?&stat.pid);
????fscanf(f,?"(%100s?",?stat.comm);
????tmp[strlen(tmp)-1]?=?'\0';
????fscanf(f,?"%c?",?&stat.state);
????fscanf(f,?"%d?",?&stat.ppid);
????fscanf(f,?"%d?",?&stat.pgid);

????fscanf?(
????????????f,
????????????"%d?%d?%d?%u?%lu?%lu?%lu?%lu?%lu?%lu?%ld?%ld?%ld?%ld?%ld?%ld?%llu?%lu?%ld?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%d?%d?%u?%u?%llu?%lu?%ld?%lu?%lu?%lu?%lu?%lu?%lu?%lu?%d",
????????????&stat.session,?&stat.tty_nr,?&stat.tpgid,?&stat.flags,?&stat.minflt,
????????????&stat.cminflt,?&stat.majflt,?&stat.cmajflt,?&stat.utime,?&stat.stime,
????????????&stat.cutime,?&stat.cstime,?&stat.priority,?&stat.nice,?&stat.num_threads,
????????????&stat.itrealvalue,?&stat.starttime,?&stat.vsize,?&stat.rss,?&stat.rsslim,
????????????&stat.startcode,?&stat.endcode,?&stat.startstack,?&stat.kstkesp,?&stat.kstkeip,
????????????&stat.signal,?&stat.blocked,?&stat.sigignore,?&stat.sigcatch,?&stat.wchan,
????????????&stat.nswap,?&stat.cnswap,?&stat.exit_signal,?&stat.processor,?&stat.rt_priority,
????????????&stat.policy,?&stat.delayacct_blkio_ticks,?&stat.guest_time,?&stat.cguest_time,?&stat.start_data,
????????????&stat.end_data,?&stat.start_brk,?&stat.arg_start,?&stat.arg_end,?&stat.env_start,
????????????&stat.env_end,?&stat.exit_code????);
????fclose(f);
????return?stat;}

和我們需要獲取的數(shù)據(jù)做了一下對比, 可以獲取以下數(shù)據(jù)

ppid父進程id
pgid進程組id
sid進程會話id
start_time父進程開始運行的時間
run_time父進程已經(jīng)運行的時間

/proc/[pid]/exe

通過/proc/[pid]/exe獲取可執(zhí)行文件的路徑, 這里/proc/[pid]/exe是指向可執(zhí)行文件的軟鏈接, 所以這里通過readlink函數(shù)獲取軟鏈接指向的地址.

這里在讀取時需要注意如果readlink讀取的文件已經(jīng)被刪除, 讀取的文件名后會多一個?(deleted), 但是agent也不能盲目刪除文件結尾時的對應字符串, 所以在寫server規(guī)則時需要注意這種情況

char?*get_proc_path(int?Pid)?{
????char?stat_path[20];
????char*?pstat_path?=?stat_path;
????char?dir[PATH_MAX]?=?{0};
????char*?pdir?=?dir;
????if?(Pid?!=?-1)?{
????????sprintf(stat_path,?"/proc/%d/exe",?Pid);
????}?else?{
????????pstat_path?=?"/proc/self/exe";
????}

????readlink(pstat_path,?dir,?PATH_MAX);
????return?pdir;}

/proc/[pid]/cmdline

獲取進程啟動的是啟動命令, 可以通過獲取/proc/[pid]/cmdline的內(nèi)容來獲得, 這個獲取里面有兩個坑點

  1. 由于啟動命令長度不定, 為了避免溢出, 需要先獲取長度, 在用malloc申請堆空間, 然后再將數(shù)據(jù)讀取進變量.

  2. /proc/self/cmdline文件里面所有的空格和回車都會變成?'\0'也不知道為啥, 所以需要手動換源回來, 而且若干個相連的空格也只會變成一個'\0'.

這里獲取長度的辦法比較蠢, 但是用fseek直接將文件指針移到文件末尾的辦法每次返回的都是0, 也不知道咋辦了, 只能先這樣

long?get_file_length(FILE*?f)?{
????fseek(f,0L,SEEK_SET);
????char?ch;
????ch?=?(char)getc(f);
????long?i;
????for?(i?=?0;ch?!=?EOF;?i++?)?{
????????ch?=?(char)getc(f);
????}
????i++;
????fseek(f,0L,SEEK_SET);
????return?i;}

獲取cmdline的內(nèi)容

char*?get_proc_cmdline(int?Pid)?{
????FILE*?f;
????char?stat_path[100]?=?{0};
????char*?pstat_path?=?stat_path;

????if?(Pid?!=?-1)?{
????????sprintf(stat_path,?"/proc/%d/cmdline",?Pid);
????}?else?{
????????pstat_path?=?"/proc/self/cmdline";
????}

????if?((f?=?fopen(pstat_path,?"r"))?==?NULL)?{
????????printf("open?file?error");
????????return?"";
????}
????char*?pcmdline?=?(char?*)malloc((size_t)get_file_length(f));
????char?ch;
????ch?=?(char)getc(f);
????for?(int?i?=?0;ch?!=?EOF;?i++?)?{
????????*(pcmdline?+?i)?=?ch;
????????ch?=?(char)getc(f);
????????if?((int)ch?==?0)?{
????????????ch?=?'?';
????????}
????}
????return?pcmdline;}

小結

這里寫的只是實現(xiàn)的一種最常見最簡單的應用級hook的方法具體實現(xiàn)和代碼已經(jīng)放在了github上, 同時github上的代碼會保持更新, 下次的文章會分享如何使用LKM修改sys_call_table來hook系統(tǒng)調(diào)用的方式來實現(xiàn)HIDS的hook.

參考文章

https://www.freebuf.com/articles/system/54263.html
http://abcdefghijklmnopqrst.xyz/2018/07/30/Linux_INT80/
https://cloud.tencent.com/developer/news/337625
https://github.com/g0dA/linuxStack/blob/master/%E8%BF%9B%E7%A8%8B%E9%9A%90%E8%97%8F%E6%8A%80%E6%9C%AF%E7%9A%84%E6%94%BB%E4%B8%8E%E9%98%B2-%E6%94%BB%E7%AF%87.md

附錄1

這里完整的說明了/proc目錄下每一個文件具體的意義是什么.
http://man7.org/linux/man-pages/man5/proc.5.html


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI