Windows下反(反)调试技术汇总

windows反调试 1. 检测是否正在被调试(使用 IsDebuggerPresent API):```cpp// 检测到调试器,执行相应操作// 或者其他操作```2. 使用 NtQueryInformationProcess 检测调试器:```cpp```3. 检查调试端口:```cpp= FALSE;```4. 使用异常处理来检测调试器:```cpp__try? 阅读详情

反调试技术,恶意代码用它识别是否被调试,或者让调试器失效。恶意代码编写者意识到分析人员经常使用调试器来观察恶意代码的操作,因此他们使用反调试技术尽可能地延长恶意代码的分析时间。为了阻止调试器的分析,当恶意代码意识到自己被调试时,它们可能改变正常的执行路径或者修改自身程序让自己崩溃,从而增加调试时间和复杂度。很多种反调试技术可以达到反调试效果。这里介绍当前常用的几种反调试技术,同时也会介绍一些逃避反调试的技巧。
一.探测Windows调试器
恶意代码会使用多种技术探测调试器调试它的痕迹,其中包括使用Windows API、手动检测调试器人工痕迹的内存结构,查询调试器遗留在系统中的痕迹等。调试器探测是恶意代码最常用的反调试技术。
1.使用Windows API
使用Windows API函数检测调试器是否存在是最简单的反调试技术。Windows操作系统中提供了这样一些API,应用程序可以通过调用这些API,来检测自己是否正在被调试。这些API中有些是专门用来检测调试器的存在的,而另外一些API是出于其他目的而设计的,但也可以被改造用来探测调试器的存在。其中很小部分API函数没有在微软官方文档显示。通常,防止恶意代码使用API进行反调试的最简单的办法是在恶意代码运行期间修改恶意代码,使其不能调用探测调试器的API函数,或者修改这些API函数的返回值,确保恶意代码执行合适的路径。与这些方法相比,较复杂的做法是挂钩这些函数,如使用rootkit技术。
1.1IsDebuggerPresent
IsDebuggerPresent查询进程环境块(PEB)中的IsDebugged标志。如果进程没有运行在调试器环境中,函数返回0;如果调试附加了进程,函数返回一个非零值。
BOOL CheckDebug()
{
    return IsDebuggerPresent();
}
1.2CheckRemoteDebuggerPresent
CheckRemoteDebuggerPresent同IsDebuggerPresent几乎一致。它不仅可以探测系统其他进程是否被调试,通过传递自身进程句柄还可以探测自身是否被调试。
BOOL CheckDebug()
{
    BOOL ret;
    CheckRemoteDebuggerPresent(GetCurrentProcess(), &ret);
    return ret;
}
1.3NtQueryInformationProcess
这个函数是Ntdll.dll中一个原生态API,它用来提取一个给定进程的信息。它的第一个参数是进程句柄,第二个参数告诉我们它需要提取进程信息的类型。为第二个参数指定特定值并调用该函数,相关信息就会设置到第三个参数。第二个参数是一个枚举类型,其中与反调试有关的成员有ProcessDebugPort(0x7)、ProcessDebugObjectHandle(0x1E)和ProcessDebugFlags(0x1F)。例如将该参数置为ProcessDebugPort,如果进程正在被调试,则返回调试端口,否则返回0。

BOOL CheckDebug()
{
    int debugPort = 0;
    HMODULE hModule = LoadLibrary("Ntdll.dll");
    NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
    NtQueryInformationProcess(GetCurrentProcess(), 0x7, &debugPort, sizeof(debugPort), NULL);
    return debugPort != 0;
}
 
BOOL CheckDebug()
{
    HANDLE hdebugObject = NULL;
    HMODULE hModule = LoadLibrary("Ntdll.dll");
    NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
    NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &hdebugObject, sizeof(hdebugObject), NULL);
    return hdebugObject != NULL;
}
 
BOOL CheckDebug()
{
    BOOL bdebugFlag = TRUE;
    HMODULE hModule = LoadLibrary("Ntdll.dll");
    NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
    NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &bdebugFlag, sizeof(bdebugFlag), NULL);
    return bdebugFlag != TRUE;
}

1.4GetLastError
编写应用程序时,经常需要涉及到错误处理问题。许多函数调用只用TRUE和FALSE来表明函数的运行结果。一旦出现错误,MSDN中往往会指出请用GetLastError()函数来获得错误原因。恶意代码可以使用异常来破坏或者探测调试器。调试器捕获异常后,并不会立即将处理权返回被调试进程处理,大多数利用异常的反调试技术往往据此来检测调试器。多数调试器默认的设置是捕获异常后不将异常传递给应用程序。如果调试器不能将异常结果正确返回到被调试进程,那么这种异常失效可以被进程内部的异常处理机制探测。
对于OutputDebugString函数,它的作用是在调试器中显示一个字符串,同时它也可以用来探测调试器的存在。使用SetLastError函数,将当前的错误码设置为一个任意值。如果进程没有被调试器附加,调用OutputDebugString函数会失败,错误码会重新设置,因此GetLastError获取的错误码应该不是我们设置的任意值。但如果进程被调试器附加,调用OutputDebugString函数会成功,这时GetLastError获取的错误码应该没改变。
BOOL CheckDebug()
{
    DWORD errorValue = 12345;
    SetLastError(errorValue);
    OutputDebugString("Test for debugger!");
    if (GetLastError() == errorValue)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}
对于DeleteFiber函数,如果给它传递一个无效的参数的话会抛出ERROR_INVALID_PARAMETER异常。如果进程正在被调试的话,异常会被调试器捕获。所以,同样可以通过验证LastError值来检测调试器的存在。如代码所示,0x57就是指ERROR_INVALID_PARAMETER。

BOOL CheckDebug()
{
    char fib[1024] = {0};
    DeleteFiber(fib);
    return (GetLastError() != 0x57);
}
同样还可以使用CloseHandle、CloseWindow产生异常,使得错误码改变。

BOOL CheckDebug()
{
    DWORD ret = CloseHandle((HANDLE)0x1234);
    if (ret != 0 || GetLastError() != ERROR_INVALID_HANDLE)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}
 
BOOL CheckDebug()
{
    DWORD ret = CloseWindow((HWND)0x1234);
    if (ret != 0 || GetLastError() != ERROR_INVALID_WINDOW_HANDLE)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

1.5ZwSetInformationThread
ZwSetInformationThread拥有两个参数,第一个参数用来接收当前线程的句柄,第二个参数表示线程信息类型,若其值设置为ThreadHideFromDebugger(0x11),使用语句ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);调用该函数后,调试进程就会被分离出来。该函数不会对正常运行的程序产生任何影响,但若运行的是调试器程序,因为该函数隐藏了当前线程,调试器无法再收到该线程的调试事件,最终停止调试。还有一个函数DebugActiveProcessStop用来分离调试器和被调试进程,从而停止调试。两个API容易混淆,需要牢记它们的区别。
2.手动检测数据结构
虽然使用Windows API是探测调试器存在的最简单办法,但手动检查数据结构是恶意代码编写者最常使用的办法。这是因为很多时候通过Windows API实现的反调试技术无效,例如这些API函数被rootkit挂钩,并返回错误信息。因此,恶意代码编写者经常手动执行与这些API功能相同的操作。在手动检测中,PEB结构中的一些标志暴露了调试器存在的信息。这里,我们关注检测调试器存在常用的一些标志。
2.1检测BeingDebugged属性
Windows操作系统维护着每个正在运行的进程的PEB结构,它包含与这个进程相关的所有用户态参数。这些参数包括进程环境数据,环境数据包括环境变量、加载的模块列表、内存地址,以及调试器状态。

进程运行时,位置fs:[30h]指向PEB的基地址。为了实现反调试技术,恶意代码通过这个位置检查BeingDebugged标志,这个标志标识进程是否正在被调试。

BOOL CheckDebug()
{
    int result = 0;
    __asm
    {
        mov eax, fs:[30h]
        mov al, BYTE PTR [eax + 2] 
        mov result, al
    }
    return result != 0;
}
这种检查有多种形式,最终,条件跳转决定代码的路径。避免这种问题最简单的方法是在执行跳转指令前,手动修改零标志,强制执行跳转(或者不跳转)。
可以或者手动修改BeingDebugged属性值为0。在OllyDbg中安装命令行插件,为了启动该插件,用OllyDbg加载恶意代码,选择Plugins->Command Line->Command Line选项,在命令行窗口输入下面的命令。

如图所示,这条命令会将BeingDebugged属性转储到转储面板窗口。右键单击BeingDebugged属性,选择Binary->Fill With 00's,这时属性被设置为0。
OllyDbg的一些插件可以帮助我们修改BeingDebugged标志。其中最流行的有HideDebugger、Hidedebug和PhantOm。以PhantOm为例,同样将dll文件拷贝到OllyDbg的安装目录下就会自动安装。选择Plugins->PhantOm->Options选项,勾选hide from PEB即可。


2.2检测ProcessHeap属性
Reserved数组中一个未公开的位置叫作ProcessHeap,它被设置为加载器为进程分配的第一个堆的位置。ProcessHeap位于PEB结构的0x18处。第一个堆头部有一个属性字段,它告诉内核这个堆是否在调试器中创建。这些属性叫作ForceFlags和Flags。在Windows XP系统中,ForceFlags属性位于堆头部偏移量0x10处;在Windows 7系统中,对于32位的应用程序来说ForceFlags属性位于堆头部偏移量0x44处。
BOOL CheckDebug()
{
    int result = 0;
    DWORD dwVersion = GetVersion();
    DWORD dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
    //for xp
    if (dwWindowsMajorVersion == 5)
    {
        __asm
        {
            mov eax, fs:[30h]
            mov eax, [eax + 18h]
            mov eax, [eax + 10h]
            mov result, eax
        }
    }
    else
    {
        __asm
        {
            mov eax, fs:[30h]
            mov eax, [eax + 18h]
            mov eax, [eax + 44h]
            mov result, eax
        }
    }
    return result != 0;
}

同样,恶意代码也可以检查Windows XP系统中偏移量0x0C处,或者Windows 7系统中偏移量0x40处的Flags属性。这个属性总与ForceFlags属性大致相同,但通常情况下Flags与值2进行比较。

BOOL CheckDebug()
{
    int result = 0;
    DWORD dwVersion = GetVersion();
    DWORD dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
    //for xp
    if (dwWindowsMajorVersion == 5)
    {
        __asm
        {
            mov eax, fs:[30h]
            mov eax, [eax + 18h]
            mov eax, [eax + 0ch]
            mov result, eax
        }
    }
    else
    {
        __asm
        {
            mov eax, fs:[30h]
            mov eax, [eax + 18h]
            mov eax, [eax + 40h]
            mov result, eax
        }
    }
    return result != 2;
}

避免这种问题方法和前面的差不多。如果用OllyDbg的命令行插件修改,输入的命令为dump ds:[fs:[30]+0x18]+0x10。如果用PhantOm插件,它会禁用调试堆创建功能而不需要手动设置。
2.3检测NTGlobalFlag
由于调试器中启动进程与正常模式下启动进程有些不同,所以它们创建内存堆的方式也不同。系统使用PEB结构偏移量0x68处的一个未公开位置,来决定如何创建堆结构。如果这个位置的值为0x70,我们就知道进程正运行在调试器中。
BOOL CheckDebug()
{
    int result = 0;
    __asm
    {
        mov eax, fs:[30h]
        mov eax, [eax + 68h]
        and eax, 0x70
        mov result, eax
    }
    return result != 0;
}
操作系统创建堆时,值0x70是下列标志的一个组合。如果进程从调试器启动,那么进程的这些标志将被设置。
(FLG_HEAP_ENABLE_TAIL_CHECK|FLG_HEAP_ENABLE_FREE_CHECK|FLG_HEAP_VALIDATE_PARAMETERS)
避免这种问题方法和前面的差不多。如果用OllyDbg的命令行插件修改,输入的命令为dump fs:[30]+0x68。如果用PhantOm插件,它会逃避使用NTGlobalFlag的反调试技术而不需要手动设置。
3.系统痕迹检测
通常,我们使用调试工具来分析恶意代码,但这些工具会在系统中驻留一些痕迹。恶意代码通过搜索这种系统痕迹,来确定你是否试图分析它。
3.1查找调试器引用的注册表项
下面是调试器在注册表中的一个常用位置。
SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug(32位系统)
SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\AeDebug(64位系统)
该注册表项指定当应用程序发生错误时,触发哪一个调试器。默认情况下,它被设置为Dr.Watson。如果该这册表的键值被修改为OllyDbg,则恶意代码就可能确定它正在被调试。
BOOL CheckDebug()
{
    BOOL is_64;
    IsWow64Process(GetCurrentProcess(), &is_64);
    HKEY hkey = NULL;
    char key[] = "Debugger";
    char reg_dir_32bit[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug";
    char reg_dir_64bit[] = "SOFTWARE\\Wow6432Node\\Microsoft\\WindowsNT\\CurrentVersion\\AeDebug";
    DWORD ret = 0;
    if (is_64)
    {
        ret = RegCreateKeyA(HKEY_LOCAL_MACHINE, reg_dir_64bit, &hkey);
    }
    else
    {
        ret = RegCreateKeyA(HKEY_LOCAL_MACHINE, reg_dir_32bit, &hkey);
    }
    if (ret != ERROR_SUCCESS)
    {
        return FALSE;
    }
    char tmp[256];
    DWORD len = 256;
    DWORD type;
    ret = RegQueryValueExA(hkey, key, NULL, &type, (LPBYTE)tmp, &len);
    if (strstr(tmp, "OllyIce")!=NULL || strstr(tmp, "OllyDBG")!=NULL || strstr(tmp, "WinDbg")!=NULL || strstr(tmp, "x64dbg")!=NULL || strstr(tmp, "Immunity")!=NULL)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

3.2查找窗体信息
FindWindow函数检索处理顶级窗口的类名和窗口名称匹配指定的字符串。

BOOL CheckDebug()
{
    if (FindWindowA("OLLYDBG", NULL)!=NULL || FindWindowA("WinDbgFrameClass", NULL)!=NULL || FindWindowA("QWidget", NULL)!=NULL)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}
EnumWindows函数枚举所有屏幕上的顶层窗口,并将窗口句柄传送给应用程序定义的回调函数。
BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lParam)  
{  
    char cur_window[1024];
    GetWindowTextA(hwnd, cur_window, 1023);
    if (strstr(cur_window, "WinDbg")!=NULL || strstr(cur_window, "x64_dbg")!=NULL || strstr(cur_window, "OllyICE")!=NULL || strstr(cur_window, "OllyDBG")!=NULL || strstr(cur_window, "Immunity")!=NULL)
    {
        *((BOOL*)lParam) = TRUE;
    }
    return TRUE;
} 
 
BOOL CheckDebug()
{
    BOOL ret = FALSE;
    EnumWindows(EnumWndProc, (LPARAM)&ret); 
    return ret;
}
GetForegroundWindow获取一个前台窗口的句柄。
BOOL CheckDebug()
{
    char fore_window[1024];
    GetWindowTextA(GetForegroundWindow(), fore_window, 1023);
    if (strstr(fore_window, "WinDbg")!=NULL || strstr(fore_window, "x64_dbg")!=NULL || strstr(fore_window, "OllyICE")!=NULL || strstr(fore_window, "OllyDBG")!=NULL || strstr(fore_window, "Immunity")!=NULL)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}
为了防范这种技术,在OllyDbg的PhantOm插件中勾选hide OllyDbg windows。
3.3查找进程信息
BOOL CheckDebug()
{
    DWORD ID;
    DWORD ret = 0;
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32); 
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 
    if(hProcessSnap == INVALID_HANDLE_VALUE) 
    { 
        return FALSE; 
    }
    BOOL bMore = Process32First(hProcessSnap, &pe32); 
    while(bMore)
    {
        if (stricmp(pe32.szExeFile, "OllyDBG.EXE")==0 || stricmp(pe32.szExeFile, "OllyICE.exe")==0 || stricmp(pe32.szExeFile, "x64_dbg.exe")==0 || stricmp(pe32.szExeFile, "windbg.exe")==0 || stricmp(pe32.szExeFile, "ImmunityDebugger.exe")==0)
        {
            return TRUE;
        }
        bMore = Process32Next(hProcessSnap, &pe32); 
    }
    CloseHandle(hProcessSnap); 
    return FALSE;
}

二.识别调试器行为
在逆向工程中,为了帮助恶意代码分析人员进行分析,可以使用调试器设置一个断点,或是单步执行一个进程。然而,在调试器中执行这些操作时,它们会修改进程中的代码。因此,恶意代码常使用几种反调试技术探测软件/硬件断点、完整性校验、时钟检测等几种类型的调试器行为。直接运行恶意代码与在调试器中运行恶意代码也会在一些细节上不同,如父进程信息、STARTUPINFO信息、SeDebugPrivilege权限等。
1.软件断点检查
调试器设置断点的基本机制是用软件中断指令INT 3临时替换运行程序中的一条指令,然后当程序运行到这条指令时,调用调试异常处理例程。INT 3指令的机器码是0xCC,因此无论何时,使用调试器设置一个断点,它都会插入一个0xCC来修改代码。恶意代码常用的一种反调试技术是在它的代码中查找机器码0xCC,来扫描调试器对它代码的INT 3修改。repne scasb指令用于在一段数据缓冲区中搜索一个字节。EDI需指向缓冲区地址,AL则包含要找的字节,ECX设为缓冲区的长度。当ECX=0或找到该字节时,比较停止。
BOOL CheckDebug()
{
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS32 pNtHeaders;
    PIMAGE_SECTION_HEADER pSectionHeader;
    DWORD dwBaseImage = (DWORD)GetModuleHandle(NULL); 
    pDosHeader = (PIMAGE_DOS_HEADER)dwBaseImage;
    pNtHeaders = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeaders + sizeof(pNtHeaders->Signature) + sizeof(IMAGE_FILE_HEADER) + 
                     (WORD)pNtHeaders->FileHeader.SizeOfOptionalHeader);
    DWORD dwAddr = pSectionHeader->VirtualAddress + dwBaseImage; 
    DWORD dwCodeSize = pSectionHeader->SizeOfRawData;    
    BOOL Found = FALSE;
    __asm
    {
        cld             
        mov     edi,dwAddr
        mov     ecx,dwCodeSize
        mov     al,0CCH 
        repne   scasb
        jnz     NotFound
        mov Found,1
NotFound:         
    }
    return Found;
}

2.硬件断点检查


在OllyDbg的寄存器窗口按下右键,点击View debug registers可以看到DR0、DR1、DR2、DR3、DR6和DR7这几个寄存器。DR0、Dr1、Dr2、Dr3用于设置硬件断点,由于只有4个硬件断点寄存器,所以同时最多只能设置4个硬件断点。DR4、DR5由系统保留。  DR6、DR7用于记录Dr0-Dr3中断点的相关属性。如果没有硬件断点,那么DR0、DR1、DR2、DR3这4个寄存器的值都为0。

BOOL CheckDebug()
{
    CONTEXT context;  
    HANDLE hThread = GetCurrentThread();  
    context.ContextFlags = CONTEXT_DEBUG_REGISTERS;  
    GetThreadContext(hThread, &context);  
    if (context.Dr0 != 0 || context.Dr1 != 0 || context.Dr2 != 0 || context.Dr3!=0)   
    {  
        return TRUE;  
    }  
    return FALSE;  
}
3.执行代码校验和检查
恶意代码可以计算代码段的校验并实现与扫描中断相同的目的。与扫描0xCC不同,这种检查仅执行恶意代码中机器码CRC或者MD5校验和检查。
BOOL CheckDebug()
{
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS32 pNtHeaders;
    PIMAGE_SECTION_HEADER pSectionHeader;
    DWORD dwBaseImage = (DWORD)GetModuleHandle(NULL); 
    pDosHeader = (PIMAGE_DOS_HEADER)dwBaseImage;
    pNtHeaders = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeaders + sizeof(pNtHeaders->Signature) + sizeof(IMAGE_FILE_HEADER) +
        (WORD)pNtHeaders->FileHeader.SizeOfOptionalHeader);
    DWORD dwAddr = pSectionHeader->VirtualAddress + dwBaseImage; 
    DWORD dwCodeSize = pSectionHeader->SizeOfRawData;    
    DWORD checksum = 0;
    __asm
    {
        cld
        mov     esi, dwAddr
        mov     ecx, dwCodeSize
        xor eax, eax
    checksum_loop :
        movzx    ebx, byte ptr[esi]
        add        eax, ebx
        rol eax, 1
        inc esi
        loop       checksum_loop
        mov checksum, eax
    }
    if (checksum != 0x46ea24)
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

4.时钟检测
被调试时,进程的运行速度大大降低,例如,单步调试大幅降低恶意代码的运行速度,所以时钟检测是恶意代码探测调试器存在的最常用方式之一。有如下两种用时钟检测来探测调试器存在的方法。
记录一段操作前后的时间戳,然后比较这两个时间戳,如果存在滞后,则可以认为存在调试器。
记录触发一个异常前后的时间戳。如果不调试进程,可以很快处理完异常,因为调试器处理异常的速度非常慢。默认情况下,调试器处理异常时需要人为干预,这导致大量延迟。虽然很多调试器允许我们忽略异常,将异常直接返回程序,但这样操作仍然存在不小的延迟。
4.1使用rdtsc指令
较常用的时钟检测方法是利用rdtsc指令(操作码0x0F31),它返回至系统重新启动以来的时钟数,并且将其作为一个64位的值存入EDX:EAX中。恶意代码运行两次rdtsc指令,然后比较两次读取之间的差值。
BOOL CheckDebug()
{
    DWORD time1, time2;
    __asm
    {
        rdtsc
        mov time1, eax
        rdtsc
        mov time2, eax
    }
    if (time2 - time1 < 0xff)
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

4.2使用QueryPerformanceCounter和GetTickCount
同rdtsc指令一样,这两个Windows API函数也被用来执行一个反调试的时钟检测。使用这种方法的前提是处理器有高分辨率能力的计数器-寄存器,它能存储处理器活跃的时钟数。为了获取比较的时间差,调用两次QueryPerformanceCounter函数查询这个计数器。若两次调用之间花费的时间过于长,则可以认为正在使用调试器。GetTickCount函数返回最近系统重启时间与当前时间的相差毫秒数(由于时钟计数器的大小原因,计数器每49.7天就被重置一次)。
BOOL CheckDebug()
{
    DWORD time1 = GetTickCount();
    __asm
    {
        mov     ecx,10           
        mov     edx,6               
        mov     ecx,10
    }
    DWORD time2 = GetTickCount();
    if (time2-time1 > 0x1A)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

5.判断父进程是否是explorer.exe 
一般双击运行的进程的父进程都是explorer.exe,但是如果进程被调试父进程则是调试器进程。也就是说如果父进程不是explorer.exe则可以认为程序正在被调试。

BOOL CheckDebug()
{
    LONG                      status;  
    DWORD                     dwParentPID = 0;  
    HANDLE                    hProcess;  
    PROCESS_BASIC_INFORMATION pbi;  
    int pid = getpid();
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);  
    if(!hProcess)  
        return -1;  
    PNTQUERYINFORMATIONPROCESS  NtQueryInformationProcess = (PNTQUERYINFORMATIONPROCESS)GetProcAddress(GetModuleHandleA("ntdll"),"NtQueryInformationProcess");
    status = NtQueryInformationProcess(hProcess,SystemBasicInformation,(PVOID)&pbi,sizeof(PROCESS_BASIC_INFORMATION),NULL);
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32); 
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 
    if(hProcessSnap == INVALID_HANDLE_VALUE) 
    { 
        return FALSE; 
    }
    BOOL bMore = Process32First(hProcessSnap, &pe32); 
    while(bMore)
    {
        if (pbi.InheritedFromUniqueProcessId == pe32.th32ProcessID)
        {
            if (stricmp(pe32.szExeFile, "explorer.exe")==0)
            {
                CloseHandle(hProcessSnap);
                return FALSE;
            }
            else
            {
                CloseHandle(hProcessSnap);
                return TRUE;
            }
        }
        bMore = Process32Next(hProcessSnap, &pe32); 
    }
    CloseHandle(hProcessSnap);
}

6.判断STARTUPINFO信息
explorer.exe创建进程的时候会把STARTUPINFO结构中的值设为0,而非explorer.exe创建进程的时候会忽略这个结构中的值,也就是结构中的值不为0。所以可以利用STARTUPINFO来判断程序是否在被调试。
BOOL CheckDebug()
{
    STARTUPINFO si;
    GetStartupInfo(&si);
    if (si.dwX!=0 || si.dwY!=0 || si.dwFillAttribute!=0 || si.dwXSize!=0 || si.dwYSize!=0 || si.dwXCountChars!=0 || si.dwYCountChars!=0)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}
7.判断是否具有SeDebugPrivilege权限
默认情况下进程是没有SeDebugPrivilege权限的,但是当进程通过调试器启动时,由于调试器本身启动了SeDebugPrivilege权限,当调试进程被加载时SeDebugPrivilege也就被继承了。所以我们可以检测进程的SeDebugPrivilege权限来间接判断是否存在调试器,而对SeDebugPrivilege权限的判断可以用能否打开csrss.exe进程来判断。

BOOL CheckDebug()
{
    DWORD ID;
    DWORD ret = 0;
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32); 
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 
    if(hProcessSnap == INVALID_HANDLE_VALUE) 
    { 
        return FALSE; 
    }
    BOOL bMore = Process32First(hProcessSnap, &pe32); 
    while(bMore)
    {
        if (strcmp(pe32.szExeFile, "csrss.exe")==0)
        {
            ID = pe32.th32ProcessID;
            break;
        }
        bMore = Process32Next(hProcessSnap, &pe32); 
    }
    CloseHandle(hProcessSnap); 
    if (OpenProcess(PROCESS_QUERY_INFORMATION, NULL, ID) != NULL)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

三.干扰调试器的功能
恶意代码可以用一些技术来干扰调试器的正常运行。例如线程本地存储(TLS)回调、插入中断、异常等。这些技术当且仅当程序处于调试器控制之下时才试图扰乱程序的运行。
1.使用TLS回调
Thread Local Storage(TLS),即线程本地存储,是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。TLS可以简单地由操作系统代为完成整个互斥过程,也可以由用户自己编写控制信号量的函数。当进程中的线程访问预先制定的内存空间时,操作系统会调用系统默认的或用户自定义的信号量函数,保证数据的完整性与正确性。下面是一个简单的TLS回调的例子,TLS_CALLBACK1函数在main函数执行前调用IsDebuggerPresent函数检查它是否正在被调试。

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
 
void NTAPI __stdcall TLS_CALLBACK1(PVOID DllHandle, DWORD dwReason, PVOID Reserved);
 
#ifdef _M_IX86
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:__tls_callback")
#else
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:_tls_callback")
#endif
EXTERN_C
#ifdef _M_X64
#pragma const_seg (".CRT$XLB")
const
#else
#pragma data_seg (".CRT$XLB")
#endif
 
PIMAGE_TLS_CALLBACK _tls_callback[] = { TLS_CALLBACK1,0};
#pragma data_seg ()
#pragma const_seg ()
 
#include <iostream>
 
void NTAPI __stdcall TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
    if (IsDebuggerPresent())
    {
        printf("TLS_CALLBACK: Debugger Detected!\n");
    }
    else
    {
        printf("TLS_CALLBACK: No Debugger Present!\n");
    }
}
 
int main(int argc, char* argv[])
{
    printf("233\n");
    return 0;
}
 

要在程序中使用TLS,必须为TLS数据单独建一个数据段,用相关数据填充此段,并通知链接器为TLS数据在PE文件头中添加数据。_tls_callback[]数组中保存了所有的TLS回调函数指针。数组必须以NULL指针结束,且数组中的每一个回调函数在程序初始化时都会被调用,程序员可按需要添加。但程序员不应当假设操作系统已何种顺序调用回调函数。如此则要求在TLS回调函数中进行反调试操作需要一定的独立性。
正常运行这个程序会打印下面的内容。
TLS_CALLBACK: No Debugger Present!
233
如果把在OllyDbg中运行,在OllyDbg暂停之前会打印下面的内容。
TLS_CALLBACK: Debugger Detected!
使用PEview查看.tls段,可以发现TLS回调函数。通常情况下,正常程序不使用.tls段,如果在可执行程序中看到.tls段,应该立即怀疑它使用了反调试技术。

在OllyDbg中选择Options->Debugging Options->Events,然后设置System break-point作为第一个暂停的位置,这样就可以让OllyDbg在TLS回调执行前暂停。


在IDA Pro中按Ctrl+E快捷键看到二进制的入口点,该组合键的作用是显示应用程序所有的入口点,其中包括TLS回调。双击函数名可以浏览回调函数。

由于TLS回调已广为人知,因此同过去相比,恶意代码使用它的次数已经明显减少。为数不多的合法程序使用TLS回调,所以可执行程序中的.tls段特别突出。
2.利用中断
因为调试器使用INT 3来设置软件断点,所以一种反调试技术就是在合法代码段中插入0xCC(INT 3)欺骗调试器,使其认为这些0xCC机器码是自己设置的断点。

BOOL CheckDebug()
{
      __try
      {
            __asm int 3
      }
      __except(1)
      {
            return FALSE;
      }
      return TRUE;
}
除了使用_try和_except以外还可以直接使用汇编代码安装SEH。在下面的代码中如果进程没有处于调试中,则正常终止;如果进程处于调试中,则跳转到非法地址0xFFFFFFFF处,无法继续调试。
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
void AD_BreakPoint()
{
    printf("SEH : BreakPoint\n");
 
    __asm {
        // install SEH
        push handler
        push DWORD ptr fs:[0]
        mov DWORD ptr fs:[0], esp
        
        // generating exception
        int 3
 
        // 1) debugging
        //    go to terminating code
        mov eax, 0xFFFFFFFF
        jmp eax                 // process terminating!!!
 
        // 2) not debugging
        //    go to normal code
handler:
        mov eax, dword ptr ss:[esp+0xc]
        mov ebx, normal_code
        mov dword ptr ds:[eax+0xb8], ebx
        xor eax, eax
        retn
 
normal_code:
        //   remove SEH
        pop dword ptr fs:[0]
        add esp, 4
    }
 
    printf("  => Not debugging...\n\n");
}
 
int _tmain(int argc, TCHAR* argv[])
{
    AD_BreakPoint();
 
    return 0;
}

双字节操作码0xCD03也可以产生INT 3中断,这是恶意代码干扰WinDbg调试器的有效方法。在调试器外,0xCD03指令产生一个STATUS_BREAKPOINT异常。然而在WinDbg调试器内,由于断点通常是单字节机器码0xCC,因此WinDbg会捕获这个断点然后将EIP加1字节。这可能导致程序在被正常运行的WinDbg调试时,执行不同的指令集(OllyDbg可以避免双字节INT 3的攻击)。

BOOL CheckDebug()
{
      __try
      {
            __asm
            {
                  __emit 0xCD
                  __emit 0x03
            }
      }
      __except(1)
      {
            return FALSE;
      }
      return TRUE; 
}

INT 2D原为内核模式中用来触发断点异常的指令,也可以在用户模式下触发异常。但程序调试运行时不会触发异常,只是忽略。INT 2D指令在ollydbg中有两个有趣的特性。在调试模式中执行INT 2D指令,下一条指令的第一个字节将被忽略。使用StepInto(F7)或者StepOver(F8)命令跟踪INT 2D指令,程序不会停在下一条指令开始的地方,而是一直运行,就像RUN(F9)一样。在下面的代码中,程序调试运行时,执行INT 2D之后不会运行SEH,而是跳过NOP,把bDebugging标志设置为1,跳转到normal_code;程序正常运行时,执行INT 2D之后触发SEH,在异常处理器中设置EIP并把bDebugging标志设置为0。

BOOL CheckDebug()
{
    BOOL bDebugging = FALSE;
 
    __asm {
        // install SEH
        push handler
        push DWORD ptr fs:[0]
        mov DWORD ptr fs:[0], esp
        
        int 0x2d
 
        nop
        mov bDebugging, 1
        jmp normal_code
 
handler:
        mov eax, dword ptr ss:[esp+0xc]
        mov dword ptr ds:[eax+0xb8], offset normal_code
        mov bDebugging, 0
        xor eax, eax
        retn
 
normal_code:
        //   remove SEH
        pop dword ptr fs:[0]
        add esp, 4
    }
 
    printf("Trap Flag (INT 2D)\n");
    if( bDebugging )  return 1;
    else              return 0;
}

片内仿真器(ICE)断点指令ICEBP(操作码0xF1)是Intel未公开的指令之一。由于使用ICE难以在任意位置设置断点,因此ICEBP指令被设计用来降低使用ICE设置断点的难度。运行ICEBP指令将会产生一个单步异常,如果通过单步调试跟踪程序,调试器会认为这是单步调试产生的异常,从而不执行先前设置的异常处理例程。利用这一点,恶意代码使用异常处理例程作为它的正常执行流程。为了防止这种反调试技术,执行ICEBP指令时不要使用单步。

BOOL CheckDebug()
{
      __try
      {
            __asm __emit 0xF1
      }
      __except(1)
      {
            return FALSE;
      }
      return TRUE;
}
3.设置陷阱标志位
EFLAGS寄存器的第八个比特位是陷阱标志位。如果设置了,就会产生一个单步异常。

BOOL CheckDebug()
{
      __try
      {
            __asm
            {
                  pushfd     
                  or word ptr[esp], 0x100            
                  popfd
                  nop        
            }
      }
      __except(1)
      {
            return FALSE;
      }
      return TRUE;
}

4.使用异常
前面已经讨论了各种使用异常机制的反调试手段。
4.1RaiseException
RaiseException函数产生的若干不同类型的异常可以被调试器捕获。
BOOL TestExceptionCode(DWORD dwCode)
{
      __try
      {
            RaiseException(dwCode, 0, 0, 0);
      }
      __except(1)
      {
            return FALSE;
      }
      return TRUE;
}
 
BOOL CheckDebug()
{
      return TestExceptionCode(DBG_RIPEXCEPTION);    
}

4.2SetUnhandledExceptionFilter
进程中发生异常时若SEH未处理或注册的SEH不存在,会调用UnhandledExceptionFilter,它会运行系统最后的异常处理器。UnhandledExceptionFilter内部调用了前面提到过的NtQueryInformationProcess以判断是否正在调试进程。若进程正常运行,则运行最后的异常处理器;若进程处于调试,则将异常派送给调试器。SetUnhandledExceptionFilter函数可以修改系统最后的异常处理器。下面的代码先触发异常,然后在新注册的最后的异常处理器内部判断进程正常运行还是调试运行。进程正常运行时pExcept->ContextRecord->Eip+=4;将发生异常的代码地址加4使得其能够继续运行;进程调试运行时产生无效的内存访问异常,从而无法继续调试。
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
LPVOID g_pOrgFilter = 0;
 
LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS pExcept)
{
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)g_pOrgFilter);
 
    // 8900    MOV DWORD PTR DS:[EAX], EAX
    // FFE0    JMP EAX
    pExcept->ContextRecord->Eip += 4;
 
    return EXCEPTION_CONTINUE_EXECUTION;
}
 
void AD_SetUnhandledExceptionFilter()
{
    printf("SEH : SetUnhandledExceptionFilter()\n");
 
    g_pOrgFilter = (LPVOID)SetUnhandledExceptionFilter(
                                (LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);
 
    __asm {
        xor eax, eax;
        mov dword ptr [eax], eax
        jmp eax                     
    }
    
    printf("  => Not debugging...\n\n");
}
 
int _tmain(int argc, TCHAR* argv[])
{
    AD_SetUnhandledExceptionFilter();
 
    return 0;
}

在OllyDbg中,选择Options->Debugging Options->Exceptions来设置把异常传递给应用程序。

四.调试器漏洞
与所有软件一样,调试器也存在漏洞,有时恶意代码编写者为了防止被调试,会攻击这些漏洞。这里我们展示几种OllyDbg调试器处理PE格式文件时的常见漏洞。
1.PE头漏洞
OllyDbg非常严格地遵循了微软对PE文件头部的规定。在PE文件的头部,通常存在一个叫作IMAGE_OPTIONAL_HEADER的结构。

需要特别注意这个结构中的最后几个元素。NumberOfRvaAndSizes属性标识后面DataDirectory数组中的元素个数。DataDirectory数组表示在这个可执行文件中的什么地方可找到其他导入可执行模块的位置,它位于可选头部结构的末尾,是一个比IMAGE_DATA_DIRECTORY略大一些的数组。数组中每个结构目录都指明了目录的相对虚拟地址和大小。DataDirectory数组的大小被设置为IMAGE_NUMBEROF_DIRECTORY_ENTRIES,它等于0x10。因为DataDirectory数组不足以容纳超过0x10个目录项,所以当NumberOfRvaAndSizes大于0x10时,Windows加载器将会忽略NumberOfRvaAndSizes。OllyDbg遵循了这个标准,并且无论NumberOfRvaAndSizes是什么值,OllyDbg都使用它。因此,设置NumberOfRvaAndSizes为一个超过0x10的值,会导致在程序退出前,OllyDbg对用户弹出一个窗口。如图所示,使用LordPE打开可执行文件,修改RVA数及大小并保存,再用OllyDbg打开,会提示错误Bad or unknown format of 32-bit executable file。

另一种PE头的欺骗与节头部有关。文件内容中包含的节包括代码节、数据节、资源节,以及一些其他信息节。每个节都拥有一个IMAGE_SECTION_HEADER结构的头部。


VirtualSize和SizeOfRawData是其中两个比较重要的属性。根据微软对PE的规定,VirtualSize应该包含载入到内存的节大小,SizeOfRawData应该包含节在硬盘中的大小。Windows加载器使用VirtualSize和SizeOfRawData中的最小值将节数据映射到内存。如果SizeOfRawData大于VirtualSize,则仅将VirtualSize大小的数据复制入内存,忽略其余数据。因为OllyDbg仅使用SizeOfRawData,所以设置SizeOfRawData为一个类似0x77777777的大数值时,会导致OllyDbg崩溃。如图所示,使用LordPE打开可执行文件,点击区段,在区段表上右击,点击编辑区段,修改物理大小并保存,再用OllyDbg打开,会提示同样的错误。


对抗这种反调试技术的最简单方法是用类似的编辑器手动修改PE头部。OllyDbg2.0和WinDbg不存在这种漏洞。
2.OutputDebugString漏洞
恶意代码常尝试利用OllyDbg1.1的格式化字符串漏洞,为OutputDebugString函数提供一个%s字符串的参数,让OllyDbg崩溃。因此,需要注意程序中可疑的OutputDebugString调用,例如OutputDebugString("%s%s%s%s%s%s%s%s%s")。如果执行了这个调用,OllyDbg将会崩溃。

总结
最后让我们总结一下提到的内容。腾讯2016游戏安全技术竞赛有一道题,大概意思就是给一个exe,要求编写一个Tencent2016D.dll,并导出多个接口函数CheckDebugX。X为1-100之间的数字,比如CheckDebug1,CheckDebug8,...,CheckDebug98。函数功能是检测自己是否处于被调试状态,是返回TRUE,否则返回FALSE。函数的原型都是typedef BOOL (WINAPI* Type_CheckDebug)();。编译好dll之后,放在Tencent2016D.exe的同目录,运行Tencent2016D.exe,点击检测按钮,正常运行时,函数接口输出为0,调试运行或者被附加运行时,接口输出1。我们把提到的知识综合一下完成这道题目。
解题的参考代码和题目相关信息:https://github.com/houjingyi233/test-debug/
参考资料:《恶意代码分析实战》第16章反调试技术、《逆向工程核心原理》第51章静态反调试技术&第52章动态反调试技术、freebuf技术分享:利用异常实现反调试、天枢战队官方博客、吾爱破解论坛、《windows anti-debugger reference》
————————————————
版权声明:本文为优快云博主「houjingyi233」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_32400847/article/details/52798050

Linux/Windows下的崩溃调试技术-Breakpad 在Windows系统中,当程序发生崩溃时,为了捕获和分析崩溃原因,通常可以使用dump文件,dump文件又可以分为:mini-dump 和 full-dump。 阅读详情

相关推荐

Windows系统高级调试技术指南

本文还有配套的精品资源,点击获取 简介:《Windows高级调试》书籍深入讲解了Windows环境下高效、精确的问题排查技术。涵盖从基础调试概念到高级内存分析、线程同步问题、驱动程序调试等领域,强调使用调试工具(如WinDbg、KD、CDB等)进行代码跟踪、性能分析和内存泄漏检测。书中还深入讨论了Windows内部工作机制、线程同步、内存管理等,并重点介绍了驱动程序调试和内...

weixin_30415591的博客 1360

反调试技术(一)--静态反调试

为了保护自己的程序不被破译等,很多软件使用了反调试技术来阻止逆向程序员对自己程序进行爆破,这同时也催生了另一种技术--反调试破解技术的出现。反调试技术分为静态反调试技术和动态反调试技术。相比来说静态反调试技术更容易实现,破解一般也只需要一次,我们这次就先谈一下静态反调试技术,以及对应的破解之道。 1.PEB结构中有几个重要的成员标示了进程是否处于被调试状态。(以下以32位为例) +0x0...

weixin_30871293的博客 350

深入探索WINDOWS内核调试技术

内核调试是分析和解决问题的一个过程,它涉及对操作系统内部运行机制的深入理解。与用户空间程序调试不同,内核调试通常需要更专业的工具和技巧,因为内核运行在硬件之上,直接管理和控制硬件资源。在Windows环境下,内核调试能够帮助开发者和系统管理员诊断和修复系统崩溃、蓝屏、驱动程序问题等。为了深入理解调试过程,我们需要对以下几个常用的Windows内核调试器有所了解:WinDbg:微软官方提供的调试工具,广泛应用于Windows系统调试,支持用户模式和内核模式调试。SoftICE。

weixin_42576804的博客 906

ZwSetInformationThread(反调试)

文章目录0x01 ZwSetInformationThread () 介绍 0x01 ZwSetInformationThread () 介绍 ​ ZwSetInformationThread 等同于 NtSetInformationThread,通过为线程设置 ThreadHideFromDebugger,可以禁止线程产生调试事件。函数原型如下: VOID DisableDebugEvent(VOID) { HINSTANCE hModule; ZW_SET_INFORMATION_TH

lwhaaaa的博客 1990

深入理解Windows高级调试技术:Pri Symstore

Pri Symstore 的主要设计目的是为了简化和自动化符号文件的管理过程。在早期,开发者和调试人员通常手动管理符号文件,这不仅耗时而且容易出错。符号文件通常分散在不同的位置,随着软件版本的更新,保持符号文件与程序版本的一致性变得非常复杂。Pri Symstore 的出现,使得符号文件的存储和分发变得有序和高效。在本章节中,我们已经学习了如何使用Pri Symstore来优化调试流程,并结合WinDbg提高调试效率和质量。下面将总结调试过程中的常见问题和故障排除的技巧。

weixin_35414484的博客 1161

HideFromDebugger

通过NtSetInformationThread来隐藏调试器,使程序无法调试

Unity debugger detected e0030 sentinel ldk protection system

工作的时候,遇到了一个很恶心人的事情,用Unity调试其C++的DLL时候,出现了下图这样的提示: 然后Unity的exe就莫名其妙退出了? 这样我就调试不了我的DLL了,这个问题折磨了我大概快一个月了,上谷歌上也查不到这个问题怎么解决,后来得知Sentinel LDK Protection System是Unity专门为在中国区下载Unity加的内容!! 估计是为了防止国人破解把。。。。。。。...

alexhu2010q的博客 7288

Redhawk解析PAD / IOPAD的实现方法

Redhawk解析IOPAD的实现方法1. IOPAD DEF的概念说明2. 生成 IOPAD GDS文件3. 生成 IOPAD DEF文件4. Redhawk解析过程中读取IOPAD DEF 本文主要简述【Redhawk解析IOPAD】的实现原理方法,具体脚本没有放在这里。 1. IOPAD DEF的概念说明 2. 生成 IOPAD GDS文件 注: getinfo2IODEF.tcl 是基于innovus获取IOPAD的摆放信息以及库信息。 run_cerate_iopadgds.csh 是基于c

kobayashiyou的博客 3154

Windows下反反调试技术汇总

Windows下反反调试技术汇总 Windows下反反调试技术汇总一、前言二、静态反调试技术2.1 进程状态检测2.2 调试环境检测三、动态反调试技术3.1 时钟检测3.2 异常处理3.3 0xCC探测3.4 硬件断点检测3.5 单步检测3.6自调试四、总结 一、前言 对于安全研究人员来说,调试过程中经常会碰到反调试技术,原因很简单:调试可以窥视程序的运行“秘密”,而程序作者想要通过反调试手段隐藏他们的“秘密”,普通程序需要防止核心代码被调试逆向,恶意代码需要隐藏自己的恶意行为防止被跟

qq138480084的博客 3661

【nnUNetv2进阶】三、nnUNetv2 自定义网络-发paper必会

对于很多朋友来说,找到nnunet的网络结构位置的代码可能比较困难,因为nnunet的网络结构从else:代码中没有网络结构的类名,那么使用的网络是哪个呢?打开预处理后的数据集中的nnUNetPlans.json文件,如下图,configurations里面有2d、3d_fullres等,这些表示nnunet的模型,下面的network_class_name指的就是训练使用的模型结构,一般都是PlainConvUNet。

AI爱好者的博客,分享计算机领域相关知识 8757

【2026最新】3分钟Windows极速部署OpenClaw,完整可复制命令+全链路避坑指南

很多新手在Windows环境部署OpenClaw时,总会遇到各种玄学问题:WSL环境配置失败、Docker镜像拉取超时、容器启动后无法访问、权限报错、数据丢失……网上大量教程要么是Linux环境的,要么是早已过时的旧版本配置,照着操作不仅没法3分钟部署,反而要花几小时踩坑填坑。

2601_94871597的博客 494

windows下常见的反调试技术汇总

因为调试器在工作时可能会对一些关键的 API 进行断点设置以跟踪程序的执行流程,一旦发现这些 API 被异常中断,就可以推断程序正在被调试。如果在调试状态下,程序的执行速度可能会变慢,导致时间间隔异常,从而判断出正在被调试。例如,故意引发一个特定的异常,观察程序的处理方式,如果与正常情况不符,则可能正在被调试。如果在调试状态下,调试器可能无法正确处理架构的切换,从而被检测到。这些反调试技术的目的是增加软件被破解和调试的难度,但也可能会对合法的软件分析和调试工作带来一定的挑战。

m0_57836225的博客 557

Android 10.0 SystemUI 屏蔽状态栏消息推送弹出的悬浮通知

在状态栏中,有一些通知类似 消息推送 弹出的 悬浮通知,弹出30秒之后会消失掉 要想屏蔽掉这些通知,首选知道这些通知是怎么产生的 通知类型 NotificationContentInflater.java 详细的介绍各种通知的类型 frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java /** * The defau

安卓兼职framework和app工程师的博客 1542

Windows反调试技术学习

元旦快乐!!!只是粗浅的两天的一点笔记和实践记录。加解密一书每一章应该都要来回啃上几遍才能理解,书中好多还是不懂的,只能先学能理解的,后面再继续深入了。下一个该学异常处理了,学完就把这一次笔记的关于异常的反调试补上。ε=(´ο`*))),元旦快乐!

