26種對付反調試的方法

目前主要有3種分析軟件的方法:html

1.數據交換分析,研究人員使用數據包嗅探工具來分析網絡數據交換。web

2.對軟件的二進制代碼進行反彙編,而後以彙編語言列出。編程

3.字節碼解碼或二進制解碼,而後以高級編程語言從新建立源代碼。安全

本文針對的是Windows操做系統中經常使用的防破0解及防逆向工程保護技術,即反調試方法,各類防逆向工程技術的主要目標是儘量多的使逆變工具儘量失效。網絡

本文的對付反調試方法,總共涉及26種:架構

1. IsDebuggerPresentapp

2. PEB(進程環境塊)less

3.如何避開IsDebuggerPresent的檢查編程語言

4. TLS回調ide

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];
        DWORD dwPathLength = GetModuleFileName(NULL, pszExecutablePath, MAX_PATH);
        if (0 == dwPathLength) __leave;
        hExecutable = CreateFile(pszExecutablePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
        if (INVALID_HANDLE_VALUE == hExecutable) __leave;
        hExecutableMapping = CreateFileMapping(hExecutable, NULL, PAGE_READONLY, 0, 0, NULL);
        if (NULL == hExecutableMapping) __leave;
        pMappedImageBase = (PBYTE)MapViewOfFile(hExecutableMapping, FILE_MAP_READ, 0, 0,
            pImageSectionHeader->PointerToRawData + pImageSectionHeader->SizeOfRawData);
        if (NULL == pMappedImageBase) __leave;
        PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pMappedImageBase);
        PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pMappedImageBase 
            + (pImageSectionHeader->PointerToRawData
                + (pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress - pImageSectionHeader->VirtualAddress)));
        if (pImageLoadConfigDirectory->GlobalFlagsClear != 0)
        {
            std::cout << "Stop debugging program!" << std::endl;
            exit(-1);
        }
    }
    __finally
    {
        if (NULL != pMappedImageBase)
            UnmapViewOfFile(pMappedImageBase);
        if (NULL != hExecutableMapping)
            CloseHandle(hExecutableMapping);
        if (INVALID_HANDLE_VALUE != hExecutable)
            CloseHandle(hExecutable);
    } 
}

在此代碼中,CheckGlobalFlagsClearInProcess函數會經過加載當前運行的進程地址查找PIMAGE_LOAD_CONFIG_DIRECTORY結構,並檢查GlobalFlagsClear字段值。若是不是0,那麼該進程可能被調試。 CheckGlobalFlagsClearInFile函數也會執行相同的檢查,但僅僅針對的是磁盤上的可執行文件。

HeapFlag和ForceFlags

PEB結構包含指向進程堆的指針— _HEAPP結構:

0:000> dt _PEB ProcessHeap @$peb
ntdll!_PEB
   +0x018 ProcessHeap : 0x00440000 Void0:000> dt _HEAP Flags ForceFlags 00440000 ntdll!_HEAP
   +0x040 Flags      : 0x40000062
   +0x044 ForceFlags : 0x40000060

Windows X64裏的進程以下:

0:000> dt _PEB ProcessHeap @$peb
ntdll!_PEB
   +0x030 ProcessHeap : 0x0000009d`94b60000 Void
0:000> dt _HEAP Flags ForceFlags 0000009d`94b60000
ntdll!_HEAP
   +0x070 Flags      : 0x40000062
   +0x074 ForceFlags : 0x40000060

若是正在調試進程,則兩個字段Flags和ForceFlags都具備特定的調試值:

1.若是Flags字段沒有設置HEAP_GROWABLE(0x00000002)標識,則正在調試進程。

2.若是ForceFlags!= 0,則正在調試進程。

不過要注意的是,_HEAP結構並未記錄,而且Flags和ForceFlags字段的偏移值可能因操做系統版本而異。如下代碼就是基於HeapFlag檢查的反調試保護:

int GetHeapFlagsOffset(bool x64)
{
    return x64 ?
        IsVistaOrHigher() ? 0x70 : 0x14: //x64 offsets
        IsVistaOrHigher() ? 0x40 : 0x0C; //x86 offsets
}
int GetHeapForceFlagsOffset(bool x64)
{
    return x64 ?
        IsVistaOrHigher() ? 0x74 : 0x18: //x64 offsets
        IsVistaOrHigher() ? 0x44 : 0x10; //x86 offsets
}
void CheckHeap()
{
    PVOID pPeb = GetPEB();
    PVOID pPeb64 = GetPEB64();
    PVOID heap = 0;
    DWORD offsetProcessHeap = 0;
    PDWORD heapFlagsPtr = 0, heapForceFlagsPtr = 0;
    BOOL x64 = FALSE;
#ifdef _WIN64
    x64 = TRUE;
    offsetProcessHeap = 0x30;
#else
    offsetProcessHeap = 0x18;
#endif
    heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb + offsetProcessHeap);
    heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(x64));
    heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(x64));
    if (*heapFlagsPtr & ~HEAP_GROWABLE || *heapForceFlagsPtr != 0)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    if (pPeb64)
    {
        heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb64 + 0x30);
        heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(true));
        heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(true));
        if (*heapFlagsPtr & ~HEAP_GROWABLE || *heapForceFlagsPtr != 0)
        {
            std::cout << "Stop debugging program!" << std::endl;
            exit(-1);
        }
    }
}

