首页 > 其他分享 >test

test

时间:2024-10-18 13:22:12浏览次数:4  
标签:TLS 检测 进程 test include 调试 调试器

反调试

  前文写过,花指令通常干扰静态分析,而反调试与之相反,主要为了干扰动态调试。

1.反调试简介

  反调试是一种用于阻碍程序动态调试的技术,首先大致说明一下反调试的工作原理。

  在操作系统内部提供了一些API,用于调试器调试。当调试器调用这些API时系统就会在被调试的进程内存中留下与调试器相关的信息。一部分信息是可以被抹除的,也有一部分信息是难以抹除的。

  当调试器附加到目标程序后,用户的很多行为将优先被调试器捕捉和处理。其中大部分是通过异常捕获通信的,包括断点的本质就是异常。如果调试器遇到不想处理的信息,一种方式是忽略,另一种方式是交给操作系统处理。

  那么目前为止,程序就有两种方式检测自己是否被调试:

  • 检测内存中是否有调试器的信息。
  • 通过特定的指令或触发特定异常,检测返回结果。

  通常来说,存在反调试的程序,当检测到自身处于调试状态时,就会控制程序绕过关键代码,防止关键代码被调试,或者干脆直接退出程序。



2.API反调试

  Windows内部提供了一些用于检测调试器的API

  其中一个APIIsDebuggerPresent,原型为:

BOOL IsDebuggerPresent();

  返回值为1表示当前进程被调试的状态,反之为0.

  另一个常用的APICheckRemoteDebuggerPresent,原型为:

BOOL CheckRemoteDebuggerPresent(HANDLE hProcess, PBOOL pbDebuggerPresent);

  返回值为1表示当前进程被调试的状态,反之为0.


3.PEB反调试

  当程序处于3环(低权限)时, FS:[0] 寄存器指向TEB(Thread Environment Block),即线程环境块结构体,TEB向后偏移0x30字节的位置保存的是PEB(Process Environment Block ),即进程环境块的结构体地址。PEB中的部分成员是与调息相关的成员,当调试器通过 Windows提供的API调试目标程序时,Windows会将一部分调试信息写人这个结构体中。

kd>dt_TEB
nt! _TEB
	...
	+0x030 ProcessEnvironmentBlock :Ptr32_PEB
	...
kd>dt_TEB
	...
	+0x002 BeingDebugged	:UChar	
	...
	+Ox018 ProcessHeap	:Ptr32 Void	
	...
	+0x068 NtGlobalF1ag	:Uint4B
    ...

  本处只介绍这两个结构体中几个重要的成员,若是想在实际调试时查看其他成员的具体内容,其中一种方法是使用WinDbg调试内核。

  在PEB结构体中中,BeingDebuggedProcessHeapNtGlobalFlag是与调试信息相关的三个重要成员。

  • BeingDebugged:当进程处于被调试状态时,值为1,否则为0。
  • ProcessHeap:指向Heap结构体,偏移0xC处为Flags成员,偏移0x10处为ForceFlags成员。通常情况下,Flags的值为2.ForceFlags的值为0,当进程被调试时会发生改变
  • NGlobalFlag:占四个字节,默认值为0。当进程处于被调试状态时,第一个字节会被置为0x70。

  通过FS.Base能够定位到TEB,再通过TEB+0x30能够定位PEB。通过在内存中检测或修改相关成员的值,便可达到反试、反反调试的效果。


4.TLS反调试

  TLS (Thread Local Storage),即线程局部存储是Windows提供的一种处理机制,每进行一次线程切换,便会调用一次TLS回调。它本意是想给每个线程都提供访问全局变量的机会。例如,需要统计当前程序进行了多少次线程切换,但并不想让其他线程访问到这个计数变量,使用TLS进行计数,便能够解决这个问题,一个程序能设置多个TLS.
  由于进程在启动时至少需要创建一个线程来运行,因此在调用main函数前就会调用一次 TLS 回调。利用这个特点,在TLS回调中写入与反调试相关的代码,便可悄无声息地令调试器失效。

#include <windows.h>
#include <iostream>

// TLS回调函数定义
void NTAPI TLS_Callback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    if (Reason == DLL_PROCESS_ATTACH) {
        // 检测是否存在调试器
        if (IsDebuggerPresent()) {
            std::cout<<"Debugger detected!\n";
            ExitProcess(1);  // 如果检测到调试器则退出
        } else {
            std::cout<<"No debugger detected.\n";
        }
    }
}

