[原創]一種通用DLL劫持技術研究
通用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.exe void 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+0x101d 01 0025eb38 6e4113c9 6e410000 00000001 00000000 mydll+0x12ec 02 0025eb4c 77d889d8 6e410000 00000001 00000000 mydll!DllMain+0x13 03 0025eb6c 77d95c41 6e4113ad 6e410000 00000001 ntdll!LdrpCallInitRoutine+0x14 04 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+0x2b5 0a 0025f09c 6848e245 013a0000 761b3c26 76b3ea5f test!start+0x21e86e 0b 0025f328 013a1918 013a0000 0037187a 00000000 test!start+0x105 0c 0025fb44 013a30b9 013a0000 00000000 0037187a test+0x1918 0d 0025fb90 761b3c45 7ffd9000 0025fbdc 77d937f5 test+0x30b9 0e 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。到此流程分析清楚,下面考慮如何修改這個值。
NTSTATUS NTAPI LdrLoadDll(IN PWSTR SearchPath OPTIONAL, IN PULONG DllCharacteristics OPTIONAL, IN PUNICODE_STRING DllName, OUT PVOID *BaseAddress) { Status = LdrpLoadDll(RedirectedDll, SearchPath, DllCharacteristics, DllName, BaseAddress, TRUE); } NTSTATUS NTAPI LdrpLoadDll(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
[推薦]看雪企服平臺,提供安全分析、定製專案開發、APP等級保護、滲透測試等安全服務!
相關文章
- [原創]注入技術系列:一個批量驗證DLL劫持的工具2020-01-31
- [原創]注入技術系列:一個批次驗證DLL劫持的工具2020-09-03
- [原創]微信PC端技術研究-訊息防撤銷2019-02-25
- 深入解析DLL劫持漏洞2020-08-19
- DLL劫持並使用MinHook2024-11-03Hook
- [原創]微信PC端技術研究(3)-如何找到訊息傳送介面2019-02-25
- 創業之初的技術題:如何構建一個較為通用的業務技術架構2016-11-08創業架構
- 【原創】做技術的出路在哪?2010-03-04
- Jerry的ABAP原創技術文章合集2018-02-10
- C# DLL注入技術2013-10-04C#
- 【原創】webservice效能研究2009-12-20Web
- DLL劫持學習及復現2021-03-05
- 【有感】白駒過隙——原創技術部落格創作一年矣2010-02-04
- 通用測試技術2024-07-25
- 百度快照劫持技術解析2013-01-03
- [原創]DevOps 的技術棧和工具2015-07-31dev
- [原創]京東技術解密讀書筆記2015-10-20解密筆記
- 通用汽車收購無人車技術初創企業Cruise2016-03-12UI
- 技術面試通用方法論2021-09-29面試
- 測試通用技術32024-07-25
- Google DNS劫持背後的技術分析2020-08-19GoDNS
- AI不是一種技術,而是一種思考方式2022-11-07AI
- 【原創】Java記憶體攻擊技術漫談2021-08-19Java記憶體
- 原創和翻譯技術書的優劣勢2011-08-23
- seo技術中的原創內容對搜尋引擎一定好嗎?2020-08-06
- ReactiveProgramming一種技術,各自表述2018-07-23React
- [原創]■■易格式初步研究筆記■■2004-11-01筆記
- 前端開發技術-剖析JavaScript單執行緒 原創2022-01-11前端JavaScript執行緒
- 插值技術研究2024-06-07
- 和信創天引領技術前瞻成立“智慧雲端計算創新研究院”2018-03-05
- OS X平臺的Dylib劫持技術(下)2020-08-19
- OS X平臺的Dylib劫持技術(上)2020-08-19
- 做技術的「五比一」原則2017-08-20
- 華為雲全域Serverless技術創新:全球首創通用Serverless平臺被ACM SIGCOMM錄用2024-08-07ServerACMGC
- 一種蜜網技術的介紹2017-11-07
- Windows Dll Injection、Process Injection、API Hook、DLL後門/惡意程式入侵技術2017-02-02WindowsAPIHook
- OpenCV學堂 | 2019原創技術文章彙總2020-01-01OpenCV
- [原創]用Session和唯一索引欄位實現通用Web分頁功能2008-08-23Session索引Web