- Preload簡(jiǎn)介
- Preload Hook原理
- 應(yīng)用Preload Hook存在的問(wèn)題和解決方案
- Preload Hook應(yīng)用之——網(wǎng)絡(luò)訪問(wèn)日志
- 總結(jié)
- 參考文檔
Preload簡(jiǎn)介
Linux常見(jiàn)Hook技術(shù)對(duì)比
技術(shù)類型 | 生效范圍 | 生效時(shí)機(jī) | 依賴注入 | 層級(jí) | 安全性 | 穩(wěn)定性 | 開(kāi)發(fā)運(yùn)維難度 |
---|---|---|---|---|---|---|---|
內(nèi)核模塊 | 所有進(jìn)程 | 加載內(nèi)核模塊后 | 否 | 低 | 最高 | 中 | 極高 |
Inline Hook | 當(dāng)前進(jìn)程 | hook后 | 是 | 中 | 中 | 中 | 高 |
Got Hook | 當(dāng)前進(jìn)程模塊 | hook后 | 是 | 中 | 低 | 良 | 中 |
Preload Hook | 所有進(jìn)程 | hook后 | 否 | 中 | 低 | 優(yōu) | 低 |
系統(tǒng)文件修改 | 所有進(jìn)程 | 文件修改后 | 否 | 中 | 高 | 中 | 高 |
<div style="padding-left:300px;">表1 Linux常見(jiàn)Hook技術(shù)對(duì)比</div>
表1注:
① 依賴注入板辽,代表在使用該技術(shù)hook第三方進(jìn)程前戏羽,是否需要先進(jìn)行注入
② 層級(jí)蜂怎,代表hook點(diǎn)在整個(gè)系統(tǒng)調(diào)用的位置,越底層則層級(jí)越低
③ 穩(wěn)定性越低喂饥,代表要通過(guò)這種技術(shù)做到穩(wěn)定所需要的技術(shù)和時(shí)間成本較高
④ 安全性消约,代表該項(xiàng)技術(shù)被繞過(guò)的難易程度
函數(shù)調(diào)用類型
??按函數(shù)調(diào)用在整個(gè)系統(tǒng)所處層級(jí)劃分,可分為系統(tǒng)調(diào)用和庫(kù)函數(shù)調(diào)用:
- 系統(tǒng)調(diào)用
??系統(tǒng)調(diào)用是指员帮,真正執(zhí)行邏輯實(shí)現(xiàn)于系統(tǒng)內(nèi)核的庫(kù)函數(shù)或粮,通常是操作系統(tǒng)資源相關(guān)的例如文件、內(nèi)存捞高、進(jìn)程氯材、線程、網(wǎng)絡(luò)等函數(shù)硝岗,如open函數(shù)氢哮。對(duì)這類函數(shù)的Hook可以做到較高安全性。/usr/include/bits/syscall.h
頭文件定義了當(dāng)前系統(tǒng)支持的所有系統(tǒng)調(diào)用 - 庫(kù)函數(shù)
??真正邏輯實(shí)現(xiàn)于用戶態(tài)動(dòng)態(tài)庫(kù)的函數(shù)型檀,除系統(tǒng)調(diào)用以外的庫(kù)函數(shù)都算該類命浴,如memcpy函數(shù)
??按函數(shù)調(diào)用時(shí)機(jī)劃分,可分為靜態(tài)調(diào)用和動(dòng)態(tài)調(diào)用
- 靜態(tài)調(diào)用
??靜態(tài)調(diào)用是指贱除,直接使用函數(shù)調(diào)用運(yùn)算符("()")調(diào)用自身或外部庫(kù)導(dǎo)出函數(shù) - 動(dòng)態(tài)調(diào)用
??動(dòng)態(tài)調(diào)用是指生闲,使用dlopen/dlsym等方式直接獲取到函數(shù)指針,進(jìn)行調(diào)用的方式
內(nèi)核模塊Hook
??通常是從內(nèi)核源碼特殊位置月幌,修改回調(diào)碍讯、修改中斷表;或修改重編譯內(nèi)核扯躺,導(dǎo)出內(nèi)部函數(shù)捉兴,從而跳轉(zhuǎn)到自定義函數(shù)蝎困,開(kāi)發(fā)內(nèi)核模塊實(shí)現(xiàn)hook。
- 可以攔截到所有應(yīng)用層系統(tǒng)調(diào)用倍啥,應(yīng)用層無(wú)法繞過(guò)
- 開(kāi)發(fā)調(diào)試復(fù)雜禾乘,測(cè)試周期長(zhǎng),升級(jí)和卸載內(nèi)核模塊帶來(lái)穩(wěn)定性問(wèn)題
應(yīng)用層Inline Hook
??應(yīng)用層內(nèi)聯(lián)hook虽缕,即直接修改二進(jìn)制函數(shù)體的匯編指令始藕,修改執(zhí)行邏輯使其跳轉(zhuǎn)到自定義函數(shù),開(kāi)發(fā)應(yīng)用層模塊實(shí)現(xiàn)hook氮趋。
- 可以攔截到系統(tǒng)調(diào)用和普通庫(kù)函數(shù)
- 由于linux系統(tǒng)本身具有多個(gè)發(fā)行版本及指令集伍派,不容易做到通用
- 可以通過(guò)自定義實(shí)現(xiàn)底層函數(shù)或恢復(fù)模塊內(nèi)存方式繞過(guò)
Got表
??當(dāng)在程序中引用某個(gè)共享庫(kù)中的符號(hào)時(shí),編譯鏈接階段并不知道這個(gè)符號(hào)的具體位置剩胁,只有等到動(dòng)態(tài)鏈接器將所需要的共享庫(kù)加載時(shí)進(jìn)內(nèi)存后诉植,符號(hào)的地址才會(huì)最終確定。因此昵观,需要有一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)保存符號(hào)的絕對(duì)地址晾腔。編譯器在編譯C/C++程序時(shí),會(huì)根據(jù)代碼中對(duì)外部庫(kù)函數(shù)的靜態(tài)調(diào)用情況構(gòu)造成一個(gè)符號(hào)索引表啊犬,使代碼中外部庫(kù)函數(shù)的調(diào)用暫時(shí)指向每個(gè)索引灼擂,在程序運(yùn)行時(shí)進(jìn)程加載器(進(jìn)程加載器的概念)會(huì)將每個(gè)索引表項(xiàng)指向真正的函數(shù)地址,從而完成外部庫(kù)函數(shù)調(diào)用椒惨。這種方式實(shí)現(xiàn)了在編譯期時(shí)處理(動(dòng)態(tài)加載模塊的)函數(shù)調(diào)用問(wèn)題缤至。
??在Windows系統(tǒng)中,可執(zhí)行程序?yàn)镻E結(jié)構(gòu)康谆,該表為import table
(俗稱導(dǎo)入表)领斥,對(duì)應(yīng).idata
段;Linux/Android系統(tǒng)中沃暗,可執(zhí)行程序?yàn)镋LF結(jié)構(gòu)月洛,該表即為got(Global Offset Table)表,對(duì)應(yīng).got
段孽锥;MacOS/iOS系統(tǒng)中嚼黔,可執(zhí)行程序?yàn)镸achO格式,該表對(duì)應(yīng)為__nl_symbol_ptr
和__la_symbol_ptr
段惜辑。在本文中統(tǒng)稱導(dǎo)入表唬涧。
應(yīng)用層Got Hook
??應(yīng)用層got表hook,即在運(yùn)行階段修改程序本身got表盛撑,這樣調(diào)用api的邏輯碎节,就會(huì)相應(yīng)的跳轉(zhuǎn)到用戶自定義函數(shù)中。
- 可以攔截系統(tǒng)調(diào)用和普通庫(kù)函數(shù)
- 由于只需要考慮ELF格式因此實(shí)現(xiàn)難度較為簡(jiǎn)單
- 可以通過(guò)自定義實(shí)現(xiàn)底層函數(shù)或恢復(fù)got表內(nèi)存方式繞過(guò)
應(yīng)用層Preload Hook
??對(duì)于Preload的解釋抵卫,詳見(jiàn)[Preload Hook原理](#Preload Hook原理)狮荔。Preload Hook是指利用系統(tǒng)支持的preload能力胎撇,將模塊自動(dòng)注入進(jìn)程實(shí)現(xiàn)hook≈呈希可以通過(guò)以下手段使用Preload技術(shù):一種是環(huán)境變量配置(LD_PRELOAD)晚树;另一種是文件配置:(/etc/ld.so.preload)。
- 若使用命令行指定LD_PRELOAD則只影響該新進(jìn)程及子進(jìn)程雅采;若寫(xiě)入全局環(huán)境變量則LD_PRELOAD對(duì)所有新進(jìn)程生效爵憎;父進(jìn)程可以控制子進(jìn)程的環(huán)境變量從而取消preload
- 文件preload方式影響所有新進(jìn)程且無(wú)法被取消
- 可以攔截到系統(tǒng)調(diào)用和普通庫(kù)函數(shù)
- 實(shí)現(xiàn)和操作最為簡(jiǎn)單,只需要編寫(xiě)同名系統(tǒng)調(diào)用函數(shù)即可實(shí)現(xiàn)hook
- 可以使用動(dòng)態(tài)調(diào)用方式或自定義實(shí)現(xiàn)方式繞過(guò)
Preload Hook原理
進(jìn)程模塊表的鏈狀結(jié)構(gòu)
??下面是從源碼中摘錄的部分定義总滩,可以看到常見(jiàn)系統(tǒng)均(至少)使用單鏈表或雙鏈表方式存儲(chǔ)模塊表纲堵,甚至MacOS和Windows操作系統(tǒng)也是采用類似方式巡雨。使用鏈表方式存儲(chǔ)闰渔,便于增刪查改(一般程序加載模塊數(shù)在百級(jí)別以內(nèi))。
// from glibc/elf/link.h for Linux
struct link_map
{
/* These first few members are part of the protocol with the debugger.
This is the same format used in SVR4. */
ElfW(Addr) l_addr; /* Difference between the address in the ELF
file and the addresses in memory. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next, *l_prev; /* 這里為鏈表結(jié)構(gòu). */
};
struct link_map _dl_loaded; // 全局模塊表頭
// from bionic/linker/linker_soinfo.h for Android 8
struct soinfo {
public:
const ElfW(Phdr)* phdr;
size_t phnum;
ElfW(Addr) base;
size_t size;
ElfW(Dyn)* dynamic;
soinfo* next; /* 這里為鏈表結(jié)構(gòu) */
....;
};
struct soinfo solist; // 全局模塊表頭
// from bionic/linker/linker.h for Android 4-7
struct soinfo
{
const char name[SOINFO_NAME_LEN];
Elf32_Phdr *phdr;
int phnum;
unsigned entry;
unsigned base;
unsigned size;
int unused; // DO NOT USE, maintained for compatibility.
unsigned *dynamic;
unsigned wrprotect_start;
unsigned wrprotect_end;
soinfo *next;
...
};
struct soinfo solist; // 全局模塊表頭
// from dyld/src/ImageLoader.h for Macos/iOS
class ImageLoader {
public:
typedef uint32_t DefinitionFlags;
static const DefinitionFlags kNoDefinitionOptions = 0;
static const DefinitionFlags kWeakDefinition = 1;
......
struct DynamicReference { /* 這里為鏈表結(jié)構(gòu) */
ImageLoader* from;
ImageLoader* to;
};
......
};
static std::vector<ImageLoader*> sAllImages; // 全局模塊表數(shù)組
// from wrk/public/sdk/inc/ntpsapi.h for Windows
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList; /* 這里為鏈表結(jié)構(gòu) */
LIST_ENTRY InMemoryOrderModuleList; /* 這里為鏈表結(jié)構(gòu) */
LIST_ENTRY InInitializationOrderModuleList; /* 這里為鏈表結(jié)構(gòu) */
PVOID EntryInProgress;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
// NtCurrentPeb()->Ldr 全局模塊表頭
Linux系統(tǒng)Preload Hook
進(jìn)程加載器的概念
??先來(lái)介紹幾個(gè)名詞
- 進(jìn)程加載器
??屬于操作系統(tǒng)的一部分铐望,用于加載程序和動(dòng)態(tài)庫(kù)冈涧。加載器是執(zhí)行程序和代碼必不可少的組件,是一種用戶態(tài)的模塊正蛙,用戶在shell中啟動(dòng)進(jìn)程后督弓,首先加載的是進(jìn)程加載器模塊,它負(fù)責(zé)將進(jìn)程文件從文件形態(tài)映射到內(nèi)存中乒验、加載相應(yīng)的依賴模塊愚隧、初始化可執(zhí)行模塊、初始化進(jìn)程所需的環(huán)境锻全,最后調(diào)用程序入口點(diǎn)(main)執(zhí)行真正的工作狂塘。加載器在模塊表中的位置總是最前。下表是常見(jiàn)系統(tǒng)的進(jìn)程加載器:
Bit | Linux | Android | MacOS/iOS | Windows |
---|---|---|---|---|
64 | /lib64/ld-linux-x86-64.so.2 | /system/bin/linker64 | /usr/lib/dyld | %SYSTEMROOT%\system32\ntdll.dll |
32 | /lib/ld-linux.so.2 | /system/bin/linker | /usr/lib/dyld | %SYSTEMROOT%\system32\ntdll.dll |
<div style="padding-left:300px;">表2 多個(gè)平臺(tái)的進(jìn)程加載器</div>
- Preload技術(shù)
??Preload技術(shù)是Linux系統(tǒng)自身支持的模塊預(yù)加載技術(shù)鳄厌,這意味著進(jìn)程加載器在加載進(jìn)程時(shí)荞胡,會(huì)在模塊表中首先插入指定的預(yù)加載模塊,然后再插入進(jìn)程所依賴的其他模塊了嚎,預(yù)加載模塊在模塊表的位置總是位于加載器之后泪漂。Linux系統(tǒng)中,ELF格式的導(dǎo)入表只存儲(chǔ)了符號(hào)(包括導(dǎo)出的全局對(duì)象歪泳、全局變量以及全局函數(shù))名萝勤,因此在進(jìn)程加載器初始化外部符號(hào)時(shí),從模塊鏈表頭開(kāi)始按模塊搜索直到遇到該符號(hào)名呐伞。這點(diǎn)不同于Windows的PE格式敌卓,后者的導(dǎo)入表同時(shí)存儲(chǔ)了符號(hào)名以及對(duì)應(yīng)的模塊名,這樣進(jìn)程加載器在初始化外部符號(hào)時(shí)已經(jīng)確知需要加載哪個(gè)模塊荸哟,若不存在則直接報(bào)錯(cuò)異常退出假哎。ELF進(jìn)程加載器這樣的搜索順序便會(huì)影響調(diào)用庫(kù)函數(shù)時(shí)產(chǎn)生的符號(hào)查找的結(jié)果瞬捕,具體會(huì)影響下面幾點(diǎn): - 靜態(tài)調(diào)用的函數(shù)
??這類函數(shù)出現(xiàn)于編譯得到二進(jìn)制文件的導(dǎo)入表中,在被進(jìn)程加載器映射到內(nèi)存并初始化相關(guān)結(jié)構(gòu)之后舵抹,導(dǎo)入表會(huì)被真實(shí)函數(shù)地址填充肪虎,而進(jìn)程加載器正是通過(guò)模塊鏈為導(dǎo)入表分配地址。假設(shè)兩個(gè)包含相同符號(hào)/函數(shù)名的模塊分別先后加載惧蛹,那么先加載的處于鏈表靠前位置扇救,進(jìn)而會(huì)被進(jìn)程加載器選中而當(dāng)做真實(shí)函數(shù)地址。
- 靜態(tài)調(diào)用的函數(shù)
- 動(dòng)態(tài)調(diào)用的函數(shù)香嗓,且不指定模塊
??如果dlsym函數(shù)第一個(gè)參數(shù)被指定為RTLD_DEFAULT時(shí)迅腔,dlsym函數(shù)會(huì)匹配模塊鏈中第一個(gè)包含該符號(hào)的模塊,進(jìn)而會(huì)被進(jìn)程加載器選中而當(dāng)做真實(shí)符號(hào)地址靠娱;若第一個(gè)參數(shù)指定的是RTLD_NEXT沧烈,dlsym函數(shù)會(huì)按模塊鏈搜索下一個(gè)包含該符號(hào)的模塊,并將該地址選為符號(hào)地址像云。(注:導(dǎo)出符號(hào)為導(dǎo)出函數(shù)或?qū)С鲎兞炕虺A?
- 動(dòng)態(tài)調(diào)用的函數(shù)香嗓,且不指定模塊
??如前述锌雀,Linux/Android系統(tǒng)支持環(huán)境變量和文件方式的Preload;MacOS/iOS系統(tǒng)則提供了環(huán)境變量DYLD_INSERT_LIBRARIES
用于Preload迅诬,同時(shí)提供__interpose
結(jié)構(gòu)用于Hook腋逆;Windows系統(tǒng)未提供環(huán)境變量方式的Preload,而提供了注冊(cè)表方式注入侈贷。
Preload特點(diǎn)
??Preload技術(shù)有自身的作用范圍惩歉,如果將Preload環(huán)境變量到全局環(huán)境(/etc/profile)或使用Preload文件時(shí),Preload便在所有在此(配置)時(shí)間節(jié)點(diǎn)之后運(yùn)行的進(jìn)程生效俏蛮,而無(wú)法影響已經(jīng)在運(yùn)行的進(jìn)程撑蚌;若將Preload環(huán)境變量作為啟動(dòng)進(jìn)程的環(huán)境變量(如shell中執(zhí)行LD_PREOAD=/lib64/inject.so ./myprocess
),則Preload只對(duì)當(dāng)前進(jìn)程生效嫁蛇。默認(rèn)情況下進(jìn)程創(chuàng)建的子進(jìn)程也會(huì)繼承Preload環(huán)境變量锨并,而父進(jìn)程可以在子進(jìn)程初始化前修改子進(jìn)程的環(huán)境變量,從而避免往子代傳播睬棚。Preload具體使用方式如下:
- LD_PRELOAD環(huán)境變量 指定動(dòng)態(tài)庫(kù)全路徑第煮,觸發(fā)時(shí)機(jī):?jiǎn)?dòng)時(shí)
- /etc/ld.so.preload 指定動(dòng)態(tài)庫(kù)全路徑,觸發(fā)時(shí)機(jī):?jiǎn)?dòng)時(shí)
??進(jìn)程在啟動(dòng)后抑党,會(huì)按照一定順序加載動(dòng)態(tài)庫(kù):
- 加載環(huán)境變量LD_PRELOAD指定的動(dòng)態(tài)庫(kù)
- 加載文件/etc/ld.so.preload指定的動(dòng)態(tài)庫(kù)
- 搜索環(huán)境變量LD_LIBRARY_PATH指定的動(dòng)態(tài)庫(kù)搜索路徑
- 搜索路徑/lib64下的動(dòng)態(tài)庫(kù)文件
??如前述包警,模塊通過(guò)Preload技術(shù)添加到模塊鏈后,進(jìn)程加載器在解析導(dǎo)入表時(shí)底靠,會(huì)按照鏈表順序依次查找符號(hào)害晦,因此如果在Preload動(dòng)態(tài)庫(kù)中實(shí)現(xiàn)并導(dǎo)出了同名(而不是C++修飾名)庫(kù)函數(shù),即可實(shí)現(xiàn)Hook。下面是一個(gè)簡(jiǎn)單例子壹瘟,捕獲所有socket函數(shù):
// inject.c
#include <stdio.h>
#include <sys/socket.h>
__attribute__ ((visibility("default"))) int socket(int family, int type, int protocol) {
printf("detect socket call\n");
return -1;
}
__attribute__((constructor)) void main() {
printf("module inject success\n");
}
/*
gcc inject.c --shared -fPIC -o inject.so
LD_PRELOAD=$PWD/inject.so ping 127.0.0.1
module inject success
detect socket call
*/
應(yīng)用Preload Hook存在的問(wèn)題和解決方案
Preload是否能作用于動(dòng)態(tài)加載(dlopen/dlsym)的函數(shù)鲫剿?
??這里和靜態(tài)引用函數(shù)相比較,如前述稻轨,若preload模塊已經(jīng)導(dǎo)出同名函數(shù)灵莲,進(jìn)程啟動(dòng)時(shí)需要借助進(jìn)程加載器處理靜態(tài)引用函數(shù),從模塊鏈中按模塊加載順序查找殴俱,直到遇到匹配的函數(shù)指針政冻,這個(gè)函數(shù)此時(shí)位于preload模塊中。而在動(dòng)態(tài)調(diào)用過(guò)程中线欲,指定動(dòng)態(tài)庫(kù)名及符號(hào)名的動(dòng)態(tài)調(diào)用會(huì)在查找時(shí)匹配動(dòng)態(tài)庫(kù)名明场,再在該動(dòng)態(tài)庫(kù)符號(hào)表中查找符號(hào)名,因此會(huì)返回原始函數(shù)指針李丰。
Preload是否能作用于子進(jìn)程(fork/vfork/clone)(execve/system/popen/...)苦锨?
??由于preload是進(jìn)程加載器的行為,因此如果能觸發(fā)進(jìn)程加載器就能觸發(fā)preload嫌套。對(duì)于第一類函數(shù)(fork/vfork/clone)逆屡,由于沒(méi)有對(duì)進(jìn)程進(jìn)行重新初始化的操作圾旨,而僅僅是對(duì)進(jìn)程空間的復(fù)制行為踱讨,因此不能觸發(fā)preload;對(duì)于第二類函數(shù)(execve/system/popen/...)砍的,其首先要調(diào)用fork產(chǎn)生一個(gè)子進(jìn)程痹筛,然后需要使用新的可執(zhí)行文件對(duì)子進(jìn)程的進(jìn)程空間重新初始化,這個(gè)過(guò)程勢(shì)必會(huì)觸發(fā)preload過(guò)程廓鞠。
??另外一個(gè)重要的考慮因素是帚稠,Preload只在部署之后對(duì)新進(jìn)程生效,因此需要作為一個(gè)獨(dú)立服務(wù)的可啟動(dòng)項(xiàng)床佳,考慮系統(tǒng)其他服務(wù)啟動(dòng)順序和時(shí)機(jī)滋早。
對(duì)Docker / Chroot的支持?
??Paas容器技術(shù)提供了和宿主隔離的獨(dú)立環(huán)境砌们,在這種環(huán)境下文件系統(tǒng)是相互隔離的杆麸,而Preload技術(shù)則基于文件系統(tǒng)(ld.so.preload),因此不存在一個(gè)絕對(duì)完美的方法提供對(duì)容器類場(chǎng)景的支持浪感。下面提供一些特殊方式來(lái)解決該需求昔头,對(duì)于Docker 分2種情況討論:
- 若容器提供了文件映射機(jī)制(如docker run -v),在這種情況下至少要求映射/etc/ld.so.preload及Preload動(dòng)態(tài)庫(kù)以實(shí)現(xiàn)基本的Preload機(jī)制影兽,而這本身又需要宿主系統(tǒng)和容器系統(tǒng)相近揭斧,保證系統(tǒng)穩(wěn)定性。
- 若容器系統(tǒng)和宿主系統(tǒng)相差較大峻堰,則需要考慮在容器系統(tǒng)內(nèi)全新部署Preload相關(guān)組件讹开。
??對(duì)于類似于使用chroot機(jī)制進(jìn)行文件隔離的場(chǎng)景盅视,需要針對(duì)性的修改chroot環(huán)境中對(duì)應(yīng)的Preload配置文件及部署攔截模塊進(jìn)行Preload。
如何攔截動(dòng)態(tài)加載的符號(hào)旦万?
??如前述左冬,Preload是進(jìn)程加載器的行為,因此對(duì)于靜態(tài)引用的符號(hào)生效纸型;這里暫且將動(dòng)態(tài)調(diào)用限制在僅使用dlopen/dlsym函數(shù)上拇砰,對(duì)于明確指定了動(dòng)態(tài)庫(kù)路徑和符號(hào)名的dlopen/dlsym的動(dòng)態(tài)調(diào)用,必然要對(duì)dlsym本身進(jìn)行hook狰腌,這樣引發(fā)的問(wèn)題是怎樣得到原始的dlopen/dlsym及其他符號(hào)名除破,因?yàn)橥ǔN覀儠?huì)在hook函數(shù)執(zhí)行前/后,執(zhí)行一次原始函數(shù)琼腔,而這個(gè)原始函數(shù)指針按常規(guī)方式需要由dlsym來(lái)獲取瑰枫。如果靜態(tài)使用dlopen/dlsym則會(huì)調(diào)用hook模塊中的自定義dlopen/dlsym中去,從而產(chǎn)生死循環(huán)丹莲。要解開(kāi)這個(gè)循環(huán)必須要使用更底層的方式來(lái)做光坝。一種選擇是手動(dòng)解析/proc/pid/map中的模塊導(dǎo)出表,從而獲取函數(shù)基址甥材。更通用的方式則是使用GNU提供的dl_iterate_phdr盯另,此函數(shù)存在于進(jìn)程加載器linker中。所以一個(gè)典型的preload hook過(guò)程如下:
- 使用dl_iterate_phdr迭代出所有需要進(jìn)行hook的函數(shù)地址洲赵,保存為原始函數(shù)
- 實(shí)現(xiàn)同名函數(shù)鸳惯,在執(zhí)行hook邏輯前/后,執(zhí)行原始函數(shù)調(diào)用
- 在自定義dlopen/dlsym叠萍,若匹配到hook函數(shù)名芝发,則返回hook模塊本身的同名函數(shù),以實(shí)現(xiàn)hook動(dòng)態(tài)調(diào)用函數(shù)的目的
如何更安全的部署preload模塊苛谷?
??由于preload函數(shù)會(huì)影響所有新啟動(dòng)的進(jìn)程辅鲸,若preload模塊存在缺陷,最差的情況是無(wú)法執(zhí)行任何命令(execve/system/popen/...)腹殿;此種情況下若進(jìn)行重啟独悴,重啟所創(chuàng)建的任何進(jìn)程均會(huì)無(wú)法正確執(zhí)行而導(dǎo)致系統(tǒng)無(wú)法使用,從而陷入死循環(huán)赫蛇,這種情況極難處理绵患。這里提供的解決方式是使用mount命令,其特點(diǎn)是悟耘,在刪除源文件或重啟后失效落蝙,利用這個(gè)特點(diǎn),首先將ld.preload.so拷貝為ld.preload.so.new,然后使用mount將ld.preload.so.new綁定到ld.preload.so上筏勒。此時(shí)若preload模塊存在缺陷移迫,只需要重啟即可去除preload。
Preload Hook應(yīng)用之——網(wǎng)絡(luò)訪問(wèn)日志
??由前述可知管行,Preload技術(shù)并不是一種安全的技術(shù)厨埋,此技術(shù)多用于非安全應(yīng)用的API攔截、日志記錄捐顷、流量監(jiān)控或打補(bǔ)丁等場(chǎng)景荡陷。在Preload技術(shù)上層即使構(gòu)造比較復(fù)雜的系統(tǒng),如果該系統(tǒng)和Preload本身是串聯(lián)的迅涮,這種情況同樣也是非安全废赞,因此這里我們只討論非安全對(duì)抗的應(yīng)用模型,以網(wǎng)絡(luò)通信監(jiān)控為例叮姑,具體應(yīng)用需要視具體業(yè)務(wù)而定唉地。
策略在so中
??如上圖所示,每個(gè)主機(jī)的代理進(jìn)程(Agent)負(fù)責(zé)維護(hù)策略传透,通過(guò)RPC從遠(yuǎn)程服務(wù)器獲取監(jiān)控策略耘沼,并下發(fā)給每個(gè)被管理(通過(guò)Preload Hook注入inject.so)的進(jìn)程(Client)。這些進(jìn)程加載和解析由代理進(jìn)程下發(fā)的策略朱盐,在觸發(fā)網(wǎng)絡(luò)通信時(shí)在inject.so中對(duì)五元組(協(xié)議/源IP/源PORT/目的IP/目的PORT)自行做判決群嗤,這種模型存在以下特點(diǎn):
- 所有Client負(fù)責(zé)加載和解析策略,存在一定內(nèi)存開(kāi)銷托享;另外Client進(jìn)程容易泄露策略數(shù)據(jù)
- 無(wú)額外網(wǎng)絡(luò)/IPC開(kāi)銷
- Agent不存在骚烧、被殺死或者崩潰時(shí),不影響Client中inject.so的攔截策略
- Preload只影響新啟動(dòng)的進(jìn)程闰围,因此策略的更新&運(yùn)維較為困難
策略在代理進(jìn)程中
??如上圖所示,每個(gè)主機(jī)的代理進(jìn)程負(fù)責(zé)維護(hù)策略既峡,同時(shí)對(duì)被管理的進(jìn)程發(fā)送的五元組數(shù)據(jù)進(jìn)行判決和響應(yīng)羡榴,這些Client在觸發(fā)網(wǎng)絡(luò)通信時(shí)會(huì)通過(guò)inject.so向代理進(jìn)程發(fā)送IPC五元組數(shù)據(jù),根據(jù)結(jié)果選擇攔截或允許运敢。這種模型存在以下特點(diǎn):
- 每個(gè)主機(jī)的Agent負(fù)責(zé)加載和解析策略
- 所有Client需要進(jìn)行IPC通信校仑,存在一定資源開(kāi)銷
- Agent不存在、被殺死或者崩潰時(shí)传惠,會(huì)影響主機(jī)中每個(gè)Client中inject.so的攔截策略
- Preload只影響新啟動(dòng)的進(jìn)程迄沫,因此策略的更新只需要向server下發(fā)策略即可
策略在遠(yuǎn)程主機(jī)上
??如上圖所示,和前一個(gè)模型不同的是卦方,每個(gè)主機(jī)的代理進(jìn)程通過(guò)RPC轉(zhuǎn)發(fā)判決請(qǐng)求到服務(wù)器并返回結(jié)果羊瘩。這種模型存在以下特點(diǎn):
- 每次觸發(fā)網(wǎng)絡(luò)訪問(wèn)時(shí)都需要進(jìn)行一次或多次RPC和IPC訪問(wèn),對(duì)網(wǎng)絡(luò)性能和主機(jī)性能要求較高
- Agent不存在、被殺死或者崩潰時(shí)尘吗,會(huì)影響主機(jī)中每個(gè)Client中inject.so的攔截策略
- Server宕機(jī)或被攻擊時(shí)逝她,會(huì)影響主機(jī)所對(duì)應(yīng)所有Client中inject.so的攔截策略
- Preload只影響新啟動(dòng)的進(jìn)程,因此策略的更新只需要向server下發(fā)策略即可
攔截函數(shù)
??需要攔截的點(diǎn)如下:
- dlopen/dlsym 攔截加載libc.so.6的行為睬捶,以便掛鉤網(wǎng)絡(luò)函數(shù)
- accept/accept4/connect 攔截TCP連入/連出請(qǐng)求
- send/recv系 攔截UDP單個(gè)連接請(qǐng)求(TCP/UDP通用)黔宛,包括send/recv/sendto/recvfrom/sendmsg/recvmsg/write/read
??在上述函數(shù)中,需要獲取三元組標(biāo)識(shí)一次通信過(guò)程擒贸,包括(協(xié)議臀晃,本地端,遠(yuǎn)端)介劫,本地端和遠(yuǎn)端均由一個(gè)地址和一個(gè)端口構(gòu)成积仗。獲取這些信息的方式一般如下:
- 判斷文件描述符fd是否為套接字。對(duì)于send/recv系的read/write蜕猫,fd可能是普通文件寂曹;另外忽略u(píng)nix socket
- 獲取協(xié)議類型。協(xié)議族(family:IPv4/IPv6)和協(xié)議類型(socktype:TCP/UDP)可以通過(guò)getsockname/getsockopt方式獲取回右。
- 獲取本地端隆圆。通過(guò)getsockname獲取本地綁定的端口和地址,若套接字未使用bind函數(shù)綁定翔烁,則使用默認(rèn)地址和隨機(jī)端口通信渺氧。適用于服務(wù)端
- 獲取遠(yuǎn)端。(in/out指代數(shù)據(jù)流方向)通過(guò)accept(in)/accept4(in)/connect(out)獲取tcp遠(yuǎn)端蹬屹,通過(guò)recvfrom(in)/recvmsg(in)/sendto(out)/sendmsg(out)獲取udp遠(yuǎn)端侣背。對(duì)于send(out)/write(out)/recv(in)/read(in)和未指定遠(yuǎn)端的sendto(out)/sendmsg(out)的套接字,會(huì)使用經(jīng)由connect函數(shù)設(shè)置的默認(rèn)遠(yuǎn)端進(jìn)行通信慨默,可以通過(guò)getpeername獲取贩耐。
總結(jié)
??本篇文章詳細(xì)介紹了Preload原理、使用方式及適用場(chǎng)景厦取,最后通過(guò)網(wǎng)絡(luò)策略攔截案例來(lái)例證該技術(shù)的適用場(chǎng)景潮太。希望對(duì)大家有幫助。
參考文檔
man手冊(cè)