从ScyllaTest 学习若干64位 反调试手法
AntiDbg是一个繁多且深入的领域,前人们的奇思构想,深入研究 诞生了许多经典的反调试方法,常见的已有20几种,若加上各种针对插件,调试器的主动攻击防御,恐有百种. 不过许多方法在最新win10 x64位已经失效,其中思想仍值得学习,目前开源的反反调试工具中十分出色的是ScyllaHide,这里仅针对ScyllaTest用到的反调试手段进行总结
Process Environment Block(PEB)
最重要的反调试检测手段,几乎所有的反调试器都会检测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: 原先调试器启动的process并不会清空startupinfo结构体内的值,ollydbg还会设置其中一个成员变量值,导致可以检测出差异,经过测试该方法对目前主流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: 当没有调试器的时候,OutputDebugString会抛出异常,从而GetLastError会返回一个不同于预先设置的值
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: 通过在一个try_catch块中抛出一个异常, 如果没有调试器,则异常会被except块捕获,从而实现检测,当存在调试器的时候,抛出的异常会被调试器捕获(2次机会),而如果调试器直接处理了异常,没有pass到程序,except块将不会被执行,从而实现检测, 应对方法是设置调试器收到异常交由被调试程序处理
TestResult Check_RaiseException()
{
__try
{
RaiseException(0xdeadbeaf, 0, 0, 0);
return TestDetected;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return TestOk;
}
}
- NtClose: 向NtClose传入一个错误的句柄, 当没有调试器的时候NtClose会返回false,而有调试器时,该函数会抛出异常
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: 函数内部调用NtQueryInformationProcess 传入(ProcessDebugPort)检测返回值
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: 当进程被调试的时候,如果调用了NtMapViewOfSection 调试器会把Section 映射进空间 从而使IO count中OtherOperationCount计数器增加数值
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;
}