Sciurdae的博客 2138

硬件工程师学习-硬件的单元测试(UT)(1)电源与时钟的测试

我们在设计的时候,通过控制电源的上电使能管脚或者通过MOSFET、或者专用的电源上电控制芯片,控制不同电源的上电时序,以确保每个芯片的电源上电时序是满足芯片的要求的。我们在测试的时候,需要对相关的要求一一测试,以确认每个上电时序满足要求的。在维基百科中,相位噪声的定义是“时域不稳定性(抖动)导致波形在相位上发生快速、短期、随机的波动,这种波动在频域中的表现即为相位噪声”。单元测试,一般是指基于开发人员自行开展的功能测试及各个功能单元的单元测试,是硬件信号级的测试,分为基本测试和信号完整性测试。

qq_43416206的博客 4034

​​​​手把手教你玩转RT-Thread操作系统专栏--​​​​第5章 设备驱动开发:RT-Thread的设备框架​​​​5.4 I2C/SPI驱动:如何驱动外部传感器(以MPU6050为例)​

通过本节学习,掌握了基于RT-Thread设备模型的I2C/SPI驱动开发流程,包括硬件初始化、寄存器读写、设备注册及应用程序调用。I2C(Inter-Integrated Circuit)和SPI(Serial Peripheral Interface)是嵌入式系统中最常用的两种串行总线协议,广泛用于连接传感器、存储器等外设。函数通过I2C协议读取外设寄存器的数据(如MPU6050的加速度寄存器)。SPI读取需先拉低片选(CS),发送寄存器地址,再读取数据,最后拉高CS。查找I2C设备,并调用。