// 声明TLS回调函数
#ifdef _MSC_VER
#pragma const_seg(".CRT$XLB")
EXTERN_C const PIMAGE_TLS_CALLBACK pTLS_CALLBACK = TLS_Callback;
#pragma const_seg()
#else
__attribute__((section(".CRT$XLB"))) PIMAGE_TLS_CALLBACK pTLS_CALLBACK = TLS_Callback;
#endif


int main() {
    std::cout<<"Program started.\n";
    return 0;
}
  • TLS_Callback:TLS的回调函数,每当一个新线程创建时,或者进程加载时,它都会被调用。

  • IsDebuggerPresent:这是Windows提供的API,用来检查当前进程是否被调试。

  • #pragma const_seg:用于指定TLS回调函数的位置,它被放在.CRT$XLB节中,这样Windows在加载时会自动执行。


5.进程名反调试

  当使用调试器调试程序时,调试器是一个独立的进程,运行在内存中。若在程序执行到某一反调试方法。阶段时遍历当前系统中的进程列表,检测是否存在与调试器相关的进程名,也不失为一种可行的方法。
  示例代码如下:

#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
#include <string>

// 检查是否存在指定的调试器进程
bool IsDebuggerProcessRunning() {
    const char* debuggerNames[] = { "ollydbg.exe", "x64dbg.exe", "ida.exe", "windbg.exe" };

    // 创建进程快照
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return false;
    }

    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // 遍历进程列表
    if (Process32First(hSnapshot, &pe32)) {
        do {
            // 遍历已知的调试器名称
            for (const auto& debuggerName : debuggerNames) {
                // 将进程名转换为小写以进行匹配
                std::string processName = pe32.szExeFile;
                for (auto& c : processName) c = tolower(c);

                if (processName == debuggerName) {
                    CloseHandle(hSnapshot);
                    return true;  // 找到匹配的调试器进程
                }
            }
        } while (Process32Next(hSnapshot, &pe32));
    }

    CloseHandle(hSnapshot);
    return false;  // 未找到调试器进程
}

int main() {
    if (IsDebuggerProcessRunning()) {
        std::cout << "Debugger process detected! Exiting...\n";
        ExitProcess(1);  // 检测到调试器,退出程序
    } else {
        std::cout << "No debugger detected.\n";
    }

    // 正常程序逻辑
    std::cout << "Program is running.\n";
    return 0;
}

  • 调试器进程名列表:在debuggerNames数组中列出了常见的调试器进程名(如ollydbg.exe, x64dbg.exe等)。可以根据需要添加更多的调试器进程名。
  • CreateToolhelp32Snapshot:这是一个Windows API,用于创建系统中所有进程的快照,以便遍历这些进程。
  • Process32First 和 Process32Next:这些函数用于遍历进程快照中的每一个进程。
  • tolower:为了匹配时忽略大小写,将进程名全部转换为小写进行比较。
  • ExitProcess:如果发现调试器进程,程序直接退出。

  代码在执行时遍历系统中所有正在运行的进程,并检查是否有已知的调试器进程在运行。如果发现某个调试器进程(如ollydbg.exe),则程序会直接退出,否则会继续运行。这种方法可以用来检测外部调试器是否正在运行,但它不是百分之百可靠,因为高级调试器可能会通过修改进程名或隐藏自己来规避检测。


6.窗口名反调试

  检测已打开窗口的窗口也是一种较为常用的反调试手段。示例代码如下

#include <windows.h>
#include <iostream>

// 定义要检查的调试器窗口名称
const char* debuggerWindowNames[] = {
    "OllyDbg",  "x64dbg", "IDA", "WinDbg"
};

// 枚举系统中所有窗口,查找是否有调试器窗口存在
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
    char windowTitle[256];
    GetWindowTextA(hwnd, windowTitle, sizeof(windowTitle));

    // 遍历已知的调试器窗口名称
    for (const auto& debuggerWindowName : debuggerWindowNames) {
        if (strstr(windowTitle, debuggerWindowName)) {
            std::cout << "Debugger window detected: " << windowTitle << "\n";
            return FALSE;  // 找到调试器窗口,停止枚举
        }
    }

    return TRUE;  // 继续枚举其他窗口
}

// 检查是否存在调试器窗口
bool IsDebuggerWindowOpen() {
    return !EnumWindows(EnumWindowsProc, 0);
}

