引言
本文主要介紹“反射型dll注入”及“柔性加載”技術(shù)。
反射型dll注入 為什么需要反射型dll注入
常規(guī)的dll注入代碼如下:
iNt main(int argc, char *argv[]) { HANDLE processHandle; PVOID remoteBuffer; wchar_t dllPath[] = TEXT("C:\experiments\evilm64.dll"); printf("Injecting DLL to PID: %in", atoi(argv[1])); processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Dword(atoi(argv[1]))); remoteBuffer = VirtualAllocEx(processHandle, null, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)dllPath, sizeof dllPath, NULL); PTHREAD_START_ROUTINE threatStartRoutineAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "LoadLibraryW"); CreateRemoteThread(processHandle, NULL, 0, threatStartRoutineAddress, remoteBuffer, 0, NULL); CloseHandle(processHandle); return 0; }
主要做了幾件事情:
- 從磁盤(pán)讀取dll到wchar_t數(shù)組
- 將該payload數(shù)組寫(xiě)入目標(biāo)內(nèi)存
- 在目標(biāo)內(nèi)存中找到LoadLibraryW函數(shù)
- 通過(guò)CreateRemoteThread調(diào)用LoadLibraryW函數(shù),參數(shù)為dll在內(nèi)存中的地址。
這樣的操作模式有幾個(gè)很高危的點(diǎn)。首先,從磁盤(pán)讀取dll需要考慮dll的靜態(tài)免殺,對(duì)此我們可以直接寫(xiě)在裝載器中并加密。
其次,在目標(biāo)內(nèi)存中找到LoadLibraryW函數(shù),需要GetProcAddress LoadLibraryW,這種調(diào)用屬于很有特征的調(diào)用模式,容易被AV/EDR歸類。對(duì)此我們的解決措施就是接下來(lái)要提及的反射型dll注入技術(shù)。
最后,CreateRemoteThread進(jìn)行遠(yuǎn)程線程注入 行為本身就很高危,同時(shí)參數(shù)是LoadLibraryW的地址,一眼malware。
對(duì)此我們優(yōu)化調(diào)用,不再使用CreateRemoteThread進(jìn)而使用創(chuàng)建新進(jìn)程的方式結(jié)合反射型dll注入技術(shù)改變dll注入技術(shù)的調(diào)用模式。
實(shí)現(xiàn)思路
早期的dll注入實(shí)現(xiàn)原理:
![]()
上圖比較清楚的寫(xiě)了反射型dll注入的原理,1,2,3步由A向B線程寫(xiě)入dll。第四步調(diào)用B線程中的embedded bootstrApper code。最后通過(guò)bootstrapper shellcode調(diào)用dll的導(dǎo)出函數(shù)reflective loader。
reflective loader實(shí)際上是一個(gè)自己實(shí)現(xiàn)的LoadLibraryW函數(shù),從內(nèi)存中找到我們寫(xiě)入的dll并修復(fù)使其成為可以被正常使用的pe文件,最后調(diào)用dllmain實(shí)現(xiàn)我們的惡意功能。
我們的具體實(shí)現(xiàn)和上面早期的思路有所區(qū)別,首先我們不使用遠(yuǎn)程進(jìn)程/線程注入的方式,其次我們不需要bootstrapper shellcode這個(gè)部分,我們可以直接在加載器部分算出reflective loader在內(nèi)存中的地址,直接調(diào)用即可。
【一一幫助安全學(xué)習(xí),所有資源關(guān)注我,私信回復(fù)“資料”獲取一一】 ①網(wǎng)絡(luò)安全學(xué)習(xí)路線 ②20份滲透測(cè)試電子書(shū) ③安全攻防357頁(yè)筆記 ④50份安全攻防面試指南 ⑤安全紅隊(duì)滲透工具包 ⑥網(wǎng)絡(luò)安全必備書(shū)籍 ⑦100個(gè)漏洞實(shí)戰(zhàn)案例 ⑧安全大廠內(nèi)部視頻資源 ⑨歷年CTF奪旗賽題解析具體實(shí)現(xiàn) 加載器部分
![]()
首先shellcode使用AES解密,這部分添加了一些c的代碼加密
![]()
后來(lái)發(fā)現(xiàn)原本項(xiàng)目的release目錄下有Python/ target=_blank class=infotextkey>Python的加密腳本:
![]()
解密載入內(nèi)存后,使用GetReflectiveLoaderOffset計(jì)算出ReflectLoader函數(shù)的偏移:
![]()
最后創(chuàng)建線程調(diào)用ReflectLoader函數(shù)。
dll部分
ReflectiveLoader一共做了5件事:
一、 解析加載DLL所需kernel32.dll WINAPI的地址(例如VirtualAlloc, LoadLibraryA等),
通過(guò)關(guān)鍵函數(shù)的hash在內(nèi)存中搜索,函數(shù)hash:
![]()
遍歷內(nèi)存進(jìn)行搜索:
![]()
二、 將DLL及其相應(yīng)的節(jié)寫(xiě)入內(nèi)存中:
![]()
三、 建立DLL導(dǎo)入表,以便DLL可以調(diào)用ntdll.dll和kernel32.dll WINAPI
![]()
四、 修復(fù)重定位表:
![]()
五、 調(diào)用DLL的入口點(diǎn):
![]()
最終我們的惡意代碼位于dllmain中,項(xiàng)目還是采用加載shellcode的方式上線cs。
柔性加載
限制使用具有RWX標(biāo)記的內(nèi)存,cs在4+可以直接進(jìn)行相關(guān)配置。
![]()
推薦配置:
set startrwx "false"; set userwx "false"; set cleanup "true"; set stomppe "true"; set obfuscate "true"; set sleep_mask "true"; set smartinject "true"; 牛刀小試 360
使用base64+xor混淆shellcode:
![]()
成功bypass:
![]()
![]()
火絨
和上述方法相同:
![]()
![]()
definder
加強(qiáng)shellcode的混淆:
std::string rest2_reference = "xxx", "==");
依舊報(bào)毒,但是類型發(fā)生改變了,說(shuō)明靜態(tài)的混淆有效果:
![]()
異或的操作,比較可疑,經(jīng)過(guò)測(cè)試發(fā)現(xiàn)是cs的shellcode出現(xiàn)在數(shù)組里就報(bào)毒,應(yīng)該是對(duì)內(nèi)存進(jìn)行的掃描。
所以我們可以使用《文章二》中提及的技術(shù)“規(guī)避常見(jiàn)的惡意API調(diào)用模式”,將shellcode分片直接寫(xiě)入連續(xù)內(nèi)存。
在測(cè)試的過(guò)程中發(fā)現(xiàn)莫名其妙的過(guò)了查殺:
![]()
很神奇,這段并沒(méi)有實(shí)現(xiàn)內(nèi)存的切片寫(xiě)入,因?yàn)閟hellcode的大小沒(méi)有達(dá)到4096,實(shí)際上相當(dāng)于直接分配了個(gè)大小為4096的數(shù)組,寫(xiě)入了shellcode。
而且把這段代碼相同的格式放外面就不行,個(gè)人感覺(jué)definder還是沒(méi)有去檢查內(nèi)存。
可能是有語(yǔ)義分析的引擎,這次剛好繞過(guò)了語(yǔ)義分析。
macfee
同上方法可以成功bypass:
![]()
正常執(zhí)行命令:
![]()
kasperky Endpoint 11 for windows
用過(guò)macfee和definder的demo2測(cè)試失敗,注釋掉代碼加載部分不報(bào)毒,改用apc和創(chuàng)建進(jìn)程的的方式加載內(nèi)存:
SIZE_T shellSize = 4096; STARTUPINFOA si = { 0 }; PROCESS_INFORMATION pi = { 0 }; CreateProcessA("C:\Windows\System32\calc.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); HANDLE victimProcess = pi.hProcess; HANDLE threadHandle = pi.hThread; LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress; WriteProcessMemory(victimProcess, shellAddress, exec, shellSize, NULL); QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL); ResumeThread(threadHandle);
依舊不行:
![]()
使用syscall調(diào)用NtCreateThreadEx。這里被坑了,WaitForSingleObject要使用,不然會(huì)異步,沒(méi)法上線:
ANtCTE( &hThread, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), (LPTHREAD_START_ROUTINE)exec, NULL, NULL, 0, 0, 0, nullptr ); WaitForSingleObject(hThread, INFINITE);
能看到效果,行為檢測(cè)依舊有問(wèn)題:
![]()
但漏洞利用防御已經(jīng)沒(méi)有相關(guān)報(bào)警:
![]()
懷疑是cs本身流量特征的問(wèn)題,為了驗(yàn)證我使用卡巴斯基本身的功能禁用了網(wǎng)絡(luò)請(qǐng)求:
![]()
確實(shí)不殺也不報(bào)警了,確定是cs通信的問(wèn)題。
ESET Endpoint Security
demo3報(bào)警,并且明顯檢測(cè)到網(wǎng)絡(luò)連接行為
![]()
靜態(tài)沒(méi)有問(wèn)題
![]()
主要應(yīng)該還是在對(duì)內(nèi)存的檢測(cè),而且感覺(jué)已經(jīng)執(zhí)行到了發(fā)包
![]()
下面根據(jù)《三》中的“beacon的內(nèi)存加密”對(duì)demo3進(jìn)行優(yōu)化,使用RefleXXion工具的第二種將內(nèi)存設(shè)為NO_ACCESS并通過(guò)注冊(cè)異常處理還原的方式進(jìn)行免殺。
![]()
設(shè)置流量的白名單:
![]()
關(guān)閉web控制后成功并上線
![]()
eset在持續(xù)在掃描內(nèi)存,但一直沒(méi)有權(quán)限,一直觸發(fā)異常,無(wú)法進(jìn)入正常的后門(mén)邏輯
![]()
能繞過(guò)內(nèi)存的檢測(cè),但無(wú)法正常使用
![]()
感覺(jué)ESET一直在我程序里進(jìn)行內(nèi)存操作,訪問(wèn)到了不可訪問(wèn)的內(nèi)存段。
可能ESET的機(jī)制是一直在掃描程序內(nèi)存,也可能是想要做一些hook。
我嘗試使用RefleXXion的第一種方法,將shellcode加密并使屬性為RW或RX的方式加載shellcode:
![]()
可以成功上線,并且正常使用:
![]()
總結(jié)
該系列文章所有的bypass edr方法都只在用戶態(tài)進(jìn)行操作,已經(jīng)能規(guī)避大多數(shù)AV/EDR的檢測(cè)。但不乏一些edr進(jìn)行了比較多的內(nèi)核層面的限制,如炭黑、fireeye等。






