26种对付反调试的方法

目前主要有3种分析软件的方法:

1.数据交换分析,研究人员使用数据包嗅探工具来分析网络数据交换。

2.对软件的二进制代码进行反汇编,然后以汇编语言列出。

3.字节码解码或二进制解码,然后以高级编程语言重新创建源代码。

本文针对的是Windows操作系统中常用的防破0解及防逆向工程保护技术,即反调试方法,各种防逆向工程技术的主要目标是尽可能多的使逆变工具尽可能失效。

本文的对付反调试方法,总共涉及26种:

1. IsDebuggerPresent

2. PEB(进程环境块)

3.如何避开IsDebuggerPresent的检查

4. TLS回调

5.NtGlobalFlag

6.如何避开NtGlobalFlag检查

7.NtGlobalFlag和IMAGE_LOAD_CONFIG_DIRECTORY

8.HeapFlag和ForceFlags

9.如何避开HeapFlag和ForceFlags

10.陷阱标识检查

11如何避开陷阱标识检查

12.CheckRemoteDebuggerPresent和NtQueryInformationProcess

13.如何避开CheckRemoteDebuggerPresent和NtQueryInformationProcess

14.基于NtQueryInformationProcess的其他反调试保护技术

15.如何避开NtQueryInformationProcess检查

16.软件和硬件的断点反应

17.SEH(结构化异常处理)

18.如何避开SHE检查

19.VEH(向量化异常处理)

20.如何避开硬件断点检查和VEH

21.NtSetInformationThread ,在调试工具中隐藏线程

22.如何避开从调试工具中隐藏线程

23.NtCreateThreadEx

24. 如何避开NtCreateThreadEx

25.处理跟踪

26.堆栈段操作

建议你在阅读本文时,先具备一定的Assembler知识,一些Windbg操作经验以及使用API函数开发Windows的经验。

IsDebuggerPresent

也许最简单的方法是调用IsDebuggerPresent函数,用此函数检测用户模式的调试器是否正在调试调用进程。下面的代码就是一个基本的保护案例:

int main()
{
    if (IsDebuggerPresent())
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    return 0;
}

如果我们来看看IsDebuggerPresent函数,我们会发现这样的代码:

0:000< u kernelbase!IsDebuggerPresent L3
KERNELBASE!IsDebuggerPresent:
751ca8d0 64a130000000    mov     eax,dword ptr fs:[00000030h]
751ca8d6 0fb64002        movzx   eax,byte ptr [eax+2]
751ca8da c3              ret

Windows X64里的进程如下:

0:000< u kernelbase!IsDebuggerPresent L3
KERNELBASE!IsDebuggerPresent:
00007ffc`ab6c1aa0 65488b042560000000 mov   rax,qword ptr gs:[60h]
00007ffc`ab6c1aa9 0fb64002           movzx eax,byte ptr [rax+2]
00007ffc`ab6c1aad c3                 ret

在FS寄存器的偏移量30h处存在PEB(进程环境块),而在X64上,PEB(进程环境块)存在于GS段寄存器的偏移量60h处。在PEB中的2个偏移量处,我们将找到BeingDebugged字段:

0:000< dt _PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar

即IsDebuggerPresent函数读取BeingDebugged字段的值。如果进程被调试,值为1,否则为0。

PEB(进程环境块)

PEB是在操作系统内使用的封闭结构。在不同地运行环境下,大家应该以不同的方式获取PEB结构指针。如下所示,你可以在下图中找到x32和x64系统的PEB指针:

// Get PEB for WOW64 Process
PVOID GetPEB64()
{
    PVOID pPeb = 0;
#ifndef _WIN64
    // 1. There are two copies of PEB - PEB64 and PEB32 in WOW64 process
    // 2. PEB64 follows after PEB32
    // 3. This is true for version less then Windows 8, else __readfsdword returns address of real PEB64
    if (IsWin8OrHigher())
    {
        BOOL isWow64 = FALSE;
        typedef BOOL(WINAPI *pfnIsWow64Process)(HANDLE hProcess, PBOOL isWow64);
        pfnIsWow64Process fnIsWow64Process = (pfnIsWow64Process)
            GetProcAddress(GetModuleHandleA("Kernel32.dll"), "IsWow64Process");
        if (fnIsWow64Process(GetCurrentProcess(), &isWow64))
        {
            if (isWow64)
            {
                pPeb = (PVOID)__readfsdword(0x0C * sizeof(PVOID));
                pPeb = (PVOID)((PBYTE)pPeb + 0x1000);
            }
        }
    }
#endif
    return pPeb;
}

检查操作系统版本的功能代码如下:

WORD GetVersionWord()
{
    OSVERSIONINFO verInfo = { sizeof(OSVERSIONINFO) };
    GetVersionEx(&verInfo);
    return MAKEWORD(verInfo.dwMinorVersion, verInfo.dwMajorVersion);
}
BOOL IsWin8OrHigher() { return GetVersionWord() >= _WIN32_WINNT_WIN8; }
BOOL IsVistaOrHigher() { return GetVersionWord() >= _WIN32_WINNT_VISTA; }

如何避开IsDebuggerPresent检查

为了做到这一点,在执行检查代码之前,需要将0置于BeingDebugged。例如,可以使用DLL注入:

mov eax, dword ptr fs:[0x30]  
mov byte ptr ds:[eax+2], 0

Windows X64里的进程如下:

DWORD64 dwpeb = __readgsqword(0x60);
*((PBYTE)(dwpeb + 2)) = 0;

TLS回调

其实,在主函数中检查调试器的存在不是最好的方法,因为TLS回调处于反汇编列表时反向工具的第一个位置。它实施的检查可以由nop指令擦除,从而解除保护。如果使用CRT库,则在将控制权转移到主函数之前,主线程就已经有一个调用堆栈了。执行调试器存在检查的一个方法便是TLS回调。如下图所示,在可执行模块入口调用之前就已经调用回调函数。

#pragma section(".CRT$XLY", long, read)
__declspec(thread) int var = 0xDEADBEEF;
VOID NTAnopPI TlsCallback(PVOID DllHandle, DWORD Reason, VOID Reserved)
{
    var = 0xB15BADB0; // Required for TLS Callback call
    if (IsDebuggerPresent())
    {
        MessageBoxA(NULL, "Stop debugging program!", "Error", MB_OK | MB_ICONERROR);
        TerminateProcess(GetCurrentProcess(), 0xBABEFACE);
    }
}
__declspec(allocate(".CRT$XLY"))PIMAGE_TLS_CALLBACK g_tlsCallback = TlsCallback;

NtGlobalFlag

在Windows NT中,存在一组标识,它们存储在全局变量NtGlobalFlag中。在系统启动时,NtGlobalFlag全局系统变量将使用系统注册表项中的值进行初始化:

[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerGlobalFlag]

该变量值用于系统跟踪,调试和控制。虽然变量标识未记录,但SDK包括gflags实用程序,它允许对一个全局标识值进行编辑。 PEB结构还包括NtGlobalFlag字段,其位结构不对应于NtGlobalFlag全局系统变量。在调试期间,这些标识在NtGlobalFlag字段中的设置如下:

FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
FLG_HEAP_ENABLE_FREE_CHECK (0x20)
FLG_HEAP_VALIDATE_PARAMETERS (0x40)

要检查进程是否使用了调试器启动,你应该检查PEB结构的NtGlobalFlag字段的值。在x32和x64系统中,该字段位于PEB结构的开始处的0x068和0x0bc偏移处。

0:000> dt _PEB NtGlobalFlag @$peb 
ntdll!_PEB
   +0x068 NtGlobalFlag : 0x70

Windows X64里的进程如下:

0:000> dt _PEB NtGlobalFlag @$peb
ntdll!_PEB
   +0x0bc NtGlobalFlag : 0x70

以下代码片段就是基于NtGlobalFlag标识检查的反调试保护:

#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)
void CheckNtGlobalFlag()
{
    PVOID pPeb = GetPEB();
    PVOID pPeb64 = GetPEB64();
    DWORD offsetNtGlobalFlag = 0;
#ifdef _WIN64
    offsetNtGlobalFlag = 0xBC;
#else
    offsetNtGlobalFlag = 0x68;
#endif
    DWORD NtGlobalFlag = *(PDWORD)((PBYTE)pPeb + offsetNtGlobalFlag);
    if (NtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    if (pPeb64)
    {
        DWORD NtGlobalFlagWow64 = *(PDWORD)((PBYTE)pPeb64 + 0xBC);
        if (NtGlobalFlagWow64 & NT_GLOBAL_FLAG_DEBUGGED)
        {
            std::cout << "Stop debugging program!" << std::endl;
            exit(-1);
        }
    }
}

如何避开NtGlobalFlag检查

在执行该检查之前,应该在通过反调试保护检查该值之前,将0调整为调试过程中PEB结构的NtGlobalFlag字段。

NtGlobalFlag和IMAGE_LOAD_CONFIG_DIRECTORY

可执行文件既包括IMAGE_LOAD_CONFIG_DIRECTORY结构,也包括系统加载程序的其他配置参数。不过在默认情况下,此结构不会内置到可执行文件中,需要使用补丁添加。此结构具有GlobalFlagsClear字段,对PEB结构中要重置的NtGlobalFlag字段进行了标识。如果最初没有对该结构或GlobalFlagsClear = 0创建可执行文件,那么在磁盘或内存中,该字段就具有非零值,隐藏的调试器就会正常运行。下面就是检查运行进程的内存和磁盘上的GlobalFlagsClear字段的代码,这是一种流行的反调试技术:

PIMAGE_NT_HEADERS GetImageNtHeaders(PBYTE pImageBase)
{
    PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pImageBase;
    return (PIMAGE_NT_HEADERS)(pImageBase + pImageDosHeader->e_lfanew);
}
PIMAGE_SECTION_HEADER FindRDataSection(PBYTE pImageBase)
{
    static const std::string rdata = ".rdata";
    PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase);
    PIMAGE_SECTION_HEADER pImageSectionHeader = IMAGE_FIRST_SECTION(pImageNtHeaders);
    int n = 0;
    for (; n < pImageNtHeaders->FileHeader.NumberOfSections; ++n)
    {
        if (rdata == (char*)pImageSectionHeader[n].Name)
        {
            break;
        }
    }
    return &pImageSectionHeader[n];
}
void CheckGlobalFlagsClearInProcess()
{
    PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL);
    PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase);
    PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pImageBase
        + pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress);
    if (pImageLoadConfigDirectory->GlobalFlagsClear != 0)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
}
void CheckGlobalFlagsClearInFile()
{
    HANDLE hExecutable = INVALID_HANDLE_VALUE;
    HANDLE hExecutableMapping = NULL;
    PBYTE pMappedImageBase = NULL;
    __try
    {
        PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL);
        PIMAGE_SECTION_HEADER pImageSectionHeader = FindRDataSection(pImageBase);
        TCHAR pszExecutablePath[MAX_PATH];
      &nb
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值