介绍6种常见的反调试方法

描述

本期作者/牛杰

前言

是一种防止逆向的方案。逆向人员如果遇到复杂的代码混淆,有时会使用调试器动态分析代码逻辑简化分析流程。例如恶意软件通常会被安全研究人员、反病毒厂商和其他安全专业人员分析和调试,以了解其行为和功能,并开发相应的安全措施来保护系统,这时,恶意软件开发人员就会使用反调试技术阻碍逆向人员的分析,以达到增加自己恶意代码的存活时间。此外,安全人员也需要了解反调试技术,当遇到反调试代码时,可以使用相对应的反反调试。在反调试技术上中,我们介绍了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附加,程序终止。

函数

总结

本篇继续介绍了其他反调试的方法,在自己的代码中使用反调试技术,可以增加逆向人员的分析难度,或是通过了解这些技术的原理,在分析恶意代码时进行反反调试,在后续的文章中,将会介绍更多的反调试方法。

 

  审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分