int main() {
    if (IsDebuggerWindowOpen()) {
        std::cout << "Debugger window detected! Exiting...\n";
        ExitProcess(1);  // 检测到调试器窗口,退出程序
    } else {
        std::cout << "No debugger window detected.\n";
    }

    std::cout << "Program is running.\n";
    return 0;
}

  • EnumWindows:这是Windows API,允许遍历系统中所有的顶层窗口。每找到一个窗口,就会调用回调函数EnumWindowsProc
  • EnumWindowsProc:这是枚举窗口的回调函数。通过GetWindowTextA函数获取窗口标题,然后使用strstr来判断窗口名是否包含已知调试器窗口名称。
  • ExitProcess:如果检测到调试器窗口,程序直接退出。

  有些调试器允许用户自定义窗口名,因此该方法并非完全可靠。高级调试器同样可能会通过修改窗口名或隐藏窗口来规避检测。例如,OD在刚启动时的窗口名为“OllyDbg - [CPU]”,而加载程序后会有所改变,但前几个字节仍然为“OllyDbg”,对于这类窗口,规定字符串的检测长度往往能取得不错的效果。


7.时间戳反调试

  正常情况下,CPU的执行速度是非常快的,每秒能执行数条指令,每条指令的执行时间非常短。而在调试状态下,由于软件中断、单步调试等因素,可能会造成指令间的执行间隔远大于正常时间,分别记录两条指令执行前后的时间戳,利用时间戳的差值便能够判断当前进程是否处于被调试状态。
  时间戳反调试有三种常用手段。

  • rdtsc: 汇编指令,能够以纳秒级记录系统启动以来的时间戳,返回值保存在EDX:EAX(高位保存到EDX,低位保存到EAX)中。
  • QueryPerformanceCounter:能够以微秒为单位高精度计时。
  • GetTickCount:返回值为自系统启动以来所经过的毫秒数。

  例如:

#include <windows.h>
#include <iostream>

int main() {
    DWORD time1 = GetTickCount();
    int result, a = 1, b = 2;
    __asm(
            "movl %1, %%ebx\n\t"
            "addl %%ebx, %0"
            : "=r" (result)
            : "r" (b), "0" (a)
            );
    DWORD time2 = GetTickCount();
    if(time2-time1>0x10)
        ExitProcess(0);
    std::cout << "Program started.\n";
    std::cout << result;
    return 0;
}

  程序执行完内联汇编后,会计算前后时间差。如果时间差超过16毫秒(0x10),程序会调用ExitProcess强制退出。这可以用于反调试:调试器往往会减缓程序的执行速度,因此通过这种时间检测方法可以检测到调试行为。

8.硬件断点检测反调试

  硬件断点是调试器常用的手段之一,它通过CPU的调试寄存器(如DR0-DR7)设置断点。可以通过检查这些寄存器是否有断点设置来检测调试器。使用GetThreadContext API来获取当前线程的上下文,检查调试寄存器(DR0DR3)是否设置断点。

CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ctx);
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) {
    ExitProcess(0);
}

9.异常处理反调试

  调试器通常会捕获异常,反调试技术可以通过故意引发异常并检查其处理方式来检测调试器。通过引发INT 3指令(断点中断)或其他异常(如除零异常),查看是否有异常处理程序被插入,然后使用SetUnhandledExceptionFilter设置自定义的异常处理程序。

__try {
    __asm { int 3 }  // 触发断点异常
} __except (EXCEPTION_EXECUTE_HANDLER) {
    // 如果捕获了异常,则说明没有调试器
    std::cout << "No debugger detected." << std::endl;
}

10.单步检测反调试

  单步检测反调试是一种通过检测CPU的单步执行(Trap Flag, TF)来判断是否有调试器介入的技术。当调试器单步执行目标程序时,CPU的TF标志会被设置为1,这会导致在每条指令执行完后触发一个调试中断。因此,程序可以通过监控TF标志的变化来检测调试行为。

  Trap Flag(TF):当EFLAGS寄存器中的TF标志被置为1时,CPU会进入单步模式,每执行一条指令后都会产生一个调试中断(INT 1)。通过检查和控制TF标志,可以判断程序是否被调试器单步执行。如果调试器处于单步调试模式,TF标志会被置1,程序可以利用这一特性检测调试行为。

例如:

#include <iostream>
#include <windows.h>

