攔截Linux動態(tài)庫API的常規(guī)方法,是基于動態(tài)符號鏈接覆蓋技術(shù)實現(xiàn)的,基本步驟是
1. 重命名要攔截的目標動態(tài)庫。
2. 創(chuàng)建新的同名動態(tài)庫,定義要攔截的同名API,在API內(nèi)部調(diào)用原動態(tài)庫對應(yīng)的API。這里的同名是指與重命名前動態(tài)庫前的名稱相同。
顯而易見,如果要攔截多個不同動態(tài)庫中的API,那么必須創(chuàng)建多個對應(yīng)的同名動態(tài)庫,這樣一來不僅繁瑣低效,還必須被優(yōu)先鏈接到客戶二進制程序中(根據(jù)動態(tài)庫鏈接原理,對重復(fù)ABI符號的處理是選擇優(yōu)先鏈接的那個動態(tài)庫)。 另外在鉤子函數(shù)的實現(xiàn)中,若某調(diào)用鏈調(diào)用到了原API,則會引起死循環(huán)而崩潰。本方法通過直接修改ELF文件中的動態(tài)庫API入口表項,解決了常規(guī)方法的上述問題。
特點
1. 不依賴于動態(tài)庫鏈接順序。
2. 能攔截多個不同動態(tài)庫中的多個API。
3. 支持運行時動態(tài)鏈接的攔截。
4. 鉤子函數(shù)內(nèi)的實現(xiàn)體,若調(diào)用到原API,則不會死循環(huán)。
實現(xiàn)
攔截映射表
為了支持特點2和3,建立了一個攔截映射表,這個映射表有2級。第1級為ELF文件到它的API鉤子映射表,鍵為ELF文件句柄,值為API鉤子映射表;第2級為API到它的鉤子函數(shù)映射表,鍵為API名稱,值為包含最老原函數(shù)地址和最新鉤子函數(shù)地址的結(jié)構(gòu)體,如下圖

計算ELF文件的映像基地址
計算映像基地址是為了得到ELF中動態(tài)符號表和重定位鏈接過程表的內(nèi)容,因為這些表的位置都是相對于基地址的偏移量,該算法在打開ELF文件時執(zhí)行,如下圖

打開ELF文件
為了支持特點2即攔截不同動態(tài)庫的多個API,節(jié)省每次掛鉤API前要打開并讀文件的開銷,獨立提供了打開ELF文件的接口操作,流程如下圖

掛鉤API
當(dāng)打開ELF文件后,就可掛鉤API了,流程如下圖

卸鉤API
當(dāng)打開ELF文件后,就可卸鉤API了,流程如下圖

關(guān)閉ELF文件
因為提供了打開ELF文件的接口操作,所以得配有關(guān)閉ELF文件的接口操作。當(dāng)不需要掛鉤API的時候,就可以關(guān)閉ELF文件了,流程如下圖

運行時動態(tài)攔截裝置

當(dāng)動態(tài)庫被進程加載的時候,會調(diào)用初始化模塊;當(dāng)被進程卸載或進程退出的時候,會調(diào)用銷毀模塊;當(dāng)通過dlsym調(diào)用API時,則會在dlsym的鉤子函數(shù)中調(diào)用轉(zhuǎn)換模塊。通過環(huán)境變量LD_PRELOAD將動態(tài)庫libhookapi.so設(shè)為預(yù)加載庫,這樣就能攔截到所有進程對dlopen及dlsym的調(diào)用,進而攔截到已掛鉤動態(tài)庫API的調(diào)用。