動態連線的訣竅:使用 LD_PRELOAD 去欺騙、注入特性和研究程式
| 2017-12-24 21:42 收藏: 4
本文假設你具備基本的 C 技能
Linux 完全在你的控制之中。雖然從每個人的角度來看似乎並不總是這樣,但是高階使用者喜歡去控制它。我將向你展示一個基本的訣竅,在很大程度上你可以去影響大多數程式的行為,它並不僅是好玩,在有時候也很有用。
一個讓我們產生興趣的示例
讓我們以一個簡單的示例開始。先樂趣,後科學。
random_num.c:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(NULL));
int i = 10;
while(i--) printf("%d\n",rand()%100);
return 0;
}
我相信,它足夠簡單吧。我不使用任何引數來編譯它,如下所示:
gcc random_num.c -o random_num
我希望它輸出的結果是明確的:從 0-99 中選擇的十個隨機數字,希望每次你執行這個程式時它的輸出都不相同。
現在,讓我們假裝真的不知道這個可執行程式的出處。甚至將它的原始檔刪除,或者把它移動到別的地方 —— 我們已不再需要它了。我們將對這個程式的行為進行重大的修改,而你並不需要接觸到它的原始碼,也不需要重新編譯它。
因此,讓我們來建立另外一個簡單的 C 檔案:
unrandom.c:
int rand(){
return 42; //the most random number in the universe
}
我們將編譯它進入一個共享庫中。
gcc -shared -fPIC unrandom.c -o unrandom.so
因此,現在我們已經有了一個可以輸出一些隨機數的應用程式,和一個定製的庫,它使用一個常數值 42
實現了一個 rand()
函式。現在 …… 就像執行 random_num
一樣,然後再觀察結果:
LD_PRELOAD=$PWD/unrandom.so ./random_nums
如果你想偷懶或者不想自動親自動手(或者不知什麼原因猜不出發生了什麼),我來告訴你 —— 它輸出了十次常數 42。
如果先這樣執行
export LD_PRELOAD=$PWD/unrandom.so
然後再以正常方式執行這個程式,這個結果也許會更讓你吃驚:一個未被改變過的應用程式在一個正常的執行方式中,看上去受到了我們做的一個極小的庫的影響 ……
等等,什麼?剛剛發生了什麼?
是的,你說對了,我們的程式生成隨機數失敗了,因為它並沒有使用 “真正的” rand()
,而是使用了我們提供的的那個 —— 它每次都返回 42
。
但是,我們告訴過它去使用真實的那個。我們程式設計讓它去使用真實的那個。另外,在建立那個程式的時候,假冒的 rand()
甚至並不存在!
這句話並不完全正確。我們只能告訴它去使用 rand()
,但是我們不能去選擇哪個 rand()
是我們希望我們的程式去使用的。
當我們的程式啟動後,(為程式提供所需要的函式的)某些庫被載入。我們可以使用 ldd
去學習它是怎麼工作的:
$ ldd random_nums
linux-vdso.so.1 => (0x00007fff4bdfe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f48c03ec000)
/lib64/ld-linux-x86-64.so.2 (0x00007f48c07e3000)
正如你看到的輸出那樣,它列出了被程式 random_nums
所需要的庫的列表。這個列表是構建進可執行程式中的,並且它是在編譯時決定的。在你的機器上的具體的輸出可能與示例有所不同,但是,一個 libc.so
肯定是有的 —— 這個檔案提供了核心的 C 函式。它包含了 “真正的” rand()
。
我使用下列的命令可以得到一個全部的函式列表,我們看一看 libc 提供了哪些函式:
nm -D /lib/libc.so.6
這個 nm
命令列出了在一個二進位制檔案中找到的符號。-D
標誌告訴它去查詢動態符號,因為 libc.so.6
是一個動態庫。這個輸出是很長的,但它確實在列出的很多標準函式中包括了 rand()
。
現在,在我們設定了環境變數 LD_PRELOAD
後發生了什麼?這個變數 為一個程式強制載入一些庫。在我們的案例中,它為 random_num
載入了 unrandom.so
,儘管程式本身並沒有這樣去要求它。下列的命令可以看得出來:
$ LD_PRELOAD=$PWD/unrandom.so ldd random_nums
linux-vdso.so.1 => (0x00007fff369dc000)
/some/path/to/unrandom.so (0x00007f262b439000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f262b044000)
/lib64/ld-linux-x86-64.so.2 (0x00007f262b63d000)
注意,它列出了我們當前的庫。實際上這就是程式碼為什麼得以執行的原因:random_num
呼叫了 rand()
,但是,如果 unrandom.so
被載入,它呼叫的是我們所提供的實現了 rand()
的庫。很清楚吧,不是嗎?
更清楚地瞭解
這還不夠。我可以用相似的方式注入一些程式碼到一個應用程式中,並且用這種方式它能夠像個正常的函式一樣工作。如果我們使用一個簡單的 return 0
去實現 open()
你就明白了。我們看到這個應用程式就像發生了故障一樣。這是 顯而易見的, 真實地去呼叫原始的 open()
:
inspect_open.c:
int open(const char *pathname, int flags){
/* Some evil injected code goes here. */
return open(pathname,flags); // Here we call the "real" open function, that is provided to us by libc.so
}
嗯,不對。這將不會去呼叫 “原始的” open(...)
。顯然,這是一個無休止的遞迴呼叫。
怎麼去訪問這個 “真正的” open()
函式呢?它需要去使用程式介面進行動態連結。它比聽起來更簡單。我們來看一個完整的示例,然後,我將詳細解釋到底發生了什麼:
inspect_open.c:
#define _GNU_SOURCE
#include <dlfcn.h>
typedef int (*orig_open_f_type)(const char *pathname, int flags);
int open(const char *pathname, int flags, ...)
{
/* Some evil injected code goes here. */
orig_open_f_type orig_open;
orig_open = (orig_open_f_type)dlsym(RTLD_NEXT,"open");
return orig_open(pathname,flags);
}
dlfcn.h
是我們後面用到的 dlsym
函式所需要的。那個奇怪的 #define
是命令編譯器去允許一些非標準的東西,我們需要它來啟用 dlfcn.h
中的 RTLD_NEXT
。那個 typedef
只是建立了一個函式指標型別的別名,它的引數等同於原始的 open
—— 它現在的別名是 orig_open_f_type
,我們將在後面用到它。
我們定製的 open(...)
的主體是由一些程式碼構成。它的最後部分建立了一個新的函式指標 orig_open
,它指向原始的 open(...)
函式。為了得到那個函式的地址,我們請求 dlsym
在動態庫堆疊上為我們查詢下一個 open()
函式。最後,我們呼叫了那個函式(傳遞了與我們的假冒 open()
一樣的引數),並且返回它的返回值。
我使用下面的內容作為我的 “邪惡的注入程式碼”:
inspect_open.c (片段):
printf("The victim used open(...) to access '%s'!!!\n",pathname); //remember to include stdio.h!
要編譯它,我需要稍微調整一下編譯引數:
gcc -shared -fPIC inspect_open.c -o inspect_open.so -ldl
我增加了 -ldl
,因此,它將這個共享庫連結到 libdl
—— 它提供了 dlsym
函式。(不,我還沒有建立一個假冒版的 dlsym
,雖然這樣更有趣)
因此,結果是什麼呢?一個實現了 open(...)
函式的共享庫,除了它有 輸出 檔案路徑的意外作用以外,其它的表現和真正的 open(...)
函式 一模一樣。:-)
如果這個強大的訣竅還沒有說服你,是時候去嘗試下面的這個示例了:
LD_PRELOAD=$PWD/inspect_open.so gnome-calculator
我鼓勵你去看看自己實驗的結果,但是簡單來說,它實時列出了這個應用程式可以訪問到的每個檔案。
我相信它並不難想像為什麼這可以用於去除錯或者研究未知的應用程式。請注意,這個特定訣竅並不完整,因為 open()
並不是唯一一個開啟檔案的函式 …… 例如,在標準庫中也有一個 open64()
,並且為了完整地研究,你也需要為它去建立一個假冒的。
可能的用法
如果你一直跟著我享受上面的過程,讓我推薦一個使用這個訣竅能做什麼的一大堆創意。記住,你可以在不損害原始應用程式的同時做任何你想做的事情!
獲得 root 許可權。你想多了!你不會透過這種方法繞過安全機制的。(一個專業的解釋是:如果 ruid != euid,庫不會透過這種方法預載入的。)- 欺騙遊戲:取消隨機化。這是我演示的第一個示例。對於一個完整的工作案例,你將需要去實現一個定製的
random()
、rand_r()
、random_r()
,也有一些應用程式是從/dev/urandom
之類的讀取,你可以透過使用一個修改過的檔案路徑來執行原始的open()
來把它們重定向到/dev/null
。而且,一些應用程式可能有它們自己的隨機數生成演算法,這種情況下你似乎是沒有辦法的(除非,按下面的第 10 點去操作)。但是對於一個新手來說,它看起來很容易上手。 - 欺騙遊戲:讓子彈飛一會 。實現所有的與時間有關的標準函式,讓假冒的時間變慢兩倍,或者十倍。如果你為時間測量和與時間相關的
sleep
或其它函式正確地計算了新的值,那麼受影響的應用程式將認為時間變慢了(你想的話,也可以變快),並且,你可以體驗可怕的 “子彈時間” 的動作。或者 甚至更進一步,你的共享庫也可以成為一個 DBus 客戶端,因此你可以使用它進行實時的通訊。繫結一些快捷方式到定製的命令,並且在你的假冒的時間函式上使用一些額外的計算,讓你可以有能力按你的意願去啟用和禁用慢進或快進任何時間。 - 研究應用程式:列出訪問的檔案。它是我演示的第二個示例,但是這也可以進一步去深化,透過記錄和監視所有應用程式的檔案 I/O。
- 研究應用程式:監視因特網訪問。你可以使用 Wireshark 或者類似軟體達到這一目的,但是,使用這個訣竅你可以真實地控制基於 web 的應用程式傳送了什麼,不僅是看看,而是也能影響到交換的資料。這裡有很多的可能性,從檢測間諜軟體到欺騙多使用者遊戲,或者分析和逆向工程使用閉源協議的應用程式。
- 研究應用程式:檢查 GTK 結構 。為什麼只侷限於標準庫?讓我們在所有的 GTK 呼叫中注入一些程式碼,因此我們就可以知道一個應用程式使用了哪些元件,並且,知道它們的構成。然後這可以渲染出一個影像或者甚至是一個 gtkbuilder 檔案!如果你想去學習一些應用程式是怎麼管理其介面的,這個方法超級有用!
- 在沙盒中執行不安全的應用程式。如果你不信任一些應用程式,並且你可能擔心它會做一些如
rm -rf /
或者一些其它不希望的檔案活動,你可以透過修改傳遞到檔案相關的函式(不僅是open
,也包括刪除目錄等)的引數,來重定向所有的檔案 I/O 操作到諸如/tmp
這樣地方。還有更難的訣竅,如 chroot,但是它也給你提供更多的控制。它可以更安全地完全 “封裝”,但除非你真的知道你在做什麼,不要以這種方式真的執行任何惡意軟體。 - 實現特性 。zlibc 是明確以這種方法執行的一個真實的庫;它可以在訪問檔案時解壓檔案,因此,任何應用程式都可以在無需實現解壓功能的情況下訪問壓縮資料。
- 修復 bug。另一個現實中的示例是:不久前(我不確定現在是否仍然如此)Skype(它是閉源的軟體)從某些網路攝像頭中捕獲影片有問題。因為 Skype 並不是自由軟體,原始檔不能被修改,這就可以透過使用預載入一個解決了這個問題的庫的方式來修復這個 bug。
- 手工方式 訪問應用程式擁有的記憶體。請注意,你可以透過這種方式去訪問所有應用程式的資料。如果你有類似的軟體,如 CheatEngine/scanmem/GameConqueror 這可能並不會讓人驚訝,但是,它們都要求 root 許可權才能工作,而
LD_PRELOAD
則不需要。事實上,透過一些巧妙的訣竅,你注入的程式碼可以訪問所有的應用程式記憶體,從本質上看,是因為它是透過應用程式自身得以執行的。你可以修改這個應用程式能修改的任何東西。你可以想像一下,它允許你做許多的底層的侵入…… ,但是,關於這個主題,我將在某個時候寫一篇關於它的文章。
這裡僅是一些我想到的創意。我希望你能找到更多,如果你做到了 —— 透過下面的評論區共享出來吧!
作者:Rafał Cieślak 譯者:qhwdw 校對:wxy
相關文章
- 再探堆疊欺騙之動態欺騙
- 初探堆疊欺騙之靜態欺騙
- 程式設計師的王牌面試訣竅程式設計師面試
- DNS欺騙和ARP欺騙是什麼?有何區別?DNS
- 寫好軟體的訣竅
- 軟體解決顯示卡欺騙器,HDMI欺騙器,如何使用ToDesk免費功能
- 欺騙機器學習模型機器學習模型
- DNS欺騙(轉)DNS
- 8.4學好linux的訣竅Linux
- Chartboost:利用移動遊戲賺錢的七大訣竅遊戲
- LLMNR欺騙工具Responder
- AMD“欺騙”使用者? 過早釋出Puma移動平臺
- 提高程式設計師工作效率的5個訣竅程式設計師
- 一個修改Oracle使用者密碼的小訣竅(轉)Oracle密碼
- 動態連結庫(DLL)的建立和使用
- 動態連結庫的生成和使用(二)
- YouGov:17%的約會應用使用者的動機是欺騙別人Go
- 長連線和短連線的使用
- 安全之——郵件欺騙
- [譯] 前端除錯技巧與訣竅前端除錯
- 千萬級架構設計訣竅架構
- 用於WebKit的CSS訣竅-圖片版WebKitCSS
- 程式設計師快速記憶英文單詞的專屬訣竅程式設計師
- 小程式骨架屏動態注入元件元件
- 欺騙防禦技術新功能:用郵件資料欺騙攻擊者
- 網路欺騙技術 (轉)
- 使用者調研資料是如何欺騙我們的
- 這些Python學習的步驟和訣竅,你聽過嗎?Python
- 動態IPvps,選用動態IPvps的意義,使用動態IPvps的說明及連線操作
- DNS 系列(三):如何免受 DNS 欺騙的侵害DNS
- Shell文字處理編寫單行指令的訣竅
- Win7工作列幾個應用的訣竅Win7
- 說服他人的訣竅 | 從SWOT模型到TOSW模型模型
- 零基礎也能學習JAVA的訣竅Java
- 遊戲被病毒式傳播的八大訣竅遊戲
- 保持Oracle資料優良效能的若干訣竅(轉)Oracle
- ·[實用]生活中15條護膚的小訣竅
- IP欺騙原理與過程分析