xiaoheshang_123的博客 125

Windows反调试技术参考

http://blog.chinaunix.net/uid-29068508-id-3845270.html 本文主要面向逆向工程师和恶意程序分析人员,分类并列举了一些基于Windows操作系统的反调试技术。 反调试技术给程序提供了检测自身是否运行在调试器下的方法,所以经常被商业性质的可执行文件保护器、加壳程序,甚至恶意程序用来保护或者减缓逆向工程的过程。在本文中,我们假设所有的程序都是在Rin...

子曰小玖的博客 1491

【雷达】基于matlab合成孔径雷达干涉测量(InSAR)时序分析【含Matlab源码 9037期】

合成孔径雷达干涉测量(InSAR)时序分析 完整代码,包运行;可提供运行操作视频!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 159

windows中的调试

windows中的调试1.事后调试有两个最基本的目标: (1) 发现程序是在哪里崩溃的 (2) 找出导致程序崩溃的原因2.按优先选择顺序排列的调试技术: (1) 使用调试版本进行本地调试 (2) 使用带有调试符号的发布版进行本地调试 (3) 使用调试版本进行远程调试 (4) 使用带有调试符号的发布版本进行远程调试 (5) 使用 Dr.Watson 日志文件进行事后调试 (6) 使用崩溃对话框的信息

