Linux Preload Hook原理與實(shí)踐

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ù)地址。
    • 動(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?

??如前述锌雀,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è)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虾攻,一起剝皮案震驚了整個(gè)濱河市铡买,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌霎箍,老刑警劉巖奇钞,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異漂坏,居然都是意外死亡景埃,警方通過(guò)查閱死者的電腦和手機(jī)媒至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纠亚,“玉大人塘慕,你說(shuō)我怎么就攤上這事〉侔” “怎么了图呢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)骗随。 經(jīng)常有香客問(wèn)我蛤织,道長(zhǎng),這世上最難降的妖魔是什么鸿染? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任指蚜,我火速辦了婚禮,結(jié)果婚禮上涨椒,老公的妹妹穿的比我還像新娘摊鸡。我一直安慰自己,他們只是感情好蚕冬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布免猾。 她就那樣靜靜地躺著,像睡著了一般囤热。 火紅的嫁衣襯著肌膚如雪猎提。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天旁蔼,我揣著相機(jī)與錄音锨苏,去河邊找鬼。 笑死棺聊,一個(gè)胖子當(dāng)著我的面吹牛伞租,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躺屁,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肯夏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了犀暑?” 一聲冷哼從身側(cè)響起楣导,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤字币,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后郭怪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沪斟,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡广辰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年暇矫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择吊。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡李根,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出几睛,到底是詐尸還是另有隱情房轿,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布所森,位于F島的核電站囱持,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏焕济。R本人自食惡果不足惜纷妆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晴弃。 院中可真熱鬧掩幢,春花似錦、人聲如沸上鞠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旗国。三九已至枯怖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間能曾,已是汗流浹背度硝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寿冕,地道東北人蕊程。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驼唱,于是被迫代替她去往敵國(guó)和親藻茂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容