int main() {
    // 保存原来的EFLAGS寄存器值
    unsigned int eflags;

    __asm {
        pushfd                   // 将EFLAGS压入栈中
        pop eax                  // 将栈顶的EFLAGS值弹出到EAX寄存器
        mov eflags, eax          // 保存EFLAGS寄存器到eflags变量
        or eax, 0x100            // 设置TF(Trap Flag)位为1,启用单步调试模式
        push eax                 // 将修改后的EFLAGS值压回栈
        popfd                    // 恢复EFLAGS寄存器,使Trap Flag生效
    }

    // 执行单步调试后检测
    __asm {
        nop                      // 一个空操作,用于单步执行检测
        pushfd                   // 将当前的EFLAGS寄存器值压入栈
        pop eax                  // 弹出EFLAGS到EAX
        mov eflags, eax          // 保存当前的EFLAGS值
    }

    // 检测Trap Flag是否被清除(如果有调试器在调试,该标志可能被复位)
    if (eflags & 0x100) {
        std::cout << "No debugger detected (TF still set)." << std::endl;
    } else {
        std::cout << "Debugger detected (TF cleared)." << std::endl;
        ExitProcess(0);           // 检测到调试器,退出程序
    }

    std::cout << "Program continues running..." << std::endl;
    return 0;
}

  如果程序在执行nop指令后,TF标志依然保持为1,则说明程序未被调试,输出“No debugger detected (TF still set)”。如果程序发现TF标志被清除(调试器可能重置了该标志),则输出“Debugger detected (TF cleared)”,并终止程序执行。

标签:TLS,检测,进程,test,include,调试,调试器
From: https://www.cnblogs.com/PaperPlaneFly/p/18474056

相关文章

  • 计量经济学(十)——正态性检验(Normality Test)
    正态性检验(NormalityTest)是一种用于判断数据是否服从正态分布的重要统计方法,广泛应用于时间序列分析、回归分析等模型的构建与诊断中。许多统计模型,如线性回归、VAR模型等,要求残差或误差项服从正态分布。这一假设是保证模型估计有效性和推断准确性的关键条件,误差项的正态性有助......
  • python: unittest
     '''生成测试报告https://www.lambdatest.com/blog/generating-xml-and-html-report-in-pyunit-for-test-automation/unittesthttps://codedec.com/tutorials/how-to-generate-html-report-for-pytest-execution/https://docs.pytest.org/en/7.1.x/_modules/......
  • The 2024 CCPC National Invitational Contest (Northeast) ADEJ
    The2024CCPCNationalInvitationalContest(Northeast)ADEJA.PaperWatering思路:有两种类型的操作,对一个数开根号或平方。平方没有什么问题,开根号由于是向下取整再平方就会产生不一样的数。那么做法也很简单了。对于一个数\(x\),\(k\)步,首先它能平方往后变\(k\)步,往前能......
  • CMSC Manual testing Completeness SNU Score
    Homework#3Due:Friday,October18that4:00pmCSTTableofContentsHomework#3GettingstartedManualtestingCompletenessSNUScoreCodeQualitySubmissionThepurposeofthisassignmentistogiveyouexperiencewithconditionals,lists,andloops.......
  • FSFOtest
    测试场景1(模拟NHSITE故障,OB在NH)【推荐】--masterobserver维护DRSITE:prdb19DGMGRL>showobserverConfiguration-dg_configPrimary:orcl2dgActiveTarget:orcl0dgObserver"prdb19"-MasterHostName:prdb19L......
  • TestNG学习
    TestNG学习笔记TestNG学习笔记常用注解TestNG最常使用的是它的注解。注解描述属性例子@Test表示方法是一个基于TestNG的测试用例enabled,priority,invocationCount,dependsOnMethods@Test(enabled=false)例子@Test(invocationCount=3,priority=1)publ......
  • CtsBiometricsTestCases 测试fail,解决方法。
    平台:高通C6490,android13测试结果:arm64-v8a CtsBiometricsTestCasesTestResultDetailsandroid.server.biometrics.BiometricActivityTests#testBiometricOrCredential_credentialButtonInvoked_biometricNotEnrolledfailjava.lang.AssertionError:AuthSessionState......
  • CtsPermissionTestCases之android.permission.cts.DebuggableTest#testNoDebuggable
    平台:高通C6490,android13测试结果:android.permission.cts.DebuggableTest#testNoDebuggablefailjunit.framework.AssertionFailedError:Packagesmarkeddebuggable:[com.skyworthdigital.autotest.all] 这个问题是该测试对应的app是否有debugable的标签。解决......
  • HIAST Collegiate Programming Contest 2024(非完全题解)
    C题HZY做的,等他补题解//#pragmaGCCoptimize("O3,unroll-loops")//#pragmaGCCtarget("avx2,bmi,bmi2,lzcnt,popcnt")////如果在不支持avx2的平台上将avx2换成avx或SSE之一#include<bits/stdc++.h>usingnamespacestd;#definexfirst#defineysecon......
  • python: pytest in thonny IDE
     classStudentData:""""""def__init__(self):self.__data=Nonedefconnect(self,datafile):withopen(datafile)asjsonfile:self.__data=json.load(jsonfile)defgetda......