反射式dll注入
??前不久實現了經典dll注入exe,實現注入到用戶層面進程,比如notepad.exe和wps.exe。(像金山毒霸kxetray.exe有自我保護機制也注入不進去)。特此記錄一下。
 ??首先什么是反射式注入?它和傳統經典注入有什么區別呢?我這里作個比喻。
經典式:
 ??在目標進程申請一段空間并放入惡意dll,創建遠線程讓目標進程調用LoadLiabrary函數加載這段內存
 ??先把病毒扔進鄰居家,告訴鄰居這是個炮仗(病毒dll),鄰居誤以為真,點燃導火索(創建線程執行),顯然鄰居被騙了
 反射式:
 ?? 在別人的內存里調用自己編寫的dll導出函數 ,自己dll導出函數里實現自我加載(加載PE的整個過程)
 ?? 先把病毒扔進鄰居家,然后自己把它直接點燃(調用dll導出函數),病毒自己爆炸(dll內部實現自己從底層dll遍歷加載函數),鄰居渾然不知(沒有通過系統LoadLibrary調用dll,沒有記錄)
區別是啥
 ??少了使用LoadLibrary的過程。
反射式注入dll的優點
?? 反射式注入方式并沒有通過LoadLibrary等API來完成DLL的裝載,DLL并沒有在操作系統中”注冊”自己的存在,因此ProcessExplorer等軟件也無法檢測出進程加載了該DLL。利用解密磁盤上加密的文件、網絡傳輸等方式避免文件落地,DLL文件可以不一定是本地文件,可來自網絡等,總之將數據寫到緩沖區即可。
 ?? 由于它沒有通過系統API對DLL進行裝載,操作系統無從得知被注入進程裝載了該DLL,所以檢測軟件也無法檢測它。同時,由于操作流程和一般的注入方式不同,反射式DLL注入被安全軟件攔截的概率也會比一般的注入方式低。
實現需求
一、注射器
 ??(將這個DLL文件寫入目標進程的虛擬空間中)實現大部分和傳統注入一樣。但它要做一件很重要的事情,就是獲得病毒dll里的一個最重要的導出函數,也即是我們要編寫的反射加載自己的函數ReflectiveLoader的文件偏移。(如果你知道自己dll這個導出函數文件偏移多少其實也沒必要再用程序讀取)
二、編寫一個邪惡的dll以及它的核心函數ReflectiveLoader
 ??要知道ReflectiveLoader函數運行時所在的DLL還沒有被裝載,它在運行時會受到諸多的限制,例如無法正常使用全局變量等。而且,由于我們無法確認我們究竟將DLL文件寫到目標進程哪一處虛擬空間上,所以我們編寫的ReflectiveLoader必須是地址無關的。
注射器實現
??1. 將待注入DLL讀入自身內存(可以在磁盤上存放一份DLL的加密后的版本,然后將其解密之后儲存在內存里)
 ??2. 利用VirtualAlloc和WriteProcessMemory在目標進程中寫入待注入的DLL文件
 ??3.利用CreateRemoteThread等函數啟動位于目標進程中的ReflectiveLoader(要首先獲得ReflectiveLoader的文件偏移地址),獲取的是DLL中反射加載函數在文件中的偏移,由于這種注入沒有使用LoadLibraryA函數,所以DLL在內存中的狀態和在磁盤中的狀態是相同的,要想調用函數,我們就需要找到文件偏移。
 ??線程函數的地址=緩沖區的基地址+文件偏移
 圖中1264十六進制就是4F0
??4.通過DLL的導出表找到這個ReflectiveLoader并調用它
 ?? 將自身合適地展開到虛擬空間中。我們都知道在PE文件包含了許多節,而為了節省存儲空間,這些節在PE文件中比較緊密地湊在一起的。而在廣闊虛擬空間中,這些節就可以映射到更大的空間中去。(更不用說還存在著.bss這樣的在PE文件中不占空間,而要在虛擬空間中占據位置的節)
ReflectiveLoader函數實現
實現中的問題:
??DLL中可能會用到其他DLL的函數,裝載一個DLL還需要將這個DLL依賴的其他動態庫裝入內存,并修改DLL的IAT指向到合適的位置,這樣對其他DLL函數的引用才能正確運作ReflectiveLoader的代碼是地址無關的,但是該DLL的其他部分的代碼卻并不是這樣的。在一份源代碼編譯、鏈接成為DLL時,編譯器都是假設該DLL會加載到一個固定的位置,生成的代碼也是基于這一個假設。在反射式注入DLL的時候,我們不太可能申請到這個預先設定好的地址,所以我們需要面對一個重定位(Rebasing)的問題。
解決方案
??1.ReflectiveLoader做的第一件事就是查找自身所在的DLL具體被寫入了哪個位置
 ULONG_PTR caller( VOID ) { return(ULONG_PTR)_ReturnAddress(); }
 借助上文找到的地址,我們逐字節的向上遍歷,當查找到符合PE格式的文件頭之后,就可以認為找到了DLL文件在內存中的地址了。
 Caller()函數:獲得當前指令的下條指令的地址。