如何避開HeapFlag和ForceFlags檢查

爲了避開基於HeapFlag檢查的反調試保護,應該爲Flags字段設置HEAP_GROWABLE標識,並將ForceFlags的值設置爲0.。但要注意的是,字段值的從新定義應該在HeapFlag檢查以前執行。

陷阱標識檢查

Trap Flag(陷阱標識)位於EFLAGS寄存器內,若是TF設置爲1,CPU將在每一個指令執行後產生INT 01h或單步異常(single-step exception)。如下就是基於TF設置和異常調用檢查的反調試:

BOOL isDebugged = TRUE;
__try
{
    __asm
    {
        pushfd
        or dword ptr[esp], 0x100 // set the Trap Flag 
        popfd                    // Load the value into EFLAGS register
        nop
    }
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
    // If an exception has been raised – debugger is not present
    isDebugged = FALSE;
}
if (isDebugged)
{
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

這裏TF有意設置爲生成異常。若是正在調試進程,則異常將被調試器捕獲。

如何避開陷阱標識檢查

爲了在調試過程當中避開TF標識檢查,應該將pushfd指令傳遞給單步異常,但要跳過它,將斷點置後,繼續執行程序。斷點後,跟蹤能夠繼續。

CheckRemoteDebuggerPresent和NtQueryInformationProcess

與IsDebuggerPresent函數不一樣,CheckRemoteDebuggerPresent會檢查一個進程是否被另外一個同步進程調試。下圖就是一個基於CheckRemoteDebuggerPresent的反調試技術:

int main(int argc, char *argv[])
{
    BOOL isDebuggerPresent = FALSE;
    if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerPresent ))
    {
        if (isDebuggerPresent )
        {
            std::cout << "Stop debugging program!" << std::endl;
            exit(-1);
        }
    }
    return 0;
}

在CheckRemoteDebuggerPresent的內部,調用NtQueryInformationProcess函數:

0:000> uf kernelbase!CheckRemotedebuggerPresent
KERNELBASE!CheckRemoteDebuggerPresent:
...
75207a24 6a00            push    0
75207a26 6a04            push    4
75207a28 8d45fc          lea     eax,[ebp-4]
75207a2b 50              push    eax
75207a2c 6a07            push    7
75207a2e ff7508          push    dword ptr [ebp+8]
75207a31 ff151c602775    call    dword ptr [KERNELBASE!_imp__NtQueryInformationProcess (7527601c)]
75207a37 85c0            test    eax,eax
75207a39 0f88607e0100    js      KERNELBASE!CheckRemoteDebuggerPresent+0x2b (7521f89f)
...

若是咱們來看看NtQueryInformationProcess文檔,那麼這個Assembler列表將向咱們展現CheckRemoteDebuggerPresent函數獲取DebugPort值,由於ProcessInformationClass參數值(第二個)爲7,如下反調試代碼就是基於調用NtQueryInformationProcess:

typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
    _In_      HANDLE           ProcessHandle,
    _In_      UINT             ProcessInformationClass,
    _Out_     PVOID            ProcessInformation,
    _In_      ULONG            ProcessInformationLength,
    _Out_opt_ PULONG           ReturnLength
    );
const UINT ProcessDebugPort = 7;
int main(int argc, char *argv[])
{
    pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;
    NTSTATUS status;
    DWORD isDebuggerPresent = 0;
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    
    if (NULL != hNtDll)
    {
        NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
        if (NULL != NtQueryInformationProcess)
        {
            status = NtQueryInformationProcess(
                GetCurrentProcess(),
                ProcessDebugPort,
                &isDebuggerPresent,
                sizeof(DWORD),
                NULL);
            if (status == 0x00000000 && isDebuggerPresent != 0)
            {
                std::cout << "Stop debugging program!" << std::endl;
                exit(-1);
            }
        }
    }
    return 0;
}

如何避開CheckRemoteDebuggerPresent和NtQueryInformationProcess

應該替換NtQueryInformationProcess函數返回的值。若是要使用mhook,就要先設置一個鉤子,能夠將DLL注入到調試過程當中,並使用mhook在DLLMain中設置一個鉤子。如下就是一個mhook用法的例子:

#include <Windows.h>
#include "mhook.h"
typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
    _In_      HANDLE           ProcessHandle,
    _In_      UINT             ProcessInformationClass,
    _Out_     PVOID            ProcessInformation,
    _In_      ULONG            ProcessInformationLength,
    _Out_opt_ PULONG           ReturnLength
    );
