diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md index 9a07928..a99957b 100644 --- a/_src/Anti-Debug/techniques/assembly.md +++ b/_src/Anti-Debug/techniques/assembly.md @@ -18,6 +18,7 @@ tags: assembly * [6. Instruction Counting](#instruction-counting) * [7. POPF and Trap Flag](#popf_and_trap_flag) * [8. Instruction Prefixes](#instruction_prefixes) +* [9. POPF and CPUID](#popf_and_cpuid) * [Mitigations](#mitigations)
@@ -404,6 +405,49 @@ bool IsDebugged()
+ +
+

9. POPF and CPUID

+ +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. + +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. + +C/C++ Code +

+ +{% highlight c %} + +bool IsDebugged() +{ +__try + { + __asm + { + pushfd + popfd + cpuid + C7 B2 + } + return true; + } +__except(GetExceptionCode() == EXCEPTION_SINGLE_STEP +? EXCEPTION_EXECUTE_HANDLER +: EXCEPTION_CONTINUE_EXECUTION) + { + return false; + } +} + +{% endhighlight %} + +
+

Mitigations

* During debugging: diff --git a/_src/Anti-Debug/techniques/interactive.md b/_src/Anti-Debug/techniques/interactive.md index ed2bc71..92db5a9 100644 --- a/_src/Anti-Debug/techniques/interactive.md +++ b/_src/Anti-Debug/techniques/interactive.md @@ -17,6 +17,7 @@ tags: interactive * [5. EnumWindows() and SuspendThread()](#suspendthread) * [6. SwitchDesktop()](#switchdesktop) * [7. OutputDebugString()](#outputdebugstring) +* [8. Process Suspension Detection](#processsuspensiondetection) * [Mitigations](#mitigations)
@@ -47,7 +48,7 @@ In the example below, we run the second instance of our process which tries to a #define EVENT_SELFDBG_EVENT_NAME L"SelfDebugging" bool IsDebugged() -{ +{ WCHAR wszFilePath[MAX_PATH], wszCmdLine[MAX_PATH]; STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; @@ -430,6 +431,74 @@ bool IsDebugged()

+ +
+ +
+ +

8. Process Suspension Detection

+ +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). + +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 %} + + +
+

Mitigations

During debugging, it is better to skip suspicious function calls (e.g. fill them with NOPs). @@ -446,4 +515,5 @@ If you write an anti-anti-debug solution, all the following functions can be hoo * user32!SwitchDesktop * kernel32!OutputDebugStringW -Hooked functions can check input arguments and modify the original function behavior. \ No newline at end of file +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_FREEZE flag. \ No newline at end of file diff --git a/_src/Anti-Debug/techniques/misc.md b/_src/Anti-Debug/techniques/misc.md index 864d4d5..103e260 100644 --- a/_src/Anti-Debug/techniques/misc.md +++ b/_src/Anti-Debug/techniques/misc.md @@ -19,6 +19,7 @@ tags: misc * [5. DbgSetDebugFilterState()](#dbgsetdebugfilterstate) * [6. NtYieldExecution() / SwitchToThread()](#switchtothread) * [7. VirtualAlloc() / GetWriteWatch()](#getwritewatch) +* [8. IFEO removal](#ifeo-removal) * [Mitigations](#mitigations)
@@ -486,6 +487,66 @@ bool Generic::CheckWrittenPages2() const {
+
+ +

8. IFEO removal

+ +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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Check if the following process names are being removed (also check if the current process name is being removed)
Pathvalue
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Optionsrundll32.exe
regsvr32.exe
dllhost.exe
msiexec.exe
explorer.exe
odbcconf.exe
+ +C/C++ Code +

+ +{% highlight c %} + +int main() { + // Define the registry key path for explorer.exe under IFEO + LPCWSTR keyPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\explorer.exe"; + + // Attempt to open the registry key + LONG result = RegDeleteKey(HKEY_LOCAL_MACHINE, keyPath); + + if (result == ERROR_SUCCESS) { + std::cout << "IFEO entry for explorer.exe successfully deleted." << std::endl; + } else { + std::cerr << "Failed to delete IFEO entry for explorer.exe. Error code: " << result << std::endl; + } + + return 0; +} + +{% endhighlight %} +

Mitigations

During debugging: Fill anti-debug pr anti-traced checks with NOPs. diff --git a/_src/Evasions/techniques/generic-os-queries.md b/_src/Evasions/techniques/generic-os-queries.md index bc3729e..8aa629b 100644 --- a/_src/Evasions/techniques/generic-os-queries.md +++ b/_src/Evasions/techniques/generic-os-queries.md @@ -370,6 +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.
@@ -436,10 +437,29 @@ int gensandbox_one_cpu_GetSystemInfo() { {% 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 + } +} + +{% endhighlight %} + +
+ Countermeasures

diff --git a/_src/Evasions/techniques/os-features.md b/_src/Evasions/techniques/os-features.md index e865229..1da6761 100644 --- a/_src/Evasions/techniques/os-features.md +++ b/_src/Evasions/techniques/os-features.md @@ -13,6 +13,8 @@ tags: os-features [1. Checking debug privileges](#checking-debug-privileges)
[2. Using unbalanced stack](#using-unbalanced-stack) +
+ [3. Detect Wine](#detect-wine)
[Countermeasures](#countermeasures)
@@ -95,6 +97,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 +207,69 @@ bool Cuckoo::CheckUnbalancedStack() const {

Signature recommendations are not provided as it's pretty tricky to track such a behavior on malware side. +
+ +

3. Detect Wine

+ +
+ +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. + +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. +

Countermeasures


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. Function - Wine + Wine kernel32.dll wine_get_unix_file_name - ntdll.dll + ntdll.dll wine_get_version + + wine_get_host_version +
diff --git a/_src/Evasions/techniques/registry.md b/_src/Evasions/techniques/registry.md index b6ce042..907772b 100644 --- a/_src/Evasions/techniques/registry.md +++ b/_src/Evasions/techniques/registry.md @@ -13,6 +13,8 @@ tags: registry [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) +
+ [3. Check if VBAWarnings enabled](#check-if-vbawarning-enabled)
[Countermeasures](#countermeasures)
@@ -735,6 +737,58 @@ then it's an indication of application trying to use the evasion technique. +
+ +
+

3. Check if VBAWarnings enabled

+ +“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. + + +
+ +Code sample +

+ +{% highlight c %} + +// Function to check if VBScript warnings are enabled in Office +bool IsVBScriptWarningEnabled() { + HKEY hKey; + LPCWSTR keyPath = L"SOFTWARE\\Microsoft\\Office\\\\Common\\Security"; + LPCWSTR valueName = L"VBAScriptWarnings"; + + // Open the registry key + LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, keyPath, 0, KEY_READ, &hKey); + if (result == ERROR_SUCCESS) { + DWORD dwType; + DWORD dwValue; + DWORD dwSize = sizeof(DWORD); + + // Query the value of VBAScriptWarnings + result = RegQueryValueEx(hKey, valueName, NULL, &dwType, reinterpret_cast(&dwValue), &dwSize); + if (result == ERROR_SUCCESS && dwType == REG_DWORD && dwValue == 1) { + // VBScript warnings are enabled + RegCloseKey(hKey); + return true; + } + RegCloseKey(hKey); + } + return false; +} + +int main() { + if (IsVBScriptWarningEnabled()) { + std::cout << "VBScript warnings are enabled in Office." << std::endl; + } else { + std::cout << "VBScript warnings are not enabled in Office." << std::endl; + } + + return 0; +} + +{% endhighlight %}

Countermeasures