本期作者/牛杰
前言
是一种防止逆向的方案。逆向人员如果遇到复杂的代码混淆,有时会使用调试器动态分析代码逻辑简化分析流程。例如恶意软件通常会被安全研究人员、反病毒厂商和其他安全专业人员分析和调试,以了解其行为和功能,并开发相应的安全措施来保护系统,这时,恶意软件开发人员就会使用反调试技术阻碍逆向人员的分析,以达到增加自己恶意代码的存活时间。此外,安全人员也需要了解反调试技术,当遇到反调试代码时,可以使用相对应的反反调试。在反调试技术上中,我们介绍了9种常见的反调试方法,本篇继续介绍6种方式。
反调试
1.NtSetInformationThread
NtSetInformationThread 是 Windows 操作系统提供的一个函数,用于设置线程的信息,该函数通常用来设置线程的优先级,此外通过设置不同的 ThreadInformationClass 参数,可以实现隐藏线程、禁止调试、设置调试状态等操作,从而增加程序的安全性和防御性,该函数原型与枚举信息如下。
__kernel_entry NTSYSCALLAPI NTSTATUS NtSetInformationThread( [in] HANDLE ThreadHandle, [in] THREADINFOCLASS ThreadInformationClass, [in] PVOID ThreadInformation, [in] ULONG ThreadInformationLength ); typedef enum _THREADINFOCLASS { ThreadBasicInformation, ThreadTimes, ThreadPriority, ThreadBasePriority, ThreadAffinityMask, ThreadImpersonationToken, ThreadDescriptorTableEntry, ThreadEnableAlignmentFaultFixup, ThreadEventPair_Reusable, ThreadQuerySetWin32StartAddress, ThreadZeroTlsCell, ThreadPerformanceCount, ThreadAmILastThread, ThreadIdealProcessor, ThreadPriorityBoost, ThreadSetTlsArrayAddress, ThreadIsIoPending, ThreadHideFromDebugger, ThreadBreakOnTermination, MaxThreadInfoClass } THREADINFOCLASS;
NtSetInformationThread通过ThreadInformationClass中ThreadHideFromDebugger来反调试 是 NtSetInformationThread 函数的一个参数,用于将当前线程隐藏起来,使得调试器无法对其进行调试。这个参数的作用是防止调试器附加到被隐藏的线程上进行调试操作。
当线程被隐藏后,调试器无法访问和控制该线程的执行。这可以用作一种反调试技术,防止恶意用户或恶意软件使用调试器来分析、修改或干扰程序的执行。
通过隐藏线程,程序可以增加自身的安全性,防止被调试器进行逆向工程、代码分析、内存调试等操作。这对于一些需要保护知识产权、防止恶意调试的应用程序或安全软件来说特别有用。
NtSetInformationThread反调试demo如下。
#include#include typedef enum _THREADINFOCLASS { ThreadBasicInformation, ThreadTimes, ThreadPriority, ThreadBasePriority, ThreadAffinityMask, ThreadImpersonationToken, ThreadDescriptorTableEntry, ThreadEnableAlignmentFaultFixup, ThreadEventPair_Reusable, ThreadQuerySetWin32StartAddress, ThreadZeroTlsCell, ThreadPerformanceCount, ThreadAmILastThread, ThreadIdealProcessor, ThreadPriorityBoost, ThreadSetTlsArrayAddress, ThreadIsIoPending, ThreadHideFromDebugger, ThreadBreakOnTermination, MaxThreadInfoClass } THREADINFOCLASS; typedef NTSTATUS(WINAPI* pNtSetInformationThread)(HANDLE, THREADINFOCLASS, PVOID, ULONG); void HideFromDebugger() { HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); pNtSetInformationThread NtSetInformationThread = (pNtSetInformationThread)GetProcAddress(hNtDll, "NtSetInformationThread"); NTSTATUS status = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0); } void ThreadHid() { HideFromDebugger(); int x = 1; while (1) { printf("%d",x); Sleep(1000); x++; }; } int main() { CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ThreadHid, 0, 0, 0); getchar(); return 0; }
使用调试器执调试该程序,可以看到程序正常执行。
然后在线程中下断点,程序会自动关闭。
需要注意的是,虽然使用 ThreadHideFromDebugger 可以增加程序的安全性,但它并不是绝对的安全措施,因为仍然存在其他方法可以绕过或检测到线程隐藏。因此,在设计安全应用程序时,应综合考虑多种防护措施,并定期进行安全评估和更新。
2.SeDebugPrivilege
默认情况下,进程没有调试权限(SeDebugPrivilege),除非自己主动开启,但是调试器启动程序并调试时,会从调试器继承该权限。使用该方式需要先调用CsrGetProcessId获取csrss.exe的pid,该函数在ntdll。获取pid后,通过OpenProcess读取句柄csrss.exe,如果能获取则说明被调试,代码如下。
HMODULE hMod = LoadLibrary(TEXT("ntdll.dll")); typedef int(*CSRGETPROCESSID)(); CSRGETPROCESSID CsrGetProcessId = (CSRGETPROCESSID)GetProcAddress(hMod, "CsrGetProcessId"); DWORD pid = CsrGetProcessId(); HANDLE handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (handle) { return true; } else { return false; }
3.时间检测
通过计算时间差,如果时间间隔过长,判断当前进程被反调试,常用API有API有:QueryPerformanceCounter、GetTickCount、GetSystemTime、GetLocalTime,这些API使用方法相似,我们使用GetTickCount举例。
DWORD starttime = GetTickCount(); DWORD endtime = GetTickCount(); if (endtime - starttime > 500) { return true; } else { return false; }
4.父进程
通过检测自身父进程来判定是否被调试,原理非常简单,我们的系统在运行程序的时候,绝大多数应用程序都是由explorer.exe这个父进程派生而来的子进程,也就是说如果没有被调试其得到的父进程就是explorer.exe的进程PID,而如果被调试则该进程的父进程PID就会变成调试器的PID值,通过对父进程的检测即可实现检测是否被调试的功能。
#include#include #include int IsDebug() { DWORD ExplorerId = 0; PROCESSENTRY32 pe32 = { 0 }; DWORD ProcessId = GetCurrentProcessId(); GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerId); HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (hProcessSnap != INVALID_HANDLE_VALUE) { pe32.dwSize = sizeof(PROCESSENTRY32); Process32First(hProcessSnap, &pe32); do { if (ProcessId == pe32.th32ProcessID) { // 判断父进程是否是 Explorer.exe if (pe32.th32ParentProcessID != ExplorerId) { return TRUE; } } } while (Process32Next(hProcessSnap, &pe32)); } return FALSE; } int main(int argc, char* argv[]) { if (IsDebug()) { printf("正在被调试 "); } system("pause"); return 0; }
5.NtYieldExecution
NtYieldExecution让当前线程主动放弃其剩余的时间片,并执行下一个等待的线程。如果没有线程被安排执行或在使用调试器单步调试时线程无法切换,NtYieldExecution函数返回为STATUS_NO_YIELD_PERFORMED (0x40000024)。
#define STATUS_NO_YIELD_PERFORMED 0x40000024 typedef NTSTATUS(WINAPI* pNtYieldExecution)(); bool isDebug() { HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); pNtYieldExecution NtYieldExecution = (pNtYieldExecution)GetProcAddress(hNtDll, "NtSetInformationThread"); INT iDebugged = 0; for (int i = 0; i < 0x20; i++) { Sleep(0xf); if (NtYieldExecution() != STATUS_NO_YIELD_PERFORMED) iDebugged++; } if (iDebugged <= 3) return false; else return true; system("pause"); }
但是这种方法其实并不可靠,因为它只显示当前进程中是否有一个高优先级的线程。然而,它可以作为一种反跟踪技术。
6.DbgUiRemoteBreakin
DbgUiRemoteBreakin打补丁可以反OD附加调试,当我们用OD附加调试时,CreateRemoteThread函数在目标程序中创建了一个远程线程,然后在远程线程中调用DbgUiRemoteBreakin函数,DbgUiRemoteBreakin内部调用了DbgBreakPoint函数,DbgBreakPoint函数内部下了一个int 3断点,触发异常让操作系统运行异常处理程序,然后操作系统把控制权交管给调试器,因此可以创建TLS回调的方式hook DbgUiRemoteBreakin,内部直接调用ExitProcess()退出程序,测试代码如下。
# include# include # include int main(int argc, char* argv[]) { BYTE bBuffer[0x10] = { 0 }; DWORD dwBreakAddress; DWORD dwOldProtect; DWORD dwNum; dwBreakAddress = (DWORD)GetProcAddress(LoadLibrary(L"ntdll.dll"), "DbgUiRemoteBreakin"); bBuffer[0] = 0xE9; *((DWORD*)(bBuffer + 1)) = (DWORD)ExitProcess - dwBreakAddress; VirtualProtect((LPVOID)dwBreakAddress, 0x10, PAGE_EXECUTE_READWRITE, &dwOldProtect); WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwBreakAddress, bBuffer, 5, &dwNum); VirtualProtect((LPVOID)dwBreakAddress, 0x10, dwOldProtect, &dwOldProtect); while (1) { static int x = 0; printf("%d ",x); Sleep(1000); x++; } return 0; }
当OD附加,程序终止。
总结
本篇继续介绍了其他反调试的方法,在自己的代码中使用反调试技术,可以增加逆向人员的分析难度,或是通过了解这些技术的原理,在分析恶意代码时进行反反调试,在后续的文章中,将会介绍更多的反调试方法。
全部0条评论
快来发表一下你的评论吧 !