{% include share.html %} diff --git a/_layouts/post_main.html b/_layouts/post_main.html new file mode 100644 index 0000000..c1b576f --- /dev/null +++ b/_layouts/post_main.html @@ -0,0 +1,25 @@ +--- +layout: default +--- +
+
+
+
+
+{{ page.title }}
+
+
+ {{ content }}
+
+
+ {% include share.html %} + + diff --git a/_src/Android/assets/icons/anti_analysis.png b/_src/Android/assets/icons/anti_analysis.png new file mode 100644 index 0000000..50ad543 Binary files /dev/null and b/_src/Android/assets/icons/anti_analysis.png differ diff --git a/_src/Android/assets/icons/anti_analysis_gray.png b/_src/Android/assets/icons/anti_analysis_gray.png new file mode 100644 index 0000000..1eb3bfc Binary files /dev/null and b/_src/Android/assets/icons/anti_analysis_gray.png differ diff --git a/_src/Android/assets/icons/emulator_flags.svg b/_src/Android/assets/icons/emulator_flags.svg new file mode 100644 index 0000000..3e52dfa --- /dev/null +++ b/_src/Android/assets/icons/emulator_flags.svg @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/_src/Android/assets/icons/emulator_flags_gray.svg b/_src/Android/assets/icons/emulator_flags_gray.svg new file mode 100644 index 0000000..3e52dfa --- /dev/null +++ b/_src/Android/assets/icons/emulator_flags_gray.svg @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/_src/Android/assets/icons/environment.png b/_src/Android/assets/icons/environment.png new file mode 100644 index 0000000..de227d0 Binary files /dev/null and b/_src/Android/assets/icons/environment.png differ diff --git a/_src/Android/assets/icons/environment_gray.png b/_src/Android/assets/icons/environment_gray.png new file mode 100644 index 0000000..55302a9 Binary files /dev/null and b/_src/Android/assets/icons/environment_gray.png differ diff --git a/_src/Android/assets/icons/human_behavior.png b/_src/Android/assets/icons/human_behavior.png new file mode 100644 index 0000000..22ab70f Binary files /dev/null and b/_src/Android/assets/icons/human_behavior.png differ diff --git a/_src/Android/assets/icons/human_behavior_gray.png b/_src/Android/assets/icons/human_behavior_gray.png new file mode 100644 index 0000000..896e9d5 Binary files /dev/null and b/_src/Android/assets/icons/human_behavior_gray.png differ diff --git a/_src/Android/assets/images/eocd_end_sequence.png b/_src/Android/assets/images/eocd_end_sequence.png new file mode 100644 index 0000000..62c6928 Binary files /dev/null and b/_src/Android/assets/images/eocd_end_sequence.png differ diff --git a/_src/Android/assets/images/eocd_multi_file_exception.png b/_src/Android/assets/images/eocd_multi_file_exception.png new file mode 100644 index 0000000..e9d3b3b Binary files /dev/null and b/_src/Android/assets/images/eocd_multi_file_exception.png differ diff --git a/_src/Android/assets/images/eocd_values_to_change.png b/_src/Android/assets/images/eocd_values_to_change.png new file mode 100644 index 0000000..4764a46 Binary files /dev/null and b/_src/Android/assets/images/eocd_values_to_change.png differ diff --git a/_src/Android/assets/images/filenames_long.png b/_src/Android/assets/images/filenames_long.png new file mode 100644 index 0000000..607172b Binary files /dev/null and b/_src/Android/assets/images/filenames_long.png differ diff --git a/_src/Android/assets/images/manifest_array_structure.png b/_src/Android/assets/images/manifest_array_structure.png new file mode 100644 index 0000000..4ee07a7 Binary files /dev/null and b/_src/Android/assets/images/manifest_array_structure.png differ diff --git a/_src/Android/assets/images/manifest_magic_actual.png b/_src/Android/assets/images/manifest_magic_actual.png new file mode 100644 index 0000000..1ce52aa Binary files /dev/null and b/_src/Android/assets/images/manifest_magic_actual.png differ diff --git a/_src/Android/assets/images/manifest_magic_possible_candidates.png b/_src/Android/assets/images/manifest_magic_possible_candidates.png new file mode 100644 index 0000000..0e04ad2 Binary files /dev/null and b/_src/Android/assets/images/manifest_magic_possible_candidates.png differ diff --git a/_src/Android/assets/images/manifest_magic_possible_candidates_consts.png b/_src/Android/assets/images/manifest_magic_possible_candidates_consts.png new file mode 100644 index 0000000..f6f2aa7 Binary files /dev/null and b/_src/Android/assets/images/manifest_magic_possible_candidates_consts.png differ diff --git a/_src/Android/assets/images/manifest_string_offset_wrong.png b/_src/Android/assets/images/manifest_string_offset_wrong.png new file mode 100644 index 0000000..824c8ce Binary files /dev/null and b/_src/Android/assets/images/manifest_string_offset_wrong.png differ diff --git a/_src/Android/assets/images/manifest_string_start.png b/_src/Android/assets/images/manifest_string_start.png new file mode 100644 index 0000000..3d059de Binary files /dev/null and b/_src/Android/assets/images/manifest_string_start.png differ diff --git a/_src/Android/assets/images/manifest_style_fix.png b/_src/Android/assets/images/manifest_style_fix.png new file mode 100644 index 0000000..4cdc201 Binary files /dev/null and b/_src/Android/assets/images/manifest_style_fix.png differ diff --git a/_src/Android/index.html b/_src/Android/index.html new file mode 100644 index 0000000..cf73e5a --- /dev/null +++ b/_src/Android/index.html @@ -0,0 +1,167 @@ +--- +layout: main +--- + + + + + + + +
+
+
+
+ +
+ + diff --git a/_src/Android/techniques/anti-analysis.md b/_src/Android/techniques/anti-analysis.md new file mode 100644 index 0000000..527d4e5 --- /dev/null +++ b/_src/Android/techniques/anti-analysis.md @@ -0,0 +1,197 @@ +--- +layout: post +title: "APK anti-analysis" +title-image: "/assets/icons/anti-analysis.svg" +categories: evasions android +tags: apk +--- + +
+ [1. Wrong ZIP header data values](#wrong-zip-header-values) +
+ [2. Wrong values in the manifest structure](#wrong-values-manifest) +
+ [2.1. Magic value](#magic-value) +
+ [2.2. Array of strings' offsets](#array-strings-offsets) +
+ [2.3. Style pool](#style-pool) +
+ [3. Long filenames](#long-filenames) +
+ [Signature recommendations](#signature-recommendations) +
+
+ +
+ +
+
+ +APK is usually not split into multi-disk archives, so this information has to be checked inside - by analyzing the ZIP header data. The necessary entry is the central directory file header. The end of this record EOCD contains information about disk count at offsets 4 and 6. We will also pay attention to the offsets 10 and 12, as explained below. + +
+ +EOCD fields + +
+
+
+
+ +EOCD marks the end of ZIP so the required byte sequence can be found at the end of the file: + +
+ +The processed struct, along with fixes (explained below), looks like this: + +
+ +Based on the very large values in the disk number fields, it is clear that the malware developers edited these fields and entries. If to compare this APK with any other legitimate APKs, it will be seen that in these APKs, values elDiskNumber and elStartDiskNumber are equal to 0. And in the current case the disk numbers should be set to 0 as well. + +Also, the values elEntriesInDirectory and elDirectorySize are the same in legitimate APKs as opposed to the target one. As elEntriesInDirectory has ushort type (2 bytes long), its maximum value is 65536 – and can’t be equal to 139122 which is set in elDirectorySize. It means that the value of elDirectorySize must be equal to the value of elEntriesInDirectory, not vice versa, i.e., we set the value of elDirectorySize to 1075. This number corresponds to the files inside the APK, i.e. ZIP archive, but we could not know about it before the fix was done indeed. + + +
+
+
+ +However, upon closer examination of this apktool issue, it appears that the only correct value in AndroidManifest header is 0x00080003, which is equal to the constant name CHUNK_AXML_FILE in the apktool source code, not CHUNK_AXML_FILE_BROKEN which stands for 0x00080001. + +
+ +And the analyzed file starts with 0x00080000. By changing it to the correct value, this issue becomes mitigated. + +
+
+ +To analyze what causes this exception, we first make a breakdown of AndroidManifest structure: + +
+ +By checking the offset shown in the exception, we understand that the issue is in the scStringOffsets array field, in its last element (0x24 + 0x160 = 0x184 – the exact offset shown in the exception). +When examining this array closely, we see that the offset of the last string is pointing out of the file. + +
+ +The string “theme” is wrongly interpreted as an offset value in the last element of the array, number 87. This means that the value of the scStringCount should be less by 1, i.e., set to 86. Now there are 87 elements in the array, all of 4 bytes, multiplication of 87*4 is equal to 348, which is 0x15C in hex. As scStringOffsets fields starts at 0x24, now it ends at 0x24+0x15C, which is equal to 0x180 – exactly what is expected in the analysis tool. + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md new file mode 100644 index 0000000..9a07928 --- /dev/null +++ b/_src/Anti-Debug/techniques/assembly.md @@ -0,0 +1,412 @@ +--- +layout: post +title: "Anti-Debug: Assembly instructions" +title-image: "/assets/icons/assembly.svg" +categories: anti-debug +tags: assembly +--- + +
+ +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm int 3; + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
Besides the short form of INT3 instruction (0xCC opcode), there is also a long form of this instruction: CD 03 opcode. + +When the exception EXCEPTION_BREAKPOINT occurs, the Windows decrements EIP register to the assumed location of the 0xCC opcode and pass the control to the exception handler. In the case of the long form of the INT3 instruction, EIP will point to the middle of the instruction (i.e. to 0x03 byte). Therefore, EIP should be edited in the exception handler if we want to continue execution after the INT3 instruction (otherwise we'll most likely get an EXCEPTION_ACCESS_VIOLATION exception). If not, we can neglect the instruction pointer modification. + +
+ +C/C++ Code + + +{% highlight c %} + +bool g_bDebugged = false; + +int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) +{ + g_bDebugged = code != EXCEPTION_BREAKPOINT; + return EXCEPTION_EXECUTE_HANDLER; +} + +bool IsDebugged() +{ + __try + { + __asm __emit(0xCD); + __asm __emit(0x03); + } + __except (filter(GetExceptionCode(), GetExceptionInformation())) + { + return g_bDebugged; + } +} + +{% endhighlight %} + + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm xor eax, eax; + __asm int 0x2d; + __asm nop; + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + DebugBreak(); + } + __except(EXCEPTION_BREAKPOINT) + { + return false; + } + + return true; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm __emit 0xF1; + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
After single-stepping in a debugger through this code, the Trap Flag will be set. Usually it's not visible as debuggers clear the Trap Flag after each debugger event is delivered. However, if we previously save EFLAGS to the stack, we'll be able to check whether the Trap Flag is set. + +
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + bool bTraced = false; + + __asm + { + push ss + pop ss + pushf + test byte ptr [esp+1], 1 + jz movss_not_being_debugged + } + + bTraced = true; + +movss_not_being_debugged: + // restore stack + __asm popf; + + return bTraced; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +#include "hwbrk.h" + +static LONG WINAPI InstructionCountingExeptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) + { + pExceptionInfo->ContextRecord->Eax += 1; + pExceptionInfo->ContextRecord->Eip += 1; + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +__declspec(naked) DWORD WINAPI InstructionCountingFunc(LPVOID lpThreadParameter) +{ + __asm + { + xor eax, eax + nop + nop + nop + nop + cmp al, 4 + jne being_debugged + } + + ExitThread(FALSE); + +being_debugged: + ExitThread(TRUE); +} + +bool IsDebugged() +{ + PVOID hVeh = nullptr; + HANDLE hThread = nullptr; + bool bDebugged = false; + + __try + { + hVeh = AddVectoredExceptionHandler(TRUE, InstructionCountingExeptionHandler); + if (!hVeh) + __leave; + + hThread = CreateThread(0, 0, InstructionCountingFunc, NULL, CREATE_SUSPENDED, 0); + if (!hThread) + __leave; + + PVOID pThreadAddr = &InstructionCountingFunc; + // Fix thread entry address if it is a JMP stub (E9 XX XX XX XX) + if (*(PBYTE)pThreadAddr == 0xE9) + pThreadAddr = (PVOID)((DWORD)pThreadAddr + 5 + *(PDWORD)((PBYTE)pThreadAddr + 1)); + + for (auto i = 0; i < m_nInstructionCount; i++) + m_hHwBps[i] = SetHardwareBreakpoint( + hThread, HWBRK_TYPE_CODE, HWBRK_SIZE_1, (PVOID)((DWORD)pThreadAddr + 2 + i)); + + ResumeThread(hThread); + WaitForSingleObject(hThread, INFINITE); + + DWORD dwThreadExitCode; + if (TRUE == GetExitCodeThread(hThread, &dwThreadExitCode)) + bDebugged = (TRUE == dwThreadExitCode); + } + __finally + { + if (hThread) + CloseHandle(hThread); + + for (int i = 0; i < 4; i++) + { + if (m_hHwBps[i]) + RemoveHardwareBreakpoint(m_hHwBps[i]); + } + + if (hVeh) + RemoveVectoredExceptionHandler(hVeh); + } + + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm + { + pushfd + mov dword ptr [esp], 0x100 + popfd + nop + } + return true; + } + __except(GetExceptionCode() == EXCEPTION_SINGLE_STEP + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_EXECUTION) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + // 0xF3 0x64 disassembles as PREFIX REP: + __asm __emit 0xF3 + __asm __emit 0x64 + // One byte INT 1 + __asm __emit 0xF1 + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
+ +
+ +
+
+
+ +Assembly Code + + +{% highlight nasm %} + call IsDebuggerPresent + test al, al + jne being_debugged + ... +being_debugged: + push 1 + call ExitProcess +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} +if (IsDebuggerPresent()) + ExitProcess(-1); +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +BOOL bDebuggerPresent; +if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && + TRUE == bDebuggerPresent) + ExitProcess(-1); + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, bDebuggerPresent] + push eax + push -1 ; GetCurrentProcess() + call CheckRemoteDebuggerPresent + cmp [bDebuggerPresent], 1 + jz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rdx, [bDebuggerPresent] + mov rcx, -1 ; GetCurrentProcess() + call CheckRemoteDebuggerPresent + cmp [bDebuggerPresent], 1 + jz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+
+ +
+ +C/C++ Code + + +{% highlight c %} + +typedef NTSTATUS (NTAPI *TNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength + ); + +HMODULE hNtdll = LoadLibraryA("ntdll.dll"); +if (hNtdll) +{ + auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( + hNtdll, "NtQueryInformationProcess"); + + if (pfnNtQueryInformationProcess) + { + DWORD dwProcessDebugPort, dwReturned; + NTSTATUS status = pfnNtQueryInformationProcess( + GetCurrentProcess(), + ProcessDebugPort, + &dwProcessDebugPort, + sizeof(DWORD), + &dwReturned); + + if (NT_SUCCESS(status) && (-1 == dwProcessDebugPort)) + ExitProcess(-1); + } +} + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, [dwReturned] + push eax ; ReturnLength + push 4 ; ProcessInformationLength + lea ecx, [dwProcessDebugPort] + push ecx ; ProcessInformation + push 7 ; ProcessInformationClass + push -1 ; ProcessHandle + call NtQueryInformationProcess + inc dword ptr [dwProcessDebugPort] + jz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rcx, [dwReturned] + push rcx ; ReturnLength + mov r9d, 4 ; ProcessInformationLength + lea r8, [dwProcessDebugPort] + ; ProcessInformation + mov edx, 7 ; ProcessInformationClass + mov rcx, -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [dwProcessDebugPort], -1 + jz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+ +
+ +C/C++ Code + + +{% highlight c %} + +typedef NTSTATUS(NTAPI *TNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN DWORD ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength + ); + +HMODULE hNtdll = LoadLibraryA("ntdll.dll"); +if (hNtdll) +{ + auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( + hNtdll, "NtQueryInformationProcess"); + + if (pfnNtQueryInformationProcess) + { + DWORD dwProcessDebugFlags, dwReturned; + const DWORD ProcessDebugFlags = 0x1f; + NTSTATUS status = pfnNtQueryInformationProcess( + GetCurrentProcess(), + ProcessDebugFlags, + &dwProcessDebugFlags, + sizeof(DWORD), + &dwReturned); + + if (NT_SUCCESS(status) && (0 == dwProcessDebugFlags)) + ExitProcess(-1); + } +} + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, [dwReturned] + push eax ; ReturnLength + push 4 ; ProcessInformationLength + lea ecx, [dwProcessDebugPort] + push ecx ; ProcessInformation + push 1Fh ; ProcessInformationClass + push -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [dwProcessDebugPort], 0 + jz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rcx, [dwReturned] + push rcx ; ReturnLength + mov r9d, 4 ; ProcessInformationLength + lea r8, [dwProcessDebugPort] + ; ProcessInformation + mov edx, 1Fh ; ProcessInformationClass + mov rcx, -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [dwProcessDebugPort], 0 + jz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+ +
+ +C/C++ Code + + +{% highlight c %} + +typedef NTSTATUS(NTAPI * TNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN DWORD ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength + ); + +HMODULE hNtdll = LoadLibraryA("ntdll.dll"); +if (hNtdll) +{ + auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( + hNtdll, "NtQueryInformationProcess"); + + if (pfnNtQueryInformationProcess) + { + DWORD dwReturned; + HANDLE hProcessDebugObject = 0; + const DWORD ProcessDebugObjectHandle = 0x1e; + NTSTATUS status = pfnNtQueryInformationProcess( + GetCurrentProcess(), + ProcessDebugObjectHandle, + &hProcessDebugObject, + sizeof(HANDLE), + &dwReturned); + + if (NT_SUCCESS(status) && (0 != hProcessDebugObject)) + ExitProcess(-1); + } +} + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, [dwReturned] + push eax ; ReturnLength + push 4 ; ProcessInformationLength + lea ecx, [hProcessDebugObject] + push ecx ; ProcessInformation + push 1Eh ; ProcessInformationClass + push -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [hProcessDebugObject], 0 + jnz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rcx, [dwReturned] + push rcx ; ReturnLength + mov r9d, 4 ; ProcessInformationLength + lea r8, [hProcessDebugObject] + ; ProcessInformation + mov edx, 1Fh ; ProcessInformationClass + mov rcx, -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [hProcessDebugObject], 0 + jnz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); + if (!SUCCEEDED(ntdll::RtlQueryProcessHeapInformation((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer))) + return false; + + ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; + return dwFlags & ~HEAP_GROWABLE; +} + +{% endhighlight %} + +
+
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); + if (!SUCCEEDED(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer))) + return false; + + ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; + return dwFlags & ~HEAP_GROWABLE; +} + +{% endhighlight %} + +
+
+ +C/C++ Code + + +{% highlight c %} + +enum { SystemKernelDebuggerInformation = 0x23 }; + +typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION { + BOOLEAN DebuggerEnabled; + BOOLEAN DebuggerNotPresent; +} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; + +bool Check() +{ + NTSTATUS status; + SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInfo; + + status = NtQuerySystemInformation( + (SYSTEM_INFORMATION_CLASS)SystemKernelDebuggerInformation, + &SystemInfo, + sizeof(SystemInfo), + NULL); + + return SUCCEEDED(status) + ? (SystemInfo.DebuggerEnabled && !SystemInfo.DebuggerNotPresent) + : false; +} + +{% endhighlight %} + +
+
+
+
+ +32Bit Process + + +{% highlight nasm %} +mov eax, fs:[30h] +cmp byte ptr [eax+2], 0 +jne being_debugged +{% endhighlight %} + +
+ +64Bit Process + + +{% highlight nasm %} +mov rax, gs:[60h] +cmp byte ptr [rax+2], 0 +jne being_debugged +{% endhighlight %} + +
+ +WOW64 Process + + +{% highlight nasm %} +mov eax, fs:[30h] +cmp byte ptr [eax+1002h], 0 +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +#endif // _WIN64 + +if (pPeb->BeingDebugged) + goto being_debugged; + +{% endhighlight %} + +
+
+ +The presence of a debugger can be detected by checking a combination of those flags. + +
+ +32Bit Process + + +{% highlight nasm %} +mov eax, fs:[30h] +mov al, [eax+68h] +and al, 70h +cmp al, 70h +jz being_debugged +{% endhighlight %} + +
+ +64Bit Process + + +{% highlight nasm %} +mov rax, gs:[60h] +mov al, [rax+BCh] +and al, 70h +cmp al, 70h +jz being_debugged +{% endhighlight %} + +
+ +WOW64 Process + + +{% highlight nasm %} +mov eax, fs:[30h] +mov al, [eax+10BCh] +and al, 70h +cmp al, 70h +jz being_debugged +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10 +#define FLG_HEAP_ENABLE_FREE_CHECK 0x20 +#define FLG_HEAP_VALIDATE_PARAMETERS 0x40 +#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS) + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68); +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC); +#endif // _WIN64 + +if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED) + goto being_debugged; + + +{% endhighlight %} + +
+
+ +When a debugger is present, the Flags field is set to a combination of these flags on Windows NT, Windows 2000, and 32-bit Windows XP: +
+ +On 64-bit Windows XP, and Windows Vista and higher, if a debugger is present, the Flags field is set to a combination of these flags: +
+ +When a debugger is present, the ForceFlags field is set to a combination of these flags: +
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ +#ifndef _WIN64 + PPEB pPeb = (PPEB)__readfsdword(0x30); + PVOID pHeapBase = !m_bIsWow64 + ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) + : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); + DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x40 + : 0x0C; + DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x44 + : 0x10; +#else + PPEB pPeb = (PPEB)__readgsqword(0x60); + PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); + DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x70 + : 0x14; + DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x74 + : 0x18; +#endif // _WIN64 + + PDWORD pdwHeapFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset); + PDWORD pdwHeapForceFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset); + return (*pdwHeapFlags & ~HEAP_GROWABLE) || (*pdwHeapForceFlags != 0); +} + +{% endhighlight %} + +
+
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + PROCESS_HEAP_ENTRY HeapEntry = { 0 }; + do + { + if (!HeapWalk(GetProcessHeap(), &HeapEntry)) + return false; + } while (HeapEntry.wFlags != PROCESS_HEAP_ENTRY_BUSY); + + PVOID pOverlapped = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; + return ((DWORD)(*(PDWORD)pOverlapped) == 0xABABABAB); +} + +{% endhighlight %} + +
+its address is fixed and has been in all Windows versions since it was introduced
+its user mode address is the same in 32 bit and 64 bit mode
+all offsets and sizes are strictly fixed, and new fields are only ever appended or added in place of unused padding space
+
+
+Hence this program will work in 32 bit Windows 2000 and 64 bit Windows 10 without recompiling". + + +
+ +C/C++ Code + + +{% highlight c %} + +bool check_kuser_shared_data_structure() +{ + unsigned char b = *(unsigned char*)0x7ffe02d4; + return ((b & 0x01) || (b & 0x02)); +} + +{% endhighlight %} + +
+
+For PEB!BeingDebugged Flag: + +Set the BeingDebugged flag to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide. + +
+ +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +#endif // _WIN64 +pPeb->BeingDebugged = 0; + +{% endhighlight %} + +
+For NtGlobalFlag: + +Set the NtGlobalFlag to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide. + +
+ +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +*(PDWORD)((PBYTE)pPeb + 0x68) = 0; +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +*(PDWORD)((PBYTE)pPeb + 0xBC); = 0; +#endif // _WIN64 + +{% endhighlight %} + +
+For Heap Flags: + +Set the Flags value to HEAP_GROWABLE, and the ForceFlags value to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide. + +
+ +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +PVOID pHeapBase = !m_bIsWow64 + ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) + : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); +DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x40 + : 0x0C; +DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x44 + : 0x10; +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); +DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x70 + : 0x14; +DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x74 + : 0x18; +#endif // _WIN64 + +*(PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset) = HEAP_GROWABLE; +*(PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset) = 0; + +{% endhighlight %} + +
+For Heap Protection: + +Manually patch 12 bytes for 32-bit and 20 bytes in a 64-bit environment after the heap. Hook kernel32!HeapAlloc() and patch the heap after its allocation. + +
+ +{% highlight c %} + +#ifndef _WIN64 +SIZE_T nBytesToPatch = 12; +#else +SIZE_T nBytesToPatch = 20; +#endif // _WIN64 + +SIZE_T nDwordsToPatch = nBytesToPatch / sizeof(DWORD); +PVOID pHeapEnd = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; +for (SIZE_T offset = 0; offset < nDwordsToPatch; offset++) + *((PDWORD)pHeapEnd + offset) = 0; + +{% endhighlight %} + +
+For KUSER_SHARED_DATA: + +For a possible mitigation, please check the link when the technique is described (with the issue for TitanHide) and also a draft code for patching kdcom.dll here. diff --git a/_src/Anti-Debug/techniques/exceptions.md b/_src/Anti-Debug/techniques/exceptions.md new file mode 100644 index 0000000..5b70967 --- /dev/null +++ b/_src/Anti-Debug/techniques/exceptions.md @@ -0,0 +1,237 @@ +--- +layout: post +title: "Anti-Debug: Exceptions" +title-image: "/assets/icons/exceptions.svg" +categories: anti-debug +tags: exceptions +--- + +
+ +
+ +
+
+ +x86 Assembly (FASM) + + +{% highlight asm %} +include 'win32ax.inc' + +.code + +start: + jmp begin + +not_debugged: + invoke MessageBox,HWND_DESKTOP,"Not Debugged","",MB_OK + invoke ExitProcess,0 + +begin: + invoke SetUnhandledExceptionFilter, not_debugged + int 3 + jmp being_debugged + +being_debugged: + invoke MessageBox,HWND_DESKTOP,"Debugged","",MB_OK + invoke ExitProcess,0 + +.end start +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) +{ + PCONTEXT ctx = pExceptionInfo->ContextRecord; + ctx->Eip += 3; // Skip \xCC\xEB\x?? + return EXCEPTION_CONTINUE_EXECUTION; +} + +bool Check() +{ + bool bDebugged = true; + SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UnhandledExceptionFilter); + __asm + { + int 3 // CC + jmp near being_debugged // EB ?? + } + bDebugged = false; + +being_debugged: + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + __try + { + RaiseException(DBG_CONTROL_C, 0, 0, NULL); + return true; + } + __except(DBG_CONTROL_C == GetExceptionCode() + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
Using Structured Exception Handlers: + +
+ +C/C++ Code + + +{% highlight c %} + +#include
+
+void MaliciousEntry()
+{
+ // ...
+}
+
+void Trampoline2()
+{
+ __try
+ {
+ __asm int 3;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ MaliciousEntry();
+ }
+}
+
+void Trampoline1()
+{
+ __try
+ {
+ __asm int 3;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ Trampoline2();
+ }
+}
+
+int main(void)
+{
+ __try
+ {
+ __asm int 3;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER) {}
+ {
+ Trampoline1();
+ }
+
+ return 0;
+}
+
+{% endhighlight %}
+
+
Using Vectored Exception Handlers: + +
+ +C/C++ Code + + +{% highlight c %} + +#include
+
+PVOID g_pLastVeh = nullptr;
+
+void MaliciousEntry()
+{
+ // ...
+}
+
+LONG WINAPI ExeptionHandler2(PEXCEPTION_POINTERS pExceptionInfo)
+{
+ MaliciousEntry();
+ ExitProcess(0);
+}
+
+LONG WINAPI ExeptionHandler1(PEXCEPTION_POINTERS pExceptionInfo)
+{
+ if (g_pLastVeh)
+ {
+ RemoveVectoredExceptionHandler(g_pLastVeh);
+ g_pLastVeh = AddVectoredExceptionHandler(TRUE, ExeptionHandler2);
+ if (g_pLastVeh)
+ __asm int 3;
+ }
+ ExitProcess(0);
+}
+
+
+int main(void)
+{
+ g_pLastVeh = AddVectoredExceptionHandler(TRUE, ExeptionHandler1);
+ if (g_pLastVeh)
+ __asm int 3;
+
+ return 0;
+}
+
+{% endhighlight %}
+
+
+ +
+
+ +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +#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; + + hDbgEvent = CreateEventW(NULL, FALSE, FALSE, EVENT_SELFDBG_EVENT_NAME); + if (!hDbgEvent) + return false; + + if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath))) + return false; + + swprintf_s(wszCmdLine, L"%s %d", wszFilePath, GetCurrentProcessId()); + if (CreateProcessW(NULL, wszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return WAIT_OBJECT_0 == WaitForSingleObject(hDbgEvent, 0); + } + + return false; +} + +bool EnableDebugPrivilege() +{ + bool bResult = false; + HANDLE hToken = NULL; + DWORD ec = 0; + + do + { + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + break; + + TOKEN_PRIVILEGES tp; + tp.PrivilegeCount = 1; + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) + break; + + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if( !AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(tp), NULL, NULL)) + break; + + bResult = true; + } + while (0); + + if (hToken) + CloseHandle(hToken); + + return bResult; +} + +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); + } + + // ... + + return 0; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool g_bDebugged{ false }; +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;
+}
+
+static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
+{
+ switch (fdwCtrlType)
+ {
+ case CTRL_C_EVENT:
+ g_bCtlCCatched.store(true);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+bool IsDebugged()
+{
+ PVOID hVeh = nullptr;
+ BOOL bCtrlHadnlerSet = FALSE;
+
+ __try
+ {
+ hVeh = AddVectoredExceptionHandler(TRUE, CtrlEventExeptionHandler);
+ if (!hVeh)
+ __leave;
+
+ bCtrlHadnlerSet = SetConsoleCtrlHandler(CtrlHandler, TRUE);
+ if (!bCtrlHadnlerSet)
+ __leave;
+
+ GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
+ while (!g_bCtlCCatched.load())
+ ;
+ }
+ __finally
+ {
+ if (bCtrlHadnlerSet)
+ SetConsoleCtrlHandler(CtrlHandler, FALSE);
+
+ if (hVeh)
+ RemoveVectoredExceptionHandler(hVeh);
+ }
+
+ return g_bDebugged;
+}
+
+{% endhighlight %}
+
+
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsHooked () +{ + BOOL bFirstResult = FALSE, bSecondResult = FALSE; + __try + { + bFirstResult = BlockInput(TRUE); + bSecondResult = BlockInput(TRUE); + } + __finally + { + BlockInput(FALSE); + } + return bFirstResult && bSecondResult; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +#define NtCurrentThread ((HANDLE)-2) + +bool AntiDebug() +{ + NTSTATUS status = ntdll::NtSetInformationThread( + NtCurrentThread, + ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, + NULL, + 0); + return status >= 0; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +DWORD g_dwDebuggerProcessId = -1; + +BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) +{ + DWORD dwProcessId = *(PDWORD)lParam; + + DWORD dwWindowProcessId; + GetWindowThreadProcessId(hwnd, &dwWindowProcessId); + + if (dwProcessId == dwWindowProcessId) + { + std::wstring wsWindowTitle{ string_heper::ToLower(std::wstring(GetWindowTextLengthW(hwnd) + 1, L'\0')) }; + GetWindowTextW(hwnd, &wsWindowTitle[0], wsWindowTitle.size()); + + if (string_heper::FindSubstringW(wsWindowTitle, L"dbg") || + string_heper::FindSubstringW(wsWindowTitle, L"debugger")) + { + g_dwDebuggerProcessId = dwProcessId; + return FALSE; + } + return FALSE; + } + + return TRUE; +} + +bool IsDebuggerProcess(DWORD dwProcessId) const +{ + EnumWindows(EnumWindowsProc, reinterpret_cast(&dwProcessId));
+ return g_dwDebuggerProcessId == dwProcessId;
+}
+
+bool SuspendDebuggerThread()
+{
+ THREADENTRY32 ThreadEntry = { 0 };
+ ThreadEntry.dwSize = sizeof(THREADENTRY32);
+
+ DWORD dwParentProcessId = process_helper::GetParentProcessId(GetCurrentProcessId());
+ if (-1 == dwParentProcessId)
+ return false;
+
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwParentProcessId);
+ if(Thread32First(hSnapshot, &ThreadEntry))
+ {
+ do
+ {
+ if ((ThreadEntry.th32OwnerProcessID == dwParentProcessId) && IsDebuggerProcess(dwParentProcessId))
+ {
+ HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, ThreadEntry.th32ThreadID);
+ if (hThread)
+ SuspendThread(hThread);
+ break;
+ }
+ } while(Thread32Next(hSnapshot, &ThreadEntry));
+ }
+
+ if (hSnapshot)
+ CloseHandle(hSnapshot);
+
+ return false;
+}
+
+{% endhighlight %}
+
+
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +BOOL Switch() +{ + HDESK hNewDesktop = CreateDesktopA( + m_pcszNewDesktopName, + NULL, + NULL, + 0, + DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, + NULL); + if (!hNewDesktop) + return FALSE; + + return SwitchDesktop(hNewDesktop); +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code (variant1) + + +{% highlight c %} + +bool IsDebugged() +{ + if (IsWindowsVistaOrGreater()) + return false; + + DWORD dwLastError = GetLastError(); + OutputDebugString(L"AntiDebug_OutputDebugString_v1"); + return GetLastError() != dwLastError; +} + +{% endhighlight %} + +
+ +C/C++ Code (variant2) + + +{% highlight c %} + +bool IsDebugged() +{ + if (IsWindowsVistaOrGreater()) + return false; + + DWORD dwErrVal = 0x666; + SetLastError(dwErrVal); + OutputDebugString(L"AntiDebug_OutputDebugString_v2"); + return GetLastError() != dwErrVal; +} + +{% endhighlight %} + +
+ +
+
+ +
+ +
+
+ +C/C++ Code + + +{% 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",
+};
+
+bool IsDebugged()
+{
+ for (auto &sWndClass : vWindowClasses)
+ {
+ if (NULL != FindWindowA(sWndClass.c_str(), NULL))
+ return true;
+ }
+ return false;
+}
+
+{% endhighlight %}
+
+
+ +
+
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + HWND hExplorerWnd = GetShellWindow(); + if (!hExplorerWnd) + return false; + + DWORD dwExplorerProcessId; + GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId); + + ntdll::PROCESS_BASIC_INFORMATION ProcessInfo; + NTSTATUS status = ntdll::NtQueryInformationProcess( + GetCurrentProcess(), + ntdll::PROCESS_INFORMATION_CLASS::ProcessBasicInformation, + &ProcessInfo, + sizeof(ProcessInfo), + NULL); + if (!NT_SUCCESS(status)) + return false; + + return (DWORD)ProcessInfo.InheritedFromUniqueProcessId != dwExplorerProcessId; +} + +{% endhighlight %} + +
+
+ +C/C++ Code + + +{% highlight c %} + +DWORD GetParentProcessId(DWORD dwCurrentProcessId) +{ + DWORD dwParentProcessId = -1; + PROCESSENTRY32W ProcessEntry = { 0 }; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(Process32FirstW(hSnapshot, &ProcessEntry)) + { + do + { + if (ProcessEntry.th32ProcessID == dwCurrentProcessId) + { + dwParentProcessId = ProcessEntry.th32ParentProcessID; + break; + } + } while(Process32NextW(hSnapshot, &ProcessEntry)); + } + + CloseHandle(hSnapshot); + return dwParentProcessId; +} + +bool IsDebugged() +{ + bool bDebugged = false; + DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); + + PROCESSENTRY32 ProcessEntry = { 0 }; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(Process32First(hSnapshot, &ProcessEntry)) + { + do + { + if ((ProcessEntry.th32ProcessID == dwParentProcessId) && + (strcmp(ProcessEntry.szExeFile, "explorer.exe"))) + { + bDebugged = true; + break; + } + } while(Process32Next(hSnapshot, &ProcessEntry)); + } + + CloseHandle(hSnapshot); + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
+ +On the 64-bit versions of Windows, single-stepping through this code will cause an access violation exception at l1 because the DS selector will be restored to its default value even before l1 is reached. On the 32-bit versions of Windows, the DS selector will not have its value restored, unless a non-debugging exception occurs. The version-specific difference in behaviors expands even further if the SS selector is used. On the 64-bit versions of Windows, the SS selector will be restored to its default value, as in the DS selector case. However, on the 32-bit versions of Windows, the SS selector value will not be restored, even if an exception occurs. + +x86-64 Assembly + + +{% 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 + ... +{% endhighlight %} + +
+ +then when the "int 3" instruction is reached at l1 and the breakpoint exception occurs, the exception handler at l2 is not called as expected. Instead, the process is simply terminated. + +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 +jne being_debugged +{% endhighlight %} + +
+ +The FS and GS selectors are special cases. For certain values, they will be affected by the single-step event, even on the 32-bit versions of Windows. However, in the case of the FS selector (and, technically, the GS selector), it will be not restored to its default value on the 32-bit versions of Windows, if it was set to a value from zero to three. Instead, it will be set to zero (the GS selector is affected in the same way, but the default value for the GS selector is zero). On the 64-bit versions of Windows, it (they) will be restored to its (their) default value. + +This code is also vulnerable to a race condition caused by a thread-switch event. When a thread-switch event occurs, it behaves like an exception, and will cause the selector values to be altered, which, in the case of the FS selector, means that it will be set to zero. + +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 +{% endhighlight %} + +
+ +However, this code is vulnerable to the problem that it was trying to detect in the first place, because it does not check if the original assignment was successful. Of course, the two code snippets can be combined to produce the desired effect, by waiting until the thread-switch event occurs, and then performing the assignment within the window of time that should exist until the next one occurs. [Ferrie] + +
+ +C/C++ Code + + +{% highlight c %} + +bool IsTraced() +{ + __asm + { + push 3 + pop gs + + __asm SeclectorsLbl: + mov ax, gs + cmp al, 3 + je SeclectorsLbl + + push 3 + pop gs + mov ax, gs + cmp al, 3 + jne Selectors_Debugged + } + + return false; + +Selectors_Debugged: + return true; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); + } + __except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) + { + return false; + } + + return true; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + BYTE ucCounter = 1; + for (int i = 0; i < 8; i++) + { + Sleep(0x0F); + ucCounter <<= (1 - SwitchToThread()); + } + + return ucCounter == 0; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code (variant 1) + + +{% highlight c %} + +bool Generic::CheckWrittenPages1() const { + 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)
+ {
+ return true;
+ }
+
+ int* buffer = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE));
+ if (buffer == NULL)
+ {
+ VirtualFree(addresses, 0, MEM_RELEASE);
+ return true;
+ }
+
+ // Read the buffer once
+ buffer[0] = 1234;
+
+ ULONG_PTR hits = SIZE_TO_CHECK;
+ DWORD granularity;
+ if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0)
+ {
+ return true;
+ }
+ else
+ {
+ VirtualFree(addresses, 0, MEM_RELEASE);
+ VirtualFree(buffer, 0, MEM_RELEASE);
+
+ return (hits == 1) ? false : true;
+ }
+}
+
+{% endhighlight %}
+
+
+ +C/C++ Code (variant 2) + + +{% highlight c %} + +bool Generic::CheckWrittenPages2() const { + BOOL result = FALSE, error = FALSE; + + 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)
+ {
+ return true;
+ }
+
+ int* buffer = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE));
+ if (buffer == NULL)
+ {
+ VirtualFree(addresses, 0, MEM_RELEASE);
+ return true;
+ }
+
+ // Make some calls where a buffer *can* be written to, but isn't actually edited because we pass invalid parameters
+ if (GlobalGetAtomName(INVALID_ATOM, (LPTSTR)buffer, 1) != FALSE
+ || GetEnvironmentVariable("This variable does not exist", (LPSTR)buffer, 4096 * 4096) != FALSE
+ || GetBinaryType("This name does not exist", (LPDWORD)buffer) != FALSE
+ || HeapQueryInformation(0, (HEAP_INFORMATION_CLASS)69, buffer, 4096, NULL) != FALSE
+ || ReadProcessMemory(INVALID_HANDLE_VALUE, (LPCVOID)0x69696969, buffer, 4096, NULL) != FALSE
+ || GetThreadContext(INVALID_HANDLE_VALUE, (LPCONTEXT)buffer) != FALSE
+ || GetWriteWatch(0, &result, 0, NULL, NULL, (PULONG)buffer) == 0)
+ {
+ result = false;
+ error = true;
+ }
+
+ if (error == FALSE)
+ {
+ // A this point all calls failed as they're supposed to
+ ULONG_PTR hits = SIZE_TO_CHECK;
+ DWORD granularity;
+ if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0)
+ {
+ result = FALSE;
+ }
+ else
+ {
+ // Should have zero reads here because GlobalGetAtomName doesn't probe the buffer until other checks have succeeded
+ // If there's an API hook or debugger in here it'll probably try to probe the buffer, which will be caught here
+ result = hits != 0;
+ }
+ }
+
+ VirtualFree(addresses, 0, MEM_RELEASE);
+ VirtualFree(buffer, 0, MEM_RELEASE);
+
+ return result;
+}
+
+{% endhighlight %}
+
+
+ +
+
+ +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +typedef DWORD (WINAPI *TCsrGetProcessId)(VOID); + +bool Check() +{ + HMODULE hNtdll = LoadLibraryA("ntdll.dll"); + if (!hNtdll) + return false; + + TCsrGetProcessId pfnCsrGetProcessId = (TCsrGetProcessId)GetProcAddress(hNtdll, "CsrGetProcessId"); + if (!pfnCsrGetProcessId) + return false; + + HANDLE hCsr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pfnCsrGetProcessId()); + if (hCsr != NULL) + { + CloseHandle(hCsr); + return true; + } + else + return false; +} + +{% endhighlight %} + +
+ +
+
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + CHAR szFileName[MAX_PATH]; + if (0 == GetModuleFileNameA(NULL, szFileName, sizeof(szFileName))) + return false; + + return INVALID_HANDLE_VALUE == CreateFileA(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); +} + +{% endhighlight %} + +
+ +
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + __try + { + CloseHandle((HANDLE)0xDEADBEEF); + return false; + } + __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode() + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) + { + return true; + } +} + +{% endhighlight %} + +
+ +
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + CHAR szBuffer[] = { "C:\\Windows\\System32\\calc.exe" }; + LoadLibraryA(szBuffer); + return INVALID_HANDLE_VALUE == CreateFileA(szBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); +} + +{% endhighlight %} + +
+ +
+ +C/C++ Code + + +{% highlight c %} + +typedef struct _OBJECT_TYPE_INFORMATION +{ + UNICODE_STRING TypeName; + ULONG TotalNumberOfHandles; + ULONG TotalNumberOfObjects; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef struct _OBJECT_ALL_INFORMATION +{ + ULONG NumberOfObjects; + OBJECT_TYPE_INFORMATION ObjectTypeInformation[1]; +} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION; + +typedef NTSTATUS (WINAPI *TNtQueryObject)( + HANDLE Handle, + OBJECT_INFORMATION_CLASS ObjectInformationClass, + PVOID ObjectInformation, + ULONG ObjectInformationLength, + PULONG ReturnLength +); + +enum { ObjectAllTypesInformation = 3 }; + +#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 + +bool Check() +{ + bool bDebugged = false; + NTSTATUS status; + LPVOID pMem = nullptr; + ULONG dwMemSize; + POBJECT_ALL_INFORMATION pObjectAllInfo; + PBYTE pObjInfoLocation; + HMODULE hNtdll; + TNtQueryObject pfnNtQueryObject; + + hNtdll = LoadLibraryA("ntdll.dll"); + if (!hNtdll) + return false; + + pfnNtQueryObject = (TNtQueryObject)GetProcAddress(hNtdll, "NtQueryObject"); + if (!pfnNtQueryObject) + return false; + + status = pfnNtQueryObject( + NULL, + (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, + &dwMemSize, sizeof(dwMemSize), &dwMemSize); + if (STATUS_INFO_LENGTH_MISMATCH != status) + goto NtQueryObject_Cleanup; + + pMem = VirtualAlloc(NULL, dwMemSize, MEM_COMMIT, PAGE_READWRITE); + if (!pMem) + goto NtQueryObject_Cleanup; + + status = pfnNtQueryObject( + (HANDLE)-1, + (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, + pMem, dwMemSize, &dwMemSize); + if (!SUCCEEDED(status)) + goto NtQueryObject_Cleanup; + + pObjectAllInfo = (POBJECT_ALL_INFORMATION)pMem; + pObjInfoLocation = (PBYTE)pObjectAllInfo->ObjectTypeInformation; + for(UINT i = 0; i < pObjectAllInfo->NumberOfObjects; i++) + { + + POBJECT_TYPE_INFORMATION pObjectTypeInfo = + (POBJECT_TYPE_INFORMATION)pObjInfoLocation; + + if (wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0) + { + if (pObjectTypeInfo->TotalNumberOfObjects > 0) + bDebugged = true; + break; + } + + // Get the address of the current entries + // string so we can find the end + pObjInfoLocation = (PBYTE)pObjectTypeInfo->TypeName.Buffer; + + // Add the size + pObjInfoLocation += pObjectTypeInfo->TypeName.Length; + + // Skip the trailing null and alignment bytes + ULONG tmp = ((ULONG)pObjInfoLocation) & -4; + + // Not pretty but it works + pObjInfoLocation = ((PBYTE)tmp) + sizeof(DWORD); + } + +NtQueryObject_Cleanup: + if (pMem) + VirtualFree(pMem, 0, MEM_RELEASE); + + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
+ +* ntdll!OpenProcess: Return NULL if the third argument is the handle of csrss.exe. +* ntdll!NtClose: You can check if it is possible to retrieve any information about the input handle using ntdll!NtQueryObject() and not throw an exception if the handle is invalid. +* ntdll!NtQueryObject: Filter debug objects from the results if the ObjectAllTypesInformation class is queried. + +The following techniques should be handled without hooks: + +* ntdll!NtCreateFile: Too generic to mitigate. However, if you write a plugin for a specific debugger, you can ensure that the handle of the debugged file is closed. +* kernel32!LoadLibraryW/A: No mitigation. diff --git a/_src/Anti-Debug/techniques/process-memory.md b/_src/Anti-Debug/techniques/process-memory.md new file mode 100644 index 0000000..42aa3e5 --- /dev/null +++ b/_src/Anti-Debug/techniques/process-memory.md @@ -0,0 +1,680 @@ +--- +layout: post +title: "Anti-Debug: Process Memory" +title-image: "/assets/icons/memory.svg" +categories: anti-debug +tags: process-memory +--- + +
+ +
+ +
+
+
+ +C/C++ Code + + +{% highlight c %} + +bool CheckForSpecificByte(BYTE cByte, PVOID pMemory, SIZE_T nMemorySize = 0) +{ + PBYTE pBytes = (PBYTE)pMemory; + for (SIZE_T i = 0; ; i++) + { + // Break on RET (0xC3) if we don't know the function's size + if (((nMemorySize > 0) && (i >= nMemorySize)) || + ((nMemorySize == 0) && (pBytes[i] == 0xC3))) + break; + + if (pBytes[i] == cByte) + return true; + } + return false; +} + +bool IsDebugged() +{ + PVOID functionsToCheck[] = { + &Function1, + &Function2, + &Function3, + }; + for (auto funcAddr : functionsToCheck) + { + if (CheckForSpecificByte(0xCC, funcAddr)) + return true; + } + return false; +} + +{% endhighlight %} + +
+
+ +
+ +C/C++ Code + + +{% highlight c %} + +#include
+#pragma intrinsic(_ReturnAddress)
+
+void foo()
+{
+ // ...
+
+ PVOID pRetAddress = _ReturnAddress();
+ if (*(PBYTE)pRetAddress == 0xCC) // int 3
+ {
+ DWORD dwOldProtect;
+ if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
+ {
+ *(PBYTE)pRetAddress = 0x90; // nop
+ VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
+ }
+ }
+
+ // ...
+}
+
+{% endhighlight %}
+
+
+ +
+ +C/C++ Code + + +{% highlight c %} + +#include
+#pragma intrinsic(_ReturnAddress)
+
+void foo()
+{
+ // ...
+
+ PVOID pRetAddress = _ReturnAddress();
+ if (*(PBYTE)pRetAddress == 0xCC) // int 3
+ {
+ DWORD dwOldProtect, dwRead;
+ CHAR szFilePath[MAX_PATH];
+ HANDLE hFile;
+
+ if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
+ {
+ if (GetModuleFileNameA(NULL, szFilePath, MAX_PATH))
+ {
+ hFile = CreateFileA(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if (INVALID_HANDLE_VALUE != hFile)
+ ReadFile(hFile, pRetAddress, 1, &dwRead, NULL);
+ }
+ VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
+ }
+ }
+
+ // ...
+}
+
+{% endhighlight %}
+
+
+ +
+ +C/C++ Code + + +{% highlight c %} + +#include
+#pragma intrinsic(_ReturnAddress)
+
+void foo()
+{
+ // ...
+
+ BYTE Patch = 0x90;
+ PVOID pRetAddress = _ReturnAddress();
+ if (*(PBYTE)pRetAddress == 0xCC)
+ {
+ DWORD dwOldProtect;
+ if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
+ {
+ WriteProcessMemory(GetCurrentProcess(), pRetAddress, &Patch, 1, NULL);
+ VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
+ }
+ }
+
+ // ...
+}
+
+{% endhighlight %}
+
+
+ +
+ +C/C++ Code + + +{% highlight c %} + +#include
+
+bool foo()
+{
+ // ..
+
+ PVOID pRetAddress = _ReturnAddress();
+ BYTE uByte;
+ if (FALSE != Toolhelp32ReadProcessMemory(GetCurrentProcessId(), _ReturnAddress(), &uByte, sizeof(BYTE), NULL))
+ {
+ if (uByte == 0xCC)
+ ExitProcess(0);
+ }
+
+ // ..
+}
+
+{% endhighlight %}
+
+
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + DWORD dwOldProtect = 0; + SYSTEM_INFO SysInfo = { 0 }; + + GetSystemInfo(&SysInfo); + PVOID pPage = VirtualAlloc(NULL, SysInfo.dwPageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (NULL == pPage) + return false; + + PBYTE pMem = (PBYTE)pPage; + *pMem = 0xC3; + + // Make the page a guard page + if (!VirtualProtect(pPage, SysInfo.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &dwOldProtect)) + return false; + + __try + { + __asm + { + mov eax, pPage + push mem_bp_being_debugged + jmp eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + VirtualFree(pPage, NULL, MEM_RELEASE); + return false; + } + +mem_bp_being_debugged: + VirtualFree(pPage, NULL, MEM_RELEASE); + return true; +} + +{% endhighlight %} + +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + CONTEXT ctx; + ZeroMemory(&ctx, sizeof(CONTEXT)); + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if(!GetThreadContext(GetCurrentThread(), &ctx)) + return false; + + return ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3; +} + +{% endhighlight %} + +
+
+
+ +NTDLL declarations + + +{% highlight c %} + +namespace ntdll +{ +//... + +#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 + +// ... + +typedef enum _MEMORY_INFORMATION_CLASS { + MemoryBasicInformation, + MemoryWorkingSetList, +} MEMORY_INFORMATION_CLASS; + +// ... + +typedef union _PSAPI_WORKING_SET_BLOCK { + ULONG Flags; + struct { + ULONG Protection :5; + ULONG ShareCount :3; + ULONG Shared :1; + ULONG Reserved :3; + ULONG VirtualPage:20; + }; +} PSAPI_WORKING_SET_BLOCK, *PPSAPI_WORKING_SET_BLOCK; + +typedef struct _MEMORY_WORKING_SET_LIST +{ + ULONG NumberOfPages; + PSAPI_WORKING_SET_BLOCK WorkingSetList[1]; +} MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST; + +// ... +} + +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ +#ifndef _WIN64 + NTSTATUS status; + PBYTE pMem = nullptr; + DWORD dwMemSize = 0; + + do + { + dwMemSize += 0x1000; + pMem = (PBYTE)_malloca(dwMemSize); + if (!pMem) + return false; + + memset(pMem, 0, dwMemSize); + status = ntdll::NtQueryVirtualMemory( + GetCurrentProcess(), + NULL, + ntdll::MemoryWorkingSetList, + pMem, + dwMemSize, + NULL); + } while (status == STATUS_INFO_LENGTH_MISMATCH); + + ntdll::PMEMORY_WORKING_SET_LIST pWorkingSet = (ntdll::PMEMORY_WORKING_SET_LIST)pMem; + for (ULONG i = 0; i < pWorkingSet->NumberOfPages; i++) + { + DWORD dwAddr = pWorkingSet->WorkingSetList[i].VirtualPage << 0x0C; + DWORD dwEIP = 0; + __asm + { + push eax + call $+5 + pop eax + mov dwEIP, eax + pop eax + } + + if (dwAddr == (dwEIP & 0xFFFFF000)) + return (pWorkingSet->WorkingSetList[i].Shared == 0) || (pWorkingSet->WorkingSetList[i].ShareCount == 0); + } +#endif // _WIN64 + return false; +} + +{% endhighlight %} + +Credits for this technique: Virus Bulletin + +
+
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebuggerPresent() +{ + HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); + if (!hKernel32) + return false; + + FARPROC pIsDebuggerPresent = GetProcAddress(hKernel32, "IsDebuggerPresent"); + if (!pIsDebuggerPresent) + return false; + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == hSnapshot) + return false; + + PROCESSENTRY32W ProcessEntry; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + + if (!Process32FirstW(hSnapshot, &ProcessEntry)) + return false; + + bool bDebuggerPresent = false; + HANDLE hProcess = NULL; + DWORD dwFuncBytes = 0; + const DWORD dwCurrentPID = GetCurrentProcessId(); + do + { + __try + { + if (dwCurrentPID == ProcessEntry.th32ProcessID) + continue; + + hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessEntry.th32ProcessID); + if (NULL == hProcess) + continue; + + if (!ReadProcessMemory(hProcess, pIsDebuggerPresent, &dwFuncBytes, sizeof(DWORD), NULL)) + continue; + + if (dwFuncBytes != *(PDWORD)pIsDebuggerPresent) + { + bDebuggerPresent = true; + break; + } + } + __finally + { + if (hProcess) + CloseHandle(hProcess); + } + } while (Process32NextW(hSnapshot, &ProcessEntry)); + + if (hSnapshot) + CloseHandle(hSnapshot); + return bDebuggerPresent; +} + +{% endhighlight %} + +Credits for this technique: Rouse_ + +
+
It is called when a debugger attaches to a running process. It allows the debugger to gain control because an exception is raised which it can intercept. If we erase the breakpoint inside ntdll!DbgBreakPoint(), the debugger won't break in and the thread will exit. + +
+ +C/C++ Code + + +{% highlight c %} + +void Patch_DbgBreakPoint() +{ + HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); + if (!hNtdll) + return; + + FARPROC pDbgBreakPoint = GetProcAddress(hNtdll, "DbgBreakPoint"); + if (!pDbgBreakPoint) + return; + + DWORD dwOldProtect; + if (!VirtualProtect(pDbgBreakPoint, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect)) + return; + + *(PBYTE)pDbgBreakPoint = (BYTE)0xC3; // ret +} + +{% endhighlight %} + +
+
As the result, the application will terminate itself once we try to attach the debugger to it. + +
+ +C/C++ Code + + +{% highlight c %} + +#pragma pack(push, 1) + struct DbgUiRemoteBreakinPatch + { + WORD push_0; + BYTE push; + DWORD CurrentPorcessHandle; + BYTE mov_eax; + DWORD TerminateProcess; + WORD call_eax; + }; +#pragma pack(pop) + +void Patch_DbgUiRemoteBreakin() +{ + HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); + if (!hNtdll) + return; + + FARPROC pDbgUiRemoteBreakin = GetProcAddress(hNtdll, "DbgUiRemoteBreakin"); + if (!pDbgUiRemoteBreakin) + return; + + HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); + if (!hKernel32) + return; + + FARPROC pTerminateProcess = GetProcAddress(hKernel32, "TerminateProcess"); + if (!pTerminateProcess) + return; + + DbgUiRemoteBreakinPatch patch = { 0 }; + patch.push_0 = '\x6A\x00'; + patch.push = '\x68'; + patch.CurrentPorcessHandle = 0xFFFFFFFF; + patch.mov_eax = '\xB8'; + patch.TerminateProcess = (DWORD)pTerminateProcess; + patch.call_eax = '\xFF\xD0'; + + DWORD dwOldProtect; + if (!VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), PAGE_READWRITE, &dwOldProtect)) + return; + + ::memcpy_s(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), + &patch, sizeof(DbgUiRemoteBreakinPatch)); + VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), dwOldProtect, &dwOldProtect); +} + +{% endhighlight %} + +Credits for this technique: Rouse_ + +
+
+ +
+
+ +
+ +
+
+ +RDPMC instruction can be used only in Kernel Mode. + +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + ULARGE_INTEGER Start, End; + __asm + { + xor ecx, ecx + rdpmc + mov Start.LowPart, eax + mov Start.HighPart, edx + } + // ... some work + __asm + { + xor ecx, ecx + rdpmc + mov End.LowPart, eax + mov End.HighPart, edx + } + return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + +
+ +RDTSC is a User Mode instruction. + +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + ULARGE_INTEGER Start, End; + __asm + { + xor ecx, ecx + rdtsc + mov Start.LowPart, eax + mov Start.HighPart, edx + } + // ... some work + __asm + { + xor ecx, ecx + rdtsc + mov End.LowPart, eax + mov End.HighPart, edx + } + return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+ + diff --git a/_techniques/cpu.md b/_src/Evasions/techniques/cpu.md similarity index 99% rename from _techniques/cpu.md rename to _src/Evasions/techniques/cpu.md index 6961686..d8afbb3 100644 --- a/_techniques/cpu.md +++ b/_src/Evasions/techniques/cpu.md @@ -273,7 +273,7 @@ MMX instructions may be used as random instructions by malware. Sometimes such s
diff --git a/_techniques/filesystem.md b/_src/Evasions/techniques/filesystem.md similarity index 100% rename from _techniques/filesystem.md rename to _src/Evasions/techniques/filesystem.md diff --git a/_techniques/firmware-tables.md b/_src/Evasions/techniques/firmware-tables.md similarity index 100% rename from _techniques/firmware-tables.md rename to _src/Evasions/techniques/firmware-tables.md diff --git a/_techniques/generic-os-queries.md b/_src/Evasions/techniques/generic-os-queries.md similarity index 100% rename from _techniques/generic-os-queries.md rename to _src/Evasions/techniques/generic-os-queries.md diff --git a/_techniques/global-os-objects.md b/_src/Evasions/techniques/global-os-objects.md similarity index 100% rename from _techniques/global-os-objects.md rename to _src/Evasions/techniques/global-os-objects.md diff --git a/_techniques/hardware.md b/_src/Evasions/techniques/hardware.md similarity index 100% rename from _techniques/hardware.md rename to _src/Evasions/techniques/hardware.md diff --git a/_techniques/hooks.md b/_src/Evasions/techniques/hooks.md similarity index 100% rename from _techniques/hooks.md rename to _src/Evasions/techniques/hooks.md diff --git a/_techniques/human-like-behavior.md b/_src/Evasions/techniques/human-like-behavior.md similarity index 97% rename from _techniques/human-like-behavior.md rename to _src/Evasions/techniques/human-like-behavior.md index a9a08aa..f1827e4 100644 --- a/_techniques/human-like-behavior.md +++ b/_src/Evasions/techniques/human-like-behavior.md @@ -174,7 +174,7 @@ More sophisticated checks rely on detection of not only the mouse movement per s First, malware captures mouse movements with the delay of 50 msec between them.
@@ -182,7 +182,7 @@ First, malware captures mouse movements with the delay of 50 msec between them. Second, the vectors are drawn out of paired captured positions.
@@ -190,7 +190,7 @@ Second, the vectors are drawn out of paired captured positions. Next, the angles are calculated between the corresponding vectors.
@@ -287,14 +287,14 @@ RTF documents consist of normal text, control words, and groups. Microsoft’s R In this code, \sp is the control word for the drawing property, \sn is the property name, and \sv contains information about the property value. The code snippet in the image below exploits a CVE-2010-3333 vulnerability that occurs when using an invalid \sv value for the pFragments shape property:
A closer look at the exploit code, as shown in the next image, reveals a series of paragraph marks (./par) that appears before the exploit code:
@@ -318,7 +318,7 @@ User activity can be checked with the call to the `GetLastInputInfo` function Although Agent Tesla v3 performs this check, it does so incorrectly. Compare the code of Agent Tesla v3 with the correct technique implementation below.
+
+ Go back
+
+ + {% include share.html %} + + diff --git a/_src/Android/assets/icons/anti_analysis.png b/_src/Android/assets/icons/anti_analysis.png new file mode 100644 index 0000000..50ad543 Binary files /dev/null and b/_src/Android/assets/icons/anti_analysis.png differ diff --git a/_src/Android/assets/icons/anti_analysis_gray.png b/_src/Android/assets/icons/anti_analysis_gray.png new file mode 100644 index 0000000..1eb3bfc Binary files /dev/null and b/_src/Android/assets/icons/anti_analysis_gray.png differ diff --git a/_src/Android/assets/icons/emulator_flags.svg b/_src/Android/assets/icons/emulator_flags.svg new file mode 100644 index 0000000..3e52dfa --- /dev/null +++ b/_src/Android/assets/icons/emulator_flags.svg @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/_src/Android/assets/icons/emulator_flags_gray.svg b/_src/Android/assets/icons/emulator_flags_gray.svg new file mode 100644 index 0000000..3e52dfa --- /dev/null +++ b/_src/Android/assets/icons/emulator_flags_gray.svg @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/_src/Android/assets/icons/environment.png b/_src/Android/assets/icons/environment.png new file mode 100644 index 0000000..de227d0 Binary files /dev/null and b/_src/Android/assets/icons/environment.png differ diff --git a/_src/Android/assets/icons/environment_gray.png b/_src/Android/assets/icons/environment_gray.png new file mode 100644 index 0000000..55302a9 Binary files /dev/null and b/_src/Android/assets/icons/environment_gray.png differ diff --git a/_src/Android/assets/icons/human_behavior.png b/_src/Android/assets/icons/human_behavior.png new file mode 100644 index 0000000..22ab70f Binary files /dev/null and b/_src/Android/assets/icons/human_behavior.png differ diff --git a/_src/Android/assets/icons/human_behavior_gray.png b/_src/Android/assets/icons/human_behavior_gray.png new file mode 100644 index 0000000..896e9d5 Binary files /dev/null and b/_src/Android/assets/icons/human_behavior_gray.png differ diff --git a/_src/Android/assets/images/eocd_end_sequence.png b/_src/Android/assets/images/eocd_end_sequence.png new file mode 100644 index 0000000..62c6928 Binary files /dev/null and b/_src/Android/assets/images/eocd_end_sequence.png differ diff --git a/_src/Android/assets/images/eocd_multi_file_exception.png b/_src/Android/assets/images/eocd_multi_file_exception.png new file mode 100644 index 0000000..e9d3b3b Binary files /dev/null and b/_src/Android/assets/images/eocd_multi_file_exception.png differ diff --git a/_src/Android/assets/images/eocd_values_to_change.png b/_src/Android/assets/images/eocd_values_to_change.png new file mode 100644 index 0000000..4764a46 Binary files /dev/null and b/_src/Android/assets/images/eocd_values_to_change.png differ diff --git a/_src/Android/assets/images/filenames_long.png b/_src/Android/assets/images/filenames_long.png new file mode 100644 index 0000000..607172b Binary files /dev/null and b/_src/Android/assets/images/filenames_long.png differ diff --git a/_src/Android/assets/images/manifest_array_structure.png b/_src/Android/assets/images/manifest_array_structure.png new file mode 100644 index 0000000..4ee07a7 Binary files /dev/null and b/_src/Android/assets/images/manifest_array_structure.png differ diff --git a/_src/Android/assets/images/manifest_magic_actual.png b/_src/Android/assets/images/manifest_magic_actual.png new file mode 100644 index 0000000..1ce52aa Binary files /dev/null and b/_src/Android/assets/images/manifest_magic_actual.png differ diff --git a/_src/Android/assets/images/manifest_magic_possible_candidates.png b/_src/Android/assets/images/manifest_magic_possible_candidates.png new file mode 100644 index 0000000..0e04ad2 Binary files /dev/null and b/_src/Android/assets/images/manifest_magic_possible_candidates.png differ diff --git a/_src/Android/assets/images/manifest_magic_possible_candidates_consts.png b/_src/Android/assets/images/manifest_magic_possible_candidates_consts.png new file mode 100644 index 0000000..f6f2aa7 Binary files /dev/null and b/_src/Android/assets/images/manifest_magic_possible_candidates_consts.png differ diff --git a/_src/Android/assets/images/manifest_string_offset_wrong.png b/_src/Android/assets/images/manifest_string_offset_wrong.png new file mode 100644 index 0000000..824c8ce Binary files /dev/null and b/_src/Android/assets/images/manifest_string_offset_wrong.png differ diff --git a/_src/Android/assets/images/manifest_string_start.png b/_src/Android/assets/images/manifest_string_start.png new file mode 100644 index 0000000..3d059de Binary files /dev/null and b/_src/Android/assets/images/manifest_string_start.png differ diff --git a/_src/Android/assets/images/manifest_style_fix.png b/_src/Android/assets/images/manifest_style_fix.png new file mode 100644 index 0000000..4cdc201 Binary files /dev/null and b/_src/Android/assets/images/manifest_style_fix.png differ diff --git a/_src/Android/index.html b/_src/Android/index.html new file mode 100644 index 0000000..cf73e5a --- /dev/null +++ b/_src/Android/index.html @@ -0,0 +1,167 @@ +--- +layout: main +--- + + + + + + + +
+
+
+
+ Android: Evasion & Anti-Debug techniques
+
++ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ APK anti-analysis
+
+
+
+
+
+
+
+
+
+
+ Environment detection [coming soon]
+
+
+
+
+
+
+
+
+
+
+ Emulator & debug flags [planned]
+
+
+
+
+
+
+
+
+
+
+ Human behavior
[planned] +
+ [planned] +
Go to the title page
+ ++ + diff --git a/_src/Android/techniques/anti-analysis.md b/_src/Android/techniques/anti-analysis.md new file mode 100644 index 0000000..527d4e5 --- /dev/null +++ b/_src/Android/techniques/anti-analysis.md @@ -0,0 +1,197 @@ +--- +layout: post +title: "APK anti-analysis" +title-image: "/assets/icons/anti-analysis.svg" +categories: evasions android +tags: apk +--- + +
Contents
+ +[APK anti-analysis methods used](#apk-anti-analysis-methods) ++ [1. Wrong ZIP header data values](#wrong-zip-header-values) +
+ [2. Wrong values in the manifest structure](#wrong-values-manifest) +
+ [2.1. Magic value](#magic-value) +
+ [2.2. Array of strings' offsets](#array-strings-offsets) +
+ [2.3. Style pool](#style-pool) +
+ [3. Long filenames](#long-filenames) +
+ [Signature recommendations](#signature-recommendations) +
+
+ +
+ +
APK anti-analysis methods used
+Techniques in this group abuse the values in specific structures, which are crucial for APK analysis. These evasions are actually possible because of the differences in how the Android launcher treats them and how the analysis tools approach the same data. While the main responsibility of the launcher is to execute an application and perform only basic sanity checks, analysis tools strive to provide as much information as possible about an APK. If some logic discrepancies are encountered during the process of analysis, the tool fails, thus showing the presence of an anti-analysis trick. + ++
1. Wrong ZIP header data values
+ +The clue about something strange inside the APK archive (which should be a usual ZIP archive) is seen in the message shown by JEB Pro: + +
+
+
++ +APK is usually not split into multi-disk archives, so this information has to be checked inside - by analyzing the ZIP header data. The necessary entry is the central directory file header. The end of this record EOCD contains information about disk count at offsets 4 and 6. We will also pay attention to the offsets 10 and 12, as explained below. + +
+ +EOCD fields + +
Offset | +Bytes | +Description | +
---|---|---|
0 | +4 | +End of central directory signature = 0x06054b50 | +
4 | +2 | +Number of this disk (or 0xffff for ZIP64) | +
6 | +2 | +Disk where central directory starts (or 0xffff for ZIP64) | +
8 | +2 | +Number of central directory records on this disk (or 0xffff for ZIP64) | +
10 | +2 | +Total number of central directory records (or 0xffff for ZIP64) | +
12 | +4 | +Size of central directory (bytes) (or 0xffffffff for ZIP64) | +
16 | +4 | +Offset of start of central directory, relative to start of archive (or 0xffffffff for ZIP64) | +
20 | +2 | +Comment length (n) | +
22 | +n | +Comment | +
+ +EOCD marks the end of ZIP so the required byte sequence can be found at the end of the file: + +
+
+
++ +The processed struct, along with fixes (explained below), looks like this: + +
+
+
++ +Based on the very large values in the disk number fields, it is clear that the malware developers edited these fields and entries. If to compare this APK with any other legitimate APKs, it will be seen that in these APKs, values elDiskNumber and elStartDiskNumber are equal to 0. And in the current case the disk numbers should be set to 0 as well. + +Also, the values elEntriesInDirectory and elDirectorySize are the same in legitimate APKs as opposed to the target one. As elEntriesInDirectory has ushort type (2 bytes long), its maximum value is 65536 – and can’t be equal to 139122 which is set in elDirectorySize. It means that the value of elDirectorySize must be equal to the value of elEntriesInDirectory, not vice versa, i.e., we set the value of elDirectorySize to 1075. This number corresponds to the files inside the APK, i.e. ZIP archive, but we could not know about it before the fix was done indeed. + + +
+
2. Wrong values in the manifest structure
+ ++
2.1. Magic value
+ +The AndroidManifest file must start with specific magic numbers (0x00080003 or 0x00080001) as expected by APKTool: + +
+
+
++ +However, upon closer examination of this apktool issue, it appears that the only correct value in AndroidManifest header is 0x00080003, which is equal to the constant name CHUNK_AXML_FILE in the apktool source code, not CHUNK_AXML_FILE_BROKEN which stands for 0x00080001. + +
+
+
++ +And the analyzed file starts with 0x00080000. By changing it to the correct value, this issue becomes mitigated. + +
+
2.2. Array of strings' offsets
+ +Another trick to fool analysis tools lies in the incorrent offset in the array of string offsets: + +
+
+
++ +To analyze what causes this exception, we first make a breakdown of AndroidManifest structure: + +
+
+
++ +By checking the offset shown in the exception, we understand that the issue is in the scStringOffsets array field, in its last element (0x24 + 0x160 = 0x184 – the exact offset shown in the exception). +When examining this array closely, we see that the offset of the last string is pointing out of the file. + +
+
+
++ +The string “theme” is wrongly interpreted as an offset value in the last element of the array, number 87. This means that the value of the scStringCount should be less by 1, i.e., set to 86. Now there are 87 elements in the array, all of 4 bytes, multiplication of 87*4 is equal to 348, which is 0x15C in hex. As scStringOffsets fields starts at 0x24, now it ends at 0x24+0x15C, which is equal to 0x180 – exactly what is expected in the analysis tool. + + +
+
2.3. Style pool
+ +In this trick, the value of the scStylePoolOffset field points out from the actual AndroidManifest file. Based on the scStyleCount field (equal to 0), it is a logical assumption that the file shouldn't contain "styles”, and the value of this field should be 0 as well. + +
+
+
++ +
+
3. Long filenames
+ +This technique is related to the files inside the APK. Developers added a large number of files inside nested directories to the asset folder. As a result, the length of the file name and path is over 300 characters. + +These files break the logic of tools that cannot remap file locations and may fail during APK decompilation – like apktool. After analyzing the API calls from bytecode, we can see that there are no actual references to these files. It means that such files can be manually removed from the APK as they are not required anymore. + + ++ +
+
Signature recommendations
+ +No signature recommendations are provided for this evasion group, as the techniques are speaking for themselves. If analysis tools fail to process a sample with some errors shown, it's likely a sign of a used anti-analysis trick. diff --git a/_src/Android/techniques/emulator.md b/_src/Android/techniques/emulator.md new file mode 100644 index 0000000..e69de29 diff --git a/_src/Android/techniques/environment.md b/_src/Android/techniques/environment.md new file mode 100644 index 0000000..e69de29 diff --git a/_src/Android/techniques/human-behavior.md b/_src/Android/techniques/human-behavior.md new file mode 100644 index 0000000..e69de29 diff --git a/_src/Anti-Debug/assets/icons/assembly.svg b/_src/Anti-Debug/assets/icons/assembly.svg new file mode 100644 index 0000000..d2adc7b --- /dev/null +++ b/_src/Anti-Debug/assets/icons/assembly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/debug_flags.svg b/_src/Anti-Debug/assets/icons/debug_flags.svg new file mode 100644 index 0000000..b19f3a3 --- /dev/null +++ b/_src/Anti-Debug/assets/icons/debug_flags.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/exceptions.svg b/_src/Anti-Debug/assets/icons/exceptions.svg new file mode 100644 index 0000000..5949380 --- /dev/null +++ b/_src/Anti-Debug/assets/icons/exceptions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/interactive.svg b/_src/Anti-Debug/assets/icons/interactive.svg new file mode 100644 index 0000000..63c0bee --- /dev/null +++ b/_src/Anti-Debug/assets/icons/interactive.svg @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/memory.svg b/_src/Anti-Debug/assets/icons/memory.svg new file mode 100644 index 0000000..d338d19 --- /dev/null +++ b/_src/Anti-Debug/assets/icons/memory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/misc.svg b/_src/Anti-Debug/assets/icons/misc.svg new file mode 100644 index 0000000..5077c53 --- /dev/null +++ b/_src/Anti-Debug/assets/icons/misc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/object-handles.svg b/_src/Anti-Debug/assets/icons/object-handles.svg new file mode 100644 index 0000000..149776f --- /dev/null +++ b/_src/Anti-Debug/assets/icons/object-handles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/icons/timing.svg b/_src/Anti-Debug/assets/icons/timing.svg new file mode 100644 index 0000000..7d811b6 --- /dev/null +++ b/_src/Anti-Debug/assets/icons/timing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_src/Anti-Debug/assets/images/dbgbreakpoint.png b/_src/Anti-Debug/assets/images/dbgbreakpoint.png new file mode 100644 index 0000000..8ba0614 Binary files /dev/null and b/_src/Anti-Debug/assets/images/dbgbreakpoint.png differ diff --git a/_src/Anti-Debug/index.html b/_src/Anti-Debug/index.html new file mode 100644 index 0000000..9545830 --- /dev/null +++ b/_src/Anti-Debug/index.html @@ -0,0 +1,209 @@ +--- +layout: main +--- + + + + + + + +
+
+
+
+ Windows: Anti-debug techniques
+
++
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Debug Flags
+
+
+
+
+
+
+
+
+
+
+ Object Handles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Exceptions
+
+
+
+
+
+
+
+
+
+ Process Memory
+
+
+
+
+
+
+
+
+
+ Assembly instructions
+
+
+
+
+
+
+
+
+
+ Interactive Checks
+
+
+
+ Go to the title page
+ ++ + diff --git a/_src/Anti-Debug/techniques/assembly.md b/_src/Anti-Debug/techniques/assembly.md new file mode 100644 index 0000000..9a07928 --- /dev/null +++ b/_src/Anti-Debug/techniques/assembly.md @@ -0,0 +1,412 @@ +--- +layout: post +title: "Anti-Debug: Assembly instructions" +title-image: "/assets/icons/assembly.svg" +categories: anti-debug +tags: assembly +--- + +
Contents
+ +[Assembly instructions](#assembly) + +* [1. INT 3](#int3) +* [2. INT 2D](#int2d) +* [3. DebugBreak](#debugbreak) +* [4. ICE](#ice) +* [5. Stack Segment Register](#ss_register) +* [6. Instruction Counting](#instruction-counting) +* [7. POPF and Trap Flag](#popf_and_trap_flag) +* [8. Instruction Prefixes](#instruction_prefixes) +* [Mitigations](#mitigations) ++ +
+ +
Assembly instructions
+The following techniques are intended to detect a debugger presence based on how debuggers behave when the CPU executes a certain instruction. + ++
1. INT 3
+Instruction INT3 is an interruption which is used as a software breakpoint. Without a debugger present, after getting to the INT3 instruction, the exception EXCEPTION_BREAKPOINT (0x80000003) is generated and an exception handler will be called. If the debugger is present, the control won't be given to the exception handler. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm int 3; + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
Besides the short form of INT3 instruction (0xCC opcode), there is also a long form of this instruction: CD 03 opcode. + +When the exception EXCEPTION_BREAKPOINT occurs, the Windows decrements EIP register to the assumed location of the 0xCC opcode and pass the control to the exception handler. In the case of the long form of the INT3 instruction, EIP will point to the middle of the instruction (i.e. to 0x03 byte). Therefore, EIP should be edited in the exception handler if we want to continue execution after the INT3 instruction (otherwise we'll most likely get an EXCEPTION_ACCESS_VIOLATION exception). If not, we can neglect the instruction pointer modification. + +
+ +C/C++ Code + + +{% highlight c %} + +bool g_bDebugged = false; + +int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) +{ + g_bDebugged = code != EXCEPTION_BREAKPOINT; + return EXCEPTION_EXECUTE_HANDLER; +} + +bool IsDebugged() +{ + __try + { + __asm __emit(0xCD); + __asm __emit(0x03); + } + __except (filter(GetExceptionCode(), GetExceptionInformation())) + { + return g_bDebugged; + } +} + +{% endhighlight %} + + +
+ +
+
2. INT 2D
+Just like in the case of INT3 instruction when the instruction INT2D is executed, the exception EXCEPTION_BREAKPOINT is raised as well. But with INT2D, Windows uses the EIP register as an exception address and then increments the EIP register value. Windows also examines the value of the EAX register while INT2D is executed. If it's 1, 3 or 4 on all versions of Windows, or 5 on Vista+, the exception address will be increased by one. + +This instruction can cause problems for some debuggers because after the EIP incrimination, the byte which follows the INT2D instruction will be skipped and the execution might continue from the damaged instruction. + +In the example, we put one-byte NOP instruction after INT2D to skip it in any case. If the program is executed without a debugger, the control will be passed to the exception handler. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm xor eax, eax; + __asm int 0x2d; + __asm nop; + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
3. DebugBreak
+As written in DebugBreak documentation, "DebugBreak causes a breakpoint exception to occur in the current process. This allows the calling thread to signal the debugger to handle the exception". + +If the program is executed without a debugger, the control will be passed to the exception handler. Otherwise, the execution will be intercepted by the debugger. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + DebugBreak(); + } + __except(EXCEPTION_BREAKPOINT) + { + return false; + } + + return true; +} + +{% endhighlight %} + +
+ +
+
4. ICE
+"ICE" is one of Intel's undocumented instructions. Its opcode is 0xF1. It can be used to detect if the program is traced. + +If ICE instruction is executed, the EXCEPTION_SINGLE_STEP (0x80000004) exception will be raised. + +However, if the program has been already traced, the debugger will consider this exception as the normal exception generated by executing the instruction with the SingleStep bit set in the Flags registers. Therefore, under a debugger, the exception handler won't be called and execution will continue after the ICE instruction. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm __emit 0xF1; + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
5. Stack Segment Register
+This is a trick that can be used to detect if the program is being traced. +The trick consists of tracing over the following sequence of assembly instructions: +{% highlight asm %} +push ss +pop ss +pushf +{% endhighlight %} + +After single-stepping in a debugger through this code, the Trap Flag will be set. Usually it's not visible as debuggers clear the Trap Flag after each debugger event is delivered. However, if we previously save EFLAGS to the stack, we'll be able to check whether the Trap Flag is set. + +
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + bool bTraced = false; + + __asm + { + push ss + pop ss + pushf + test byte ptr [esp+1], 1 + jz movss_not_being_debugged + } + + bTraced = true; + +movss_not_being_debugged: + // restore stack + __asm popf; + + return bTraced; +} + +{% endhighlight %} + +
+ +
+
6. Instruction Counting
+This technique abuses how some debuggers handle EXCEPTION_SINGLE_STEP exceptions. + +The idea of this trick is to set hardware breakpoints to each instruction in some predefined sequence (e.g. sequence of NOPs). Execution of the instruction with a hardware breakpoint on it raises the EXCEPTION_SINGLE_STEP exception which can be caught by a vectored exception handler. In the exception handler, we increment a register which plays the role of instruction counter (EAX in our case) and the instruction pointer EIP to pass the control to the next instruction in the sequence. Therefore, each time the control is passed to the next instruction in our sequence, the exception is raised and the counter is incremented. After the sequence is finished, we check the counter and if it is not equal to the length of our sequence, we consider it as if the program is being debugged. + ++ +C/C++ Code + + +{% highlight c %} + +#include "hwbrk.h" + +static LONG WINAPI InstructionCountingExeptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) + { + pExceptionInfo->ContextRecord->Eax += 1; + pExceptionInfo->ContextRecord->Eip += 1; + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +__declspec(naked) DWORD WINAPI InstructionCountingFunc(LPVOID lpThreadParameter) +{ + __asm + { + xor eax, eax + nop + nop + nop + nop + cmp al, 4 + jne being_debugged + } + + ExitThread(FALSE); + +being_debugged: + ExitThread(TRUE); +} + +bool IsDebugged() +{ + PVOID hVeh = nullptr; + HANDLE hThread = nullptr; + bool bDebugged = false; + + __try + { + hVeh = AddVectoredExceptionHandler(TRUE, InstructionCountingExeptionHandler); + if (!hVeh) + __leave; + + hThread = CreateThread(0, 0, InstructionCountingFunc, NULL, CREATE_SUSPENDED, 0); + if (!hThread) + __leave; + + PVOID pThreadAddr = &InstructionCountingFunc; + // Fix thread entry address if it is a JMP stub (E9 XX XX XX XX) + if (*(PBYTE)pThreadAddr == 0xE9) + pThreadAddr = (PVOID)((DWORD)pThreadAddr + 5 + *(PDWORD)((PBYTE)pThreadAddr + 1)); + + for (auto i = 0; i < m_nInstructionCount; i++) + m_hHwBps[i] = SetHardwareBreakpoint( + hThread, HWBRK_TYPE_CODE, HWBRK_SIZE_1, (PVOID)((DWORD)pThreadAddr + 2 + i)); + + ResumeThread(hThread); + WaitForSingleObject(hThread, INFINITE); + + DWORD dwThreadExitCode; + if (TRUE == GetExitCodeThread(hThread, &dwThreadExitCode)) + bDebugged = (TRUE == dwThreadExitCode); + } + __finally + { + if (hThread) + CloseHandle(hThread); + + for (int i = 0; i < 4; i++) + { + if (m_hHwBps[i]) + RemoveHardwareBreakpoint(m_hHwBps[i]); + } + + if (hVeh) + RemoveVectoredExceptionHandler(hVeh); + } + + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
7. POPF and Trap Flag
+This is another trick that can indicate whether a program is being traced. + +There is a Trap Flag in the Flags register. When the Trap Flag is set, the exception SINGLE_STEP is raised. However, if we traced the code, the Trap Flag will be cleared by a debugger so we won't see the exception. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + __asm + { + pushfd + mov dword ptr [esp], 0x100 + popfd + nop + } + return true; + } + __except(GetExceptionCode() == EXCEPTION_SINGLE_STEP + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_EXECUTION) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
8. Instruction Prefixes
+This trick works only in some debuggers. It abuses the way how these debuggers handle instruction prefixes. + +If we execute the following code in OllyDbg, after stepping to the first byte F3, we'll immediately get to the end of try block. The debugger just skips the prefix and gives the control to the INT1 instruction. + +If we run the same code without a debugger, an exception will be raised and we'll get to except block. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + // 0xF3 0x64 disassembles as PREFIX REP: + __asm __emit 0xF3 + __asm __emit 0x64 + // One byte INT 1 + __asm __emit 0xF1 + return true; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
Mitigations
+* During debugging: + * The best way to mitigate all the following checks is to patch them with NOP instructions. + * Regarding anti-tracing techniques: instead of patching the code, we can simply set a breakpoint in the code which follows the check and run the program till this breakpoint. +* For anti-anti-debug tool development: No mitigation. diff --git a/_src/Anti-Debug/techniques/debug-flags.md b/_src/Anti-Debug/techniques/debug-flags.md new file mode 100644 index 0000000..85defa0 --- /dev/null +++ b/_src/Anti-Debug/techniques/debug-flags.md @@ -0,0 +1,872 @@ +--- +layout: post +title: "Anti-Debug: Debug Flags" +title-image: "/assets/icons/debug-flags.svg" +categories: anti-debug +tags: debug-flags +--- + +Contents
+ +[Debug Flags](#debug-flags) + +* [1. Using Win32 API](#using-win32-api) + * [1.1. IsDebuggerPresent()](#using-win32-api-isdebuggerpresent) + * [1.2. CheckRemoteDebuggerPresent()](#using-win32-api-checkremotedebuggerpresent) + * [1.3. NtQueryInformationProcess()](#using-win32-api-ntqueryinformationprocess) + * [1.3.1. ProcessDebugPort](#using-win32-api-ntqueryinformationprocess-processdebugport) + * [1.3.2. ProcessDebugFlags](#using-win32-api-ntqueryinformationprocess-processdebugflags) + * [1.3.3. ProcessDebugObjectHandle](#using-win32-api-ntqueryinformationprocess-processdebugobjecthandle) + * [1.4. RtlQueryProcessHeapInformation()](#using-win32-api-checks-rtlqueryprocessheapinformation) + * [1.5. RtlQueryProcessDebugInformation()](#using-win32-api-checks-rtlqueryprocessdebuginformation) + * [1.6. NtQuerySystemInformation()](#using-win32-api-checks-ntquerysysteminformation) + * [Mitigations](#mitigations-using-win32-api) +* [2. Manual checks](#manual-checks) + * [2.1. PEB!BeingDebugged Flag](#manual-checks-peb-beingdebugged-flag) + * [2.2. NtGlobalFlag](#manual-checks-ntglobalflag) + * [2.3. Heap Flags](#manual-checks-heap-flags) + * [2.4. Heap Protection](#manual-checks-heap-protection) + * [2.5. Check KUSER_SHARED_DATA structure](#kuser_shared_data) + * [Mitigations](#mitigations-manual-checks) ++ +
+ +
Debug Flags
+Special flags in system tables, which dwell in process memory and which an operation system sets, can be used to indicate that the process is being debugged. The states of these flags can be verified either by using specific API functions or examining the system tables in memory. + +These techniques are the most commonly used by malware. + ++
1. Using Win32 API
+The following techniques use existing API functions (WinAPI or NativeAPI) that check system structures in the process memory for particular flags that indicate the process is being debugged right now. + ++
1.1. IsDebuggerPresent()
+The function kernel32!IsDebuggerPresent() determines whether the current process is being debugged by a user-mode debugger such as OllyDbg or x64dbg. Generally, the function only checks the BeingDebugged flag of the Process Environment Block (PEB). + +The following code can be used to terminate process if it is being debugged: + ++ +Assembly Code + + +{% highlight nasm %} + call IsDebuggerPresent + test al, al + jne being_debugged + ... +being_debugged: + push 1 + call ExitProcess +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} +if (IsDebuggerPresent()) + ExitProcess(-1); +{% endhighlight %} + +
+ +
+
1.2. CheckRemoteDebuggerPresent()
+The function kernel32!CheckRemoteDebuggerPresent() checks if a debugger (in a different process on the same machine) is attached to the current process. + ++ +C/C++ Code + + +{% highlight c %} + +BOOL bDebuggerPresent; +if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && + TRUE == bDebuggerPresent) + ExitProcess(-1); + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, bDebuggerPresent] + push eax + push -1 ; GetCurrentProcess() + call CheckRemoteDebuggerPresent + cmp [bDebuggerPresent], 1 + jz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rdx, [bDebuggerPresent] + mov rcx, -1 ; GetCurrentProcess() + call CheckRemoteDebuggerPresent + cmp [bDebuggerPresent], 1 + jz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+
1.3. NtQueryInformationProcess()
+The function ntdll!NtQueryInformationProcess() can retrieve a different kind of information from a process. It accepts a ProcessInformationClass parameter which specifies the information you want to get and defines the output type of the ProcessInformation parameter. + ++ +
1.3.1. ProcessDebugPort
+It is possible to retrieve the port number of the debugger for the process using the ntdll!NtQueryInformationProcess(). There is a documented class ProcessDebugPort, which retrieves a DWORD value equal to 0xFFFFFFFF (decimal -1) if the process is being debugged. + ++ +C/C++ Code + + +{% highlight c %} + +typedef NTSTATUS (NTAPI *TNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength + ); + +HMODULE hNtdll = LoadLibraryA("ntdll.dll"); +if (hNtdll) +{ + auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( + hNtdll, "NtQueryInformationProcess"); + + if (pfnNtQueryInformationProcess) + { + DWORD dwProcessDebugPort, dwReturned; + NTSTATUS status = pfnNtQueryInformationProcess( + GetCurrentProcess(), + ProcessDebugPort, + &dwProcessDebugPort, + sizeof(DWORD), + &dwReturned); + + if (NT_SUCCESS(status) && (-1 == dwProcessDebugPort)) + ExitProcess(-1); + } +} + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, [dwReturned] + push eax ; ReturnLength + push 4 ; ProcessInformationLength + lea ecx, [dwProcessDebugPort] + push ecx ; ProcessInformation + push 7 ; ProcessInformationClass + push -1 ; ProcessHandle + call NtQueryInformationProcess + inc dword ptr [dwProcessDebugPort] + jz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rcx, [dwReturned] + push rcx ; ReturnLength + mov r9d, 4 ; ProcessInformationLength + lea r8, [dwProcessDebugPort] + ; ProcessInformation + mov edx, 7 ; ProcessInformationClass + mov rcx, -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [dwProcessDebugPort], -1 + jz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+ +
1.3.2. ProcessDebugFlags
+A kernel structure called EPROCESS, which represents a process object, contains the field NoDebugInherit. The inverse value of this field can be retrieved using an undocumented class ProcessDebugFlags (0x1f). Therefore, if the return value is 0, a debugger is present. + ++ +C/C++ Code + + +{% highlight c %} + +typedef NTSTATUS(NTAPI *TNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN DWORD ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength + ); + +HMODULE hNtdll = LoadLibraryA("ntdll.dll"); +if (hNtdll) +{ + auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( + hNtdll, "NtQueryInformationProcess"); + + if (pfnNtQueryInformationProcess) + { + DWORD dwProcessDebugFlags, dwReturned; + const DWORD ProcessDebugFlags = 0x1f; + NTSTATUS status = pfnNtQueryInformationProcess( + GetCurrentProcess(), + ProcessDebugFlags, + &dwProcessDebugFlags, + sizeof(DWORD), + &dwReturned); + + if (NT_SUCCESS(status) && (0 == dwProcessDebugFlags)) + ExitProcess(-1); + } +} + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, [dwReturned] + push eax ; ReturnLength + push 4 ; ProcessInformationLength + lea ecx, [dwProcessDebugPort] + push ecx ; ProcessInformation + push 1Fh ; ProcessInformationClass + push -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [dwProcessDebugPort], 0 + jz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rcx, [dwReturned] + push rcx ; ReturnLength + mov r9d, 4 ; ProcessInformationLength + lea r8, [dwProcessDebugPort] + ; ProcessInformation + mov edx, 1Fh ; ProcessInformationClass + mov rcx, -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [dwProcessDebugPort], 0 + jz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+ +
1.3.3. ProcessDebugObjectHandle
+When debugging begins, a kernel object called "debug object" is created. It is possible to query for the value of this handle by using the undocumented ProcessDebugObjectHandle (0x1e) class. + ++ +C/C++ Code + + +{% highlight c %} + +typedef NTSTATUS(NTAPI * TNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN DWORD ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength + ); + +HMODULE hNtdll = LoadLibraryA("ntdll.dll"); +if (hNtdll) +{ + auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( + hNtdll, "NtQueryInformationProcess"); + + if (pfnNtQueryInformationProcess) + { + DWORD dwReturned; + HANDLE hProcessDebugObject = 0; + const DWORD ProcessDebugObjectHandle = 0x1e; + NTSTATUS status = pfnNtQueryInformationProcess( + GetCurrentProcess(), + ProcessDebugObjectHandle, + &hProcessDebugObject, + sizeof(HANDLE), + &dwReturned); + + if (NT_SUCCESS(status) && (0 != hProcessDebugObject)) + ExitProcess(-1); + } +} + +{% endhighlight %} + +
+ +x86 Assembly + + +{% highlight nasm %} + lea eax, [dwReturned] + push eax ; ReturnLength + push 4 ; ProcessInformationLength + lea ecx, [hProcessDebugObject] + push ecx ; ProcessInformation + push 1Eh ; ProcessInformationClass + push -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [hProcessDebugObject], 0 + jnz being_debugged + ... +being_debugged: + push -1 + call ExitProcess +{% endhighlight %} + +
+ +x86-64 Assembly + + +{% highlight nasm %} + lea rcx, [dwReturned] + push rcx ; ReturnLength + mov r9d, 4 ; ProcessInformationLength + lea r8, [hProcessDebugObject] + ; ProcessInformation + mov edx, 1Fh ; ProcessInformationClass + mov rcx, -1 ; ProcessHandle + call NtQueryInformationProcess + cmp dword ptr [hProcessDebugObject], 0 + jnz being_debugged + ... +being_debugged: + mov ecx, -1 + call ExitProcess +{% endhighlight %} + +
+
1.4. RtlQueryProcessHeapInformation()
+The ntdll!RtlQueryProcessHeapInformation() function can be used to read the heap flags from the process memory of the current process. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); + if (!SUCCEEDED(ntdll::RtlQueryProcessHeapInformation((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer))) + return false; + + ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; + return dwFlags & ~HEAP_GROWABLE; +} + +{% endhighlight %} + +
+
1.5. RtlQueryProcessDebugInformation()
+The ntdll!RtlQueryProcessDebugInformation() function can be used to read certain fields from the process memory of the requested process, including the heap flags. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); + if (!SUCCEEDED(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer))) + return false; + + ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; + return dwFlags & ~HEAP_GROWABLE; +} + +{% endhighlight %} + +
+
1.6. NtQuerySystemInformation()
+The ntdll!NtQuerySystemInformation() function accepts a parameter which is the class of information to query. Most of the classes are not documented. This includes the SystemKernelDebuggerInformation (0x23) class, which has existed since Windows NT. The SystemKernelDebuggerInformation class returns the value of two flags: KdDebuggerEnabled in al, and KdDebuggerNotPresent in ah. Therefore, the return value in ah is zero if a kernel debugger is present. + ++ +C/C++ Code + + +{% highlight c %} + +enum { SystemKernelDebuggerInformation = 0x23 }; + +typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION { + BOOLEAN DebuggerEnabled; + BOOLEAN DebuggerNotPresent; +} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; + +bool Check() +{ + NTSTATUS status; + SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInfo; + + status = NtQuerySystemInformation( + (SYSTEM_INFORMATION_CLASS)SystemKernelDebuggerInformation, + &SystemInfo, + sizeof(SystemInfo), + NULL); + + return SUCCEEDED(status) + ? (SystemInfo.DebuggerEnabled && !SystemInfo.DebuggerNotPresent) + : false; +} + +{% endhighlight %} + +
+
Mitigations
+-
+
+
- For IsDebuggerPresent(): Set the BeingDebugged flag of the Process Environment Block (PEB) to 0. See BeingDebugged Flag Mitigation for further information. + +
- For CheckRemoteDebuggerPresent() and NtQueryInformationProcess():
As CheckRemoteDebuggerPresent() calls NtQueryInformationProcess(), the only way is to hook the NtQueryInformationProcess() and set the following values in return buffers:
+
+ - 0 (or any value except -1) in case of a ProcessDebugPort query. +
- Non-zero value in case of a ProcessDebugFlags query. +
- 0 in case of a ProcessDebugObjectHandle query. +
- The only way to mitigate these checks with RtlQueryProcessHeapInformation(), RtlQueryProcessDebugInformation() and NtQuerySystemInformation() functions is to hook them and modify the returned values: +
- RTL_PROCESS_HEAPS::HeapInformation::Heaps[0]::Flags to HEAP_GROWABLE for
+ RtlQueryProcessHeapInformation() and RtlQueryProcessDebugInformation().
+ - SYSTEM_KERNEL_DEBUGGER_INFORMATION::DebuggerEnabled to 0 and
+ SYSTEM_KERNEL_DEBUGGER_INFORMATION::DebuggerNotPresent to 1 for the
+ NtQuerySystemInformation() function in case of a SystemKernelDebuggerInformation query.
+
-
+
-
+
+
2. Manual checks
+The following approaches are used to validate debugging flags in system structures. They examine the process memory manually without using special debug API functions. + ++
2.1. PEB!BeingDebugged Flag
+This method is just another way to check BeingDebugged flag of PEB without calling IsDebuggerPresent(). + ++ +32Bit Process + + +{% highlight nasm %} +mov eax, fs:[30h] +cmp byte ptr [eax+2], 0 +jne being_debugged +{% endhighlight %} + +
+ +64Bit Process + + +{% highlight nasm %} +mov rax, gs:[60h] +cmp byte ptr [rax+2], 0 +jne being_debugged +{% endhighlight %} + +
+ +WOW64 Process + + +{% highlight nasm %} +mov eax, fs:[30h] +cmp byte ptr [eax+1002h], 0 +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +#endif // _WIN64 + +if (pPeb->BeingDebugged) + goto being_debugged; + +{% endhighlight %} + +
+
2.2. NtGlobalFlag
+The NtGlobalFlag field of the Process Environment Block (0x68 offset on 32-Bit and 0xBC on 64-bit Windows) is 0 by default. Attaching a debugger doesn't change the value of NtGlobalFlag. However, if the process was created by a debugger, the following flags will be set: +-
+
- FLG_HEAP_ENABLE_TAIL_CHECK (0x10) +
- FLG_HEAP_ENABLE_FREE_CHECK (0x20) +
- FLG_HEAP_VALIDATE_PARAMETERS (0x40) +
+ +The presence of a debugger can be detected by checking a combination of those flags. + +
+ +32Bit Process + + +{% highlight nasm %} +mov eax, fs:[30h] +mov al, [eax+68h] +and al, 70h +cmp al, 70h +jz being_debugged +{% endhighlight %} + +
+ +64Bit Process + + +{% highlight nasm %} +mov rax, gs:[60h] +mov al, [rax+BCh] +and al, 70h +cmp al, 70h +jz being_debugged +{% endhighlight %} + +
+ +WOW64 Process + + +{% highlight nasm %} +mov eax, fs:[30h] +mov al, [eax+10BCh] +and al, 70h +cmp al, 70h +jz being_debugged +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10 +#define FLG_HEAP_ENABLE_FREE_CHECK 0x20 +#define FLG_HEAP_VALIDATE_PARAMETERS 0x40 +#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS) + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68); +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC); +#endif // _WIN64 + +if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED) + goto being_debugged; + + +{% endhighlight %} + +
+
2.3. Heap Flags
+The heap contains two fields which are affected by the presence of a debugger. Exactly how they are affected depends on the Windows version. These fields are Flags and ForceFlags. + +The values of Flags and ForceFlags are normally set to HEAP_GROWABLE and 0, respectively. + ++ +When a debugger is present, the Flags field is set to a combination of these flags on Windows NT, Windows 2000, and 32-bit Windows XP: +
-
+
- HEAP_GROWABLE (2) +
- HEAP_TAIL_CHECKING_ENABLED (0x20) +
- HEAP_FREE_CHECKING_ENABLED (0x40) +
- HEAP_SKIP_VALIDATION_CHECKS (0x10000000) +
- HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000) +
+ +On 64-bit Windows XP, and Windows Vista and higher, if a debugger is present, the Flags field is set to a combination of these flags: +
-
+
- HEAP_GROWABLE (2) +
- HEAP_TAIL_CHECKING_ENABLED (0x20) +
- HEAP_FREE_CHECKING_ENABLED (0x40) +
- HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000) +
+ +When a debugger is present, the ForceFlags field is set to a combination of these flags: +
-
+
- HEAP_TAIL_CHECKING_ENABLED (0x20) +
- HEAP_FREE_CHECKING_ENABLED (0x40) +
- HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000) +
+ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ +#ifndef _WIN64 + PPEB pPeb = (PPEB)__readfsdword(0x30); + PVOID pHeapBase = !m_bIsWow64 + ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) + : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); + DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x40 + : 0x0C; + DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x44 + : 0x10; +#else + PPEB pPeb = (PPEB)__readgsqword(0x60); + PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); + DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x70 + : 0x14; + DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x74 + : 0x18; +#endif // _WIN64 + + PDWORD pdwHeapFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset); + PDWORD pdwHeapForceFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset); + return (*pdwHeapFlags & ~HEAP_GROWABLE) || (*pdwHeapForceFlags != 0); +} + +{% endhighlight %} + +
+
2.4. Heap Protection
+If the HEAP_TAIL_CHECKING_ENABLED flag is set in NtGlobalFlag, the sequence 0xABABABAB will be appended (twice in 32-Bit and 4 times in 64-Bit Windows) at the end of the allocated heap block. + +If the HEAP_FREE_CHECKING_ENABLED flag is set in NtGlobalFlag, the sequence 0xFEEEFEEE will be appended if additional bytes are required to fill in the empty space until the next memory block. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + PROCESS_HEAP_ENTRY HeapEntry = { 0 }; + do + { + if (!HeapWalk(GetProcessHeap(), &HeapEntry)) + return false; + } while (HeapEntry.wFlags != PROCESS_HEAP_ENTRY_BUSY); + + PVOID pOverlapped = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; + return ((DWORD)(*(PDWORD)pOverlapped) == 0xABABABAB); +} + +{% endhighlight %} + +
+
2.5. Check KUSER_SHARED_DATA structure
+This technique was originally described as an issue for TitanHide, a kernel driver to hide debuggers from detection. The detailed documentation for the structure KUSER_SHARED_DATA and its fields is available here. + +Here is what the author of the issue wrote in the post regarding the features of the structure and its appropriate field: +"0x7ffe02d4 is actually 0x7ffe0000 + 0x2d4. 0x7ffe0000 is the fixed user mode address of the KUSER_SHARED_DATA structure that contains data that is shared between user mode and the kernel (though user mode doesn't have write access to it). The struct has some interesting properties: ++Hence this program will work in 32 bit Windows 2000 and 64 bit Windows 10 without recompiling". + + +
+ +C/C++ Code + + +{% highlight c %} + +bool check_kuser_shared_data_structure() +{ + unsigned char b = *(unsigned char*)0x7ffe02d4; + return ((b & 0x01) || (b & 0x02)); +} + +{% endhighlight %} + +
+
Mitigations
+ ++For PEB!BeingDebugged Flag: + +Set the BeingDebugged flag to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide. + +
+ +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +#endif // _WIN64 +pPeb->BeingDebugged = 0; + +{% endhighlight %} + +
+For NtGlobalFlag: + +Set the NtGlobalFlag to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide. + +
+ +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +*(PDWORD)((PBYTE)pPeb + 0x68) = 0; +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +*(PDWORD)((PBYTE)pPeb + 0xBC); = 0; +#endif // _WIN64 + +{% endhighlight %} + +
+For Heap Flags: + +Set the Flags value to HEAP_GROWABLE, and the ForceFlags value to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide. + +
+ +{% highlight c %} + +#ifndef _WIN64 +PPEB pPeb = (PPEB)__readfsdword(0x30); +PVOID pHeapBase = !m_bIsWow64 + ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) + : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); +DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x40 + : 0x0C; +DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x44 + : 0x10; +#else +PPEB pPeb = (PPEB)__readgsqword(0x60); +PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); +DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() + ? 0x70 + : 0x14; +DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() + ? 0x74 + : 0x18; +#endif // _WIN64 + +*(PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset) = HEAP_GROWABLE; +*(PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset) = 0; + +{% endhighlight %} + +
+For Heap Protection: + +Manually patch 12 bytes for 32-bit and 20 bytes in a 64-bit environment after the heap. Hook kernel32!HeapAlloc() and patch the heap after its allocation. + +
+ +{% highlight c %} + +#ifndef _WIN64 +SIZE_T nBytesToPatch = 12; +#else +SIZE_T nBytesToPatch = 20; +#endif // _WIN64 + +SIZE_T nDwordsToPatch = nBytesToPatch / sizeof(DWORD); +PVOID pHeapEnd = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; +for (SIZE_T offset = 0; offset < nDwordsToPatch; offset++) + *((PDWORD)pHeapEnd + offset) = 0; + +{% endhighlight %} + +
+For KUSER_SHARED_DATA: + +For a possible mitigation, please check the link when the technique is described (with the issue for TitanHide) and also a draft code for patching kdcom.dll here. diff --git a/_src/Anti-Debug/techniques/exceptions.md b/_src/Anti-Debug/techniques/exceptions.md new file mode 100644 index 0000000..5b70967 --- /dev/null +++ b/_src/Anti-Debug/techniques/exceptions.md @@ -0,0 +1,237 @@ +--- +layout: post +title: "Anti-Debug: Exceptions" +title-image: "/assets/icons/exceptions.svg" +categories: anti-debug +tags: exceptions +--- + +
Contents
+ +[Exceptions](#exceptions) + +* [1. UnhandledExceptionFilter()](#unhandledexceptionfilter) +* [2. RaiseException()](#raiseexception) +* [3. Hiding Control Flow with Exception Handlers](#hiding-cf-with-eh) +* [Mitigations](#mitigations) ++ +
+ +
Exceptions
+The following methods deliberately cause exceptions to verify if the further behavior is not typical for a process running without a debugger. + ++
1. UnhandledExceptionFilter()
+If an exception occurs and no exception handler is registered (or it is registered but doesn't handle such an exception), the kernel32!UnhandledExceptionFilter() function will be called. It is possible to register a custom unhandled exception filter using the kernel32!SetUnhandledExceptionFilter(). But if the program is running under a debugger, the custom filter won't be called and the exception will be passed to the debugger. Therefore, if the unhandled exception filter is registered and the control is passed to it, then the process is not running with a debugger. + ++ +x86 Assembly (FASM) + + +{% highlight asm %} +include 'win32ax.inc' + +.code + +start: + jmp begin + +not_debugged: + invoke MessageBox,HWND_DESKTOP,"Not Debugged","",MB_OK + invoke ExitProcess,0 + +begin: + invoke SetUnhandledExceptionFilter, not_debugged + int 3 + jmp being_debugged + +being_debugged: + invoke MessageBox,HWND_DESKTOP,"Debugged","",MB_OK + invoke ExitProcess,0 + +.end start +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) +{ + PCONTEXT ctx = pExceptionInfo->ContextRecord; + ctx->Eip += 3; // Skip \xCC\xEB\x?? + return EXCEPTION_CONTINUE_EXECUTION; +} + +bool Check() +{ + bool bDebugged = true; + SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UnhandledExceptionFilter); + __asm + { + int 3 // CC + jmp near being_debugged // EB ?? + } + bDebugged = false; + +being_debugged: + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
2. RaiseException()
+Exceptions such as DBC_CONTROL_C or DBG_RIPEVENT are not passed to exception handlers of the current process and are consumed by a debugger. This lets us register an exception handler, raise these exceptions using the kernel32!RaiseException() function, and check whether the control is passed to our handler. If the exception handler is not called, the process is likely under debugging. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + __try + { + RaiseException(DBG_CONTROL_C, 0, 0, NULL); + return true; + } + __except(DBG_CONTROL_C == GetExceptionCode() + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) + { + return false; + } +} + +{% endhighlight %} + +
+ +
+
3. Hiding Control Flow with Exception Handlers
+This approach does not check whether a debugger is present, but it helps to hide the control flow of the program in the sequence of exception handlers. + +We can register an exception handler (structured or vectored) which raises another exception which is passed to the next handler which raises the next exception, and so on. Finally, the sequence of handlers should lead to the procedure that we wanted to hide. + +Using Structured Exception Handlers: + +
+ +C/C++ Code + + +{% highlight c %} + +#include
Using Vectored Exception Handlers: + +
+ +C/C++ Code + + +{% highlight c %} + +#include
+ +
+
Mitigations
+* During debugging: + * For debugger detection checks: Just fill the corresponding check with NOPs. + * For Control Flow hiding: You have to manually trace the program till the payload. +* For anti-anti-debug tool development: The issue with these type of techniques is that different debuggers consume different exceptions and do not return them to the debugger. This means that you have to implement a plugin for a specific debugger and change the behavior of the event handlers which are triggered after the corresponding exceptions. diff --git a/_src/Anti-Debug/techniques/interactive.md b/_src/Anti-Debug/techniques/interactive.md new file mode 100644 index 0000000..ed2bc71 --- /dev/null +++ b/_src/Anti-Debug/techniques/interactive.md @@ -0,0 +1,449 @@ +--- +layout: post +title: "Anti-Debug: Direct debugger interaction" +title-image: "/assets/icons/interactive.svg" +categories: anti-debug +tags: interactive +--- + +Contents
+ +[Direct debugger interaction](#interactive) + +* [1. Self-Debugging](#self-debugging) +* [2. GenerateConsoleCtrlEvent()](#generateconsolectrlevent) +* [3. BlockInput()](#blockinput) +* [4. NtSetInformationThread()](#ntsetinformationthread) +* [5. EnumWindows() and SuspendThread()](#suspendthread) +* [6. SwitchDesktop()](#switchdesktop) +* [7. OutputDebugString()](#outputdebugstring) +* [Mitigations](#mitigations) ++ +
+ +
Direct debugger interaction
+The following techniques let the running process manage a user interface or engage with its parent process to discover inconsistencies that are inherent for a debugged process. + ++
1. Self-Debugging
+There are at least three functions that can be used to attach as a debugger to a running process: + +* kernel32!DebugActiveProcess() +* ntdll!DbgUiDebugActiveProcess() +* ntdll!NtDebugActiveProcess() + +As only one debugger can be attached to a process at a time, a failure to attach to the process might indicate the presence of another debugger. + +In the example below, we run the second instance of our process which tries to attach a debugger to its parent (the first instance of the process). If kenel32!DebugActiveProcess() finishes unsuccessfully, we set the named event which was created by the first instance. If the event is set, the first instance understands that a debugger is present. + ++ +C/C++ Code + + +{% highlight c %} + +#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; + + hDbgEvent = CreateEventW(NULL, FALSE, FALSE, EVENT_SELFDBG_EVENT_NAME); + if (!hDbgEvent) + return false; + + if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath))) + return false; + + swprintf_s(wszCmdLine, L"%s %d", wszFilePath, GetCurrentProcessId()); + if (CreateProcessW(NULL, wszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return WAIT_OBJECT_0 == WaitForSingleObject(hDbgEvent, 0); + } + + return false; +} + +bool EnableDebugPrivilege() +{ + bool bResult = false; + HANDLE hToken = NULL; + DWORD ec = 0; + + do + { + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + break; + + TOKEN_PRIVILEGES tp; + tp.PrivilegeCount = 1; + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) + break; + + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if( !AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(tp), NULL, NULL)) + break; + + bResult = true; + } + while (0); + + if (hToken) + CloseHandle(hToken); + + return bResult; +} + +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); + } + + // ... + + return 0; +} + +{% endhighlight %} + +
+ +
+
2. GenerateConsoleCtrlEvent()
+When a user presses Ctrl+C or Ctrl+Break and a console window is in the focus, Windows checks if there is a handler for this event. All console processes have a default handler function that calls the kernel32!ExitProcess() function. However, we can register a custom handler for these events which neglects the Ctrl+C or Ctrl+Break signals. + +However, if a console process is being debugged and CTRL+C signals have not been disabled, the system generates a DBG_CONTROL_C exception. Usually this exception is intercepted by a debugger, but if we register an exception handler, we will be able to check whether DBG_CONTROL_C is raised. If we intercepted the DBG_CONTROL_C exception in our own exception handler, it may indicate that the process is being debugged. + ++ +C/C++ Code + + +{% highlight c %} + +bool g_bDebugged{ false }; +std::atomic
+ +
+
3. BlockInput()
+The function user32!BlockInput() can block all mouse and keyboard events, which is quite an effective way to disable a debugger. On Windows Vista and higher versions, this call requires administrator privileges. + +We can also detect whether a tool that hooks the user32!BlockInput() and other anti-debug calls is present. The function allows the input to be blocked only once. The second call will return FALSE. If the function returns TRUE regardless of the input, it may indicate that some hooking solution is present. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsHooked () +{ + BOOL bFirstResult = FALSE, bSecondResult = FALSE; + __try + { + bFirstResult = BlockInput(TRUE); + bSecondResult = BlockInput(TRUE); + } + __finally + { + BlockInput(FALSE); + } + return bFirstResult && bSecondResult; +} + +{% endhighlight %} + +
+ +
+
4. NtSetInformationThread()
+The function ntdll!NtSetInformationThread() can be used to hide a thread from a debugger. It is possible with a help of the undocumented value THREAD_INFORMATION_CLASS::ThreadHideFromDebugger (0x11). This is intended to be used by an external process, but any thread can use it on itself. + +After the thread is hidden from the debugger, it will continue running but the debugger won’t receive events related to this thread. This thread can perform anti-debugging checks such as code checksum, debug flags verification, etc. + +However, if there is a breakpoint in the hidden thread or if we hide the main thread from the debugger, the process will crash and the debugger will be stuck. + +In the example, we hide the current thread from the debugger. This means that if we trace this code in the debugger or put a breakpoint to any instruction of this thread, the debugging will be stuck once ntdll!NtSetInformationThread() is called. + ++ +C/C++ Code + + +{% highlight c %} + +#define NtCurrentThread ((HANDLE)-2) + +bool AntiDebug() +{ + NTSTATUS status = ntdll::NtSetInformationThread( + NtCurrentThread, + ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, + NULL, + 0); + return status >= 0; +} + +{% endhighlight %} + +
+ +
+
5. EnumWindows() and SuspendThread()
+The idea of this technique is to suspend the owning thread of the parent process. + +First, we need to verify whether the parent process is a debugger. This can be achieved by enumerating all top-level windows on the screen (using user32!EnumWindows() or user32!EnumThreadWindows()), searching the window for which process ID is the ID of the parent process (using user32!GetWindowThreadProcessId()), and checking the title of this window (by user32!GetWindowTextW()). If the window title of the parent process looks like a debugger title, we can suspend the owning thread using kernel32!SuspendThread() or ntdll!NtSuspendThread(). + ++ +C/C++ Code + + +{% highlight c %} + +DWORD g_dwDebuggerProcessId = -1; + +BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) +{ + DWORD dwProcessId = *(PDWORD)lParam; + + DWORD dwWindowProcessId; + GetWindowThreadProcessId(hwnd, &dwWindowProcessId); + + if (dwProcessId == dwWindowProcessId) + { + std::wstring wsWindowTitle{ string_heper::ToLower(std::wstring(GetWindowTextLengthW(hwnd) + 1, L'\0')) }; + GetWindowTextW(hwnd, &wsWindowTitle[0], wsWindowTitle.size()); + + if (string_heper::FindSubstringW(wsWindowTitle, L"dbg") || + string_heper::FindSubstringW(wsWindowTitle, L"debugger")) + { + g_dwDebuggerProcessId = dwProcessId; + return FALSE; + } + return FALSE; + } + + return TRUE; +} + +bool IsDebuggerProcess(DWORD dwProcessId) const +{ + EnumWindows(EnumWindowsProc, reinterpret_cast
+ +
+
6. SwitchDesktop()
+Windows supports multiple desktops per session. It is possible to select a different active desktop, which has the effect of hiding the windows of the previously active desktop, and with no obvious way to switch back to the old desktop. + +Further, the mouse and keyboard events from the debugged process desktop will no longer be delivered to the debugger, because their source is no longer shared. This obviously makes debugging impossible. + ++ +C/C++ Code + + +{% highlight c %} + +BOOL Switch() +{ + HDESK hNewDesktop = CreateDesktopA( + m_pcszNewDesktopName, + NULL, + NULL, + 0, + DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, + NULL); + if (!hNewDesktop) + return FALSE; + + return SwitchDesktop(hNewDesktop); +} + +{% endhighlight %} + +
+ +
+
7. OutputDebugString()
+This technique is deprecated as it works only for Windows versions earlier than Vista. However, this technique is too well known to not mention here. + +The idea is simple. If a debugger is not present and kernel32!OutputDebugString is called, then an error will occur. + ++ +C/C++ Code (variant1) + + +{% highlight c %} + +bool IsDebugged() +{ + if (IsWindowsVistaOrGreater()) + return false; + + DWORD dwLastError = GetLastError(); + OutputDebugString(L"AntiDebug_OutputDebugString_v1"); + return GetLastError() != dwLastError; +} + +{% endhighlight %} + +
+ +C/C++ Code (variant2) + + +{% highlight c %} + +bool IsDebugged() +{ + if (IsWindowsVistaOrGreater()) + return false; + + DWORD dwErrVal = 0x666; + SetLastError(dwErrVal); + OutputDebugString(L"AntiDebug_OutputDebugString_v2"); + return GetLastError() != dwErrVal; +} + +{% endhighlight %} + +
+ +
+
Mitigations
+During debugging, it is better to skip suspicious function calls (e.g. fill them with NOPs). + +If you write an anti-anti-debug solution, all the following functions can be hooked: + +* kernel32!DebugActiveProcess +* ntdll!DbgUiDebugActiveProcess +* ntdll!NtDebugActiveProcess +* kernel32!GenerateConsoleCtrlEvent() +* user32!NtUserBlockInput +* ntdll!NtSetInformationThread +* user32!NtUserBuildHwndList (for filtering EnumWindows output) +* kernel32!SuspendThread +* user32!SwitchDesktop +* kernel32!OutputDebugStringW + +Hooked functions can check input arguments and modify the original function behavior. \ No newline at end of file diff --git a/_src/Anti-Debug/techniques/misc.md b/_src/Anti-Debug/techniques/misc.md new file mode 100644 index 0000000..864d4d5 --- /dev/null +++ b/_src/Anti-Debug/techniques/misc.md @@ -0,0 +1,512 @@ +--- +layout: post +title: "Anti-Debug: Misc" +title-image: "/assets/icons/misc.svg" +categories: anti-debug +tags: misc +--- + +Contents
+ +[Misc](#misc) + +* [1. FindWindow()](#findwindow) +* [2. Parent Process Check](#parent-process-check) + * [2.1. NtQueryInformationProcess()](#parent-process-check-ntqueryinformationprocess) + * [2.2. CreateToolhelp32Snapshot()](#parent-process-check-createtoolhelp32snapshot) +* [3. Selectors](#selectors) +* [4. DbgPrint()](#dbgprint) +* [5. DbgSetDebugFilterState()](#dbgsetdebugfilterstate) +* [6. NtYieldExecution() / SwitchToThread()](#switchtothread) +* [7. VirtualAlloc() / GetWriteWatch()](#getwritewatch) +* [Mitigations](#mitigations) ++ +
+ +
Misc
+ ++
1. FindWindow()
+This technique includes the simple enumeration of window classes in the system and comparing them with known windows classes of debuggers. + +The following functions can be used: + +* user32!FindWindowW() +* user32!FindWindowA() +* user32!FindWindowExW() +* user32!FindWindowExA() + ++ +C/C++ Code + + +{% highlight c %} + +const std::vector
+ +
+
2. Parent Process Check
+Normally, a user-mode process is executed by double-clicking on a file icon. If the process is executed this way, its parent process will be the shell process ("explorer.exe"). + +The main idea of the two following methods is to compare the PID of the parent process with the PID of "explorer.exe". + ++
2.1. NtQueryInformationProcess()
+This method includes obtaining the shell process window handle using user32!GetShellWindow() and obtaining its process ID by calling user32!GetWindowThreadProcessId(). + +Then, the parent process ID can be obtained from the PROCESS_BASIC_INFORMATION structure by calling ntdll!NtQueryInformationProcess() with the ProcessBasicInformation class. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + HWND hExplorerWnd = GetShellWindow(); + if (!hExplorerWnd) + return false; + + DWORD dwExplorerProcessId; + GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId); + + ntdll::PROCESS_BASIC_INFORMATION ProcessInfo; + NTSTATUS status = ntdll::NtQueryInformationProcess( + GetCurrentProcess(), + ntdll::PROCESS_INFORMATION_CLASS::ProcessBasicInformation, + &ProcessInfo, + sizeof(ProcessInfo), + NULL); + if (!NT_SUCCESS(status)) + return false; + + return (DWORD)ProcessInfo.InheritedFromUniqueProcessId != dwExplorerProcessId; +} + +{% endhighlight %} + +
+
2.2. CreateToolhelp32Snapshot()
+The parent process ID and the parent process name can be obtained using the kernel32!CreateToolhelp32Snapshot() and kernel32!Process32Next() functions. + ++ +C/C++ Code + + +{% highlight c %} + +DWORD GetParentProcessId(DWORD dwCurrentProcessId) +{ + DWORD dwParentProcessId = -1; + PROCESSENTRY32W ProcessEntry = { 0 }; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(Process32FirstW(hSnapshot, &ProcessEntry)) + { + do + { + if (ProcessEntry.th32ProcessID == dwCurrentProcessId) + { + dwParentProcessId = ProcessEntry.th32ParentProcessID; + break; + } + } while(Process32NextW(hSnapshot, &ProcessEntry)); + } + + CloseHandle(hSnapshot); + return dwParentProcessId; +} + +bool IsDebugged() +{ + bool bDebugged = false; + DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); + + PROCESSENTRY32 ProcessEntry = { 0 }; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(Process32First(hSnapshot, &ProcessEntry)) + { + do + { + if ((ProcessEntry.th32ProcessID == dwParentProcessId) && + (strcmp(ProcessEntry.szExeFile, "explorer.exe"))) + { + bDebugged = true; + break; + } + } while(Process32Next(hSnapshot, &ProcessEntry)); + } + + CloseHandle(hSnapshot); + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
3. Selectors
+Selector values might appear to be stable, but they are actually volatile in certain circumstances, and also depending on the version of Windows. For example, a selector value can be set within a thread, but it might not hold that value for very long. Certain events might cause the selector value to be changed back to its default value. One such event is an exception. In the context of a debugger, the single-step exception is still an +exception, which can cause some unexpected behavior. + +x86 Assembly + + +{% highlight nasm %} + xor eax, eax + push fs + pop ds +l1: xchg [eax], cl + xchg [eax], cl +{% endhighlight %} + ++ +On the 64-bit versions of Windows, single-stepping through this code will cause an access violation exception at l1 because the DS selector will be restored to its default value even before l1 is reached. On the 32-bit versions of Windows, the DS selector will not have its value restored, unless a non-debugging exception occurs. The version-specific difference in behaviors expands even further if the SS selector is used. On the 64-bit versions of Windows, the SS selector will be restored to its default value, as in the DS selector case. However, on the 32-bit versions of Windows, the SS selector value will not be restored, even if an exception occurs. + +x86-64 Assembly + + +{% 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 + ... +{% endhighlight %} + +
+ +then when the "int 3" instruction is reached at l1 and the breakpoint exception occurs, the exception handler at l2 is not called as expected. Instead, the process is simply terminated. + +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 +jne being_debugged +{% endhighlight %} + +
+ +The FS and GS selectors are special cases. For certain values, they will be affected by the single-step event, even on the 32-bit versions of Windows. However, in the case of the FS selector (and, technically, the GS selector), it will be not restored to its default value on the 32-bit versions of Windows, if it was set to a value from zero to three. Instead, it will be set to zero (the GS selector is affected in the same way, but the default value for the GS selector is zero). On the 64-bit versions of Windows, it (they) will be restored to its (their) default value. + +This code is also vulnerable to a race condition caused by a thread-switch event. When a thread-switch event occurs, it behaves like an exception, and will cause the selector values to be altered, which, in the case of the FS selector, means that it will be set to zero. + +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 +{% endhighlight %} + +
+ +However, this code is vulnerable to the problem that it was trying to detect in the first place, because it does not check if the original assignment was successful. Of course, the two code snippets can be combined to produce the desired effect, by waiting until the thread-switch event occurs, and then performing the assignment within the window of time that should exist until the next one occurs. [Ferrie] + +
+ +C/C++ Code + + +{% highlight c %} + +bool IsTraced() +{ + __asm + { + push 3 + pop gs + + __asm SeclectorsLbl: + mov ax, gs + cmp al, 3 + je SeclectorsLbl + + push 3 + pop gs + mov ax, gs + cmp al, 3 + jne Selectors_Debugged + } + + return false; + +Selectors_Debugged: + return true; +} + +{% endhighlight %} + +
+ +
+
4. DbgPrint()
+The debug functions such as ntdll!DbgPrint() and kernel32!OutputDebugStringW() cause the exception DBG_PRINTEXCEPTION_C (0x40010006). If a program is executed with an attached debugger, then the debugger will handle this exception. But if no debugger is present, and an exception handler is registered, this exception will be caught by the exception handler. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + __try + { + RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); + } + __except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) + { + return false; + } + + return true; +} + +{% endhighlight %} + +
+ +
+
5. DbgSetDebugFilterState()
+The functions ntdll!DbgSetDebugFilterState() and ntdll!NtSetDebugFilterState() only set a flag which will be checked be a kernel-mode debugger if it is present. Therefore, if a kernel debugger is attached to the system, these functions will succeed. However, the functions can also succeed because of side-effects caused by some user-mode debuggers. These functions require administrator privileges. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); +} + +{% endhighlight %} + +
+ +
+
6. NtYieldExecution() / SwitchToThread()
+This method is not really reliable because it only shows if there a high priority thread in the current process. However, it could be used as an anti-tracing technique. + +When an application is traced in a debugger and a single-step is executed, the context can't be switched to other thread. This means that ntdll!NtYieldExecution() returns STATUS_NO_YIELD_PERFORMED (0x40000024), which leads to kernel32!SwitchToThread() returning zero. + +The strategy of using this technique is that there is a loop which modifies some counter if kernel32!SwitchToThread() returns zero, or ntdll!NtYieldExecution() returns STATUS_NO_YIELD_PERFORMED. This can be a loop which decrypts strings or some other loop which is supposed to be analyzed manually in a debugger. If the counter has the expected value (expected i.e. the value if all kernel32!SwitchToThread() returned zero) after leaving the loop, we consider that the debugger is present. + +In the example below, we define a one-byte counter (initialized with 0) which shifts one bit to the left if kernel32!SwitchToThread returns zero. If it shifts 8 times, then the value of the counter will become 0 and the debugger is considered to be present. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + BYTE ucCounter = 1; + for (int i = 0; i < 8; i++) + { + Sleep(0x0F); + ucCounter <<= (1 - SwitchToThread()); + } + + return ucCounter == 0; +} + +{% endhighlight %} + +
+ +
+
7. VirtualAlloc() / GetWriteWatch()
+This technique was described as a suggestion for a famous al-khaser solution, a tool for testing VMs, debuggers, sandboxes, AV, etc. against many malware-like defences. + +The idea is drawn from the documentation for GetWriteWatch function where the following is stated in a "Remarks" section: + +"When you call the VirtualAlloc function to reserve or commit memory, you can specify MEM_WRITE_WATCH. This value causes the system to keep track of the pages that are written to in the committed memory region. You can call the GetWriteWatch function to retrieve the addresses of the pages that have been written to since the region has been allocated or the write-tracking state has been reset". + +This feature can be used to track debuggers that may modify memory pages outside the expected pattern. + ++ +C/C++ Code (variant 1) + + +{% highlight c %} + +bool Generic::CheckWrittenPages1() const { + const int SIZE_TO_CHECK = 4096; + + PVOID* addresses = static_cast
+ +C/C++ Code (variant 2) + + +{% highlight c %} + +bool Generic::CheckWrittenPages2() const { + BOOL result = FALSE, error = FALSE; + + const int SIZE_TO_CHECK = 4096; + + PVOID* addresses = static_cast
+ +
+
Mitigations
+During debugging: Fill anti-debug pr anti-traced checks with NOPs. + + 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. + +2. For Parent Process Checks: Hook ntdll!NtQuerySystemInformation(). If SystemInformationClass is one of the following values: +* SystemProcessInformation +* SystemSessionProcessInformation +* SystemExtendedProcessInformation + + and the process name looks suspicious, then the hook must modify the process name. + +3. For Selectors: No mitigations. + +4. For DbgPrint: you have to implement a plugin for a specific debugger and change the behavior of event handler which is triggered after the DBG_PRINTEXCEPTION_C exception has arrived. + +5. For DbgSetDebugFilterState(): Hook ntdll!NtSetDebugFilterState(). If the process is running with debug privileges, return unsuccessfully from the hook. + +6. For SwitchToThread: Hook ntdll!NtYieldExecution() and return an unsuccessful status from the hook. + +7. For GetWriteWatch: Hook VirtualAlloc() and GetWriteWatch() to track if VirtualAlloc() is called with MEM_WRITE_WATCH flag. If it is the case, check what is the region to track and return the expected value in GetWriteWatch(). diff --git a/_src/Anti-Debug/techniques/object-handles.md b/_src/Anti-Debug/techniques/object-handles.md new file mode 100644 index 0000000..276fdf9 --- /dev/null +++ b/_src/Anti-Debug/techniques/object-handles.md @@ -0,0 +1,268 @@ +--- +layout: post +title: "Anti-Debug: Object Handles" +title-image: "/assets/icons/object-handles.svg" +categories: anti-debug +tags: object-handles +--- + +Contents
+ +[Object Handles](#object-handles) + +* [1. OpenProcess()](#openprocess) +* [2. CreateFile()](#createfile) +* [3. CloseHandle()](#closehandle) +* [4. LoadLibrary()](#loadlibrary) +* [5. NtQueryObject()](#ntqueryobject) +* [Mitigations](#mitigations) ++ +
+ +
Object Handles
+The following set of techniques represents the checks which use kernel objects handles to detect a debugger presence. Some WinAPI functions that accept kernel object handles as their parameters can behave differently under debugging or cause side-effects that emerge because of debuggers' implementation. Moreover, there are specific kernel objecst that are created by the operation system when debugging begins. + ++
1. OpenProcess()
+Some debuggers can be detected by using the kernel32!OpenProcess() function on the csrss.exe process. The call will succeed only if the user for the process is a member of the administrators group and has debug privileges. + ++ +C/C++ Code + + +{% highlight c %} + +typedef DWORD (WINAPI *TCsrGetProcessId)(VOID); + +bool Check() +{ + HMODULE hNtdll = LoadLibraryA("ntdll.dll"); + if (!hNtdll) + return false; + + TCsrGetProcessId pfnCsrGetProcessId = (TCsrGetProcessId)GetProcAddress(hNtdll, "CsrGetProcessId"); + if (!pfnCsrGetProcessId) + return false; + + HANDLE hCsr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pfnCsrGetProcessId()); + if (hCsr != NULL) + { + CloseHandle(hCsr); + return true; + } + else + return false; +} + +{% endhighlight %} + +
+ +
+
2. CreateFile()
+When the CREATE_PROCESS_DEBUG_EVENT event occurs, the handle of the debugged file is stored in the CREATE_PROCESS_DEBUG_INFO structure. Therefore, debuggers can read the debug information from this file. If this handle is not closed by the debugger, the file won't be opened with exclusive access. Some debuggers can forget to close the handle. + +This trick uses kernel32!CreateFileW() (or kernel32!CreateFileA()) to exclusively open the file of the current process. If the call fails, we can consider that the current process is being run in the presence of a debugger. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + CHAR szFileName[MAX_PATH]; + if (0 == GetModuleFileNameA(NULL, szFileName, sizeof(szFileName))) + return false; + + return INVALID_HANDLE_VALUE == CreateFileA(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); +} + +{% endhighlight %} + +
+ +
3. CloseHandle()
+If a process is running under a debugger and an invalid handle is passed to the ntdll!NtClose() or kernel32!CloseHandle() function, then the EXCEPTION_INVALID_HANDLE (0xC0000008) exception will be raised. The exception can be cached by an exception handler. If the control is passed to the exception handler, it indicates that a debugger is present. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + __try + { + CloseHandle((HANDLE)0xDEADBEEF); + return false; + } + __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode() + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) + { + return true; + } +} + +{% endhighlight %} + +
+ +
4. LoadLibrary()
+When a file is loaded to process memory using the kernel32!LoadLibraryW() (or kernel32!LoadLibraryA()) function, the LOAD_DLL_DEBUG_EVENT event occurs. The handle of the loaded file will be stored in the LOAD_DLL_DEBUG_INFO structure. Therefore, debuggers can read the debug information from this file. If this handle is not closed by the debugger, the file won't be opened with exclusive access. Some debuggers can forget to close the handle. + +To check for the debugger presence, we can load any file using kernel32!LoadLibraryA() and try to exclusively open it using kernel32!CreateFileA(). If the kernel32!CreateFileA() call fails, it indicates that the debugger is present. + ++ +C/C++ Code + + +{% highlight c %} + +bool Check() +{ + CHAR szBuffer[] = { "C:\\Windows\\System32\\calc.exe" }; + LoadLibraryA(szBuffer); + return INVALID_HANDLE_VALUE == CreateFileA(szBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); +} + +{% endhighlight %} + +
+ +
5. NtQueryObject()
+When a debugging session begins, a kernel object called "debug object" is created, and a handle is associated with it. Using the ntdll!NtQueryObject() function, it is possible to query for the list of existing objects, and check the number of handles associated with any debug object that exists. + +However this technique can't say for sure if the current process is being debugged right now. It only shows if the debugger is running on the system at all since the system's boot. + ++ +C/C++ Code + + +{% highlight c %} + +typedef struct _OBJECT_TYPE_INFORMATION +{ + UNICODE_STRING TypeName; + ULONG TotalNumberOfHandles; + ULONG TotalNumberOfObjects; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef struct _OBJECT_ALL_INFORMATION +{ + ULONG NumberOfObjects; + OBJECT_TYPE_INFORMATION ObjectTypeInformation[1]; +} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION; + +typedef NTSTATUS (WINAPI *TNtQueryObject)( + HANDLE Handle, + OBJECT_INFORMATION_CLASS ObjectInformationClass, + PVOID ObjectInformation, + ULONG ObjectInformationLength, + PULONG ReturnLength +); + +enum { ObjectAllTypesInformation = 3 }; + +#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 + +bool Check() +{ + bool bDebugged = false; + NTSTATUS status; + LPVOID pMem = nullptr; + ULONG dwMemSize; + POBJECT_ALL_INFORMATION pObjectAllInfo; + PBYTE pObjInfoLocation; + HMODULE hNtdll; + TNtQueryObject pfnNtQueryObject; + + hNtdll = LoadLibraryA("ntdll.dll"); + if (!hNtdll) + return false; + + pfnNtQueryObject = (TNtQueryObject)GetProcAddress(hNtdll, "NtQueryObject"); + if (!pfnNtQueryObject) + return false; + + status = pfnNtQueryObject( + NULL, + (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, + &dwMemSize, sizeof(dwMemSize), &dwMemSize); + if (STATUS_INFO_LENGTH_MISMATCH != status) + goto NtQueryObject_Cleanup; + + pMem = VirtualAlloc(NULL, dwMemSize, MEM_COMMIT, PAGE_READWRITE); + if (!pMem) + goto NtQueryObject_Cleanup; + + status = pfnNtQueryObject( + (HANDLE)-1, + (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, + pMem, dwMemSize, &dwMemSize); + if (!SUCCEEDED(status)) + goto NtQueryObject_Cleanup; + + pObjectAllInfo = (POBJECT_ALL_INFORMATION)pMem; + pObjInfoLocation = (PBYTE)pObjectAllInfo->ObjectTypeInformation; + for(UINT i = 0; i < pObjectAllInfo->NumberOfObjects; i++) + { + + POBJECT_TYPE_INFORMATION pObjectTypeInfo = + (POBJECT_TYPE_INFORMATION)pObjInfoLocation; + + if (wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0) + { + if (pObjectTypeInfo->TotalNumberOfObjects > 0) + bDebugged = true; + break; + } + + // Get the address of the current entries + // string so we can find the end + pObjInfoLocation = (PBYTE)pObjectTypeInfo->TypeName.Buffer; + + // Add the size + pObjInfoLocation += pObjectTypeInfo->TypeName.Length; + + // Skip the trailing null and alignment bytes + ULONG tmp = ((ULONG)pObjInfoLocation) & -4; + + // Not pretty but it works + pObjInfoLocation = ((PBYTE)tmp) + sizeof(DWORD); + } + +NtQueryObject_Cleanup: + if (pMem) + VirtualFree(pMem, 0, MEM_RELEASE); + + return bDebugged; +} + +{% endhighlight %} + +
+ +
+
Mitigations
+The simplest way to mitigate these checks is to just manually trace the program till a check and then skip it (e.g. patch with NOPs or change the instruction pointer or change the Zero Flag after the check). + +If you write an anti-anti-debug solution, you need to hook the listed functions and change return values after analyzing their input:+ +* ntdll!OpenProcess: Return NULL if the third argument is the handle of csrss.exe. +* ntdll!NtClose: You can check if it is possible to retrieve any information about the input handle using ntdll!NtQueryObject() and not throw an exception if the handle is invalid. +* ntdll!NtQueryObject: Filter debug objects from the results if the ObjectAllTypesInformation class is queried. + +The following techniques should be handled without hooks: + +* ntdll!NtCreateFile: Too generic to mitigate. However, if you write a plugin for a specific debugger, you can ensure that the handle of the debugged file is closed. +* kernel32!LoadLibraryW/A: No mitigation. diff --git a/_src/Anti-Debug/techniques/process-memory.md b/_src/Anti-Debug/techniques/process-memory.md new file mode 100644 index 0000000..42aa3e5 --- /dev/null +++ b/_src/Anti-Debug/techniques/process-memory.md @@ -0,0 +1,680 @@ +--- +layout: post +title: "Anti-Debug: Process Memory" +title-image: "/assets/icons/memory.svg" +categories: anti-debug +tags: process-memory +--- + +
Contents
+ +[Process Memory](#process-memory) + +* [1. Breakpoints](#breakpoints) + * [1.1. Software Breakpoints (INT3)](#software-breakpoints) + * [1.2. Anti-Step-Over](#anti-step-over) + * [1.2.1. Direct Memory Modification](#direct-memory-modification) + * [1.2.2. ReadFile()](#readfile) + * [1.2.3. WriteProcessMemory()](#writeprocessmemory) + * [1.2.4. Toolhelp32ReadProcessMemory()](#toolhelp32readprocessmemory) + * [1.3. Memory Breakpoints](#memory-breakpoints) + * [1.4. Hardware Breakpoints](#hardware-breakpoints) +* [2. Other memory checks](#other-memory-checks) + * [2.1. NtQueryVirtualMemory()](#ntqueryvirtualmemory) + * [2.2. Detecting a function patch](#detecting-a-function-patch) + * [2.3. Patch ntdll!DbgBreakPoint()](#patch_ntdll_dbgbreakpoint) + * [2.4. Patch ntdll!DbgUiRemoteBreakin()](#patch_ntdll_dbguiremotebreakin) + * [2.5 Performing Code Checksums](#code-checksums) +* [Mitigations](#mitigations) ++ +
+ +
Process Memory
+A process can examine its own memory to either detect the debugger presence or interfere with the debugger. + +This section includes the process memory and examining the thread contexts, searching for breakpoints, and function patching as anti-attaching methods. + ++
1. Breakpoints
+It is always possible to examine the process memory and search for software breakpoints in the code, or check the CPU debug registers to determine if hardware breakpoints are set. + ++
1.1. Software Breakpoints (INT3)
+The idea is to identify the machine code of some functions for 0xCC byte which stands for INT 3 assembly instruction. + +This method can generate many false-positive cases and should therefore be used with caution. + ++ +C/C++ Code + + +{% highlight c %} + +bool CheckForSpecificByte(BYTE cByte, PVOID pMemory, SIZE_T nMemorySize = 0) +{ + PBYTE pBytes = (PBYTE)pMemory; + for (SIZE_T i = 0; ; i++) + { + // Break on RET (0xC3) if we don't know the function's size + if (((nMemorySize > 0) && (i >= nMemorySize)) || + ((nMemorySize == 0) && (pBytes[i] == 0xC3))) + break; + + if (pBytes[i] == cByte) + return true; + } + return false; +} + +bool IsDebugged() +{ + PVOID functionsToCheck[] = { + &Function1, + &Function2, + &Function3, + }; + for (auto funcAddr : functionsToCheck) + { + if (CheckForSpecificByte(0xCC, funcAddr)) + return true; + } + return false; +} + +{% endhighlight %} + +
+
1.2. Anti-Step-Over
+Debuggers allow you to step over the function call. In such a case, the debugger implicitly sets a software breakpoint on the instruction which follows the call (i.e. the return address of the called function). + +To detect if there was an attempt to step over the function, we can examine the first byte of memory at the return address. If a software breakpoint (0xCC) is located at the return address, we can patch it with some other instruction (e.g. NOP). It will most likely break the code and crash the process. On the other hand, we can patch the return address with some meaningful code instead of NOP and change the control flow of the program. + ++ +
1.2.1. Direct Memory Modification
+It is possible to check from inside a function if there is a software breakpoint after the call of this function. We can read one byte at the return address and if the byte is equal to 0xCC (INT 3), it can be rewritten by 0x90 (NOP). The process will probably crash because we damage the instruction at the return address. However, if you know which instruction follows the function call, you can rewrite the breakpoint with the first byte of this instruction. + ++ +C/C++ Code + + +{% highlight c %} + +#include
+ +
1.2.2. ReadFile()
+The method uses the kernel32!ReadFile() function to patch the code at the return address. + +The idea is to read the executable file of the current process and pass the return address as the output buffer to kernel32!ReadFile(). The byte at the return address will be patched with 'M' character (the first byte of PE image) and the process will probably crash. + ++ +C/C++ Code + + +{% highlight c %} + +#include
+ +
1.2.3. WriteProcessMemory()
+This method uses the kernel32!WriteProcessMemory() function for patching the code at the return address. + ++ +C/C++ Code + + +{% highlight c %} + +#include
+ +
1.2.4. Toolhelp32ReadProcessMemory()
+The function kernel32!Toolhelp32ReadProcessMemory() allows you to read the memory of other processes. However, it can be used for checking an anti-step-over condition. + ++ +C/C++ Code + + +{% highlight c %} + +#include
+
1.3. Memory Breakpoints
+Memory breakpoints are implemented by using guard pages (at least, in OllyDbg and ImmunityDebugger). A guard page provides a one-shot alarm for memory page access. When a guard page is executed, the exception STATUS_GUARD_PAGE_VIOLATION is raised. + +A guard page can be created by setting the PAGE_GUARD page protection modifier in the kernel32!VirtualAlloc(), kernel32!VirtualAllocEx(), kernel32!VirtualProtect(), and kernel32!VirtualProtectEx() functions. + +However, we can abuse the way the debuggers implement memory breakpoints to check whether the program is executed under a debugger. We can allocate an executable buffer which contains only one byte 0xC3 which stands for RET instruction. We then mark this buffer as a guard page, push the address where the case if a debugger is present is handled to the stack, and jump to the allocated buffer. The instruction RET will be executed and if the debugger (OllyDbg or ImmunityDebugger) is present, we'll get to the address we had pushed to the stack. If the program is executed without the debugger, we'll get to an exception handler. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + DWORD dwOldProtect = 0; + SYSTEM_INFO SysInfo = { 0 }; + + GetSystemInfo(&SysInfo); + PVOID pPage = VirtualAlloc(NULL, SysInfo.dwPageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (NULL == pPage) + return false; + + PBYTE pMem = (PBYTE)pPage; + *pMem = 0xC3; + + // Make the page a guard page + if (!VirtualProtect(pPage, SysInfo.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &dwOldProtect)) + return false; + + __try + { + __asm + { + mov eax, pPage + push mem_bp_being_debugged + jmp eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + VirtualFree(pPage, NULL, MEM_RELEASE); + return false; + } + +mem_bp_being_debugged: + VirtualFree(pPage, NULL, MEM_RELEASE); + return true; +} + +{% endhighlight %} + +
+
1.4. Hardware Breakpoints
+Debug registers DR0, DR1, DR2 and DR3 can be retrieved from the thread context. If they contain non-zero values, it may mean that the process is executed under a debugger and a hardware breakpoint was set. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ + CONTEXT ctx; + ZeroMemory(&ctx, sizeof(CONTEXT)); + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if(!GetThreadContext(GetCurrentThread(), &ctx)) + return false; + + return ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3; +} + +{% endhighlight %} + +
+
2. Other memory checks
+This section contains techniques which directly examine or manipulate the virtual memory of running processes to detect or prevent the debugging. + ++
2.1. NtQueryVirtualMemory()
+Memory pages of the process where code is located are shared between all processes until a page is written. Afterward, the OS makes a copy of this page and map it to the process virtual memory so this page is no longer "shared". + +Therefore, we can query the Working Set of the current process and check the Shared and ShareCount fields of the Working Set Block for the page with code. If there were software breakpoints in the code, these fields must not be set. + ++ +NTDLL declarations + + +{% highlight c %} + +namespace ntdll +{ +//... + +#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 + +// ... + +typedef enum _MEMORY_INFORMATION_CLASS { + MemoryBasicInformation, + MemoryWorkingSetList, +} MEMORY_INFORMATION_CLASS; + +// ... + +typedef union _PSAPI_WORKING_SET_BLOCK { + ULONG Flags; + struct { + ULONG Protection :5; + ULONG ShareCount :3; + ULONG Shared :1; + ULONG Reserved :3; + ULONG VirtualPage:20; + }; +} PSAPI_WORKING_SET_BLOCK, *PPSAPI_WORKING_SET_BLOCK; + +typedef struct _MEMORY_WORKING_SET_LIST +{ + ULONG NumberOfPages; + PSAPI_WORKING_SET_BLOCK WorkingSetList[1]; +} MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST; + +// ... +} + +{% endhighlight %} + +
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged() +{ +#ifndef _WIN64 + NTSTATUS status; + PBYTE pMem = nullptr; + DWORD dwMemSize = 0; + + do + { + dwMemSize += 0x1000; + pMem = (PBYTE)_malloca(dwMemSize); + if (!pMem) + return false; + + memset(pMem, 0, dwMemSize); + status = ntdll::NtQueryVirtualMemory( + GetCurrentProcess(), + NULL, + ntdll::MemoryWorkingSetList, + pMem, + dwMemSize, + NULL); + } while (status == STATUS_INFO_LENGTH_MISMATCH); + + ntdll::PMEMORY_WORKING_SET_LIST pWorkingSet = (ntdll::PMEMORY_WORKING_SET_LIST)pMem; + for (ULONG i = 0; i < pWorkingSet->NumberOfPages; i++) + { + DWORD dwAddr = pWorkingSet->WorkingSetList[i].VirtualPage << 0x0C; + DWORD dwEIP = 0; + __asm + { + push eax + call $+5 + pop eax + mov dwEIP, eax + pop eax + } + + if (dwAddr == (dwEIP & 0xFFFFF000)) + return (pWorkingSet->WorkingSetList[i].Shared == 0) || (pWorkingSet->WorkingSetList[i].ShareCount == 0); + } +#endif // _WIN64 + return false; +} + +{% endhighlight %} + +Credits for this technique: Virus Bulletin + +
+
2.2. Detecting a function patch
+A popular way to detect a debugger is to call kernel32!IsDebuggerPresent(). It's simple to mitigate this check e.g. to change the result in the EAX register or to patch the kernel32!IsDebuggerPresent() function's code. + +Therefore, instead of examining the process memory for breakpoints, we can verify if kernel32!IsDebuggerPresent() was modified. We can read the first bytes of this function and compare them to these bytes of the same function from other processes. Even with enabled ASLR, Windows libraries are loaded to the same base addresses in all the processes. The base addresses are changed only after a reboot, but for all the processes they will stay the same during the session. + ++ +C/C++ Code + + +{% highlight c %} + +bool IsDebuggerPresent() +{ + HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); + if (!hKernel32) + return false; + + FARPROC pIsDebuggerPresent = GetProcAddress(hKernel32, "IsDebuggerPresent"); + if (!pIsDebuggerPresent) + return false; + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == hSnapshot) + return false; + + PROCESSENTRY32W ProcessEntry; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); + + if (!Process32FirstW(hSnapshot, &ProcessEntry)) + return false; + + bool bDebuggerPresent = false; + HANDLE hProcess = NULL; + DWORD dwFuncBytes = 0; + const DWORD dwCurrentPID = GetCurrentProcessId(); + do + { + __try + { + if (dwCurrentPID == ProcessEntry.th32ProcessID) + continue; + + hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessEntry.th32ProcessID); + if (NULL == hProcess) + continue; + + if (!ReadProcessMemory(hProcess, pIsDebuggerPresent, &dwFuncBytes, sizeof(DWORD), NULL)) + continue; + + if (dwFuncBytes != *(PDWORD)pIsDebuggerPresent) + { + bDebuggerPresent = true; + break; + } + } + __finally + { + if (hProcess) + CloseHandle(hProcess); + } + } while (Process32NextW(hSnapshot, &ProcessEntry)); + + if (hSnapshot) + CloseHandle(hSnapshot); + return bDebuggerPresent; +} + +{% endhighlight %} + +Credits for this technique: Rouse_ + +
+
2.3. Patch ntdll!DbgBreakPoint()
+The function ntdll!DbgBreakPoint() has the following implementation: + +
+
+
+
+It is called when a debugger attaches to a running process. It allows the debugger to gain control because an exception is raised which it can intercept. If we erase the breakpoint inside ntdll!DbgBreakPoint(), the debugger won't break in and the thread will exit. + +
+ +C/C++ Code + + +{% highlight c %} + +void Patch_DbgBreakPoint() +{ + HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); + if (!hNtdll) + return; + + FARPROC pDbgBreakPoint = GetProcAddress(hNtdll, "DbgBreakPoint"); + if (!pDbgBreakPoint) + return; + + DWORD dwOldProtect; + if (!VirtualProtect(pDbgBreakPoint, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect)) + return; + + *(PBYTE)pDbgBreakPoint = (BYTE)0xC3; // ret +} + +{% endhighlight %} + +
+
2.4. Patch ntdll!DbgUiRemoteBreakin()
+When a debugger calls the kernel32!DebugActiveProcess(), a debugger calls ntdll!DbgUiRemoteBreakin() correspondingly. To prevent the debugger from attaching to the process, we can patch ntdll!DbgUiRemoteBreakin() code to invoke the kernel32!TerminateProcess(). + +In the example below we patch ntdll!DbgUiRemoteBreakin() with the following code: +{% highlight asm %} +6A 00 push 0 +68 FF FF FF FF push -1 ; GetCurrentProcess() result +B8 XX XX XX XX mov eax, kernel32!TreminateProcess +FF D0 call eax +{% endhighlight %} + +As the result, the application will terminate itself once we try to attach the debugger to it. + +
+ +C/C++ Code + + +{% highlight c %} + +#pragma pack(push, 1) + struct DbgUiRemoteBreakinPatch + { + WORD push_0; + BYTE push; + DWORD CurrentPorcessHandle; + BYTE mov_eax; + DWORD TerminateProcess; + WORD call_eax; + }; +#pragma pack(pop) + +void Patch_DbgUiRemoteBreakin() +{ + HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); + if (!hNtdll) + return; + + FARPROC pDbgUiRemoteBreakin = GetProcAddress(hNtdll, "DbgUiRemoteBreakin"); + if (!pDbgUiRemoteBreakin) + return; + + HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); + if (!hKernel32) + return; + + FARPROC pTerminateProcess = GetProcAddress(hKernel32, "TerminateProcess"); + if (!pTerminateProcess) + return; + + DbgUiRemoteBreakinPatch patch = { 0 }; + patch.push_0 = '\x6A\x00'; + patch.push = '\x68'; + patch.CurrentPorcessHandle = 0xFFFFFFFF; + patch.mov_eax = '\xB8'; + patch.TerminateProcess = (DWORD)pTerminateProcess; + patch.call_eax = '\xFF\xD0'; + + DWORD dwOldProtect; + if (!VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), PAGE_READWRITE, &dwOldProtect)) + return; + + ::memcpy_s(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), + &patch, sizeof(DbgUiRemoteBreakinPatch)); + VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), dwOldProtect, &dwOldProtect); +} + +{% endhighlight %} + +Credits for this technique: Rouse_ + +
+
2.5 Performing Code Checksums
+Verifying code checksum is a reliable way to detect software breakpoints, debugger's step-overs, functions' inline hooks, or data modification. + +The example below shows how it is possible to verify the checksum of a function. + +C/C++ Code + + +{% highlight c %} + +PVOID g_pFuncAddr; +DWORD g_dwFuncSize; +DWORD g_dwOriginalChecksum; + +static void VeryImportantFunction() +{ + // ... +} + +static DWORD WINAPI ThreadFuncCRC32(LPVOID lpThreadParameter) +{ + while (true) + { + if (CRC32((PBYTE)g_pFuncAddr, g_dwFuncSize) != g_dwOriginalChecksum) + ExitProcess(0); + Sleep(10000); + } + return 0; +} + +size_t DetectFunctionSize(PVOID pFunc) +{ + PBYTE pMem = (PBYTE)pFunc; + size_t nFuncSize = 0; + do + { + ++nFuncSize; + } while (*(pMem++) != 0xC3); + return nFuncSize; +} + +int main() +{ + g_pFuncAddr = (PVOID)&VeryImportantFunction; + g_dwFuncSize = DetectFunctionSize(g_pFuncAddr); + g_dwOriginalChecksum = CRC32((PBYTE)g_pFuncAddr, g_dwFuncSize); + + HANDLE hChecksumThread = CreateThread(NULL, NULL, ThreadFuncCRC32, NULL, NULL, NULL); + + // ... + + return 0; +} + +{% endhighlight %} + ++ +
+
Mitigations
+* During debugging: + * For Anti-Step-Over tricks: Step in the function which performs the Step-Over check and execute it till the end (Ctrl+F9 in OllyDbg/x32/x64dbg). + * The best way to mitigate all the "memory" tricks (including Anti-Step-Over) is to find the exact check and patch it with NOPs, or set the return value which allows the application to execute further. +* For anti-anti-debug tool development: + * Breakpoints scan: + * Software Breakpoint & Anti-Step-Over: There is no possibility of interfering with these checks as they don't need to use API and they access memory directly. + * Memory Breakpoints: In general, it is possible to track the sequence of function that are called to apply this check. + * Hardware Breakpoints: Hook kernel32!GetThreadContext() and modify debug registers. + * Other checks: No mitigations. diff --git a/_src/Anti-Debug/techniques/timing.md b/_src/Anti-Debug/techniques/timing.md new file mode 100644 index 0000000..c0371a1 --- /dev/null +++ b/_src/Anti-Debug/techniques/timing.md @@ -0,0 +1,264 @@ +--- +layout: post +title: "Anti-Debug: Timing" +title-image: "/assets/icons/timing.svg" +categories: anti-debug +tags: timing +--- + +Contents
+ +[Timing](#timing) + +* [1. RDPMC/RDTSC](#rdpmc_rdtsc) +* [2. GetLocalTime()](#getlocaltime) +* [3. GetSystemTime()](#getsystemtime) +* [4. GetTickCount()](#gettickcount) +* [5. ZwGetTickCount() / KiGetTickCount()](#kernel-timing) +* [6. QueryPerformanceCounter()](#queryperformancecounter) +* [7. timeGetTime()](#timegettime) +* [Mitigations](#mitigations) ++ +
+ +
Timing
+When a process is traced in a debugger, there is a huge delay between instructions and execution. The "native" delay between some parts of code can be measured and compared with the actual delay using several approaches. + ++
1. RDPMC/RDTSC
+These instructions require the flag PCE to be set in CR4 register. + ++ +RDPMC instruction can be used only in Kernel Mode. + +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + ULARGE_INTEGER Start, End; + __asm + { + xor ecx, ecx + rdpmc + mov Start.LowPart, eax + mov Start.HighPart, edx + } + // ... some work + __asm + { + xor ecx, ecx + rdpmc + mov End.LowPart, eax + mov End.HighPart, edx + } + return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + +
+ +RDTSC is a User Mode instruction. + +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + ULARGE_INTEGER Start, End; + __asm + { + xor ecx, ecx + rdtsc + mov Start.LowPart, eax + mov Start.HighPart, edx + } + // ... some work + __asm + { + xor ecx, ecx + rdtsc + mov End.LowPart, eax + mov End.HighPart, edx + } + return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + +
+ +
+
2. GetLocalTime()
+ +C/C++ Code + + +{% highlight c %} +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + SYSTEMTIME stStart, stEnd; + FILETIME ftStart, ftEnd; + ULARGE_INTEGER uiStart, uiEnd; + + GetLocalTime(&stStart); + // ... some work + GetLocalTime(&stEnd); + + if (!SystemTimeToFileTime(&stStart, &ftStart)) + return false; + if (!SystemTimeToFileTime(&stEnd, &ftEnd)) + return false; + + uiStart.LowPart = ftStart.dwLowDateTime; + uiStart.HighPart = ftStart.dwHighDateTime; + uiEnd.LowPart = ftEnd.dwLowDateTime; + uiEnd.HighPart = ftEnd.dwHighDateTime; + return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + ++ +
+
3. GetSystemTime()
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + SYSTEMTIME stStart, stEnd; + FILETIME ftStart, ftEnd; + ULARGE_INTEGER uiStart, uiEnd; + + GetSystemTime(&stStart); + // ... some work + GetSystemTime(&stEnd); + + if (!SystemTimeToFileTime(&stStart, &ftStart)) + return false; + if (!SystemTimeToFileTime(&stEnd, &ftEnd)) + return false; + + uiStart.LowPart = ftStart.dwLowDateTime; + uiStart.HighPart = ftStart.dwHighDateTime; + uiEnd.LowPart = ftEnd.dwLowDateTime; + uiEnd.HighPart = ftEnd.dwHighDateTime; + return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + ++ +
+
4. GetTickCount()
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD dwNativeElapsed) +{ + DWORD dwStart = GetTickCount(); + // ... some work + return (GetTickCount() - dwStart) > dwNativeElapsed; +} + +{% endhighlight %} + ++ +
+
5. ZwGetTickCount() / KiGetTickCount()
+Both functions are used only from Kernel Mode. + +Just like User Mode GetTickCount() or GetSystemTime(), Kernel Mode ZwGetTickCount() reads from the KUSER_SHARED_DATA page. This page is mapped read-only into the user mode range of the virtual address and read-write in the kernel range. The system clock tick updates the system time, which is stored directly in this page. + +ZwGetTickCount() is used the same way as GetTickCount(). Using KiGetTickCount() is faster than calling ZwGetTickCount(), but slightly slower than reading from the KUSER_SHARED_DATA page directly. + +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + ULARGE_INTEGER Start, End; + __asm + { + int 2ah + mov Start.LowPart, eax + mov Start.HighPart, edx + } + // ... some work + __asm + { + int 2ah + mov End.LowPart, eax + mov End.HighPart, edx + } + return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + ++ +
+
6. QueryPerformanceCounter()
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD64 qwNativeElapsed) +{ + LARGE_INTEGER liStart, liEnd; + QueryPerformanceCounter(&liStart); + // ... some work + QueryPerformanceCounter(&liEnd); + return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed; +} + +{% endhighlight %} + ++ +
+
7. timeGetTime()
+ +C/C++ Code + + +{% highlight c %} + +bool IsDebugged(DWORD dwNativeElapsed) +{ + DWORD dwStart = timeGetTime(); + // ... some work + return (timeGetTime() - dwStart) > dwNativeElapsed; +} + +{% endhighlight %} + ++ +
+
Mitigations
+* During debugging: Just fill timing checks with NOPs and set the result of these checks to the appropriate value. +* For anti-anti-debug solution development: There is no great need to do anything with it, as all timing checks are not very reliable. You can still hook timing functions and accelerate the time between calls. + diff --git a/assets/icons/cpu.svg b/_src/Evasions/assets/icons/cpu.svg similarity index 100% rename from assets/icons/cpu.svg rename to _src/Evasions/assets/icons/cpu.svg diff --git a/assets/icons/cpu_gray.svg b/_src/Evasions/assets/icons/cpu_gray.svg similarity index 100% rename from assets/icons/cpu_gray.svg rename to _src/Evasions/assets/icons/cpu_gray.svg diff --git a/assets/icons/filesystem.svg b/_src/Evasions/assets/icons/filesystem.svg similarity index 100% rename from assets/icons/filesystem.svg rename to _src/Evasions/assets/icons/filesystem.svg diff --git a/assets/icons/filesystem_gray.svg b/_src/Evasions/assets/icons/filesystem_gray.svg similarity index 100% rename from assets/icons/filesystem_gray.svg rename to _src/Evasions/assets/icons/filesystem_gray.svg diff --git a/assets/icons/firmware-tables.svg b/_src/Evasions/assets/icons/firmware-tables.svg similarity index 100% rename from assets/icons/firmware-tables.svg rename to _src/Evasions/assets/icons/firmware-tables.svg diff --git a/assets/icons/firmware-tables_gray.svg b/_src/Evasions/assets/icons/firmware-tables_gray.svg similarity index 100% rename from assets/icons/firmware-tables_gray.svg rename to _src/Evasions/assets/icons/firmware-tables_gray.svg diff --git a/assets/icons/generic-os-queries.svg b/_src/Evasions/assets/icons/generic-os-queries.svg similarity index 100% rename from assets/icons/generic-os-queries.svg rename to _src/Evasions/assets/icons/generic-os-queries.svg diff --git a/assets/icons/generic-os-queries_gray.svg b/_src/Evasions/assets/icons/generic-os-queries_gray.svg similarity index 100% rename from assets/icons/generic-os-queries_gray.svg rename to _src/Evasions/assets/icons/generic-os-queries_gray.svg diff --git a/assets/icons/global-os-objects.svg b/_src/Evasions/assets/icons/global-os-objects.svg similarity index 100% rename from assets/icons/global-os-objects.svg rename to _src/Evasions/assets/icons/global-os-objects.svg diff --git a/assets/icons/global-os-objects_gray.svg b/_src/Evasions/assets/icons/global-os-objects_gray.svg similarity index 100% rename from assets/icons/global-os-objects_gray.svg rename to _src/Evasions/assets/icons/global-os-objects_gray.svg diff --git a/assets/icons/hardware.svg b/_src/Evasions/assets/icons/hardware.svg similarity index 100% rename from assets/icons/hardware.svg rename to _src/Evasions/assets/icons/hardware.svg diff --git a/assets/icons/hardware_gray.svg b/_src/Evasions/assets/icons/hardware_gray.svg similarity index 100% rename from assets/icons/hardware_gray.svg rename to _src/Evasions/assets/icons/hardware_gray.svg diff --git a/assets/icons/hooks.svg b/_src/Evasions/assets/icons/hooks.svg similarity index 100% rename from assets/icons/hooks.svg rename to _src/Evasions/assets/icons/hooks.svg diff --git a/assets/icons/hooks_gray.svg b/_src/Evasions/assets/icons/hooks_gray.svg similarity index 100% rename from assets/icons/hooks_gray.svg rename to _src/Evasions/assets/icons/hooks_gray.svg diff --git a/assets/icons/human-like-behavior.svg b/_src/Evasions/assets/icons/human-like-behavior.svg similarity index 100% rename from assets/icons/human-like-behavior.svg rename to _src/Evasions/assets/icons/human-like-behavior.svg diff --git a/assets/icons/human-like-behavior_gray.svg b/_src/Evasions/assets/icons/human-like-behavior_gray.svg similarity index 100% rename from assets/icons/human-like-behavior_gray.svg rename to _src/Evasions/assets/icons/human-like-behavior_gray.svg diff --git a/assets/icons/network.svg b/_src/Evasions/assets/icons/network.svg similarity index 100% rename from assets/icons/network.svg rename to _src/Evasions/assets/icons/network.svg diff --git a/assets/icons/network_gray.svg b/_src/Evasions/assets/icons/network_gray.svg similarity index 100% rename from assets/icons/network_gray.svg rename to _src/Evasions/assets/icons/network_gray.svg diff --git a/assets/icons/os-features.svg b/_src/Evasions/assets/icons/os-features.svg similarity index 100% rename from assets/icons/os-features.svg rename to _src/Evasions/assets/icons/os-features.svg diff --git a/assets/icons/os-features_gray.svg b/_src/Evasions/assets/icons/os-features_gray.svg similarity index 100% rename from assets/icons/os-features_gray.svg rename to _src/Evasions/assets/icons/os-features_gray.svg diff --git a/assets/icons/processes.svg b/_src/Evasions/assets/icons/processes.svg similarity index 100% rename from assets/icons/processes.svg rename to _src/Evasions/assets/icons/processes.svg diff --git a/assets/icons/processes_gray.svg b/_src/Evasions/assets/icons/processes_gray.svg similarity index 100% rename from assets/icons/processes_gray.svg rename to _src/Evasions/assets/icons/processes_gray.svg diff --git a/assets/icons/registry.svg b/_src/Evasions/assets/icons/registry.svg similarity index 100% rename from assets/icons/registry.svg rename to _src/Evasions/assets/icons/registry.svg diff --git a/assets/icons/registry_gray.svg b/_src/Evasions/assets/icons/registry_gray.svg similarity index 100% rename from assets/icons/registry_gray.svg rename to _src/Evasions/assets/icons/registry_gray.svg diff --git a/assets/icons/timing.svg b/_src/Evasions/assets/icons/timing.svg similarity index 100% rename from assets/icons/timing.svg rename to _src/Evasions/assets/icons/timing.svg diff --git a/assets/icons/timing_gray.svg b/_src/Evasions/assets/icons/timing_gray.svg similarity index 100% rename from assets/icons/timing_gray.svg rename to _src/Evasions/assets/icons/timing_gray.svg diff --git a/assets/icons/ui-artifacts.svg b/_src/Evasions/assets/icons/ui-artifacts.svg similarity index 100% rename from assets/icons/ui-artifacts.svg rename to _src/Evasions/assets/icons/ui-artifacts.svg diff --git a/assets/icons/ui-artifacts_gray.svg b/_src/Evasions/assets/icons/ui-artifacts_gray.svg similarity index 100% rename from assets/icons/ui-artifacts_gray.svg rename to _src/Evasions/assets/icons/ui-artifacts_gray.svg diff --git a/assets/icons/wmi.svg b/_src/Evasions/assets/icons/wmi.svg similarity index 100% rename from assets/icons/wmi.svg rename to _src/Evasions/assets/icons/wmi.svg diff --git a/assets/icons/wmi_gray.svg b/_src/Evasions/assets/icons/wmi_gray.svg similarity index 100% rename from assets/icons/wmi_gray.svg rename to _src/Evasions/assets/icons/wmi_gray.svg diff --git a/assets/images/agent_tesla_v3_technique.png b/_src/Evasions/assets/images/agent_tesla_v3_technique.png similarity index 100% rename from assets/images/agent_tesla_v3_technique.png rename to _src/Evasions/assets/images/agent_tesla_v3_technique.png diff --git a/assets/images/date_anti_sb.png b/_src/Evasions/assets/images/date_anti_sb.png similarity index 100% rename from assets/images/date_anti_sb.png rename to _src/Evasions/assets/images/date_anti_sb.png diff --git a/assets/images/mmx_anti_sb.png b/_src/Evasions/assets/images/mmx_anti_sb.png similarity index 100% rename from assets/images/mmx_anti_sb.png rename to _src/Evasions/assets/images/mmx_anti_sb.png diff --git a/assets/images/rtf_exploit.png b/_src/Evasions/assets/images/rtf_exploit.png similarity index 100% rename from assets/images/rtf_exploit.png rename to _src/Evasions/assets/images/rtf_exploit.png diff --git a/assets/images/rtf_stub.png b/_src/Evasions/assets/images/rtf_stub.png similarity index 100% rename from assets/images/rtf_stub.png rename to _src/Evasions/assets/images/rtf_stub.png diff --git a/assets/images/sleep_skipping.png b/_src/Evasions/assets/images/sleep_skipping.png similarity index 100% rename from assets/images/sleep_skipping.png rename to _src/Evasions/assets/images/sleep_skipping.png diff --git a/assets/images/sleep_skipping_detection.png b/_src/Evasions/assets/images/sleep_skipping_detection.png similarity index 100% rename from assets/images/sleep_skipping_detection.png rename to _src/Evasions/assets/images/sleep_skipping_detection.png diff --git a/assets/images/trigonometry_mouse_check_1.webp b/_src/Evasions/assets/images/trigonometry_mouse_check_1.webp similarity index 100% rename from assets/images/trigonometry_mouse_check_1.webp rename to _src/Evasions/assets/images/trigonometry_mouse_check_1.webp diff --git a/assets/images/trigonometry_mouse_check_2.webp b/_src/Evasions/assets/images/trigonometry_mouse_check_2.webp similarity index 100% rename from assets/images/trigonometry_mouse_check_2.webp rename to _src/Evasions/assets/images/trigonometry_mouse_check_2.webp diff --git a/assets/images/trigonometry_mouse_check_3.webp b/_src/Evasions/assets/images/trigonometry_mouse_check_3.webp similarity index 100% rename from assets/images/trigonometry_mouse_check_3.webp rename to _src/Evasions/assets/images/trigonometry_mouse_check_3.webp diff --git a/assets/images/unbalanced_stack_hook.png b/_src/Evasions/assets/images/unbalanced_stack_hook.png similarity index 100% rename from assets/images/unbalanced_stack_hook.png rename to _src/Evasions/assets/images/unbalanced_stack_hook.png diff --git a/assets/images/unbalanced_stack_unhook.png b/_src/Evasions/assets/images/unbalanced_stack_unhook.png similarity index 100% rename from assets/images/unbalanced_stack_unhook.png rename to _src/Evasions/assets/images/unbalanced_stack_unhook.png diff --git a/_src/Evasions/index.html b/_src/Evasions/index.html new file mode 100644 index 0000000..4e9f7e1 --- /dev/null +++ b/_src/Evasions/index.html @@ -0,0 +1,284 @@ +--- +layout: main +--- + + + + + + + +
+
+
+
+ Windows: Evasion techniques
+
++ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Filesystem
+
+
+
+
+
+
+
+
+
+
+ Generic OS queries
+
+
+
+
+
+
+
+
+
+
+
+
+ Global OS objects
+
+
+
+
+
+
+
+
+
+ UI artifacts
+
+
+
+
+
+
+
+
+
+ OS features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Firmware tables
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Human-like behavior
+
+
+ Go to the title page
+ ++ + diff --git a/_techniques/cpu.md b/_src/Evasions/techniques/cpu.md similarity index 99% rename from _techniques/cpu.md rename to _src/Evasions/techniques/cpu.md index 6961686..d8afbb3 100644 --- a/_techniques/cpu.md +++ b/_src/Evasions/techniques/cpu.md @@ -273,7 +273,7 @@ MMX instructions may be used as random instructions by malware. Sometimes such s
-
+
diff --git a/_techniques/filesystem.md b/_src/Evasions/techniques/filesystem.md similarity index 100% rename from _techniques/filesystem.md rename to _src/Evasions/techniques/filesystem.md diff --git a/_techniques/firmware-tables.md b/_src/Evasions/techniques/firmware-tables.md similarity index 100% rename from _techniques/firmware-tables.md rename to _src/Evasions/techniques/firmware-tables.md diff --git a/_techniques/generic-os-queries.md b/_src/Evasions/techniques/generic-os-queries.md similarity index 100% rename from _techniques/generic-os-queries.md rename to _src/Evasions/techniques/generic-os-queries.md diff --git a/_techniques/global-os-objects.md b/_src/Evasions/techniques/global-os-objects.md similarity index 100% rename from _techniques/global-os-objects.md rename to _src/Evasions/techniques/global-os-objects.md diff --git a/_techniques/hardware.md b/_src/Evasions/techniques/hardware.md similarity index 100% rename from _techniques/hardware.md rename to _src/Evasions/techniques/hardware.md diff --git a/_techniques/hooks.md b/_src/Evasions/techniques/hooks.md similarity index 100% rename from _techniques/hooks.md rename to _src/Evasions/techniques/hooks.md diff --git a/_techniques/human-like-behavior.md b/_src/Evasions/techniques/human-like-behavior.md similarity index 97% rename from _techniques/human-like-behavior.md rename to _src/Evasions/techniques/human-like-behavior.md index a9a08aa..f1827e4 100644 --- a/_techniques/human-like-behavior.md +++ b/_src/Evasions/techniques/human-like-behavior.md @@ -174,7 +174,7 @@ More sophisticated checks rely on detection of not only the mouse movement per s First, malware captures mouse movements with the delay of 50 msec between them.
-
+
+
@@ -182,7 +182,7 @@ First, malware captures mouse movements with the delay of 50 msec between them. Second, the vectors are drawn out of paired captured positions.
-
+
+
@@ -190,7 +190,7 @@ Second, the vectors are drawn out of paired captured positions. Next, the angles are calculated between the corresponding vectors.
-
+
+
@@ -287,14 +287,14 @@ RTF documents consist of normal text, control words, and groups. Microsoft’s R In this code, \sp is the control word for the drawing property, \sn is the property name, and \sv contains information about the property value. The code snippet in the image below exploits a CVE-2010-3333 vulnerability that occurs when using an invalid \sv value for the pFragments shape property:
-
+
+
A closer look at the exploit code, as shown in the next image, reveals a series of paragraph marks (./par) that appears before the exploit code:
-
+
+
@@ -318,7 +318,7 @@ User activity can be checked with the call to the `GetLastInputInfo` function Although Agent Tesla v3 performs this check, it does so incorrectly. Compare the code of Agent Tesla v3 with the correct technique implementation below.
-
+
+
Evasion technique as implemented in Agent Tesla v3. This function is called after a delay of 30 seconds.
diff --git a/_techniques/network.md b/_src/Evasions/techniques/network.md
similarity index 100%
rename from _techniques/network.md
rename to _src/Evasions/techniques/network.md
diff --git a/_techniques/os-features.md b/_src/Evasions/techniques/os-features.md
similarity index 97%
rename from _techniques/os-features.md
rename to _src/Evasions/techniques/os-features.md
index 1d9e22e..e865229 100644
--- a/_techniques/os-features.md
+++ b/_src/Evasions/techniques/os-features.md
@@ -114,8 +114,8 @@ To track process behaviour, the CuckooMon/Cuckoo Monitor module hooks relevant f
However, this can also be used to detect a sandbox.
@@ -218,7 +218,7 @@ which checks the current date and verifies if the day is either the 16th, 17th o of a given month.
@@ -421,7 +421,7 @@ with different speeds in the different processes. After a delay we should synchr the current time in the two processes. A big difference in measured time values indicates sleep skipping was performed.
+ +
-Raman Ladutska ( @DaCuriousBro) + +
+ +
+ +
diff --git a/assets/icons/android.png b/assets/icons/android.png new file mode 100644 index 0000000..b051f51 Binary files /dev/null and b/assets/icons/android.png differ diff --git a/assets/icons/anti-debug.png b/assets/icons/anti-debug.png new file mode 100644 index 0000000..1844733 Binary files /dev/null and b/assets/icons/anti-debug.png differ diff --git a/assets/icons/evasions.png b/assets/icons/evasions.png new file mode 100644 index 0000000..33079d1 Binary files /dev/null and b/assets/icons/evasions.png differ diff --git a/assets/icons/mac_gray.svg b/assets/icons/mac_gray.svg deleted file mode 100644 index 02220b7..0000000 --- a/assets/icons/mac_gray.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/back-arrow.svg b/assets/images/back-arrow.svg new file mode 100644 index 0000000..1ab87b2 --- /dev/null +++ b/assets/images/back-arrow.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/index.html b/index.html index 4d0873d..5a7b51f 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,9 @@ display: flex; align-items: center; justify-content: center; + overflow: hidden; + width: 100%; + height: 40vh; } .flex-container { @@ -24,27 +27,28 @@ align-content: space-around; align-items: center; justify-content: space-evenly; - max-width: 800px; + min-width: 1400px; } .flex-container > div { flex: 0 0 25%; text-align: center; + font-size: 20px; margin-bottom: 30px; display: grid; } .outer-img-pink { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(255,86,136); padding: 10px; border: 0px; } .outer-img-pink:hover { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(255,86,136); padding: 10px; @@ -52,16 +56,16 @@ } .outer-img-grey { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(159,159,159); padding: 10px; border: 0px; } .outer-img-grey:hover { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(159,159,159); padding: 10px; @@ -69,16 +73,16 @@ } .inner-img { - width: 100px; - height: 100px; + width: 160px; + height: 160px; display: inline-flex; - background-size: 100px 100px; + background-size: 160px 160px; } .inner-img-grey { - width: 100px; - height: 100px; + width: 160px; + height: 160px; display: inline-flex; - background-size: 100px 100px; + background-size: 160px 160px; } .grey-text { @@ -87,7 +91,7 @@ } .icon { - min-width: 180px; + min-width: 160px; height: 140px; } @@ -110,181 +114,50 @@
-
-
+ +
Stack on non-hooked and on hooked function call.
diff --git a/_techniques/processes.md b/_src/Evasions/techniques/processes.md
similarity index 100%
rename from _techniques/processes.md
rename to _src/Evasions/techniques/processes.md
diff --git a/_techniques/registry.md b/_src/Evasions/techniques/registry.md
similarity index 100%
rename from _techniques/registry.md
rename to _src/Evasions/techniques/registry.md
diff --git a/_techniques/timing.md b/_src/Evasions/techniques/timing.md
similarity index 99%
rename from _techniques/timing.md
rename to _src/Evasions/techniques/timing.md
index 4873af4..bc3ad9b 100644
--- a/_techniques/timing.md
+++ b/_src/Evasions/techniques/timing.md
@@ -63,7 +63,7 @@ sandbox has a sleep skipping feature that replaces delays with a very short valu
its malicious activity before an analysis timeout.
+ +
Stack on non-hooked and on hooked function call.
-
+
However, this can also be used to detect a sandbox.
@@ -218,7 +218,7 @@ which checks the current date and verifies if the day is either the 16th, 17th o of a given month.
Example:
-
+
@@ -421,7 +421,7 @@ with different speeds in the different processes. After a delay we should synchr the current time in the two processes. A big difference in measured time values indicates sleep skipping was performed.
-
+
The current version of the Cuckoo monitor disables sleep skipping after creating new threads or processes.
diff --git a/_techniques/ui-artifacts.md b/_src/Evasions/techniques/ui-artifacts.md
similarity index 100%
rename from _techniques/ui-artifacts.md
rename to _src/Evasions/techniques/ui-artifacts.md
diff --git a/_techniques/wmi.md b/_src/Evasions/techniques/wmi.md
similarity index 100%
rename from _techniques/wmi.md
rename to _src/Evasions/techniques/wmi.md
diff --git a/_techniques/macos.md b/_src/MacOS/macos.md
similarity index 99%
rename from _techniques/macos.md
rename to _src/MacOS/macos.md
index 9898285..7304ecb 100644
--- a/_techniques/macos.md
+++ b/_src/MacOS/macos.md
@@ -1,5 +1,5 @@
---
-layout: post
+layout: post_main
title: "Evasions: macOS"
title-image: "/assets/icons/macos.svg"
categories: evasions
diff --git a/about.md b/about.md
index d774517..546bc44 100644
--- a/about.md
+++ b/about.md
@@ -10,9 +10,12 @@ This encyclopedia wouldn't be possible without invaluable assistance of the foll
-Description
++ +
Windows Evasions repository
As malicious threats evolve, the necessity in automated solutions to analyze such threats emerges. It's a very common case when malware samples are executed in some kind of virtualized environment. @@ -45,6 +48,47 @@ If you want to contribute to this encyclopedia, you're more than welcome to crea So check out all the repositories, browse through evasions encyclopedia and enjoy the journey!-Raman Ladutska ( @DaCuriousBro) + +
Windows Anti-Debug repository
+ +Debugging is the essential part of malware analysis. Every time we need to drill down into malware behavior, restore encryption methods or examine communication protocols – generally, whenever we need to examine memory at a certain moment of time – we use debuggers. + +Debuggers interfere with the debugged process in a way that usually produces side-effects. These side-effects are often used by malicious programs to verify if they are executed under debugging. In turn knowledge of anti-debug techniques helps us detect when the malware tries to prevent us from debugging it and mitigate the interference. + +This encyclopedia contains the description of anti-debug tricks which work on the latest Windows releases with the most popular debuggers (such as OllyDbg, WinDbg, x64dbg). Deprecated techniques (e.g. for SoftICE, etc.) are not included (despite all the love to SoftICE). + +Anti-Debug tricks are grouped by the way in which they trigger side-effects (“meh, yet another classification”, you might think). Each group includes the description of corresponding tricks, their implementation in C/C++ or x86/x86-64 Assembly language, and recommendations of how to mitigate the trick for developers who want to create their own anti-anti-debug solution. In general, for bypassing anti-debug techniques we recommend using the ScyllaHide plugin which supports OllyDbg, x64dbg and IDA Pro. + +All the techniques which are described in this encyclopedia are implemented in our ShowStopper open-source project. The encyclopedia can help you to better understand how these techniques work or to assess debuggers and anti-anti-debug plugins. + +References
+-
+
- P. Ferrie. The “Ultimate”Anti-Debugging Reference +
- N. Falliere. Windows Anti-Debug Reference +
- J. Jackson. An Anti-Reverse Engineering Guide +
- Anti Debugging Protection Techniques with Examples +
- simpliFiRE.AntiRE +
+ +
Android evasions repository
+ +This repository is made in the same style and format as its Windows couterparts. However, due to the specifics of the Android platform and low number of techniques in comparison to Windows, evasions and anti-debug are present in one repository. Where applicable, the code examples are provided. + ++ +
Authors
+ +The author of Windows Anti-Debug repository and the corresponding "About" section: +-
+
- Yaraslau Harakhavik ( @slevin_by) +
-
+
- Raman Ladutska ( @DaCuriousBro) +
diff --git a/assets/icons/android.png b/assets/icons/android.png new file mode 100644 index 0000000..b051f51 Binary files /dev/null and b/assets/icons/android.png differ diff --git a/assets/icons/anti-debug.png b/assets/icons/anti-debug.png new file mode 100644 index 0000000..1844733 Binary files /dev/null and b/assets/icons/anti-debug.png differ diff --git a/assets/icons/evasions.png b/assets/icons/evasions.png new file mode 100644 index 0000000..33079d1 Binary files /dev/null and b/assets/icons/evasions.png differ diff --git a/assets/icons/mac_gray.svg b/assets/icons/mac_gray.svg deleted file mode 100644 index 02220b7..0000000 --- a/assets/icons/mac_gray.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/back-arrow.svg b/assets/images/back-arrow.svg new file mode 100644 index 0000000..1ab87b2 --- /dev/null +++ b/assets/images/back-arrow.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/index.html b/index.html index 4d0873d..5a7b51f 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,9 @@ display: flex; align-items: center; justify-content: center; + overflow: hidden; + width: 100%; + height: 40vh; } .flex-container { @@ -24,27 +27,28 @@ align-content: space-around; align-items: center; justify-content: space-evenly; - max-width: 800px; + min-width: 1400px; } .flex-container > div { flex: 0 0 25%; text-align: center; + font-size: 20px; margin-bottom: 30px; display: grid; } .outer-img-pink { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(255,86,136); padding: 10px; border: 0px; } .outer-img-pink:hover { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(255,86,136); padding: 10px; @@ -52,16 +56,16 @@ } .outer-img-grey { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(159,159,159); padding: 10px; border: 0px; } .outer-img-grey:hover { - height: 120px; - width: 120px; + height: 180px; + width: 180px; display: inline-flex; background-color: rgb(159,159,159); padding: 10px; @@ -69,16 +73,16 @@ } .inner-img { - width: 100px; - height: 100px; + width: 160px; + height: 160px; display: inline-flex; - background-size: 100px 100px; + background-size: 160px 160px; } .inner-img-grey { - width: 100px; - height: 100px; + width: 160px; + height: 160px; display: inline-flex; - background-size: 100px 100px; + background-size: 160px 160px; } .grey-text { @@ -87,7 +91,7 @@ } .icon { - min-width: 180px; + min-width: 160px; height: 140px; } @@ -110,181 +114,50 @@
-
-
-
-
-
-
-
-
-
-
-
-
- UI artifacts
-
-
-
-
-
-
-
-
-
- OS features
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Firmware tables
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Human-like behavior
-
-
-