作战在奇不在正,带兵在正不在奇 3413

基于机器学习和深度学习的新能源功率预测研究综述--学术论文模式--个人梳理

本文是博主根据前人的研究成果总结的机器学习和深度学习等理论方法在新能源功率预测领域的相关研究内容要点,按照学术科技论文写作的模式进行的呈现,包括文章引用等也都做了详细的批注,花了很多时间和心血,一方面是对自己学习成果的一些总结,另一方面也希望对大家有用(尽量不要直接copy哦,转载请标注,如有不当请评论或私信指出哦可再生能源的间歇性给新型电力系统的运行带来了巨大挑战,准确地预测新能源出力对于电网的安全可靠运行尤为重要。

干吧滴! 6320

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

第一章 Windows 2000对调试技术的支持

第一章  Windows 2000对调试技术的支持翻译:Kendiv更新:Friday, January 14, 2005 内核调试器的命令尽管调试器的命令已经注意了易记性,但有时总是难以回忆起它们。因此,我把它们都整理到了附录A中。表A-1是其快速参考。这个表是调试器的help信息(使用?命令)的整理版。命令所需参数的类型汇总在表A-2。 前面提到过,内核调试器执行扩

GT2L的专栏 1524

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

调试windows service的OnStart事件及调试service的一些方法汇总

关于调试windows service, 其实这是一个老生常谈的问题了.    通常的处理办法是, 在service运行后, 在调试器中选择attach to process.    然而这种做法也有一定的局限性, 例如在service启动时的OnStart事件中的代码, 基本上很难调试. 往往当attach到我们的service的时候, 这部分代码已经执行过了. 于是, 有人提出, 可以

jiangxinyu的专栏 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Speakeasy安全研究:仿真环境中的反调试与反仿真技术对抗

Speakeasy作为一款强大的Windows恶意代码仿真框架,通过模拟Windows运行时环境而非完整虚拟机,为安全研究人员提供了执行二进制文件、驱动程序和shellcode的能力。在恶意代码分析领域,反调试与反仿真技术是恶意软件逃避检测的重要手段,而Speakeasy通过创新的技术手段有效应对这些挑战,本文将深入探讨仿真环境中的技术对抗策略。 ## 反调试与反仿真技术的常见手段 🕵️‍♂️

gitblog_00063的博客 380
上一篇: 过TP保护
下一篇: 逆向分析CRACKME 第一章 Acid burn
nb009网
博客等级 码龄18年 10粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值