Jump to content

在Windows上,第三方產品有多種方式將其代碼注入其他正在運行的進程。這樣做的原因有很多,最常見的是殺毒軟件、硬件驅動程序、屏幕閱讀器和銀行的需要,當然惡意軟件也會趁機而入。

將第三方產品的DLL注入Firefox進程是非常常見的,超過70%的Windows用戶至少有一個這樣的DLL!需要明確的是,這意味著沒有經過Mozilla或操作系統部分數字簽名的任何DLL。

大多數用戶不在Windows上,第三方產品有多種方式將其代碼注入其他正在運行的進程。這樣做的原因有很多,最常見的是殺毒軟件、硬件驅動程序、屏幕閱讀器和銀行的需要,當然惡意軟件也會趁機而入。

將第三方產品的DLL注入Firefox進程是非常常見的,超過70%的Windows用戶至少有一個這樣的DLL!需要明確的是,這意味著沒有經過Mozilla或操作系統部分數字簽名的任何DLL。

大多數用戶不知道DLL何時被注入Firefox,因為大多數時候除了檢查about:third-party page.之外,沒有明顯的跡象表明正在發生這種情況。

不過,將DLL注入Firefox可能會導致性能、安全性或穩定性問題。原因如下:

1.DLL通常會掛鉤到Firefox的內部函數中,這些函數會隨著版本的不同而變化。所以,第三方產品的發行商必須努力使用新版本的Firefox進行測試,以避免穩定性問題。

2.Firefox作為一種網絡瀏覽器,可以從不受信任和潛在的惡意網站加載並運行代碼。所以,安全研究人員需要付出很多努力來保護Firefox的安全,第三方產品可能對安全性沒有這麼關注。

3.研究人員在Firefox上運行了大量的測試,第三方產品可能不會測試到這種程度,因為它們可能不是專門為配合Firefox而設計的。

事實上,我們的數據顯示,在所有Windows上的Firefox崩潰報告中,只有2%以上是第三方代碼造成的。儘管Firefox已經阻止了許多已知會導致崩潰的特定第三方DLL,但情況依然如此。

這也低估了由第三方DLL間接引起的崩潰,因為研究人員的指標只在調用堆棧中直接查找第三方DLL。此外,第三方DLL在啟動時更容易導致崩潰,這對用戶來說要嚴重得多。

Firefox有第三方注入策略,只要有可能,我們建議第三方使用擴展來集成到Firefox中,因為這是官方支持的,而且更穩定。

為什麼不在默認情況下阻止所有DLL注入?為了獲得最大的穩定性和性能,Firefox可以嘗試阻止所有第三方DLL注入其進程。然而,這會破壞一些有用的產品,比如用戶希望能夠與Firefox一起使用的屏幕閱讀器。這在技術上也很有挑戰性,不可能阻止每個第三方DLL,尤其是使用比Firefox更高權限運行的第三方產品。

自2010年以來,Mozilla已經能夠為Firefox的所有Windows用戶屏蔽特定的第三方DLL。這樣做只是作為最後的手段,在嘗試與供應商溝通以解決潛在問題後,研究人員會盡可能嚴格地進行調整,以使Firefox用戶不再崩潰。目前研究人員只能阻止特定版本的DLL,並且只能在特定的Firefox進程中阻止它。這是一個有用的工具,但只有當特定的第三方DLL導致大量崩潰時,研究人員才會考慮使用它,這樣它就會出現在Firefox崩潰列表中。

即使我們知道第三方DLL會導致Firefox崩潰,但有時DLL提供的功能對用戶來說是必不可少的,用戶不希望安全人員代表他們阻止DLL。如果用戶的銀行或當地政府需要一些軟件來訪問他們的賬戶或報稅,我們屏蔽它不會給他們帶來任何好處,即使屏蔽它會使Firefox更加穩定。

賦予用戶阻止注入DLL的權限在Firefox 110中,用戶可以阻止第三方dll加載到Firefox中。這可以在about:third-party上完成,該頁面已經列出了所有加載的第三方模塊。 about:third-party還顯示了哪些第三方DLL與之前的Firefox崩潰有關,還有就是DLL發布者的信息也會顯示,希望這能讓用戶在知情的情況下決定是否阻止DLL。下面是一個最近導致Firefox崩潰的DLL示例,點擊帶有破折號的按鈕將阻止它:

1.png

以下是阻止DLL並重新啟動Firefox後的情況:

2.png

如果阻止DLL導致問題,在故障排除模式下啟動Firefox將禁用該運行的Firefox的所有第三方DLL阻止,並且可以像往常一樣在about:third-party上阻止或取消阻止DLL。

工作原理阻止DLL加載到進程中是一項棘手的業務,為了檢測加載到Firefox進程中的所有DLL,必須在啟動過程中儘早設置阻止列表。為此,使用啟動進程,它創建處於掛起狀態的主瀏覽器進程。然後,它設置任何沙盒策略,從磁盤加載阻止列表文件,並在啟動該進程之前將條目複製到瀏覽器進程中。

複製是以一種有趣的方式完成的,啟動程序進程使用CreateFileMapping()創建一個操作系統支持的文件映射對象,在用塊列表條目填充後,複製句柄並使用WriteProcessMemory()將句柄值寫入瀏覽器進程。具有諷刺意味的是,WriteProcessMemory()經常被用作第三方DLL將自己注入其他進程的一種方式,這裡我們使用它在已知位置設置一個變量,因為啟動器進程和瀏覽器進程是從同一個.exe文件運行的!

因為所有的事情都發生在啟動的早期,在加載Firefox配置文件之前,被阻止的dll列表是按Windows用戶而不是按Firefox配置文件存儲的。具體來說,文件位於%AppData%\Mozilla\Firefox中,文件名格式為blocklist-{install hash},其中install hash是Firefox磁盤上位置的哈希值。這是一種簡單的方法,可以將不同Firefox安裝的阻止列表分開。

