Learn some 64 bit anti debugging techniques from ScyllaTest
AntiDbg is a wide and in-depth field. Many classic anti debugging methods have been born through in-depth research on the fantastic ideas of previous people. There are more than 20 common anti debugging methods. If various active attack defense against plug-ins and debuggers are added, there may be hundreds of them. However, many methods have failed in the latest win10 x64 bit, and the idea is still worth learning, At present, ScyllaHide is an excellent open-source anti debugging tool. Here we only summarize the anti debugging methods used by ScyllaTest
Process Environment Block(PEB)
The most important anti debugging detection means is that almost all anti debugging testers will detect several fields of PEB
- BeingDebugged: IsDebuggerPresent() check this value
TestResult Check_PEB_BeingDebugged() { PEB* peb = GetPebAddress(g_proc_handle); TEST_FAIL_IF(!peb); return TEST_CHECK(peb->BeingDebugged == 0); }
- NtGlobalFlag
TestResult Check_PEB_NtGlobalFlag() { DWORD bad_flags = FLG_HEAP_ENABLE_FREE | FLG_HEAP_ENABLE_TAIL | FLG_HEAP_VALIDATE_PARAMETERS; PEB* peb = GetPebAddress(g_proc_handle); TEST_FAIL_IF(!peb); return TEST_CHECK((peb->NtGlobalFlag & bad_flags) == 0); }
- HeapFlags
TestResult Check_PEB_HeapFlags() { DWORD bad_flags = HEAP_TAIL_CHECKING_ENABLED | HEAP_FREE_CHECKING_ENABLED | HEAP_SKIP_VALIDATION_CHECKS | HEAP_VALIDATE_PARAMETERS_ENABLED; PEB* peb = GetPebAddress(g_proc_handle); TEST_FAIL_IF(!peb); void** heaps = (void**)peb->ProcessHeaps; for (DWORD i = 0; i < peb->NumberOfHeaps; i++) { DWORD flags = *(DWORD*)((BYTE*)heaps[i] + 0x70); DWORD force_flags = *(DWORD*)((BYTE*)heaps[i] + 0x74); if ((flags & bad_flags) || (force_flags & bad_flags)) return TestDetected; } return TestOk; }
-
startupinfo: the process started by the original debugger will not clear the value in the startupinfo structure. ollydbg will also set the value of one of the member variables, so that the difference can be detected. After testing, this method is invalid for the current mainstream x64 dbg
-
ProcessParameters
TestResult Check_PEB_ProcessParameters() { PEB* peb = GetPebAddress(g_proc_handle); TEST_FAIL_IF(!peb); return TEST_CHECK((peb->ProcessParameters->Flags & 0x4000) != 0); }
Exception
- OutputDebugString: when there is no debugger, OutputDebugString will throw an exception, so GetLastError will return a value different from the preset value
TestResult Check_OutputDebugStringA_LastError() { DWORD last_error = 0xDEAD; SetLastError(last_error); OutputDebugStringA("I don't wish debugger recieve this message!"); //printf("%d", GetLastError()); return TEST_CHECK(GetLastError() != last_error); }
- RaiseException: through a try_ An exception is thrown in the catch block. If there is no debugger, the exception will be caught by the exception block to realize detection. When there is a debugger, the thrown exception will be caught by the debugger (two chances). If the debugger directly handles the exception and does not pass to the program, the exception block will not be executed to realize detection, The solution is to set the debugger to handle the exception received by the debugged program
TestResult Check_RaiseException() { __try { RaiseException(0xdeadbeaf, 0, 0, 0); return TestDetected; } __except (EXCEPTION_EXECUTE_HANDLER) { return TestOk; } }
- NtClose: pass an error handle to NtClose. If there is no debugger, NtClose will return false. If there is a debugger, the function will throw EXCEPTION_INVALID_HANDLE
TestResult Check_NtClose() { __try { NtClose((HANDLE)(ULONG_PTR)0x1234); return TestOk; } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode() == EXCEPTION_INVALID_HANDLE ? TestDetected : TestFail; } }
NtQuery***
- CheckRemoteDebuggerPresent: the function calls NtQueryInformationProcess internally to detect the return value
TestResult Check_CheckRemoteDebuggerPresent() { BOOL present; CheckRemoteDebuggerPresent(g_proc_handle, &present); return TEST_CHECK(!present); }
- NtQueryInformationProcess
- ProcessDebugPort:
TestResult Check_NtQueryInformationProcess_ProcessDebugPort() { HANDLE handle = nullptr; TEST_FAIL_IF(!NT_SUCCESS(NtQueryInformationProcess(g_proc_handle, ProcessDebugPort, &handle, sizeof(handle), nullptr))); return TEST_CHECK(handle == nullptr); }
- KernelDebugger:
TestResult Check_NtQuerySystemInformation_KernelDebugger() { SYSTEM_KERNEL_DEBUGGER_INFORMATION SysKernDebInfo; TEST_FAIL_IF(!NT_SUCCESS(NtQuerySystemInformation(SystemKernelDebuggerInformation, &SysKernDebInfo, sizeof(SysKernDebInfo), NULL))); if (SysKernDebInfo.KernelDebuggerEnabled || !SysKernDebInfo.KernelDebuggerNotPresent) { return TestDetected; } return TestOk; }
Misc
- OtherOperationCount: when the process is debugged, if NtMapViewOfSection is called, the debugger will map the Section into space, so as to increase the OtherOperationCount counter in IO count
ULONGLONG GetOtherOperationCount() { DWORD size; NtQuerySystemInformation(SystemProcessInformation, nullptr, 0, &size); PSYSTEM_PROCESS_INFORMATION SystemProcessInfo = (PSYSTEM_PROCESS_INFORMATION)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, size * 2); NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, SystemProcessInfo, size * 2, nullptr); PSYSTEM_PROCESS_INFORMATION entry = SystemProcessInfo; ULONGLONG otherOperationCount = 0; while (1) { //wprintf(L"%s\n", entry->ImageName.Buffer); if (entry->UniqueProcessId == NtCurrentTeb()->ClientId.UniqueProcess) { otherOperationCount = entry->OtherOperationCount.QuadPart; break; } if (entry->NextEntryOffset == 0) break; entry = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)entry + entry->NextEntryOffset); } return otherOperationCount; } TestResult Check_OtherOperationCount() { //Just open a file IO_STATUS_BLOCK ioStatusBlock; UNICODE_STRING ntdllPath = RTL_CONSTANT_STRING(L"\\SystemRoot\\System32\\ntdll.dll"); OBJECT_ATTRIBUTES objectAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES((PUNICODE_STRING)&ntdllPath, OBJ_CASE_INSENSITIVE); HANDLE fileHandle; NTSTATUS status = NtCreateFile(&fileHandle, SYNCHRONIZE | FILE_EXECUTE, &objectAttributes, &ioStatusBlock, nullptr, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0); //Create section. SEC_IMAGE HANDLE SectionHandle; status = NtCreateSection(&SectionHandle, SECTION_MAP_EXECUTE, nullptr, nullptr, PAGE_EXECUTE, SEC_IMAGE, fileHandle); NtClose(fileHandle); //Query other operation count (before map) ULONGLONG otherOperationCountBefore = GetOtherOperationCount(); //Map a view of section PVOID baseAddress = nullptr; SIZE_T viewSize = 0; NtMapViewOfSection(SectionHandle, g_proc_handle, &baseAddress, 0, 0, nullptr, &viewSize, ViewUnmap, 0, PAGE_EXECUTE); NtClose(SectionHandle); //Query other operation count (after map) ULONGLONG otherOperationCountAfter = GetOtherOperationCount(); NtUnmapViewOfSection(g_proc_handle, baseAddress); //if the other operation count was incremented, the image was mapped into a debugger. if (otherOperationCountAfter > otherOperationCountBefore) return TestDetected; return TestOk; }