+
+To detect the use of a VM in a sandbox, malware could check the behavior of the CPU after the trap flag is set.
+The trap flag is a flag bit in the processor's flags register that is used for debugging purposes.
+When the Trap Flag is set, the processor enters a single-step mode, which causes it to execute only one instruction at a time and then generate a debug exception.
+
+For example, The popf instruction pops the top value from the stack and loads it into the flags register. Based on the value on the stack that has the Trap Flag bit set, the processor enters a single-step mode (SINGLE_STEP_EXCEPTION) after executing the next instruction.
+
+But the next instruction is cpuid which behaves differently in VM. When in a physical machine, this exception stops the CPU execution to allow the contents of the registers and memory location to be examined by the exception handler after thecpuid instruction which moves away the instruction pointer from the next bytes. In a VM, executing cpuid will result in a VM exit. During the VM exit the hypervisor will carry out its usual tasks of emulating the behaviors of the cpuid instruction which will make the Trap Flag be delayed and the code execution will continue to the next instruction with the C7 B2 bytes. This results in an exception because of an illegal instruction exception.
+
+
+
+
+This evasion depends on having the thread creation flag THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE that Microsoft added into 19H1. This flag makes the thread ignore any PsSuspendProcess API being called.
+
+Then an attcker can create two threads with this flag, one of which keeps suspending the other one until the suspend counter limit which is 127 is reached (suspend count is a signed 8-bit value).
+
+When you get to the limit, every call for PsSuspendProcess doesn’t increment the suspend counter and returns STATUS_SUSPEND_COUNT_EXCEEDED. But what happens if someone calls NtResumeProcess? It decrements the suspend count! So when someone decides to suspend and resume the thread, they’ll leave the count in a state it wasn’t previously in.
+
+
+
+
+
+This technique involves modifying the Image File Execution Options (IFEO) registry key, which is used by the Windows operating system to set debugging options for executable files.
+When an executable file is launched, the operating system checks the corresponding IFEO registry key for any specified debugging options. If the key exists, the operating system launches the specified debugger instead of the executable file.
+Removing these entries further complicates analysis efforts by eliminating one potential avenue for researchers to attach debuggers to the malware process.
+
During debugging: Fill anti-debug pr anti-traced checks with NOPs.
- For anti-anti-debug tool development:
+For anti-anti-debug tool development:
1. For FindWindow(): Hook user32!NtUserFindWindowEx(). In the hook, call the original user32!NtUserFindWindowEx() function. If it is called from the debugged process and the parent process looks suspicious, then return unsuccessfully from the hook.
@@ -499,7 +508,7 @@ During debugging: Fill anti-debug pr anti-traced checks with NOPs.
* SystemSessionProcessInformation
* SystemExtendedProcessInformation
- and the process name looks suspicious, then the hook must modify the process name.
+ and the process name looks suspicious, then the hook must modify the process name.
3. For Selectors: No mitigations.
diff --git a/_src/Evasions/index.html b/_src/Evasions/index.html
index 4e9f7e1..3044f5f 100644
--- a/_src/Evasions/index.html
+++ b/_src/Evasions/index.html
@@ -28,7 +28,7 @@
}
.flex-container > div {
- flex: 0 0 20%;
+ flex: 0 0 25%;
text-align: center;
margin-bottom: 30px;
display: grid;
@@ -122,7 +122,7 @@
Filesystem
-
+
+
+
+
+The MulDiv [API](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-muldiv) is being called with specific arguments (`MulDiv(1, 0x80000000, 0x80000000)`) which should logically return 1 - however, due to a bug with the ancient implementation on Windows, it returns 2.
+
+There are more known evasion methods to detect Wine like the good old check of searching for the existence of one of Wine’s exclusive APIs such as `kernel32.dll!wine_get_unix_file_name` or `ntdll.dll!wine_get_host_version`).
+
+
+
+
+One way to evade Windows Defender is by adding its processes and paths to its exclusion list.
+It is being done by adding the values to the registry keys: HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Exclusions\Paths and HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Exclusions\Processes.
+
+
+
+
+
+EtwEventWrite is a function used for logging events in the Event Tracing for Windows (ETW) system.
+Malware may patch this function to prevent its own activities from being logged or detected by security monitoring tools that rely on ETW for event logging and analysis.
+By intercepting calls to EtwEventWrite, malware can suppress or alter the events that would otherwise be recorded, making it more difficult for security analysts to detect and analyze the malware's behavior.
+
+
+
+C/C++ Code
+
+
+{% highlight c %}
+
+void Patch_DbgBreakPoint()
+{
+HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
+if (!hNtdll)
+return;
+
+ FARPROC pDbgBreakPoint = GetProcAddress(hNtdll, "EtwEventWrite");
+ if (!pDbgBreakPoint)
+ return;
+
+ DWORD dwOldProtect;
+ if (!VirtualProtect(pDbgBreakPoint, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
+ return;
+
+ *(PBYTE)pDbgBreakPoint = (BYTE)0xC3; // ret
+}
+
+{% endhighlight %}
+
+
+
+
versus Detect Wine:If Using Wine, hook MulDiv to return 2 or modify the implementation as it works in Windows.
+
+
+
diff --git a/_src/Evasions/techniques/generic-os-queries.md b/_src/Evasions/techniques/generic-os-queries.md
index bc3729e..aab9efe 100644
--- a/_src/Evasions/techniques/generic-os-queries.md
+++ b/_src/Evasions/techniques/generic-os-queries.md
@@ -1,7 +1,7 @@
---
layout: post
title: "Evasions: Generic OS queries"
-categories: evasions
+categories: evasions
tags: generic-os-queries
---
@@ -9,25 +9,25 @@ tags: generic-os-queries
[Generic OS queries](#generic-os-queries)
- [1. Check if the username is specific](#check-if-username-is-specific)
+[1. Check if the username is specific](#check-if-username-is-specific)
- [2. Check if the computer name is specific](#check-if-computer-name-is-specific)
+[2. Check if the computer name is specific](#check-if-computer-name-is-specific)
- [3. Check if the host name is specific](#check-if-host-name-is-specific)
+[3. Check if the host name is specific](#check-if-host-name-is-specific)
- [4. Check if the total RAM is low](#check-if-total-ram-is-low)
+[4. Check if the total RAM is low](#check-if-total-ram-is-low)
- [5. Check if the screen resolution is non-usual for host OS](#check-if-screen-res)
+[5. Check if the screen resolution is non-usual for host OS](#check-if-screen-res)
- [6. Check if the number of processors is low](#check-if-number-of-processors)
+[6. Check if the number of processors is low](#check-if-number-of-processors)
- [7. Check if the quantity of monitors is small](#check-if-quantity-of-monitors)
+[7. Check if the quantity of monitors is small](#check-if-quantity-of-monitors)
- [8. Check if the hard disk drive size and free space are small](#check-if-hard-disk)
+[8. Check if the hard disk drive size and free space are small](#check-if-hard-disk)
- [9. Check if the system uptime is small](#check-if-system-uptime)
+[9. Check if the system uptime is small](#check-if-system-uptime)
- [10. Check if the OS was boot from virtual hard disk (Win8+)](#check-if-os-was-boot-from-virtual-disk)
+[10. Check if the OS was boot from virtual hard disk (Win8+)](#check-if-os-was-boot-from-virtual-disk)
[Countermeasures](#countermeasures)
@@ -65,9 +65,9 @@ Function used:
{% highlight c %}
bool is_user_name_match(const std::string &s) {
- auto out_length = MAX_PATH;
- std::vector user_name(out_length, 0);
- ::GetUserNameA((LPSTR)user_name.data(), (LPDWORD)&out_length);
+auto out_length = MAX_PATH;
+std::vector user_name(out_length, 0);
+::GetUserNameA((LPSTR)user_name.data(), (LPDWORD)&out_length);
return (!lstrcmpiA((LPCSTR)user_name.data(), s.c_str()));
}
@@ -185,9 +185,9 @@ Function used:
{% highlight c %}
bool is_computer_name_match(const std::string &s) {
- auto out_length = MAX_PATH;
- std::vector comp_name(out_length, 0);
- ::GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length);
+auto out_length = MAX_PATH;
+std::vector comp_name(out_length, 0);
+::GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length);
return (!lstrcmpiA((LPCSTR)comp_name.data(), s.c_str()));
}
@@ -251,9 +251,9 @@ Function used:
{% highlight c %}
bool is_host_name_match(const std::string &s) {
- auto out_length = MAX_PATH;
- std::vector dns_host_name(out_length, 0);
- ::GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length);
+auto out_length = MAX_PATH;
+std::vector dns_host_name(out_length, 0);
+::GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length);
return (!lstrcmpiA((LPCSTR)dns_host_name.data(), s.c_str()));
}
@@ -304,8 +304,8 @@ Functions used to get executable path:
{% highlight c %}
BOOL memory_space()
{
- DWORDLONG ullMinRam = (1024LL * (1024LL * (1024LL * 1LL))); // 1GB
-
+DWORDLONG ullMinRam = (1024LL * (1024LL * (1024LL * 1LL))); // 1GB
+
MEMORYSTATUSEX statex = {0};
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex); // calls NtQuerySystemInformation
@@ -370,7 +370,7 @@ Function used:
Besides this function numbers of processors can be obtained from PEB, via either asm inline or intrinsic function, see code samples below.
-
+It can be also obtained (ActiveProcessorCount flag) from the KUSER_SHARED_DATA structure.
Code sample (variant 1, al-khaser project)
@@ -381,9 +381,9 @@ Besides this function numbers of processors can be obtained from PEB, via either
BOOL NumberOfProcessors()
{
#if defined (ENV64BIT)
- PULONG ulNumberProcessors = (PULONG)(__readgsqword(0x30) + 0xB8);
+PULONG ulNumberProcessors = (PULONG)(__readgsqword(0x30) + 0xB8);
#elif defined(ENV32BIT)
- PULONG ulNumberProcessors = (PULONG)(__readfsdword(0x30) + 0x64);
+PULONG ulNumberProcessors = (PULONG)(__readfsdword(0x30) + 0x64);
#endif
if (*ulNumberProcessors < 2)
@@ -405,9 +405,9 @@ BOOL NumberOfProcessors()
__declspec(naked)
DWORD get_number_of_processors() {
- __asm {
- ; get pointer to Process Environment Block (PEB)
- mov eax, fs:0x30
+__asm {
+; get pointer to Process Environment Block (PEB)
+mov eax, fs:0x30
; read the field containing target number
mov eax, [eax + 0x64]
@@ -429,9 +429,9 @@ DWORD get_number_of_processors() {
{% highlight c %}
int gensandbox_one_cpu_GetSystemInfo() {
- SYSTEM_INFO si;
- GetSystemInfo(&si);
- return si.dwNumberOfProcessors < 2 ? TRUE : FALSE;
+SYSTEM_INFO si;
+GetSystemInfo(&si);
+return si.dwNumberOfProcessors < 2 ? TRUE : FALSE;
}
{% endhighlight %}
@@ -440,6 +440,24 @@ int gensandbox_one_cpu_GetSystemInfo() {
+Code sample (variant 4)
+
+
+{% highlight c %}
+
+__declspec(naked)
+DWORD get_number_of_active_processors() {
+__asm {
+mov eax, 0x7ffe0000 ; KUSER_SHARED_DATA structure fixed address
+mov eax, byte ptr [eax+0x3c0] ; checking ActiveProcessorCount
+retn ; return from function
+}
+}
+
+{% endhighlight %}
+
+
+
Countermeasures
@@ -466,17 +484,17 @@ Functions used:
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
- int *Count = (int*)dwData;
- (*Count)++;
- return TRUE;
+int *Count = (int*)dwData;
+(*Count)++;
+return TRUE;
}
int MonitorCount()
{
- int Count = 0;
- if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&Count))
- return Count;
- return -1; // signals an error
+int Count = 0;
+if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&Count))
+return Count;
+return -1; // signals an error
}
{% endhighlight %}
@@ -508,8 +526,8 @@ Functions used:
{% highlight c %}
int gensandbox_drive_size() {
- GET_LENGTH_INFORMATION size;
- DWORD lpBytesReturned;
+GET_LENGTH_INFORMATION size;
+DWORD lpBytesReturned;
HANDLE drive = CreateFile("\\\\.\\PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (drive == INVALID_HANDLE_VALUE) {
@@ -540,7 +558,7 @@ int gensandbox_drive_size() {
{% highlight c %}
int gensandbox_drive_size2() {
- ULARGE_INTEGER total_bytes;
+ULARGE_INTEGER total_bytes;
if (GetDiskFreeSpaceExA("C:\\", NULL, &total_bytes, NULL))
{
@@ -567,7 +585,7 @@ int gensandbox_drive_size2() {
PARTITION_INFO_EX
-Against checking free space: patch/hook NtQueryVolumeInformationFile to process these classes:
+Against checking free space: patch/hook NtQueryVolumeInformationFile to process these classes:
diff --git a/_src/Evasions/techniques/registry.md b/_src/Evasions/techniques/registry.md
index b6ce042..26e7b48 100644
--- a/_src/Evasions/techniques/registry.md
+++ b/_src/Evasions/techniques/registry.md
@@ -2,7 +2,7 @@
layout: post
title: "Evasions: Registry"
title-image: "/assets/icons/registry.svg"
-categories: evasions
+categories: evasions
tags: registry
---
@@ -10,9 +10,11 @@ tags: registry
[Registry detection methods](#registry-detection-methods)
- [1. Check if particular registry paths exist](#check-if-particular-registry-paths-exist)
+[1. Check if particular registry paths exist](#check-if-particular-registry-paths-exist)
- [2. Check if particular registry keys contain specified strings](#check-if-keys-contain-strings)
+[2. Check if particular registry keys contain specified strings](#check-if-keys-contain-strings)
+
+[3. Check if VBAWarnings enabled](#check-if-vbawarning-enabled)
[Countermeasures](#countermeasures)
@@ -60,13 +62,13 @@ Take a look at [title section](#registry-detection-methods) to get the list of u
/* sample of usage: see detection of VirtualBox in the table below to check registry path */
int vbox_reg_key7() {
- return pafish_exists_regkey(HKEY_LOCAL_MACHINE, "HARDWARE\\ACPI\\FADT\\VBOX__");
+return pafish_exists_regkey(HKEY_LOCAL_MACHINE, "HARDWARE\\ACPI\\FADT\\VBOX__");
}
/* code is taken from "pafish" project, see references on the parent page */
int pafish_exists_regkey(HKEY hKey, char * regkey_s) {
- HKEY regkey;
- LONG ret;
+HKEY regkey;
+LONG ret;
/* regkey_s == "HARDWARE\\ACPI\\FADT\\VBOX__"; */
if (pafish_iswow64()) {
@@ -342,16 +344,16 @@ Take a look at [title section](#registry-detection-methods) to get the list of u
{% highlight c %}
/* sample of usage: see detection of VirtualBox in the table below to check registry path and key values */
int vbox_reg_key2() {
- return pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE, "HARDWARE\\Description\\System", "SystemBiosVersion", "VBOX");
+return pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE, "HARDWARE\\Description\\System", "SystemBiosVersion", "VBOX");
}
/* code is taken from "pafish" project, see references on the parent page */
int pafish_exists_regkey_value_str(HKEY hKey, char * regkey_s, char * value_s, char * lookup) {
- /*
- regkey_s == "HARDWARE\\Description\\System";
- value_s == "SystemBiosVersion";
- lookup == "VBOX";
- */
+/*
+regkey_s == "HARDWARE\\Description\\System";
+value_s == "SystemBiosVersion";
+lookup == "VBOX";
+*/
HKEY regkey;
LONG ret;
@@ -737,14 +739,27 @@ then it's an indication of application trying to use the evasion technique.
+
+
+
+
+
+
+“Enable all macros” prompt in Office documents means the macros can be executed without any user interaction. This behavior is common for sandboxes.
+A malware can use that in order to check if it is running on a sandbox checking the flag in the registry keys SOFTWARE\Microsoft\Office\\Word\Security\VBAWarnings while the version is between 12.0 to 19.0.
+
+
+
This evasion depends on having the thread creation flag THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE that Microsoft added into 19H1. This flag makes the thread ignore any PsSuspendProcess API being called.
-Then an attcker can create two threads with this flag, one of which keeps suspending the other one until the suspend counter limit which is 127 is reached (suspend count is a signed 8-bit value).
+Then an attacker can create two threads with this flag, one of which keeps suspending the other one until the suspend counter limit which is 127 is reached (suspend count is a signed 8-bit value).
-When you get to the limit, every call for PsSuspendProcess doesn’t increment the suspend counter and returns STATUS_SUSPEND_COUNT_EXCEEDED. But what happens if someone calls NtResumeProcess? It decrements the suspend count! So when someone decides to suspend and resume the thread, they’ll leave the count in a state it wasn’t previously in.
+When you get to the limit, every call for PsSuspendProcess doesn’t increment the suspend counter and returns STATUS_SUSPEND_COUNT_EXCEEDED. But what happens if someone calls NtResumeProcess? It decrements the suspend count!
+So when someone decides to suspend and resume the thread, they’ll leave the count in a state it wasn’t previously in.
-
+C/C++ Code
+
+
+{% highlight c %}
+
+// Maximum suspend count before STATUS_SUSPEND_COUNT_EXCEEDED error is returned
+#define MAX_SUSPEND_COUNT 127
+
+// Function to spawn the thread that suspends the other thread
+DWORD WINAPI SuspendThreadFunction(LPVOID lpParam) {
+ HANDLE hThread = (HANDLE)lpParam;
+ while (true) {
+ // Suspend the other thread
+ SuspendThread(hThread);
+ // Sleep for some time before resuming the other thread
+ Sleep(1000);
+ }
+ return 0;
+}
+
+int main() {
+ // Create two threads
+ HANDLE hThread1 = NULL, hThread2 = NULL;
+ hThread1 = CreateThread(NULL, 0, SuspendThreadFunction, &hThread2, THREAD_CREATE_FLAGS_BYPASS_PROCESS_SUSPEND, NULL);
+ hThread2 = CreateThread(NULL, 0, SuspendThreadFunction, &hThread1, THREAD_CREATE_FLAGS_BYPASS_PROCESS_SUSPEND, NULL);
+
+ // Loop to periodically suspend the second thread and check if it's externally suspended
+ while (true) {
+ // Suspend the second thread
+ SuspendThread(hThread2);
+ // Check if the last error is STATUS_SUSPEND_COUNT_EXCEEDED
+ DWORD lastError = GetLastError();
+ if (lastError != STATUS_SUSPEND_COUNT_EXCEEDED) {
+ printf("Thread is externally suspended!\n");
+ // Terminate the process
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ // Sleep for some time before the next check
+ Sleep(2000);
+ }
+ // Close thread handles
+ CloseHandle(hThread1);
+ CloseHandle(hThread2);
+
+ return 0;
+}
+
+
+{% endhighlight %}
+
+
+
@@ -494,12 +495,63 @@ This technique involves modifying the Image File Execution Options (IFEO) regist
When an executable file is launched, the operating system checks the corresponding IFEO registry key for any specified debugging options. If the key exists, the operating system launches the specified debugger instead of the executable file.
Removing these entries further complicates analysis efforts by eliminating one potential avenue for researchers to attach debuggers to the malware process.
+
+
+
Check if the following process names are being removed (also check if the current process name is being removed)
During debugging: Fill anti-debug pr anti-traced checks with NOPs.
-For anti-anti-debug tool development:
+ For anti-anti-debug tool development:
1. For FindWindow(): Hook user32!NtUserFindWindowEx(). In the hook, call the original user32!NtUserFindWindowEx() function. If it is called from the debugged process and the parent process looks suspicious, then return unsuccessfully from the hook.
@@ -508,7 +560,7 @@ For anti-anti-debug tool development:
* SystemSessionProcessInformation
* SystemExtendedProcessInformation
- and the process name looks suspicious, then the hook must modify the process name.
+ and the process name looks suspicious, then the hook must modify the process name.
3. For Selectors: No mitigations.
diff --git a/_src/Evasions/techniques/AVBypass.md b/_src/Evasions/techniques/AVBypass.md
deleted file mode 100644
index 8d0a28a..0000000
--- a/_src/Evasions/techniques/AVBypass.md
+++ /dev/null
@@ -1,92 +0,0 @@
----
-layout: post
-title: "Evasions: Bypassing Security Solutions"
-title-image: "/assets/icons/AVBypass.svg"
-categories: evasions
-tags: AVBypass
----
-
-
-
-
-
-The MulDiv [API](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-muldiv) is being called with specific arguments (`MulDiv(1, 0x80000000, 0x80000000)`) which should logically return 1 - however, due to a bug with the ancient implementation on Windows, it returns 2.
-
-There are more known evasion methods to detect Wine like the good old check of searching for the existence of one of Wine’s exclusive APIs such as `kernel32.dll!wine_get_unix_file_name` or `ntdll.dll!wine_get_host_version`).
-
-
-
-
-One way to evade Windows Defender is by adding its processes and paths to its exclusion list.
-It is being done by adding the values to the registry keys: HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Exclusions\Paths and HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Exclusions\Processes.
-
-
-
-
-
-EtwEventWrite is a function used for logging events in the Event Tracing for Windows (ETW) system.
-Malware may patch this function to prevent its own activities from being logged or detected by security monitoring tools that rely on ETW for event logging and analysis.
-By intercepting calls to EtwEventWrite, malware can suppress or alter the events that would otherwise be recorded, making it more difficult for security analysts to detect and analyze the malware's behavior.
-
-
-
-C/C++ Code
-
-
-{% highlight c %}
-
-void Patch_DbgBreakPoint()
-{
-HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
-if (!hNtdll)
-return;
-
- FARPROC pDbgBreakPoint = GetProcAddress(hNtdll, "EtwEventWrite");
- if (!pDbgBreakPoint)
- return;
-
- DWORD dwOldProtect;
- if (!VirtualProtect(pDbgBreakPoint, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
- return;
-
- *(PBYTE)pDbgBreakPoint = (BYTE)0xC3; // ret
-}
-
-{% endhighlight %}
-
-
-
-
versus Detect Wine:If Using Wine, hook MulDiv to return 2 or modify the implementation as it works in Windows.
-
-
-
diff --git a/_src/Evasions/techniques/generic-os-queries.md b/_src/Evasions/techniques/generic-os-queries.md
index aab9efe..d3a62e6 100644
--- a/_src/Evasions/techniques/generic-os-queries.md
+++ b/_src/Evasions/techniques/generic-os-queries.md
@@ -1,7 +1,7 @@
---
layout: post
title: "Evasions: Generic OS queries"
-categories: evasions
+categories: evasions
tags: generic-os-queries
---
@@ -9,25 +9,25 @@ tags: generic-os-queries
[Generic OS queries](#generic-os-queries)
-[1. Check if the username is specific](#check-if-username-is-specific)
+ [1. Check if the username is specific](#check-if-username-is-specific)
-[2. Check if the computer name is specific](#check-if-computer-name-is-specific)
+ [2. Check if the computer name is specific](#check-if-computer-name-is-specific)
-[3. Check if the host name is specific](#check-if-host-name-is-specific)
+ [3. Check if the host name is specific](#check-if-host-name-is-specific)
-[4. Check if the total RAM is low](#check-if-total-ram-is-low)
+ [4. Check if the total RAM is low](#check-if-total-ram-is-low)
-[5. Check if the screen resolution is non-usual for host OS](#check-if-screen-res)
+ [5. Check if the screen resolution is non-usual for host OS](#check-if-screen-res)
-[6. Check if the number of processors is low](#check-if-number-of-processors)
+ [6. Check if the number of processors is low](#check-if-number-of-processors)
-[7. Check if the quantity of monitors is small](#check-if-quantity-of-monitors)
+ [7. Check if the quantity of monitors is small](#check-if-quantity-of-monitors)
-[8. Check if the hard disk drive size and free space are small](#check-if-hard-disk)
+ [8. Check if the hard disk drive size and free space are small](#check-if-hard-disk)
-[9. Check if the system uptime is small](#check-if-system-uptime)
+ [9. Check if the system uptime is small](#check-if-system-uptime)
-[10. Check if the OS was boot from virtual hard disk (Win8+)](#check-if-os-was-boot-from-virtual-disk)
+ [10. Check if the OS was boot from virtual hard disk (Win8+)](#check-if-os-was-boot-from-virtual-disk)
[Countermeasures](#countermeasures)
@@ -65,9 +65,9 @@ Function used:
{% highlight c %}
bool is_user_name_match(const std::string &s) {
-auto out_length = MAX_PATH;
-std::vector user_name(out_length, 0);
-::GetUserNameA((LPSTR)user_name.data(), (LPDWORD)&out_length);
+ auto out_length = MAX_PATH;
+ std::vector user_name(out_length, 0);
+ ::GetUserNameA((LPSTR)user_name.data(), (LPDWORD)&out_length);
return (!lstrcmpiA((LPCSTR)user_name.data(), s.c_str()));
}
@@ -185,9 +185,9 @@ Function used:
{% highlight c %}
bool is_computer_name_match(const std::string &s) {
-auto out_length = MAX_PATH;
-std::vector comp_name(out_length, 0);
-::GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length);
+ auto out_length = MAX_PATH;
+ std::vector comp_name(out_length, 0);
+ ::GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length);
return (!lstrcmpiA((LPCSTR)comp_name.data(), s.c_str()));
}
@@ -251,9 +251,9 @@ Function used:
{% highlight c %}
bool is_host_name_match(const std::string &s) {
-auto out_length = MAX_PATH;
-std::vector dns_host_name(out_length, 0);
-::GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length);
+ auto out_length = MAX_PATH;
+ std::vector dns_host_name(out_length, 0);
+ ::GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length);
return (!lstrcmpiA((LPCSTR)dns_host_name.data(), s.c_str()));
}
@@ -304,8 +304,8 @@ Functions used to get executable path:
{% highlight c %}
BOOL memory_space()
{
-DWORDLONG ullMinRam = (1024LL * (1024LL * (1024LL * 1LL))); // 1GB
-
+ DWORDLONG ullMinRam = (1024LL * (1024LL * (1024LL * 1LL))); // 1GB
+
MEMORYSTATUSEX statex = {0};
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex); // calls NtQuerySystemInformation
@@ -371,6 +371,7 @@ Function used:
Besides this function numbers of processors can be obtained from PEB, via either asm inline or intrinsic function, see code samples below.
It can be also obtained (ActiveProcessorCount flag) from the KUSER_SHARED_DATA structure.
+
Code sample (variant 1, al-khaser project)
@@ -381,9 +382,9 @@ It can be also obtained (ActiveProcessorCount flag) from the KUSER_SHARED_DATA s
BOOL NumberOfProcessors()
{
#if defined (ENV64BIT)
-PULONG ulNumberProcessors = (PULONG)(__readgsqword(0x30) + 0xB8);
+ PULONG ulNumberProcessors = (PULONG)(__readgsqword(0x30) + 0xB8);
#elif defined(ENV32BIT)
-PULONG ulNumberProcessors = (PULONG)(__readfsdword(0x30) + 0x64);
+ PULONG ulNumberProcessors = (PULONG)(__readfsdword(0x30) + 0x64);
#endif
if (*ulNumberProcessors < 2)
@@ -405,9 +406,9 @@ PULONG ulNumberProcessors = (PULONG)(__readfsdword(0x30) + 0x64);
__declspec(naked)
DWORD get_number_of_processors() {
-__asm {
-; get pointer to Process Environment Block (PEB)
-mov eax, fs:0x30
+ __asm {
+ ; get pointer to Process Environment Block (PEB)
+ mov eax, fs:0x30
; read the field containing target number
mov eax, [eax + 0x64]
@@ -429,35 +430,37 @@ mov eax, fs:0x30
{% highlight c %}
int gensandbox_one_cpu_GetSystemInfo() {
-SYSTEM_INFO si;
-GetSystemInfo(&si);
-return si.dwNumberOfProcessors < 2 ? TRUE : FALSE;
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ return si.dwNumberOfProcessors < 2 ? TRUE : FALSE;
}
{% endhighlight %}
-Credits for this code sample: pafish project
-
-
-
Code sample (variant 4)
{% highlight c %}
__declspec(naked)
-DWORD get_number_of_active_processors() {
-__asm {
-mov eax, 0x7ffe0000 ; KUSER_SHARED_DATA structure fixed address
-mov eax, byte ptr [eax+0x3c0] ; checking ActiveProcessorCount
-retn ; return from function
-}
+ DWORD get_number_of_active_processors() {
+ __asm {
+ mov eax, 0x7ffe0000 ; KUSER_SHARED_DATA structure fixed address
+ mov eax, byte ptr [eax+0x3c0] ; checking ActiveProcessorCount
+ retn ; return from function
+ }
}
{% endhighlight %}
+
+
+Credits for this code sample: pafish project
+
+
+
Countermeasures
@@ -484,17 +487,17 @@ Functions used:
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
-int *Count = (int*)dwData;
-(*Count)++;
-return TRUE;
+ int *Count = (int*)dwData;
+ (*Count)++;
+ return TRUE;
}
int MonitorCount()
{
-int Count = 0;
-if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&Count))
-return Count;
-return -1; // signals an error
+ int Count = 0;
+ if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&Count))
+ return Count;
+ return -1; // signals an error
}
{% endhighlight %}
@@ -526,8 +529,8 @@ Functions used:
{% highlight c %}
int gensandbox_drive_size() {
-GET_LENGTH_INFORMATION size;
-DWORD lpBytesReturned;
+ GET_LENGTH_INFORMATION size;
+ DWORD lpBytesReturned;
HANDLE drive = CreateFile("\\\\.\\PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (drive == INVALID_HANDLE_VALUE) {
@@ -558,7 +561,7 @@ DWORD lpBytesReturned;
{% highlight c %}
int gensandbox_drive_size2() {
-ULARGE_INTEGER total_bytes;
+ ULARGE_INTEGER total_bytes;
if (GetDiskFreeSpaceExA("C:\\", NULL, &total_bytes, NULL))
{
@@ -585,7 +588,7 @@ ULARGE_INTEGER total_bytes;
PARTITION_INFO_EX
-Against checking free space: patch/hook NtQueryVolumeInformationFile to process these classes:
+Against checking free space: patch/hook NtQueryVolumeInformationFile to process these classes:
diff --git a/_src/Evasions/techniques/os-features.md b/_src/Evasions/techniques/os-features.md
index e865229..8fb59c3 100644
--- a/_src/Evasions/techniques/os-features.md
+++ b/_src/Evasions/techniques/os-features.md
@@ -14,6 +14,9 @@ tags: os-features
[2. Using unbalanced stack](#using-unbalanced-stack)
+ [3. Detect Wine](#detect-wine)
+
+
[Countermeasures](#countermeasures)
[Credits](#credits)
@@ -95,6 +98,7 @@ BOOL CanOpenCsrss()
+
Signature recommendations
If OpenProcess requests all the possible rights when opening one of the critical system processes — it's a strong indicator of malware trying to apply this evasion technique.
@@ -204,12 +208,68 @@ bool Cuckoo::CheckUnbalancedStack() const {
Signature recommendations are not provided as it's pretty tricky to track such a behavior on malware side.
+
+
+
+
+
+
+The MulDiv [API](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-muldiv) is being called with specific arguments (`MulDiv(1, 0x80000000, 0x80000000)`) which should logically return 1 - however, due to a bug with the ancient implementation on Windows, it returns 2.
+
+There are more known evasion methods to detect Wine like the good old check of searching for the existence of one of Wine’s exclusive APIs such as `kernel32.dll!wine_get_unix_file_name` or `ntdll.dll!wine_get_host_version`).
+
+Code sample
+
+
+
+
+{% highlight c %}
+
+int Check_MulDiv_1() {
+ // Call MulDiv with specific arguments
+ int result = MulDiv(1, 0x80000000, 0x80000000);
+
+ // Check if the result matches the expected value
+ if (result != 2) {
+ std::cout << "MulDiv evasion method detected: Wine environment." << std::endl;
+ } else {
+ std::cout << "MulDiv evasion method not detected." << std::endl;
+ }
+
+ return 0;
+}
+
+int Check_MulDiv_2() {
+ // Check for the existence of Wine's exclusive APIs
+ HMODULE hKernel32 = GetModuleHandle("kernel32.dll");
+ FARPROC wineGetUnixFileName = GetProcAddress(hKernel32, "wine_get_unix_file_name");
+ HMODULE hNtdll = GetModuleHandle("ntdll.dll");
+ FARPROC wineGetHostVersion = GetProcAddress(hNtdll, "wine_get_host_version");
+
+ if (wineGetUnixFileName || wineGetHostVersion) {
+ std::cout << "Wine's exclusive APIs detected: Wine environment." << std::endl;
+ } else {
+ std::cout << "Wine's exclusive APIs not detected." << std::endl;
+ }
+
+ return 0;
+}
+
+{% endhighlight %}
+
+
+
+Signature recommendations
+
+Check if `MulDiv(1, 0x80000000, 0x80000000)` is being called
+
versus checking debug privileges: hook OpenProcess and track for critical system processes PIDs — then return an error.
versus using unbalanced stack: 1) stack adjusting before function call; 2) kernel-mode hooking.
+
versus Detect Wine: If Using Wine, hook MulDiv to return 2 or modify the implementation as it works in Windows.
diff --git a/_src/Evasions/techniques/registry.md b/_src/Evasions/techniques/registry.md
index 26e7b48..04f2174 100644
--- a/_src/Evasions/techniques/registry.md
+++ b/_src/Evasions/techniques/registry.md
@@ -2,7 +2,7 @@
layout: post
title: "Evasions: Registry"
title-image: "/assets/icons/registry.svg"
-categories: evasions
+categories: evasions
tags: registry
---
@@ -10,12 +10,13 @@ tags: registry
[Registry detection methods](#registry-detection-methods)
-[1. Check if particular registry paths exist](#check-if-particular-registry-paths-exist)
+ [1. Check if particular registry paths exist](#check-if-particular-registry-paths-exist)
-[2. Check if particular registry keys contain specified strings](#check-if-keys-contain-strings)
+ [2. Check if particular registry keys contain specified strings](#check-if-keys-contain-strings)
-[3. Check if VBAWarnings enabled](#check-if-vbawarning-enabled)
+ [3. Check if VBAWarnings enabled](#check-if-vbawarning-enabled)
+
[Countermeasures](#countermeasures)
[Credits](#credits)
@@ -62,13 +63,13 @@ Take a look at [title section](#registry-detection-methods) to get the list of u
/* sample of usage: see detection of VirtualBox in the table below to check registry path */
int vbox_reg_key7() {
-return pafish_exists_regkey(HKEY_LOCAL_MACHINE, "HARDWARE\\ACPI\\FADT\\VBOX__");
+ return pafish_exists_regkey(HKEY_LOCAL_MACHINE, "HARDWARE\\ACPI\\FADT\\VBOX__");
}
/* code is taken from "pafish" project, see references on the parent page */
int pafish_exists_regkey(HKEY hKey, char * regkey_s) {
-HKEY regkey;
-LONG ret;
+ HKEY regkey;
+ LONG ret;
/* regkey_s == "HARDWARE\\ACPI\\FADT\\VBOX__"; */
if (pafish_iswow64()) {
@@ -344,16 +345,16 @@ Take a look at [title section](#registry-detection-methods) to get the list of u
{% highlight c %}
/* sample of usage: see detection of VirtualBox in the table below to check registry path and key values */
int vbox_reg_key2() {
-return pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE, "HARDWARE\\Description\\System", "SystemBiosVersion", "VBOX");
+ return pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE, "HARDWARE\\Description\\System", "SystemBiosVersion", "VBOX");
}
/* code is taken from "pafish" project, see references on the parent page */
int pafish_exists_regkey_value_str(HKEY hKey, char * regkey_s, char * value_s, char * lookup) {
-/*
-regkey_s == "HARDWARE\\Description\\System";
-value_s == "SystemBiosVersion";
-lookup == "VBOX";
-*/
+ /*
+ regkey_s == "HARDWARE\\Description\\System";
+ value_s == "SystemBiosVersion";
+ lookup == "VBOX";
+ */
HKEY regkey;
LONG ret;
@@ -737,9 +738,6 @@ then it's an indication of application trying to use the evasion technique.
-
-
-
@@ -748,18 +746,60 @@ then it's an indication of application trying to use the evasion technique.
“Enable all macros” prompt in Office documents means the macros can be executed without any user interaction. This behavior is common for sandboxes.
A malware can use that in order to check if it is running on a sandbox checking the flag in the registry keys SOFTWARE\Microsoft\Office\\Word\Security\VBAWarnings while the version is between 12.0 to 19.0.
-
-
-This technique is similar to 7. POPF and Trap Flag.
+This technique is similar to 7. POPF and Trap Flag.
To detect the use of a VM in a sandbox, malware could check the behavior of the CPU after the trap flag is set.
The trap flag is a flag bit in the processor's flags register that is used for debugging purposes.
When the Trap Flag is set, the processor enters a single-step mode, which causes it to execute only one instruction at a time and then generate a debug exception.
@@ -426,25 +426,26 @@ But the next instruction is cpuid which behaves differently in VM. When
bool IsDebugged()
{
__try
-{
-__asm
-{
-pushfd
-popfd
-cpuid
-C7 B2
-}
-return true;
-}
+ {
+ __asm
+ {
+ pushfd
+ popfd
+ cpuid
+ C7 B2
+ }
+ return true;
+ }
__except(GetExceptionCode() == EXCEPTION_SINGLE_STEP
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_EXECUTION)
-{
-return false;
-}
+ {
+ return false;
+ }
}
{% endhighlight %}
+
diff --git a/_src/Anti-Debug/techniques/interactive.md b/_src/Anti-Debug/techniques/interactive.md
index 27e2d41..92db5a9 100644
--- a/_src/Anti-Debug/techniques/interactive.md
+++ b/_src/Anti-Debug/techniques/interactive.md
@@ -438,7 +438,7 @@ bool IsDebugged()
-This evasion depends on having the thread creation flag THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE that Microsoft added into 19H1. This flag makes the thread ignore any PsSuspendProcess API being called.
+This evasion depends on having the thread creation flag THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE that Microsoft added into Windows 10, version 1903 (19H1). This flag makes the thread ignore any PsSuspendProcess API being called.
Then an attacker can create two threads with this flag, one of which keeps suspending the other one until the suspend counter limit which is 127 is reached (suspend count is a signed 8-bit value).
@@ -516,4 +516,4 @@ If you write an anti-anti-debug solution, all the following functions can be hoo
* kernel32!OutputDebugStringW
Hooked functions can check input arguments and modify the original function behavior.
-To circumvent the Process Suspension detection evasion, you can hook NtCreateThread to omit the THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZEIt flag.
\ No newline at end of file
+To circumvent the Process Suspension detection evasion, you can hook NtCreateThread to omit the THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE flag.
\ No newline at end of file
diff --git a/_src/Evasions/index.html b/_src/Evasions/index.html
index 3044f5f..4e9f7e1 100644
--- a/_src/Evasions/index.html
+++ b/_src/Evasions/index.html
@@ -28,7 +28,7 @@
}
.flex-container > div {
- flex: 0 0 25%;
+ flex: 0 0 20%;
text-align: center;
margin-bottom: 30px;
display: grid;
@@ -122,7 +122,7 @@
Filesystem
-
+
diff --git a/_src/Evasions/techniques/generic-os-queries.md b/_src/Evasions/techniques/generic-os-queries.md
index d3a62e6..8aa629b 100644
--- a/_src/Evasions/techniques/generic-os-queries.md
+++ b/_src/Evasions/techniques/generic-os-queries.md
@@ -437,6 +437,11 @@ int gensandbox_one_cpu_GetSystemInfo() {
{% endhighlight %}
+
+Credits for this code sample: pafish project
+
+
+
Code sample (variant 4)
@@ -455,12 +460,6 @@ __declspec(naked)
-
-
-Credits for this code sample: pafish project
-
-
-
Countermeasures
diff --git a/_src/Evasions/techniques/os-features.md b/_src/Evasions/techniques/os-features.md
index 8fb59c3..2e46c2a 100644
--- a/_src/Evasions/techniques/os-features.md
+++ b/_src/Evasions/techniques/os-features.md
@@ -216,7 +216,8 @@ Signature recommendations are not provided as it's pretty tricky to track such a
The MulDiv [API](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-muldiv) is being called with specific arguments (`MulDiv(1, 0x80000000, 0x80000000)`) which should logically return 1 - however, due to a bug with the ancient implementation on Windows, it returns 2.
-There are more known evasion methods to detect Wine like the good old check of searching for the existence of one of Wine’s exclusive APIs such as `kernel32.dll!wine_get_unix_file_name` or `ntdll.dll!wine_get_host_version`).
+There are more known evasion methods to detect Wine like the good old check of searching for the existence of one of Wine’s exclusive APIs such as `kernel32.dll!wine_get_unix_file_name` or `ntdll.dll!wine_get_host_version`) as also mentioned in Processes evasion techniques .
Code sample
@@ -261,7 +262,7 @@ int Check_MulDiv_2() {
Signature recommendations
-Check if `MulDiv(1, 0x80000000, 0x80000000)` is being called
+Check if `MulDiv(1, 0x80000000, 0x80000000)` is being called.
diff --git a/_src/Evasions/techniques/processes.md b/_src/Evasions/techniques/processes.md
index b59cdd8..43ed7ad 100644
--- a/_src/Evasions/techniques/processes.md
+++ b/_src/Evasions/techniques/processes.md
@@ -338,14 +338,17 @@ then it's an indication of application trying to use this evasion technique.
-This technique is similar to 7. POPF and Trap Flag.
+This technique is similar to [7. POPF and Trap Flag](#popf_and_trap_flag).
To detect the use of a VM in a sandbox, malware could check the behavior of the CPU after the trap flag is set.
The trap flag is a flag bit in the processor's flags register that is used for debugging purposes.
When the Trap Flag is set, the processor enters a single-step mode, which causes it to execute only one instruction at a time and then generate a debug exception.
@@ -426,22 +426,22 @@ But the next instruction is cpuid which behaves differently in VM. When
bool IsDebugged()
{
__try
- {
- __asm
- {
- pushfd
- popfd
- cpuid
- C7 B2
- }
- return true;
- }
+ {
+ __asm
+ {
+ pushfd
+ popfd
+ cpuid
+ C7 B2
+ }
+ return true;
+ }
__except(GetExceptionCode() == EXCEPTION_SINGLE_STEP
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_EXECUTION)
- {
- return false;
- }
+ {
+ return false;
+ }
}
{% endhighlight %}
diff --git a/_src/Anti-Debug/techniques/misc.md b/_src/Anti-Debug/techniques/misc.md
index f2bf9a1..103e260 100644
--- a/_src/Anti-Debug/techniques/misc.md
+++ b/_src/Anti-Debug/techniques/misc.md
@@ -495,7 +495,7 @@ This technique involves modifying the Image File Execution Options (IFEO) regist
When an executable file is launched, the operating system checks the corresponding IFEO registry key for any specified debugging options. If the key exists, the operating system launches the specified debugger instead of the executable file.
Removing these entries further complicates analysis efforts by eliminating one potential avenue for researchers to attach debuggers to the malware process.
-
+
Check if the following process names are being removed (also check if the current process name is being removed)
diff --git a/_src/Evasions/techniques/os-features.md b/_src/Evasions/techniques/os-features.md
index 2e46c2a..1da6761 100644
--- a/_src/Evasions/techniques/os-features.md
+++ b/_src/Evasions/techniques/os-features.md
@@ -16,7 +16,6 @@ tags: os-features
[3. Detect Wine](#detect-wine)
-
[Countermeasures](#countermeasures)
[Credits](#credits)
@@ -217,7 +216,7 @@ Signature recommendations are not provided as it's pretty tricky to track such a
The MulDiv [API](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-muldiv) is being called with specific arguments (`MulDiv(1, 0x80000000, 0x80000000)`) which should logically return 1 - however, due to a bug with the ancient implementation on Windows, it returns 2.
There are more known evasion methods to detect Wine like the good old check of searching for the existence of one of Wine’s exclusive APIs such as `kernel32.dll!wine_get_unix_file_name` or `ntdll.dll!wine_get_host_version`) as also mentioned in Processes evasion techniques .
+">Processes evasion techniques.
Code sample
@@ -237,7 +236,7 @@ int Check_MulDiv_1() {
std::cout << "MulDiv evasion method not detected." << std::endl;
}
- return 0;
+ return 0;
}
int Check_MulDiv_2() {
diff --git a/_src/Evasions/techniques/registry.md b/_src/Evasions/techniques/registry.md
index 04f2174..907772b 100644
--- a/_src/Evasions/techniques/registry.md
+++ b/_src/Evasions/techniques/registry.md
@@ -16,7 +16,6 @@ tags: registry
[3. Check if VBAWarnings enabled](#check-if-vbawarning-enabled)
-
[Countermeasures](#countermeasures)
[Credits](#credits)