const UINT ProcessDebugPort = 7;
pfnNtQueryInformationProcess g_origNtQueryInformationProcess = NULL;
NTSTATUS NTAPI HookNtQueryInformationProcess(
    _In_      HANDLE           ProcessHandle,
    _In_      UINT             ProcessInformationClass,
    _Out_     PVOID            ProcessInformation,
    _In_      ULONG            ProcessInformationLength,
    _Out_opt_ PULONG           ReturnLength
    )
{
    NTSTATUS status = g_origNtQueryInformationProcess(
        ProcessHandle,
        ProcessInformationClass,
        ProcessInformation,
        ProcessInformationLength,
        ReturnLength);
    if (status == 0x00000000 && ProcessInformationClass == ProcessDebugPort)
    {
        *((PDWORD_PTR)ProcessInformation) = 0;
    }
    return status;
}
DWORD SetupHook(PVOID pvContext)
{
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    if (NULL != hNtDll)
    {
        g_origNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
        if (NULL != g_origNtQueryInformationProcess)
        {
            Mhook_SetHook((PVOID*)&g_origNtQueryInformationProcess, HookNtQueryInformationProcess);
        }
    }
    return 0;
}
BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hInstDLL);
        CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)SetupHook, NULL, NULL, NULL);
        Sleep(20);
    case DLL_PROCESS_DETACH:
        if (NULL != g_origNtQueryInformationProcess)
        {
            Mhook_Unhook((PVOID*)&g_origNtQueryInformationProcess);
        }
        break;
    }
    return TRUE;
}

基於NtQueryInformationProcess的其餘反調試保護技術

能夠從NtQueryInformationProcess函數提供的信息知道,還有更多的調試器檢測技術:

1.ProcessDebugPort 0x07,已在上面討論過。

2.ProcessDebugObjectHandle 0x1E

3.ProcessDebugFlags 0x1F

4.ProcessBasicInformation 0x00

ProcessDebugObjectHandle

從Windows XP開始,研究人員就爲調試過程建立了調試對象。如下就是檢查當前進程調試對象的案例:

status = NtQueryInformationProcess(
            GetCurrentProcess(),
            ProcessDebugObjectHandle,
            &hProcessDebugObject,
            sizeof(HANDLE),
            NULL);
if (0x00000000 == status && NULL != hProcessDebugObject)
{
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

若是有調試對象,則正在調試該進程。

ProcessDebugFlags

當檢查該標識時,它會返回到EPROCESS內核結構的NoDebugInherit位的反轉值。若是NtQueryInformationProcess函數的返回值爲0,則正在調試進程。如下就是一個這樣的反調試檢查的例子:

status = NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessDebugObjectHandle,
    &debugFlags,
    sizeof(ULONG),
    NULL);
if (0x00000000 == status && NULL != debugFlags)
{
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

ProcessBasicInformation

當使用ProcessBasicInformation標識調用NtQueryInformationProcess函數時,會返回PROCESS_BASIC_INFORMATION結構:

typedef struct _PROCESS_BASIC_INFORMATION {
    NTSTATUS ExitStatus;
    PVOID PebBaseAddress;
    ULONG_PTR AffinityMask;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

該結構中最有趣的是InheritedFromUniqueProcessId字段。在這裏,咱們須要獲取父進程的名稱並將其與流行調試器的名稱進行比較,如下是這種反調試檢查的列表:

std::wstring GetProcessNameById(DWORD pid)
{
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE)
    {
        return 0;
    }
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    std::wstring processName = L"";
    if (!Process32First(hProcessSnap, &pe32))
    {
        CloseHandle(hProcessSnap);
        return processName;
    }
    do
    {
        if (pe32.th32ProcessID == pid)
        {
            processName = pe32.szExeFile;
            break;
        }
    } while (Process32Next(hProcessSnap, &pe32));
    
    CloseHandle(hProcessSnap);
    return processName;
}
status = NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessBasicInformation,
    &processBasicInformation,
    sizeof(PROCESS_BASIC_INFORMATION),
    NULL);
