From 6410860dcc43d0ac4ada4033a536688bac1b1b1e Mon Sep 17 00:00:00 2001 From: israelgu <35104766+israelgu@users.noreply.github.com> Date: Thu, 2 May 2024 11:37:06 +0300 Subject: [PATCH 1/4] added POPF and CPUID Added a new technique - POPF and CPUID that was on RaspberryRobin --- .idea/.gitignore | 3 + .idea/Evasions.iml | 9 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + _src/Anti-Debug/techniques/assembly.md | 15 ++ _src/Anti-Debug/techniques/interactive.md | 176 ++++++++++-------- _src/Anti-Debug/techniques/misc.md | 161 ++++++++-------- _src/Evasions/index.html | 17 +- _src/Evasions/techniques/AVBypass.md | 92 +++++++++ .../Evasions/techniques/generic-os-queries.md | 124 ++++++------ _src/Evasions/techniques/registry.md | 41 ++-- 12 files changed, 432 insertions(+), 226 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Evasions.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 _src/Evasions/techniques/AVBypass.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Evasions.iml b/.idea/Evasions.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Evasions.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..99de62e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md index 9a07928..b837f3f 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,20 @@ bool IsDebugged()
+ +
+

9. POPF and CPUID

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

Mitigations

* During debugging: diff --git a/_src/Anti-Debug/techniques/interactive.md b/_src/Anti-Debug/techniques/interactive.md index ed2bc71..35b859a 100644 --- a/_src/Anti-Debug/techniques/interactive.md +++ b/_src/Anti-Debug/techniques/interactive.md @@ -2,7 +2,7 @@ layout: post title: "Anti-Debug: Direct debugger interaction" title-image: "/assets/icons/interactive.svg" -categories: anti-debug +categories: anti-debug tags: interactive --- @@ -17,8 +17,9 @@ tags: interactive * [5. EnumWindows() and SuspendThread()](#suspendthread) * [6. SwitchDesktop()](#switchdesktop) * [7. OutputDebugString()](#outputdebugstring) +* [8. Process Suspension Detection](#processsuspensiondetection) * [Mitigations](#mitigations) -
+

@@ -48,10 +49,10 @@ In the example below, we run the second instance of our process which tries to a bool IsDebugged() { - WCHAR wszFilePath[MAX_PATH], wszCmdLine[MAX_PATH]; - STARTUPINFO si = { sizeof(si) }; - PROCESS_INFORMATION pi; - HANDLE hDbgEvent; +WCHAR wszFilePath[MAX_PATH], wszCmdLine[MAX_PATH]; +STARTUPINFO si = { sizeof(si) }; +PROCESS_INFORMATION pi; +HANDLE hDbgEvent; hDbgEvent = CreateEventW(NULL, FALSE, FALSE, EVENT_SELFDBG_EVENT_NAME); if (!hDbgEvent) @@ -73,11 +74,11 @@ bool IsDebugged() return false; } -bool EnableDebugPrivilege() +bool EnableDebugPrivilege() { - bool bResult = false; - HANDLE hToken = NULL; - DWORD ec = 0; +bool bResult = false; +HANDLE hToken = NULL; +DWORD ec = 0; do { @@ -105,25 +106,25 @@ bool EnableDebugPrivilege() int main(int argc, char **argv) { - if (argc < 2) - { - if (IsDebugged()) - ExitProcess(0); - } - else - { - DWORD dwParentPid = atoi(argv[1]); - HANDLE hEvent = OpenEventW(EVENT_MODIFY_STATE, FALSE, EVENT_SELFDBG_EVENT_NAME); - if (hEvent && EnableDebugPrivilege()) - { - if (FALSE == DebugActiveProcess(dwParentPid)) - SetEvent(hEvent); - else - DebugActiveProcessStop(dwParentPid); - } - ExitProcess(0); - } - +if (argc < 2) +{ +if (IsDebugged()) +ExitProcess(0); +} +else +{ +DWORD dwParentPid = atoi(argv[1]); +HANDLE hEvent = OpenEventW(EVENT_MODIFY_STATE, FALSE, EVENT_SELFDBG_EVENT_NAME); +if (hEvent && EnableDebugPrivilege()) +{ +if (FALSE == DebugActiveProcess(dwParentPid)) +SetEvent(hEvent); +else +DebugActiveProcessStop(dwParentPid); +} +ExitProcess(0); +} + // ... return 0; @@ -151,30 +152,30 @@ std::atomic g_bCtlCCatched{ false }; static LONG WINAPI CtrlEventExeptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - if (pExceptionInfo->ExceptionRecord->ExceptionCode == DBG_CONTROL_C) - { - g_bDebugged = true; - g_bCtlCCatched.store(true); - } - return EXCEPTION_CONTINUE_EXECUTION; +if (pExceptionInfo->ExceptionRecord->ExceptionCode == DBG_CONTROL_C) +{ +g_bDebugged = true; +g_bCtlCCatched.store(true); +} +return EXCEPTION_CONTINUE_EXECUTION; } static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) { - switch (fdwCtrlType) - { - case CTRL_C_EVENT: - g_bCtlCCatched.store(true); - return TRUE; - default: - return FALSE; - } +switch (fdwCtrlType) +{ +case CTRL_C_EVENT: +g_bCtlCCatched.store(true); +return TRUE; +default: +return FALSE; +} } bool IsDebugged() { - PVOID hVeh = nullptr; - BOOL bCtrlHadnlerSet = FALSE; +PVOID hVeh = nullptr; +BOOL bCtrlHadnlerSet = FALSE; __try { @@ -221,17 +222,17 @@ We can also detect whether a tool that hooks the user32!BlockInput() an bool IsHooked () { - BOOL bFirstResult = FALSE, bSecondResult = FALSE; - __try - { - bFirstResult = BlockInput(TRUE); - bSecondResult = BlockInput(TRUE); - } - __finally - { - BlockInput(FALSE); - } - return bFirstResult && bSecondResult; +BOOL bFirstResult = FALSE, bSecondResult = FALSE; +__try +{ +bFirstResult = BlockInput(TRUE); +bSecondResult = BlockInput(TRUE); +} +__finally +{ +BlockInput(FALSE); +} +return bFirstResult && bSecondResult; } {% endhighlight %} @@ -259,12 +260,12 @@ In the example, we hide the current thread from the debugger. This means that if bool AntiDebug() { - NTSTATUS status = ntdll::NtSetInformationThread( - NtCurrentThread, - ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, - NULL, - 0); - return status >= 0; +NTSTATUS status = ntdll::NtSetInformationThread( +NtCurrentThread, +ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, +NULL, +0); +return status >= 0; } {% endhighlight %} @@ -288,7 +289,7 @@ DWORD g_dwDebuggerProcessId = -1; BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { - DWORD dwProcessId = *(PDWORD)lParam; +DWORD dwProcessId = *(PDWORD)lParam; DWORD dwWindowProcessId; GetWindowThreadProcessId(hwnd, &dwWindowProcessId); @@ -312,14 +313,14 @@ BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) bool IsDebuggerProcess(DWORD dwProcessId) const { - EnumWindows(EnumWindowsProc, reinterpret_cast(&dwProcessId)); - return g_dwDebuggerProcessId == dwProcessId; +EnumWindows(EnumWindowsProc, reinterpret_cast(&dwProcessId)); +return g_dwDebuggerProcessId == dwProcessId; } bool SuspendDebuggerThread() { - THREADENTRY32 ThreadEntry = { 0 }; - ThreadEntry.dwSize = sizeof(THREADENTRY32); +THREADENTRY32 ThreadEntry = { 0 }; +ThreadEntry.dwSize = sizeof(THREADENTRY32); DWORD dwParentProcessId = process_helper::GetParentProcessId(GetCurrentProcessId()); if (-1 == dwParentProcessId) @@ -365,15 +366,15 @@ Further, the mouse and keyboard events from the debugged process desktop will no BOOL Switch() { - HDESK hNewDesktop = CreateDesktopA( - m_pcszNewDesktopName, - NULL, - NULL, - 0, - DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, - NULL); - if (!hNewDesktop) - return FALSE; +HDESK hNewDesktop = CreateDesktopA( +m_pcszNewDesktopName, +NULL, +NULL, +0, +DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, +NULL); +if (!hNewDesktop) +return FALSE; return SwitchDesktop(hNewDesktop); } @@ -397,8 +398,8 @@ The idea is simple. If a debugger is not present and kernel32!OutputDebugStr bool IsDebugged() { - if (IsWindowsVistaOrGreater()) - return false; +if (IsWindowsVistaOrGreater()) +return false; DWORD dwLastError = GetLastError(); OutputDebugString(L"AntiDebug_OutputDebugString_v1"); @@ -416,8 +417,8 @@ bool IsDebugged() bool IsDebugged() { - if (IsWindowsVistaOrGreater()) - return false; +if (IsWindowsVistaOrGreater()) +return false; DWORD dwErrVal = 0x666; SetLastError(dwErrVal); @@ -430,6 +431,18 @@ 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 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. + +
+ +

Mitigations

During debugging, it is better to skip suspicious function calls (e.g. fill them with NOPs). @@ -446,4 +459,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_FREEZEIt 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..3f0d9be 100644 --- a/_src/Anti-Debug/techniques/misc.md +++ b/_src/Anti-Debug/techniques/misc.md @@ -2,7 +2,7 @@ layout: post title: "Anti-Debug: Misc" title-image: "/assets/icons/misc.svg" -categories: anti-debug +categories: anti-debug tags: misc --- @@ -19,8 +19,9 @@ tags: misc * [5. DbgSetDebugFilterState()](#dbgsetdebugfilterstate) * [6. NtYieldExecution() / SwitchToThread()](#switchtothread) * [7. VirtualAlloc() / GetWriteWatch()](#getwritewatch) +* [8. IFEO removal](#ifeo-removal) * [Mitigations](#mitigations) -
+

@@ -45,26 +46,26 @@ The following functions can be used: {% highlight c %} const std::vector vWindowClasses = { - "antidbg", - "ID", // Immunity Debugger - "ntdll.dll", // peculiar name for a window class - "ObsidianGUI", - "OLLYDBG", - "Rock Debugger", - "SunAwtFrame", - "Qt5QWindowIcon" - "WinDbgFrameClass", // WinDbg - "Zeta Debugger", +"antidbg", +"ID", // Immunity Debugger +"ntdll.dll", // peculiar name for a window class +"ObsidianGUI", +"OLLYDBG", +"Rock Debugger", +"SunAwtFrame", +"Qt5QWindowIcon" +"WinDbgFrameClass", // WinDbg +"Zeta Debugger", }; bool IsDebugged() { - for (auto &sWndClass : vWindowClasses) - { - if (NULL != FindWindowA(sWndClass.c_str(), NULL)) - return true; - } - return false; +for (auto &sWndClass : vWindowClasses) +{ +if (NULL != FindWindowA(sWndClass.c_str(), NULL)) +return true; +} +return false; } {% endhighlight %} @@ -92,9 +93,9 @@ Then, the parent process ID can be obtained from the PROCESS_BASIC_INFORMATI bool IsDebugged() { - HWND hExplorerWnd = GetShellWindow(); - if (!hExplorerWnd) - return false; +HWND hExplorerWnd = GetShellWindow(); +if (!hExplorerWnd) +return false; DWORD dwExplorerProcessId; GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId); @@ -127,9 +128,9 @@ The parent process ID and the parent process name can be obtained using the DWORD GetParentProcessId(DWORD dwCurrentProcessId) { - DWORD dwParentProcessId = -1; - PROCESSENTRY32W ProcessEntry = { 0 }; - ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); +DWORD dwParentProcessId = -1; +PROCESSENTRY32W ProcessEntry = { 0 }; +ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(Process32FirstW(hSnapshot, &ProcessEntry)) @@ -150,8 +151,8 @@ DWORD GetParentProcessId(DWORD dwCurrentProcessId) bool IsDebugged() { - bool bDebugged = false; - DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); +bool bDebugged = false; +DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); PROCESSENTRY32 ProcessEntry = { 0 }; ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); @@ -187,11 +188,11 @@ exception, which can cause some unexpected behavior.

{% highlight nasm %} - xor eax, eax - push fs - pop ds -l1: xchg [eax], cl - xchg [eax], cl +xor eax, eax +push fs +pop ds +l1: xchg [eax], cl +xchg [eax], cl {% endhighlight %}
@@ -202,18 +203,18 @@ On the 64-bit versions of Windows, single-stepping through this code will cause

{% highlight nasm %} - xor eax, eax - push offset l2 - push d fs:[eax] - mov fs:[eax], esp - push fs - pop ss - xchg [eax], cl - xchg [eax], cl -l1: int 3 ;force exception to occur -l2: ;looks like it would be reached - ;if an exception occurs - ... +xor eax, eax +push offset l2 +push d fs:[eax] +mov fs:[eax], esp +push fs +pop ss +xchg [eax], cl +xchg [eax], cl +l1: int 3 ;force exception to occur +l2: ;looks like it would be reached +;if an exception occurs +... {% endhighlight %}
@@ -223,10 +224,10 @@ then when the "int 3" instruction is reached at l1 and the bre A variation of this technique detects the single-step event by simply checking if the assignment was successful. {% highlight nasm %} -push 3 -pop gs -mov ax, gs -cmp al, 3 +push 3 +pop gs +mov ax, gs +cmp al, 3 jne being_debugged {% endhighlight %} @@ -239,11 +240,11 @@ This code is also vulnerable to a race condition caused by a thread-switch event A variation of this technique solves that problem by waiting intentionally for a thread-switch event to occur. {% highlight nasm %} - push 3 - pop gs -l1: mov ax, gs - cmp al, 3 - je l1 +push 3 +pop gs +l1: mov ax, gs +cmp al, 3 +je l1 {% endhighlight %}
@@ -259,10 +260,10 @@ However, this code is vulnerable to the problem that it was trying to detect in bool IsTraced() { - __asm - { - push 3 - pop gs +__asm +{ +push 3 +pop gs __asm SeclectorsLbl: mov ax, gs @@ -279,7 +280,7 @@ bool IsTraced() return false; Selectors_Debugged: - return true; +return true; } {% endhighlight %} @@ -299,14 +300,14 @@ The debug functions such as ntdll!DbgPrint() and kernel32!OutputDeb bool IsDebugged() { - __try - { - RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); - } - __except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) - { - return false; - } +__try +{ +RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); +} +__except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) +{ +return false; +} return true; } @@ -328,7 +329,7 @@ The functions ntdll!DbgSetDebugFilterState() and ntdll!NtSetDebugFi bool IsDebugged() { - return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); +return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); } {% endhighlight %} @@ -354,12 +355,12 @@ In the example below, we define a one-byte counter (initialized with 0) which sh bool IsDebugged() { - BYTE ucCounter = 1; - for (int i = 0; i < 8; i++) - { - Sleep(0x0F); - ucCounter <<= (1 - SwitchToThread()); - } +BYTE ucCounter = 1; +for (int i = 0; i < 8; i++) +{ +Sleep(0x0F); +ucCounter <<= (1 - SwitchToThread()); +} return ucCounter == 0; } @@ -386,7 +387,7 @@ This feature can be used to track debuggers that may modify memory pages outside {% highlight c %} bool Generic::CheckWrittenPages1() const { - const int SIZE_TO_CHECK = 4096; +const int SIZE_TO_CHECK = 4096; PVOID* addresses = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); if (addresses == NULL) @@ -429,7 +430,7 @@ bool Generic::CheckWrittenPages1() const { {% highlight c %} bool Generic::CheckWrittenPages2() const { - BOOL result = FALSE, error = FALSE; +BOOL result = FALSE, error = FALSE; const int SIZE_TO_CHECK = 4096; @@ -485,12 +486,20 @@ bool Generic::CheckWrittenPages2() const { {% endhighlight %}
+
+ +

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

Mitigations

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 - +
@@ -152,7 +152,7 @@ - Global OS objects + Global OS objects
@@ -275,6 +275,17 @@ Human-like behavior + +

Go to the title page

diff --git a/_src/Evasions/techniques/AVBypass.md b/_src/Evasions/techniques/AVBypass.md new file mode 100644 index 0000000..8d0a28a --- /dev/null +++ b/_src/Evasions/techniques/AVBypass.md @@ -0,0 +1,92 @@ +--- +layout: post +title: "Evasions: Bypassing Security Solutions" +title-image: "/assets/icons/AVBypass.svg" +categories: evasions +tags: AVBypass +--- + +

Contents

+ +[Bypassing Security Solutions detection methods](#avbypass-detection-methods) +
+[1. Detect Wine](#detect-wine) +
+[2. Add to Windows defender exclusion list](#add-to-windows-defender-exclusion-list) +
+[3. Patch EtwEventWrite](#patch-etweventwrite) +
+[Signature recommendations](#signature-recommendations) +
+[Countermeasures](#countermeasures) +
+
+ +
+ +

Bypassing Security Solutions detection methods

+Techniques described in this group abuse different vendors and AVs and try to bypass them without being detected. +
+

1. 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`). + +
+ +

2. Add to Windows defender exclusion list

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

3. Patch EtwEventWrite

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

Signature recommendations

+
  • versus Detect Wine: Check if `MulDiv(1, 0x80000000, 0x80000000)` is being called
  • +
  • versus Add to Windows defender exclusion list Check if those registry keys are set with new values of untrusted files
  • +
  • versus Patch EtwEventWrite Check if first byte of EtwEventWrite is changed to C3
  • + +
    +

    Countermeasures

    + +
      +
    • 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:
    • FileFsSizeInformation
    • FileFsFullSizeInformation
    • @@ -591,8 +609,8 @@ Function used: {% highlight c %} bool Generic::CheckSystemUptime() const { - const DWORD uptime = 1000 * 60 * 12; // 12 minutes - return GetTickCount() < uptime; +const DWORD uptime = 1000 * 60 * 12; // 12 minutes +return GetTickCount() < uptime; } {% endhighlight %} @@ -604,8 +622,8 @@ bool Generic::CheckSystemUptime() const { #define MIN_UPTIME_MINUTES 12 BOOL uptime_check() { - ULONGLONG uptime_minutes = GetTickCount64() / (60 * 1000); - return uptime_minutes < MIN_UPTIME_MINUTES; +ULONGLONG uptime_minutes = GetTickCount64() / (60 * 1000); +return uptime_minutes < MIN_UPTIME_MINUTES; } {% endhighlight %}
      @@ -613,11 +631,11 @@ BOOL uptime_check() {% highlight c %} BOOL uptime_check2() { - SYSTEM_TIME_OF_DAY_INFORMATION SysTimeInfo; - ULONGLONG uptime_minutes; - NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0); - uptime_minutes = (SysTimeInfo.CurrentTime.QuadPart - SysTimeInfo.BootTime.QuadPart) / (60 * 1000 * 10000); - return uptime_minutes < MIN_UPTIME_MINUTES; +SYSTEM_TIME_OF_DAY_INFORMATION SysTimeInfo; +ULONGLONG uptime_minutes; +NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0); +uptime_minutes = (SysTimeInfo.CurrentTime.QuadPart - SysTimeInfo.BootTime.QuadPart) / (60 * 1000 * 10000); +return uptime_minutes < MIN_UPTIME_MINUTES; } {% endhighlight %} @@ -652,7 +670,7 @@ Take a look at the excerpt from malware

      Credits

      -Credits go to open-source projects from where code samples were taken: +Credits go to open-source projects from where code samples were taken:
      • al-khaser project on github
      • pafish project on github
      • 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.
        + +
        + +
        +

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

        Countermeasures

        Hook target functions and return appropriate results if indicators (registry strings from tables) are checked.
        + +

        Credits

        -Credits go to open-source project from where code samples were taken: +Credits go to open-source project from where code samples were taken: From 7a7d3ada7445cbc482f80614eba9631633f49dab Mon Sep 17 00:00:00 2001 From: israelgu <35104766+israelgu@users.noreply.github.com> Date: Mon, 6 May 2024 16:35:22 +0300 Subject: [PATCH 2/4] Fixed issues --- .idea/codeStyles/Project.xml | 117 +++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + _src/Anti-Debug/techniques/assembly.md | 28 +++ _src/Anti-Debug/techniques/interactive.md | 226 +++++++++++------- _src/Anti-Debug/techniques/misc.md | 204 ++++++++++------ _src/Evasions/techniques/AVBypass.md | 92 ------- .../Evasions/techniques/generic-os-queries.md | 127 +++++----- _src/Evasions/techniques/os-features.md | 60 +++++ _src/Evasions/techniques/registry.md | 80 +++++-- 9 files changed, 604 insertions(+), 335 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 _src/Evasions/techniques/AVBypass.md diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..4bec4ea --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,117 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md index b837f3f..1ddf931 100644 --- a/_src/Anti-Debug/techniques/assembly.md +++ b/_src/Anti-Debug/techniques/assembly.md @@ -409,6 +409,7 @@ bool IsDebugged()

        9. POPF and CPUID

        +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. @@ -417,6 +418,33 @@ For example, The popf instruction pops the top value from the stack and 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 %}

        diff --git a/_src/Anti-Debug/techniques/interactive.md b/_src/Anti-Debug/techniques/interactive.md index 35b859a..27e2d41 100644 --- a/_src/Anti-Debug/techniques/interactive.md +++ b/_src/Anti-Debug/techniques/interactive.md @@ -2,7 +2,7 @@ layout: post title: "Anti-Debug: Direct debugger interaction" title-image: "/assets/icons/interactive.svg" -categories: anti-debug +categories: anti-debug tags: interactive --- @@ -19,7 +19,7 @@ tags: interactive * [7. OutputDebugString()](#outputdebugstring) * [8. Process Suspension Detection](#processsuspensiondetection) * [Mitigations](#mitigations) -
        +

        @@ -48,11 +48,11 @@ 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; -HANDLE hDbgEvent; +{ + WCHAR wszFilePath[MAX_PATH], wszCmdLine[MAX_PATH]; + STARTUPINFO si = { sizeof(si) }; + PROCESS_INFORMATION pi; + HANDLE hDbgEvent; hDbgEvent = CreateEventW(NULL, FALSE, FALSE, EVENT_SELFDBG_EVENT_NAME); if (!hDbgEvent) @@ -74,11 +74,11 @@ HANDLE hDbgEvent; return false; } -bool EnableDebugPrivilege() +bool EnableDebugPrivilege() { -bool bResult = false; -HANDLE hToken = NULL; -DWORD ec = 0; + bool bResult = false; + HANDLE hToken = NULL; + DWORD ec = 0; do { @@ -106,25 +106,25 @@ DWORD ec = 0; int main(int argc, char **argv) { -if (argc < 2) -{ -if (IsDebugged()) -ExitProcess(0); -} -else -{ -DWORD dwParentPid = atoi(argv[1]); -HANDLE hEvent = OpenEventW(EVENT_MODIFY_STATE, FALSE, EVENT_SELFDBG_EVENT_NAME); -if (hEvent && EnableDebugPrivilege()) -{ -if (FALSE == DebugActiveProcess(dwParentPid)) -SetEvent(hEvent); -else -DebugActiveProcessStop(dwParentPid); -} -ExitProcess(0); -} - + if (argc < 2) + { + if (IsDebugged()) + ExitProcess(0); + } + else + { + DWORD dwParentPid = atoi(argv[1]); + HANDLE hEvent = OpenEventW(EVENT_MODIFY_STATE, FALSE, EVENT_SELFDBG_EVENT_NAME); + if (hEvent && EnableDebugPrivilege()) + { + if (FALSE == DebugActiveProcess(dwParentPid)) + SetEvent(hEvent); + else + DebugActiveProcessStop(dwParentPid); + } + ExitProcess(0); + } + // ... return 0; @@ -152,30 +152,30 @@ std::atomic g_bCtlCCatched{ false }; static LONG WINAPI CtrlEventExeptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { -if (pExceptionInfo->ExceptionRecord->ExceptionCode == DBG_CONTROL_C) -{ -g_bDebugged = true; -g_bCtlCCatched.store(true); -} -return EXCEPTION_CONTINUE_EXECUTION; + if (pExceptionInfo->ExceptionRecord->ExceptionCode == DBG_CONTROL_C) + { + g_bDebugged = true; + g_bCtlCCatched.store(true); + } + return EXCEPTION_CONTINUE_EXECUTION; } static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) { -switch (fdwCtrlType) -{ -case CTRL_C_EVENT: -g_bCtlCCatched.store(true); -return TRUE; -default: -return FALSE; -} + switch (fdwCtrlType) + { + case CTRL_C_EVENT: + g_bCtlCCatched.store(true); + return TRUE; + default: + return FALSE; + } } bool IsDebugged() { -PVOID hVeh = nullptr; -BOOL bCtrlHadnlerSet = FALSE; + PVOID hVeh = nullptr; + BOOL bCtrlHadnlerSet = FALSE; __try { @@ -222,17 +222,17 @@ We can also detect whether a tool that hooks the user32!BlockInput() an bool IsHooked () { -BOOL bFirstResult = FALSE, bSecondResult = FALSE; -__try -{ -bFirstResult = BlockInput(TRUE); -bSecondResult = BlockInput(TRUE); -} -__finally -{ -BlockInput(FALSE); -} -return bFirstResult && bSecondResult; + BOOL bFirstResult = FALSE, bSecondResult = FALSE; + __try + { + bFirstResult = BlockInput(TRUE); + bSecondResult = BlockInput(TRUE); + } + __finally + { + BlockInput(FALSE); + } + return bFirstResult && bSecondResult; } {% endhighlight %} @@ -260,12 +260,12 @@ In the example, we hide the current thread from the debugger. This means that if bool AntiDebug() { -NTSTATUS status = ntdll::NtSetInformationThread( -NtCurrentThread, -ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, -NULL, -0); -return status >= 0; + NTSTATUS status = ntdll::NtSetInformationThread( + NtCurrentThread, + ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, + NULL, + 0); + return status >= 0; } {% endhighlight %} @@ -289,7 +289,7 @@ DWORD g_dwDebuggerProcessId = -1; BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { -DWORD dwProcessId = *(PDWORD)lParam; + DWORD dwProcessId = *(PDWORD)lParam; DWORD dwWindowProcessId; GetWindowThreadProcessId(hwnd, &dwWindowProcessId); @@ -313,14 +313,14 @@ DWORD dwProcessId = *(PDWORD)lParam; bool IsDebuggerProcess(DWORD dwProcessId) const { -EnumWindows(EnumWindowsProc, reinterpret_cast(&dwProcessId)); -return g_dwDebuggerProcessId == dwProcessId; + EnumWindows(EnumWindowsProc, reinterpret_cast(&dwProcessId)); + return g_dwDebuggerProcessId == dwProcessId; } bool SuspendDebuggerThread() { -THREADENTRY32 ThreadEntry = { 0 }; -ThreadEntry.dwSize = sizeof(THREADENTRY32); + THREADENTRY32 ThreadEntry = { 0 }; + ThreadEntry.dwSize = sizeof(THREADENTRY32); DWORD dwParentProcessId = process_helper::GetParentProcessId(GetCurrentProcessId()); if (-1 == dwParentProcessId) @@ -366,15 +366,15 @@ Further, the mouse and keyboard events from the debugged process desktop will no BOOL Switch() { -HDESK hNewDesktop = CreateDesktopA( -m_pcszNewDesktopName, -NULL, -NULL, -0, -DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, -NULL); -if (!hNewDesktop) -return FALSE; + HDESK hNewDesktop = CreateDesktopA( + m_pcszNewDesktopName, + NULL, + NULL, + 0, + DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, + NULL); + if (!hNewDesktop) + return FALSE; return SwitchDesktop(hNewDesktop); } @@ -398,8 +398,8 @@ The idea is simple. If a debugger is not present and kernel32!OutputDebugStr bool IsDebugged() { -if (IsWindowsVistaOrGreater()) -return false; + if (IsWindowsVistaOrGreater()) + return false; DWORD dwLastError = GetLastError(); OutputDebugString(L"AntiDebug_OutputDebugString_v1"); @@ -417,8 +417,8 @@ return false; bool IsDebugged() { -if (IsWindowsVistaOrGreater()) -return false; + if (IsWindowsVistaOrGreater()) + return false; DWORD dwErrVal = 0x666; SetLastError(dwErrVal); @@ -432,16 +432,72 @@ return false;
        +
        + +
        +

        8. Process Suspension Detection

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

        Mitigations

        During debugging, it is better to skip suspicious function calls (e.g. fill them with NOPs). @@ -460,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_FREEZEIt flag. \ No newline at end of file diff --git a/_src/Anti-Debug/techniques/misc.md b/_src/Anti-Debug/techniques/misc.md index 3f0d9be..f2bf9a1 100644 --- a/_src/Anti-Debug/techniques/misc.md +++ b/_src/Anti-Debug/techniques/misc.md @@ -2,7 +2,7 @@ layout: post title: "Anti-Debug: Misc" title-image: "/assets/icons/misc.svg" -categories: anti-debug +categories: anti-debug tags: misc --- @@ -21,7 +21,7 @@ tags: misc * [7. VirtualAlloc() / GetWriteWatch()](#getwritewatch) * [8. IFEO removal](#ifeo-removal) * [Mitigations](#mitigations) -
        +

        @@ -46,26 +46,26 @@ The following functions can be used: {% highlight c %} const std::vector vWindowClasses = { -"antidbg", -"ID", // Immunity Debugger -"ntdll.dll", // peculiar name for a window class -"ObsidianGUI", -"OLLYDBG", -"Rock Debugger", -"SunAwtFrame", -"Qt5QWindowIcon" -"WinDbgFrameClass", // WinDbg -"Zeta Debugger", + "antidbg", + "ID", // Immunity Debugger + "ntdll.dll", // peculiar name for a window class + "ObsidianGUI", + "OLLYDBG", + "Rock Debugger", + "SunAwtFrame", + "Qt5QWindowIcon" + "WinDbgFrameClass", // WinDbg + "Zeta Debugger", }; bool IsDebugged() { -for (auto &sWndClass : vWindowClasses) -{ -if (NULL != FindWindowA(sWndClass.c_str(), NULL)) -return true; -} -return false; + for (auto &sWndClass : vWindowClasses) + { + if (NULL != FindWindowA(sWndClass.c_str(), NULL)) + return true; + } + return false; } {% endhighlight %} @@ -93,9 +93,9 @@ Then, the parent process ID can be obtained from the PROCESS_BASIC_INFORMATI bool IsDebugged() { -HWND hExplorerWnd = GetShellWindow(); -if (!hExplorerWnd) -return false; + HWND hExplorerWnd = GetShellWindow(); + if (!hExplorerWnd) + return false; DWORD dwExplorerProcessId; GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId); @@ -128,9 +128,9 @@ The parent process ID and the parent process name can be obtained using the DWORD GetParentProcessId(DWORD dwCurrentProcessId) { -DWORD dwParentProcessId = -1; -PROCESSENTRY32W ProcessEntry = { 0 }; -ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + DWORD dwParentProcessId = -1; + PROCESSENTRY32W ProcessEntry = { 0 }; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(Process32FirstW(hSnapshot, &ProcessEntry)) @@ -151,8 +151,8 @@ ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); bool IsDebugged() { -bool bDebugged = false; -DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); + bool bDebugged = false; + DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); PROCESSENTRY32 ProcessEntry = { 0 }; ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); @@ -188,11 +188,11 @@ exception, which can cause some unexpected behavior.

        {% highlight nasm %} -xor eax, eax -push fs -pop ds -l1: xchg [eax], cl -xchg [eax], cl + xor eax, eax + push fs + pop ds +l1: xchg [eax], cl + xchg [eax], cl {% endhighlight %}
        @@ -203,18 +203,18 @@ On the 64-bit versions of Windows, single-stepping through this code will cause

        {% highlight nasm %} -xor eax, eax -push offset l2 -push d fs:[eax] -mov fs:[eax], esp -push fs -pop ss -xchg [eax], cl -xchg [eax], cl -l1: int 3 ;force exception to occur -l2: ;looks like it would be reached -;if an exception occurs -... + xor eax, eax + push offset l2 + push d fs:[eax] + mov fs:[eax], esp + push fs + pop ss + xchg [eax], cl + xchg [eax], cl +l1: int 3 ;force exception to occur +l2: ;looks like it would be reached + ;if an exception occurs + ... {% endhighlight %}
        @@ -224,10 +224,10 @@ then when the "int 3" instruction is reached at l1 and the bre A variation of this technique detects the single-step event by simply checking if the assignment was successful. {% highlight nasm %} -push 3 -pop gs -mov ax, gs -cmp al, 3 +push 3 +pop gs +mov ax, gs +cmp al, 3 jne being_debugged {% endhighlight %} @@ -240,11 +240,11 @@ This code is also vulnerable to a race condition caused by a thread-switch event A variation of this technique solves that problem by waiting intentionally for a thread-switch event to occur. {% highlight nasm %} -push 3 -pop gs -l1: mov ax, gs -cmp al, 3 -je l1 + push 3 + pop gs +l1: mov ax, gs + cmp al, 3 + je l1 {% endhighlight %}
        @@ -260,10 +260,10 @@ However, this code is vulnerable to the problem that it was trying to detect in bool IsTraced() { -__asm -{ -push 3 -pop gs + __asm + { + push 3 + pop gs __asm SeclectorsLbl: mov ax, gs @@ -280,7 +280,7 @@ pop gs return false; Selectors_Debugged: -return true; + return true; } {% endhighlight %} @@ -300,14 +300,14 @@ The debug functions such as ntdll!DbgPrint() and kernel32!OutputDeb bool IsDebugged() { -__try -{ -RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); -} -__except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) -{ -return false; -} + __try + { + RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); + } + __except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) + { + return false; + } return true; } @@ -329,7 +329,7 @@ The functions ntdll!DbgSetDebugFilterState() and ntdll!NtSetDebugFi bool IsDebugged() { -return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); + return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); } {% endhighlight %} @@ -355,12 +355,12 @@ In the example below, we define a one-byte counter (initialized with 0) which sh bool IsDebugged() { -BYTE ucCounter = 1; -for (int i = 0; i < 8; i++) -{ -Sleep(0x0F); -ucCounter <<= (1 - SwitchToThread()); -} + BYTE ucCounter = 1; + for (int i = 0; i < 8; i++) + { + Sleep(0x0F); + ucCounter <<= (1 - SwitchToThread()); + } return ucCounter == 0; } @@ -387,7 +387,7 @@ This feature can be used to track debuggers that may modify memory pages outside {% highlight c %} bool Generic::CheckWrittenPages1() const { -const int SIZE_TO_CHECK = 4096; + const int SIZE_TO_CHECK = 4096; PVOID* addresses = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); if (addresses == NULL) @@ -430,7 +430,7 @@ const int SIZE_TO_CHECK = 4096; {% highlight c %} bool Generic::CheckWrittenPages2() const { -BOOL result = FALSE, error = FALSE; + BOOL result = FALSE, error = FALSE; const int SIZE_TO_CHECK = 4096; @@ -486,6 +486,7 @@ BOOL result = FALSE, error = FALSE; {% endhighlight %}
        +

        8. IFEO removal

        @@ -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)
        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. -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 ---- - -

        Contents

        - -[Bypassing Security Solutions detection methods](#avbypass-detection-methods) -
        -[1. Detect Wine](#detect-wine) -
        -[2. Add to Windows defender exclusion list](#add-to-windows-defender-exclusion-list) -
        -[3. Patch EtwEventWrite](#patch-etweventwrite) -
        -[Signature recommendations](#signature-recommendations) -
        -[Countermeasures](#countermeasures) -
        -
        - -
        - -

        Bypassing Security Solutions detection methods

        -Techniques described in this group abuse different vendors and AVs and try to bypass them without being detected. -
        -

        1. 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`). - -
        - -

        2. Add to Windows defender exclusion list

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

        3. Patch EtwEventWrite

        -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 %} - -
        - -

        Signature recommendations

        -
      • versus Detect Wine: Check if `MulDiv(1, 0x80000000, 0x80000000)` is being called
      • -
      • versus Add to Windows defender exclusion list Check if those registry keys are set with new values of untrusted files
      • -
      • versus Patch EtwEventWrite Check if first byte of EtwEventWrite is changed to C3
      • - -
        -

        Countermeasures

        - -
          -
        • 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:
      • FileFsSizeInformation
      • FileFsFullSizeInformation
      • @@ -609,8 +612,8 @@ Function used: {% highlight c %} bool Generic::CheckSystemUptime() const { -const DWORD uptime = 1000 * 60 * 12; // 12 minutes -return GetTickCount() < uptime; + const DWORD uptime = 1000 * 60 * 12; // 12 minutes + return GetTickCount() < uptime; } {% endhighlight %} @@ -622,8 +625,8 @@ return GetTickCount() < uptime; #define MIN_UPTIME_MINUTES 12 BOOL uptime_check() { -ULONGLONG uptime_minutes = GetTickCount64() / (60 * 1000); -return uptime_minutes < MIN_UPTIME_MINUTES; + ULONGLONG uptime_minutes = GetTickCount64() / (60 * 1000); + return uptime_minutes < MIN_UPTIME_MINUTES; } {% endhighlight %}
        @@ -631,11 +634,11 @@ return uptime_minutes < MIN_UPTIME_MINUTES; {% highlight c %} BOOL uptime_check2() { -SYSTEM_TIME_OF_DAY_INFORMATION SysTimeInfo; -ULONGLONG uptime_minutes; -NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0); -uptime_minutes = (SysTimeInfo.CurrentTime.QuadPart - SysTimeInfo.BootTime.QuadPart) / (60 * 1000 * 10000); -return uptime_minutes < MIN_UPTIME_MINUTES; + SYSTEM_TIME_OF_DAY_INFORMATION SysTimeInfo; + ULONGLONG uptime_minutes; + NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0); + uptime_minutes = (SysTimeInfo.CurrentTime.QuadPart - SysTimeInfo.BootTime.QuadPart) / (60 * 1000 * 10000); + return uptime_minutes < MIN_UPTIME_MINUTES; } {% endhighlight %} @@ -670,7 +673,7 @@ Take a look at the excerpt from malware

        Credits

        -Credits go to open-source projects from where code samples were taken: +Credits go to open-source projects from where code samples were taken:
        • al-khaser project on github
        • pafish project on github
        • 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. +
          + +

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

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

          Countermeasures

          +
          -Hook target functions and return appropriate results if indicators (registry strings from tables) are checked. +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

          +Hook target functions and return appropriate results if indicators (registry strings from tables) are checked. +

          Credits

          -Credits go to open-source project from where code samples were taken: +Credits go to open-source project from where code samples were taken: From 6ee8adb35ba39bdb53cc6011bac1e635b8daa554 Mon Sep 17 00:00:00 2001 From: israelgu <35104766+israelgu@users.noreply.github.com> Date: Tue, 21 May 2024 15:15:40 +0300 Subject: [PATCH 3/4] Fixed issue 2 --- .idea/.gitignore | 3 - .idea/Evasions.iml | 9 -- .idea/codeStyles/Project.xml | 117 ------------------ .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/misc.xml | 6 - .idea/modules.xml | 8 -- .idea/vcs.xml | 6 - _src/Anti-Debug/techniques/assembly.md | 29 ++--- _src/Anti-Debug/techniques/interactive.md | 4 +- _src/Evasions/index.html | 17 +-- .../Evasions/techniques/generic-os-queries.md | 11 +- _src/Evasions/techniques/os-features.md | 5 +- _src/Evasions/techniques/processes.md | 7 +- 13 files changed, 33 insertions(+), 194 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/Evasions.iml delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/Evasions.iml b/.idea/Evasions.iml deleted file mode 100644 index d6ebd48..0000000 --- a/.idea/Evasions.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 4bec4ea..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a1..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 639900d..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 99de62e..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md index 1ddf931..b7282cb 100644 --- a/_src/Anti-Debug/techniques/assembly.md +++ b/_src/Anti-Debug/techniques/assembly.md @@ -409,7 +409,7 @@ bool IsDebugged()

          9. POPF and CPUID

          -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()

          8. Process Suspension Detection

          -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 - +
          @@ -152,7 +152,7 @@ - Global OS objects + Global OS objects
          @@ -275,17 +275,6 @@ Human-like behavior - -

          Go to the title page

          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.

          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 +
          From 5b19ce117c7edde155794d76276015ac47accb01 Mon Sep 17 00:00:00 2001 From: israelgu <35104766+israelgu@users.noreply.github.com> Date: Thu, 23 May 2024 10:30:59 +0300 Subject: [PATCH 4/4] last fixes --- _src/Anti-Debug/techniques/assembly.md | 28 ++++++++++++------------- _src/Anti-Debug/techniques/misc.md | 2 +- _src/Evasions/techniques/os-features.md | 5 ++--- _src/Evasions/techniques/registry.md | 1 - 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md index b7282cb..a99957b 100644 --- a/_src/Anti-Debug/techniques/assembly.md +++ b/_src/Anti-Debug/techniques/assembly.md @@ -409,7 +409,7 @@ bool IsDebugged()

          9. POPF and CPUID

          -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. - +
          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)
          Check if the following process names are being removed (also check if the current process name is being removed)