檢測並阻止加載DLL為了檢測DLL何時試圖加載,Firefox使用了一種稱為函數攔截或掛鉤的技術。這會修改內存中的現有函數,以便在現有函數開始執行之前可以調用另一個函數。之所以如此,原因有很多,它允許更改函數的行為,即使函數不是為了允許更改而設計的。 Microsoft Detours是一種常用於攔截函數的工具。

在Firefox中,研究人員感興趣的函數是NtMapViewOfSection(),每當加載DLL時都會調用它。我們的目標是在發生這種情況時得到通知,這樣我們就可以檢查阻止列表,並禁止加載DLL(如果它在阻止列表上)。

為此,Firefox使用一個自定義的函數攔截器來攔截對NtMapViewOfSection()的調用,並返回如果DLL在阻止列表上則映射失敗的消息。為此,攔截器嘗試了兩種不同的技術:

在32位x86平台上,從DLL導出的一些函數將以一條不執行任何操作的兩字節指令(mov edi, edi)開始,並且在此(nop或int 3)之前有五條未使用的一字節指令,例如:

3.png

如果攔截器檢測到這種情況,它可以將未使用指令的五個字節替換為要調用的函數地址的jmp。由於研究人員是在32位平台上,因此只需要一個字節來指示跳轉,四個字節來表示地址,因此:

4.png

當修復的函數想要調用未修復版本的DLLFunction()時,它只需跳過DLLFunction()地址2個字節即可啟動實際的函數代碼

否則,事情會變得更加複雜。以x64的情況為例。跳轉到已修復函數的指令需要13個字節:10個字節用於將地址加載到寄存器中,3個字節用於跳轉到該寄存器的位置。因此,攔截器需要將至少前13字節的指令移動到一個蹦床函數中,如果需要的話,還要加上完成最後一條指令所需的足夠的字節。之所以被稱為蹦床,因為通常代碼會跳轉到那裡,這會導致一些指令運行,然後跳轉到目標函數的其餘部分。讓我們看看一個真實的示例,下面是我們要截取的一個簡單函數,首先是C源代碼(Godbolt編譯器資源管理器鏈接):

5.png

以上是用-O3編譯的,所以它有點密集:

6.png

現在,從fn()開始計算13個字節將我們置於lea eax,[rdi+rdi*2]指令的中間,因此我們必須將所有內容複製到蹦床上。

最終結果如下所示:

7.png

如果Firefox 修復函數想要調用未修復的fn(),那麼修復程序已經存儲了蹦床的地址(在本例中為0x3000000)。在C++代碼中,我們將其封裝在FuncHook類中,修復後的函數可以使用與普通函數調用相同的語法來調用蹦床。

整個過程比第一種情況要復雜得多,你可以看到第一個示例的修復只有200行左右,而處理這個示例的修復有1700多行,不過有些注意事項要注意:

1.並非所有轉移到蹦床上的指令都必須保持完全相同,一個示例是跳轉到一個沒有移動到蹦床的相對地址,由於指令已經在內存中移動了,修復程序需要用絕對跳躍來代替它。修復程序並不能處理所有類型的x64指令,否則它必須更長!但研究人員已經進行了自動化測試,以確保能夠成功攔截所知道Firefox需要的Windows函數。

2.研究人員專門使用了r11來加載修復函數的地址,因為根據x64調用約定,r11是一個不需要被調用方保存的易失性寄存器。

3.由於我們使用jmp從fn()返回到修復函數,而不是ret,並且類似地從蹦床返回到fn()的主代碼,這使代碼堆棧保持中立。因此,調用其他函數和從fn()返回都可以正確地處理堆棧的位置。

4.如果從fn()的後面跳轉到前13個字節,這些字節現在將跳轉到修復函數的中間,肯定會發生問題。幸運的是,這是非常罕見的。大多數函數在開始時都在進行函數序言操作,所以對於Firefox攔截的函數來說,這不是問題。

5.類似地,在某些情況下,fn()在前13個字節中存儲了一些數據,這些數據將被後面的指令使用,將這些數據移動到蹦床將導致後面的指令獲得錯誤的數據。我們已經遇到了這個問題,如果我們可以在前2GB的地址空間內為蹦床分配空間,那麼可以通過使用較短的mov指令來解決這個問題。這將導致10字節的修復而不是13字節的修復,在許多情況下,這足以避免問題。

其他一些需要注意的複雜情況:

6.Firefox也有一種跨進程攔截的方法;

7.對於Control Flow Guard安全措施來說,蹦床很棘手:由於它們是合法的間接調用目標,在編譯時不存在,所以需要特別注意允許Firefox修復過的函數調用它們;

8.蹦床還包括一些額外的異常處理;

9.如果DLL在阻止列表中,我們的修復版本NtMapViewOfSection()將返回映射失敗,這將導致整個DLL加載失敗。這不會阻止所有類型的注入,但它確實阻止了大多數注入;

一些DLL將通過修改firefox.exe的導入地址表來自我注入,該表是firefox.exe調用的外部函數的列表。如果其中一個函數加載失敗,Windows將終止Firefox進程。因此,如果Firefox檢測到這種注入並想要阻止DLL,我們將把DLL的DllMain()重定向到一個什麼都不做的函數。

總結希望讀者在閱讀本文後,可以讓Firefox用戶更加安全地訪問互聯網。用戶大可不必在卸載有用的第三方產品和Firefox的穩定性問題之間做出選擇,現在用戶有了第三種選擇,即保留第三方的產品並阻止其註入Firefox!

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...