??2.我們需要的函數是kernel32.dll中的LoadLibraryA(), GetProcAddress(),VirtualAlloc()以及ntdll.dll中的NtFlushInstructionCache()函數。我們需要遍歷已經加載的模塊,從中找到我們需要的模塊,獲得以上幾個函數的地址。
 #define KERNEL32DLL_HASH 0x6A4ABC5B
 #define NTDLLDLL_HASH 0x3CFA685D
 #define LOADLIBRARYA_HASH 0xEC0E4E8E
 #define GETPROCADDRESS_HASH 0x7C0DFCAA
 #define VIRTUALALLOC_HASH 0x91AFCA54
 #define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
 // compute the hash values for this function name
 dwHashValue = hash((char *)(uiBaseAddress + DEREF_32(uiNameArray)));
 每一個線程都具有一個TEB結構,記錄了相關線程的一些基本信息。線程運行時,其FS段寄存器記錄了其TEB的位置。
 獲取PEB的方法:FS:[0x30]和GS:[0x60],前者為32位系統,后者為64位系統。
 這里需要用到之前TEB定位與導出函數定位的知識。
??3. 分配一片用來裝載DLL的空間。
 雖然在ReflectiveLoader運行時,DLL文件已經在進程內存中了,但是要裝載這個DLL,我們還需要更大的空間。借助在第2)步得到的函數VirtualAlloc(),我們可以分配一片更大的內存空間用于加載DLL。在PE頭中的IMAGE_OPTIONAL_HEADER結構體中的SizeOfImage成員記載DLL被裝載后的大小,我們按照這個大小分配內存即可。
 uiBaseAddress=(ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage,
 MEM_RESERVE|MEM_COMMIT,
 PAGE_EXECUTE_READWRITE );
 uiBaseAddress記錄了VirtualAlloc的返回值,也就是分配內存空間的起始地址。于是uiBaseAddress就成為了DLL被裝載后的基地址。
??4.復制PE文件頭和各個節
 分配了用于裝載的空間后,ReflectiveLoader將DLL文件的頭部(也就是DOS文件頭、DOS插樁代碼和PE文件頭)復制到新的空間的首部。再根據PE文件的節表將各個節復制到相應的位置中。
??5.根據節表加載節
??6. 處理DLL的導入表
 被注入的DLL可能還依賴于其他的DLL,因此我們還需要裝載這些被依賴的DLL,并修改本DLL的引入表,使這些被引入的函數能正常運行。
 無論是以什么方式導入,我們都要需要找到對應的函數,然后將其地址填入FirstThunk指向的IMAGE_THUNK_DATA數組中。這個跑起來是IAT
??7.對DLL進行重定位
 ??由于基址改變,所以程序中的一些直接尋址等會出問題,所以要更改重定向表。
 ??被注入的DLL只有其ReflectiveLoader中的代碼是故意寫成地址無關、不需要重定位的,其他部分的代碼則需要經過重定位才能正確運行。
 ??我們首先計算得到基地址的偏移量,也就是實際的DLL加載地址減去DLL的推薦加載地址。DLL推薦加載地址保存在NT可選印象頭中的ImageBase成員中,而實際DLL加載地址則是我們在第3)步中函數VirtualAlloc()的返回值。然后我們將VirtualAddress和Typeoffset合力組成的地址所指向的雙字加上這個偏移量,重定位就完成了。
 (DWORD)(VirtualAddress + Typeoffset的低12位) += (實際DLL加載地址 – 推薦DLL加載地址)
 ??在完成所有的重定位后,我們最后調用第2)步得到的NtFlushInstructionCache()清除指令緩存以避免問題。
 8. 調用DLL入口點
 ??至此,ReflectiveLoader的任務全部完成,最后它將控制權轉交給DLL文件的入口點,這個入口點可以通過NT可選印象頭中的AddressOfEntryPoint找到。一般地,它會完成C運行庫的初始化,執行一系列安全檢查并調用dllmain。
用到的底層C++語言編程
??dll遍歷kernel32與ntdll的導出函數部分用匯編語言更加直接簡短,這部分代碼同樣是大部分加殼程序的殼內核心代碼,有時間一定會細心研究一下。
 ??_rotr函數,功能是value右循環移動shift位( unsigned int value, int shift );
 ??register 關鍵字請求“編譯器”將局部變量存儲于寄存器中——最快的關鍵字
 ??register 這個關鍵字請求編譯器盡可能的將變量存在CPU內部寄存器,而不是通過內存尋址訪問,以提高效率。注意是盡可能,不是絕對。
 ??數據從內存里拿出來先放到寄存器,然后CPU 再從寄存器里讀取數據來處理,處理完后同樣把數據通過寄存器存放到內存里,CPU 不直接和內存打交道。
 內存 寄存器 CPU
 ??寄存器其實就是一塊一塊小的存儲空間,只不過其存取速度要比內存快得多。CPU從寄存器里拿數據比在大內存里去尋找某個地址上的數據快多了。
 ??__readgsqword(0x60) 從GS寄存器里讀取數值
查殺建議
??回想我們注射器實現的過程中所調用的函數,與正常的注入似乎沒有太大的區別,像CreateRemoteProcess這種危險函數殺軟抓的很嚴,可以被替換掉。整個過程沒有發現LoadLibraryA函數。但這個樣本有明顯的特征:解析PE結構,所以當我們遇到這種樣本的時候,可以考慮為反射式DLL注入。
 ??用IDA逆向程序時,解析PE結構的過程,編程中會出現很多+0xnumber 這里number代表十六進制數字。
 ??在磁盤發現某dll很可疑,如果該dll被注入到進程中已經在運行,刪除或移動dll時會彈窗正在某某程序中使用,可以看到它是不是注入的dll。
 ??另外注入到進程的惡意Dll程序執行錯誤時崩潰并不會導致被注入程序實際的崩潰。
參考
來源https://bbs.pediy.com/thread-224241.htm
總結
 
                            
                        - 上一篇: 学硕论文选题计算机,研究生计算机论文题目
- 下一篇: IP地址是什么