std::wstring parentProcessName = GetProcessNameById((DWORD)processBasicInformation.InheritedFromUniqueProcessId);
if (L"devenv.exe" == parentProcessName)
{
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

如何避開NtQueryInformationProcess檢查

避開是很是簡單的, NtQueryInformationProcess函數返回的值應該更改成那些不指示調試器存在的值:

1.將ProcessDebugObjectHandle設置爲0

2.將ProcessDebugFlags設置爲1

3.對於ProcessBasicInformation,將InheritedFromUniqueProcessId值更改成另外一個進程ID,例如, Explorer.exe的

斷點

斷點,調試器的功能之一,可讓程序中斷在須要的地方,從而方便其分析。兩種類型的斷點:

1.軟件斷點

2.硬件斷點

在沒有斷點的狀況下很難進行逆向工程,因此目前流行的反逆向工程策略都是基於檢測斷點,而後提供一系列相應的反調試方法。

軟件斷點

在IA-32架構中,有一個特定的指令 – int 3h,帶有0xCC操做碼,用於調用調試句柄。當CPU執行該指令時,會產生中斷並將控制傳輸到調試器。爲了達到控制的目的,調試器必須將int 3h指令注入到代碼中。要檢測斷點,咱們能夠計算函數的校驗和。

DWORD CalcFuncCrc(PUCHAR funcBegin, PUCHAR funcEnd)
{
    DWORD crc = 0;
    for (; funcBegin < funcEnd; ++funcBegin)
    {
        crc += *funcBegin;
    }
    return crc;
}
#pragma auto_inline(off)
VOID DebuggeeFunction()
{
    int calc = 0;
    calc += 2;
    calc <<= 8;
    calc -= 3;
}
VOID DebuggeeFunctionEnd()
{
};
#pragma auto_inline(on)
DWORD g_origCrc = 0x2bd0;
int main()
{
    DWORD crc = CalcFuncCrc((PUCHAR)DebuggeeFunction, (PUCHAR)DebuggeeFunctionEnd);
    if (g_origCrc != crc)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    return 0;
}

要注意的是,若是設置了/ INCREMENTAL:NO連接器選項,那麼在獲取函數地址來計算校驗和的狀況下,將會獲得相對跳轉地址:

DebuggeeFunction:
013C16DB  jmp         DebuggeeFunction (013C4950h)

g_origCrc全局變量包含已由CalcFuncCrc函數計算的crc。爲了終止檢測函數,咱們使用了存根函數的技巧。按着函數代碼順序排列,DebuggeeFunction函數的末尾是DebuggeeFunctionEnd函數的開頭。咱們還使用#pragma auto_inline(off)指令來防止編譯器的嵌入函數。

如何避開軟件斷點檢查

因爲目前尚未一個通用的方法,因此爲了避開此保護,應該找到代碼計算校驗和並用常量替換返回的值,以及存儲函數校驗和的全部變量的值。

硬件斷點

在Windows x86架構中,開發人員在檢查和調試代碼時使用了一組調試寄存器。這些寄存器容許在訪問內存讀取或寫入時中斷程序執行並將控制傳輸到調試器。調試寄存器是一種特權資源,只能在具備特權級別CPL = 0的實模式或安全模式下由程序使用。8字節的調試寄存器DR0-DR7有:

1.DR0-DR3 -斷點寄存器

2.DR4,DR5 -儲藏

3.DR6 -調試狀態

4.DR7 – 調試控制

DR0-DR3包含斷點的線性地址,這些地址的比較是在物理地址轉換以前進行的。這些斷點中的每個都會在DR7寄存器中被單獨描述。 DR6寄存器會指示哪一個斷點被激活。 DR7經過訪問模式定義斷點激活模式:讀,寫,執行。如下是硬件斷點檢查的示例:

CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if (GetThreadContext(GetCurrentThread(), &ctx))
{
    if (ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
}

也能夠經過SetThreadContext函數重置硬件斷點,如下是硬件斷點重置示例:

CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
SetThreadContext(GetCurrentThread(), &ctx);

咱們能夠看到,全部DRx寄存器都設置爲了0。

如何避開硬件斷點檢查和重置

若是咱們看看GetThreadContext函數,咱們會明白它調用了NtGetContextThread函數:

0:000> u KERNELBASE!GetThreadContext L6
KERNELBASE!GetThreadContext:
7538d580 8bff            mov     edi,edi
7538d582 55              push    ebp
7538d583 8bec            mov     ebp,esp
7538d585 ff750c          push    dword ptr [ebp+0Ch]
7538d588 ff7508          push    dword ptr [ebp+8]
7538d58b ff1504683975    call    dword ptr [KERNELBASE!_imp__NtGetContextThread (75396804)]

若是反調試保護在Dr0-Dr7中接收到零值,應該在CONTEXT結構的ContextFlags字段中重置CONTEXT_DEBUG_REGISTERS標識,而後在原始的NtGetContextThread函數調用後恢復其值。對於GetThreadContext函數,它在內部調用NtSetContextThread。如下就是避開硬件斷點的檢查和重置:

typedef NTSTATUS(NTAPI *pfnNtGetContextThread)(
    _In_  HANDLE             ThreadHandle,
    _Out_ PCONTEXT           pContext
    );
typedef NTSTATUS(NTAPI *pfnNtSetContextThread)(
    _In_ HANDLE              ThreadHandle,
    _In_ PCONTEXT            pContext
    );
pfnNtGetContextThread g_origNtGetContextThread = NULL;
pfnNtSetContextThread g_origNtSetContextThread = NULL;
NTSTATUS NTAPI HookNtGetContextThread(
    _In_  HANDLE              ThreadHandle,
    _Out_ PCONTEXT            pContext)
{
    DWORD backupContextFlags = pContext->ContextFlags;
    pContext->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
    NTSTATUS status = g_origNtGetContextThread(ThreadHandle, pContext);
    pContext->ContextFlags = backupContextFlags;
    return status;
}
NTSTATUS NTAPI HookNtSetContextThread(
    _In_ HANDLE              ThreadHandle,
    _In_ PCONTEXT            pContext)
{
    DWORD backupContextFlags = pContext->ContextFlags;
    pContext->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
    NTSTATUS status = g_origNtSetContextThread(ThreadHandle, pContext);   
    pContext->ContextFlags = backupContextFlags;
    return status;
}
void HookThreadContext()
{
HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
g_origNtGetContextThread = (pfnNtGetContextThread)GetProcAddress(hNtDll, "NtGetContextThread");
g_origNtSetContextThread = (pfnNtSetContextThread)GetProcAddress(hNtDll, "NtSetContextThread");
Mhook_SetHook((PVOID*)&g_origNtGetContextThread, HookNtGetContextThread);
Mhook_SetHook((PVOID*)&g_origNtSetContextThread, HookNtSetContextThread);
}

SEH(結構化異常處理)

SEH是Windows操做系統提供給程序設計者的強有力的處理程序錯誤或異常處理的工具,容許其接收關於異常狀況的通知,例如除數是0致使的錯誤,引用不存在的指針或執行受限制的指令。這種機制容許處理應用程序中的異常,而無需操做系統操做。若是不對異常進行處理,則會致使異常程序終止。開發人員一般能在堆棧中找到SEH的指針,它們被稱爲SEH Frame。當前SEH Frame地址位於x64系統的FS選擇器或GS選擇器的0的偏移處,這個地址指向ntdll!_EXCEPTION_REGISTRATION_RECORD結構:

0:000> dt ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : Ptr32 _EXCEPTION_DISPOSITION

當啓動異常時,控制將傳輸到當前的SEH處理程序。根據狀況,SEH處理程序應返回一個_EXCEPTION_DISPOSITION值:

typedef enum _EXCEPTION_DISPOSITION {
    ExceptionContinueExecution,
    ExceptionContinueSearch,
    ExceptionNestedException,
    ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;

若是處理程序返回ExceptionContinueSearch,系統會繼續執行指令,從而觸發異常。若是處理程序不知道如何處理異常,它應該返回ExceptionContinueSearch,轉到系統中的下一個處理程序。你可使用windbg調試器中的!exchain命令瀏覽當前的異常鏈:

0:000> !exchain
00a5f3bc: AntiDebug!_except_handler4+0 (008b7530)
  CRT scope  0, filter: AntiDebug!SehInternals+67 (00883d67)
                func:   AntiDebug!SehInternals+6d (00883d6d)
00a5f814: AntiDebug!__scrt_stub_for_is_c_termination_complete+164b (008bc16b)
00a5f87c: AntiDebug!_except_handler4+0 (008b7530)
  CRT scope  0, filter: AntiDebug!__scrt_common_main_seh+1b0 (008b7c60)
                func:   AntiDebug!__scrt_common_main_seh+1cb (008b7c7b)
00a5f8e8: ntdll!_except_handler4+0 (775674a0)
  CRT scope  0, filter: ntdll!__RtlUserThreadStart+54386 (7757f076)
                func:   ntdll!__RtlUserThreadStart+543cd (7757f0bd)
00a5f900: ntdll!FinalExceptionHandlerPad4+0 (77510213)

鏈中的最後一部分是系統分配的默認處理程序。若是之前沒有異常的處理程序,則系統處理程序將轉到註冊表以獲取

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionAeDebug

根據AeDebug密鑰值,應用程序將被終止或控制被傳送到調試器。調試器路徑應在調試器REG_SZ中指示。

在建立新進程時,系統會向其添加主要SEH Frame。主SEH Frame的處理程序也由系統定義。主要的SEH Frame自己幾乎位於最初爲進程分配的堆棧內存中。 SEH處理函數簽名以下所示:

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    __in struct _EXCEPTION_RECORD *ExceptionRecord,
    __in PVOID EstablisherFrame,
    __inout struct _CONTEXT *ContextRecord,
    __inout PVOID DispatcherContext
    );

若是正在調試應用程序,在int 3h中斷生成後,控制將被調試器攔截。不然,控制將被轉移

到SEH處理程序。如下代碼就是基於SEH Frame的反調試保護:

BOOL g_isDebuggerPresent = TRUE;
EXCEPTION_DISPOSITION ExceptionRoutine(
    PEXCEPTION_RECORD ExceptionRecord,
    PVOID             EstablisherFrame,
    PCONTEXT          ContextRecord,
    PVOID             DispatcherContext)
{
    g_isDebuggerPresent = FALSE;
    ContextRecord->Eip += 1;
    return ExceptionContinueExecution;
}
int main()
{
    __asm
    {
        // set SEH handler
        push ExceptionRoutine
        push dword ptr fs:[0]
        mov  dword ptr fs:[0], esp
        // generate interrupt
        int  3h
        // return original SEH handler
        mov  eax, [esp]
        mov  dword ptr fs:[0], eax
        add  esp, 8
    }
    if (g_isDebuggerPresent)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    return 0
}

上面的示例顯示了對SEH處理程序的設置,指向SEH Frame的指針放在處理程序鏈的開頭,而後產生int 3h中斷。若是應用程序未被調試,則控制將被傳送到SEH處理程序,g_isDebuggerPresent值將被設置爲FALSE。 ContextRecord-> Eip + = 1行更改執行流程中下一條指令的地址,這將致使執行int 3h後的指令。而後代碼返回原始SEH處理程序,清除堆棧,並檢查調試器是否存在。

如何避開SEH檢查

 

雖然不存在一個廣泛的方法,但仍是有一些技術,可以實現這一點。咱們來看看利用調用堆棧如何致使SEH處理程序調用:

0:000> kn
 # ChildEBP RetAddr  
00 0059f06c 775100b1 AntiDebug!ExceptionRoutine 
01 0059f090 77510083 ntdll!ExecuteHandler2+0x26
02 0059f158 775107ff ntdll!ExecuteHandler+0x24
03 0059f158 003b11a5 ntdll!KiUserExceptionDispatcher+0xf
04 0059fa90 003d7f4e AntiDebug!main+0xb5
05 0059faa4 003d7d9a AntiDebug!invoke_main+0x1e
06 0059fafc 003d7c2d AntiDebug!__scrt_common_main_seh+0x15a 
07 0059fb04 003d7f68 AntiDebug!__scrt_common_main+0xd 
08 0059fb0c 753e7c04 AntiDebug!mainCRTStartup+0x8
09 0059fb20 7752ad1f KERNEL32!BaseThreadInitThunk+0x24
0a 0059fb68 7752acea ntdll!__RtlUserThreadStart+0x2f
0b 0059fb78 00000000 ntdll!_RtlUserThreadStart+0x1b

咱們能夠看到來自ntdll!ExecuteHandler2的調用,此函數是調用任何SEH處理程序的起始點,由此能夠推斷是斷點,斷點能夠設置爲調用指令:

0:000> u ntdll!ExecuteHandler2+24 L3
ntdll!ExecuteHandler2+0x24:
775100af ffd1            call    ecx
775100b1 648b2500000000  mov     esp,dword ptr fs:[0]
775100b8 648f0500000000  pop     dword ptr fs:[0]
0:000> bp 775100af

接着咱們就要分析每一個被稱爲SEH處理程序的代碼,若是保護是基於SEH處理程序的屢次呼叫,則很難就行反調試了。

VEH(向量化異常處理)

VEH是從Windows XP開始引入的,雖然它是SEH的一個變體,但二者彼此獨立運行, VEH優先權高於SHE,只有VEH不處理某個異常的時候,異常處理權纔會到達SEH.。當添加新的VEH處理程序時,SEH鏈不受影響,由於VEH處理程序的列表存儲在ntdll!LdrpVectorHandlerList非導出的變量中。 VEH和SEH機制很是類似,惟一的區別是記錄功能被用於設置和刪除VEN處理程序。添加和刪除VEH處理程序以及VEH處理函數的原函數簽名以下:

PVOID WINAPI AddVectoredExceptionHandler(
    ULONG                       FirstHandler,
    PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
ULONG WINAPI RemoveVectoredExceptionHandler(
    PVOID Handler
);
LONG CALLBACK VectoredHandler(
    PEXCEPTION_POINTERS ExceptionInfo
);
The _EXCEPTION_POINTERS structure looks like this:  
typedef struct _EXCEPTION_POINTERS {
  PEXCEPTION_RECORD ExceptionRecord;
  PCONTEXT          ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

在處理程序中收到控制權後,系統會收集當前進程的上下文並經過ContextRecord參數進行傳遞。如下就是一個使用向量異常處理的反調試保護代碼:

LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
    PCONTEXT ctx = ExceptionInfo->ContextRecord;
    if (ctx->Dr0 != 0 || ctx->Dr1 != 0 || ctx->Dr2 != 0 || ctx->Dr3 != 0)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    ctx->Eip += 2;
    return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
    AddVectoredExceptionHandler(0, ExceptionHandler);
    __asm int 1h;
    return 0;
}

在這裏,咱們設置了一個VEH處理程序併產生中斷(不須要int 1h)。在中斷生成時,出現異常,控制被傳送到VEH處理程序。若是設置了硬件斷點,程序執行中止。若是沒有硬件斷點,則EIP寄存器值會增長2,以在int 1h處生成指令後繼續執行。

如何避開硬件斷點檢查和VEH

咱們來看看致使VEH處理程序的調用堆棧:

0:000> kn
 # ChildEBP RetAddr  
00 001cf21c 774d6822 AntiDebug!ExceptionHandler 
01 001cf26c 7753d151 ntdll!RtlpCallVectoredHandlers+0xba02 001cf304 775107ff ntdll!RtlDispatchException+0x7203 001cf304 00bf4a69 ntdll!KiUserExceptionDispatcher+0xf04 001cfc1c 00c2680e AntiDebug!main+0x59 
05 001cfc30 00c2665a AntiDebug!invoke_main+0x1e 
06 001cfc88 00c264ed AntiDebug!__scrt_common_main_seh+0x15a 
07 001cfc90 00c26828 AntiDebug!__scrt_common_main+0xd 
08 001cfc98 753e7c04 AntiDebug!mainCRTStartup+0x8 
09 001cfcac 7752ad1f KERNEL32!BaseThreadInitThunk+0x24
0a 001cfcf4 7752acea ntdll!__RtlUserThreadStart+0x2f
0b 001cfd04 00000000 ntdll!_RtlUserThreadStart+0x1b

咱們能夠看到,控制從主+ 0x59轉移到ntdll!KiUserExceptionDispatcher。讓咱們看看主+ 0x59中的什麼指令致使了這個調用:

0:000> u main+59 L1
AntiDebug!main+0x59
00bf4a69 cd02            int     1

這是產生中斷的指令, KiUserExceptionDispatcher函數是系統從內核模式調用到用戶模式的回調方法之一。如下是它的簽名:

VOID NTAPI KiUserExceptionDispatcher(
    PEXCEPTION_RECORD pExcptRec, 
    PCONTEXT ContextFrame
);

下面就是避開了應用KiUserExceptionDispatcher函數鉤子的硬件斷點檢查的代碼:

typedef  VOID (NTAPI *pfnKiUserExceptionDispatcher)(
    PEXCEPTION_RECORD pExcptRec,
    PCONTEXT ContextFrame
    );
pfnKiUserExceptionDispatcher g_origKiUserExceptionDispatcher = NULL;
VOID NTAPI HandleKiUserExceptionDispatcher(PEXCEPTION_RECORD pExcptRec, PCONTEXT ContextFrame)
{
    if (ContextFrame && (CONTEXT_DEBUG_REGISTERS & ContextFrame->ContextFlags))
    {
        ContextFrame->Dr0 = 0;
        ContextFrame->Dr1 = 0;
        ContextFrame->Dr2 = 0;
        ContextFrame->Dr3 = 0;
        ContextFrame->Dr6 = 0;
        ContextFrame->Dr7 = 0;
        ContextFrame->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
    }
}
__declspec(naked) VOID NTAPI HookKiUserExceptionDispatcher() 
// Params: PEXCEPTION_RECORD pExcptRec, PCONTEXT ContextFrame
{
    __asm
    {
        mov eax, [esp + 4]
        mov ecx, [esp]
        push eax
        push ecx
        call HandleKiUserExceptionDispatcher
        jmp g_origKiUserExceptionDispatcher
    }
}
int main()
{
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    g_origKiUserExceptionDispatcher = (pfnKiUserExceptionDispatcher)GetProcAddress(hNtDll, "KiUserExceptionDispatcher");
    Mhook_SetHook((PVOID*)&g_origKiUserExceptionDispatcher, HookKiUserExceptionDispatcher);
    return 0;
}

在這個例子中,DRx寄存器的值在HookKiUserExceptionDispatcher函數中被複位,即在VEH處理程序調用以前。

NtSetInformationThread —從調試器隱藏線程

在Windows 2000中,ThreadHideFromDebugger這項技術用到了經常被用來設置線程優先級的API ntdll!NtSetInformationThread(),它是Windows提供的第一個反調試技術之一,功能很是強大。若是把該標識設置爲一個線程,它將中止發送關於調試事件的通知。若是爲主線程設置了ThreadHideFromDebugger,則這些事件包括關於程序完成的斷點和通知。該標識的值存儲在_ETHREAD結構的HideFromDebugger字段中,以下所示:

1: kd> dt _ETHREAD HideFromDebugger 86bfada8
ntdll!_ETHREAD
   +0x248 HideFromDebugger : 0y1

如下是設置ThreadHideFromDebugger的示例:

typedef NTSTATUS (NTAPI *pfnNtSetInformationThread)(
    _In_ HANDLE ThreadHandle,
    _In_ ULONG  ThreadInformationClass,
    _In_ PVOID  ThreadInformation,
    _In_ ULONG  ThreadInformationLength
    );
const ULONG ThreadHideFromDebugger = 0x11;
void HideFromDebugger()
{
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    pfnNtSetInformationThread NtSetInformationThread = (pfnNtSetInformationThread)
        GetProcAddress(hNtDll, "NtSetInformationThread");
    NTSTATUS status = NtSetInformationThread(GetCurrentThread(), 
        ThreadHideFromDebugger, NULL, 0);
}

如何避開從調試器隱藏線程

爲了防止應用程序將線程隱藏到調試器中,須要鉤住NtSetInformationThread函數調用。如下就是一個鉤子代碼:

pfnNtSetInformationThread g_origNtSetInformationThread = NULL;
NTSTATUS NTAPI HookNtSetInformationThread(
    _In_ HANDLE ThreadHandle,
    _In_ ULONG  ThreadInformationClass,
    _In_ PVOID  ThreadInformation,
    _In_ ULONG  ThreadInformationLength
    )
{
    if (ThreadInformationClass == ThreadHideFromDebugger && 
        ThreadInformation == 0 && ThreadInformationLength == 0)
    {
        return STATUS_SUCCESS;
    }
    return g_origNtSetInformationThread(ThreadHandle, 
        ThreadInformationClass, ThreadInformation, ThreadInformationLength
}
                                        
void SetHook()
{
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    if (NULL != hNtDll)
    {
        g_origNtSetInformationThread = (pfnNtSetInformationThread)GetProcAddress(hNtDll, "NtSetInformationThread");
        if (NULL != g_origNtSetInformationThread)
        {
            Mhook_SetHook((PVOID*)&g_origNtSetInformationThread, HookNtSetInformationThread);
        }
    }
}

在鉤子函數中,當以正確的方式調用它時,就將返回STATUS_SUCCESS,而不將控制權轉移到原始的NtSetInformationThread函數。

NtCreateThreadEx

Windows Vista引入了NtCreateThreadEx函數,其函數以下:

NTSTATUS NTAPI NtCreateThreadEx (
    _Out_    PHANDLE              ThreadHandle,
    _In_     ACCESS_MASK          DesiredAccess,
    _In_opt_ POBJECT_ATTRIBUTES   ObjectAttributes,
    _In_     HANDLE               ProcessHandle,
    _In_     PVOID                StartRoutine,
    _In_opt_ PVOID                Argument,
    _In_     ULONG                CreateFlags,
    _In_opt_ ULONG_PTR            ZeroBits,
    _In_opt_ SIZE_T               StackSize,
    _In_opt_ SIZE_T               MaximumStackSize,
    _In_opt_ PVOID                AttributeList
);

最有趣的參數是CreateFlags,這個參數的標識以下:

#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004
#define THREAD_CREATE_FLAGS_HAS_SECURITY_DESCRIPTOR 0x00000010
#define THREAD_CREATE_FLAGS_ACCESS_CHECK_IN_TARGET 0x00000020
#define THREAD_CREATE_FLAGS_INITIAL_THREAD 0x00000080

若是一個新線程獲取了THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER標識,它將在調試器建立時被隱藏。它是由NtSetInformationThread函數設置的ThreadHideFromDebugger,負責安全運行的代碼能夠在設置了THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER標識的線程中執行。

如何避開NtCreateThreadEx

該技術能夠經過鉤子NtCreateThreadEx函數來避開,其中THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER將被重置。

如何處理跟蹤

從Windows XP開始,系統就配置了內核對象句柄跟蹤的機制。當跟蹤模式打開時,具備處理程序的全部操做都將保存到循環緩衝區,同時也嘗試使用不存在的處理程序,例如,使用CloseHandle函數關閉它,將生成EXCEPTION_INVALID_HADNLE異常。若是進程不是從調試器啓動,那麼CloseHandle函數將返回FALSE。如下就是基於CloseHandle的防調試保護:

EXCEPTION_DISPOSITION ExceptionRoutine(
    PEXCEPTION_RECORD ExceptionRecord,
    PVOID             EstablisherFrame,
    PCONTEXT          ContextRecord,
    PVOID             DispatcherContext)
{
    if (EXCEPTION_INVALID_HANDLE == ExceptionRecord->ExceptionCode)
    {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    return ExceptionContinueExecution;
}
int main()
{
    __asm
    {
        // set SEH handler
        push ExceptionRoutine
        push dword ptr fs : [0]
        mov  dword ptr fs : [0], esp
    }
    CloseHandle((HANDLE)0xBAAD);
    __asm
    {
        // return original SEH handler
        mov  eax, [esp]
        mov  dword ptr fs : [0], eax
        add  esp, 8
    }
    return 0
}

堆棧段操做

當使用ss堆棧段寄存器進行操做時,調試器將跳過指令跟蹤。以下圖所示,調試器將當即移至xor edx,edx指令,同時執行上一條指令:

__asm
{
    push ss
    pop  ss
    mov  eax, 0xC000C1EE // This line will be traced over by debugger
    xor  edx, edx        // Debugger will step to this line
}

總結

本文介紹了一系列對付反調試技術的技術,都是一些易於操做的方法,其實還有不少方法:

1.自調試過程;

2.使用FindWindow函數進行調試器檢測;

3.時間計算方法;

4.NtQueryObject;

5. BlockInput;

6.NtSetDebugFilterState;

7.自修改代碼;

最後想再次強調,即便是最好的對付反調試的方法也不能徹底防止惡意軟件的攻擊,對付反調試技術的主要目標是儘量的使那些的惡意軟件的攻擊變得困難。

本文翻譯自:https://www.codeproject.com/Articles/1090943/Anti-Debug-Protection-Techniques-Implementation-an如若轉載,請註明原文地址: https://www.4hou.com/web/15211.html
相關文章
相關標籤/搜索