Windows平台反调试技术学习

前言

前俩天的学习记录Windows上面的反调试学习,主要是参考《恶意代码实战分析》和《加密与解密》里面的,给每个小技术都写了程序示例,自己编译反调试了一遍。对于加解密一书是还有很多不理解的地方的,目前只能记录到这了,后面继续慢慢学吧,欢迎一起交流讨论,谢谢师傅。

Windows反调试

通过 API 调用

IsDebuggerPresent

IsDebuggerPresent函数通过获取进程环境块(PEB)中的BeingDebugged标志来检测进程是否处于调试状态。

其实现代码:

1

2

3

4

BOOL WINAPI IsDebuggerPresent(VOID)

{

    return NtCurrentPeb() -> BeingDebugged;

}

BeingDebugged是PEB中的一个标志。每个运行中的进程都有一个PEB结构,其0x02偏移处就是BeingDebugged标志,如果程序处于调试状态,该标志的值会被设置为非零值。相关Windows API就是通过访问该值来进行反调试操作。

如何访问PEB?PEB的地址储存在另一个名为线程环境块(TEB)中。

Windows在调入进程、创建线程时,操作系统会为每个线程分配TEB,而且FS段寄存器总是被设置成使得FS:[0]指向当前线程的TEB数据。而TEB结构中的0x30偏移处正是PEB的地址。

Windows一般通过TEB间接获取PEB的地址:

mov eax,fs:[18h]		//获取当前线程的TEB地址
mov eax,[eax+30h]		//在TEB偏移30h处获得PEB地址

TIB+18h处为Self。它是TIB的自身指针,指向TEB的首地址。因此也可以省略它直接使用fs:[30h]得到自己进程的PEB。

如何过掉IsDebuggerPresent?

示例程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#include <stdio.h>

#include <windows.h>

BOOL CheckDebugger()

{

    // 直接调用IsDebuggerPresent函数,不需要定义额外的指针

    return IsDebuggerPresent();

}

int main(int argc, char *argv[])

{

    if (CheckDebugger())

    {

        printf("[-] 进程正在被调试 \n");

    }

    else

    {

        printf("[+] 进程没有被调试 \n");

    }

    system("pause");

    return 0;

}

第一种方法是IDA中在修改恶意代码(例如jz跳转将其改为jnz,或者修改cmp)

​​​​​​​

第二种使用xdbg,在数据窗口使用“Ctrl+G”搜索fs:[30]+2

这里的01就是BeingDebugged标志,将其用“Ctrl+E”修改成00,运行程序,程序输出:

CheckRemoteDebuggerPresent(NtQueryInformationProcess)

CheckRemoteDebuggerPresent不仅可以探测进程自身是否被调试,也可以探测系统其他进程是否被调试。函数接收两个参数进程句柄和一个指向布尔值的指针。如果指定的进程正在被调试,则函数会把指向布尔值的指针设为 TRUE,否则设为FALSE。

函数原型:

1

2

3

4

BOOL CheckRemoteDebuggerPresent{

    HANDLE hProcess,

    PBOOL pbDebuggerPresent

};

程序示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

#include <stdio.h>

#include <windows.h>

// 定义指针

typedef BOOL(WINAPI *CHECK_REMOTE_DEBUG_PROCESS)(HANDLEPBOOL);

BOOL CheckDebugger()

{

    BOOL bDebug = FALSE;

    CHECK_REMOTE_DEBUG_PROCESS CheckRemoteDebuggerPresent;

    HINSTANCE hModule = GetModuleHandle("kernel32");

    CheckRemoteDebuggerPresent = (CHECK_REMOTE_DEBUG_PROCESS)GetProcAddress(hModule, "CheckRemoteDebuggerPresent");

    HANDLE hProcess = GetCurrentProcess();

    CheckRemoteDebuggerPresent(hProcess, &bDebug);

    return bDebug;

}

int main(int argc, char *argv[])

{

    if (CheckDebugger())

    {

        printf("[-] 进程正在被调试 \n");

    }

    else

    {

        printf("[+] 进程没有被调试 \n");

    }

    system("pause");

    return 0;

}

在《恶意代码分析实战》一书中提到该函数也检测了BeingDebugged标志,而在《加密与解密》中说该函数并没有使用BeingDebugged标志,按照过IsDebuggerPresent的方式再试一次,发现CheckRemoteDebuggerPresent的确没有用到BeingDebugged标志。

 

image-20231231134736373

将BeingDebugged标志位设为00后,程序依旧处于反调试状态。

那么CheckRemoteDebuggerPresent是通过什么来进行反调试的?

在xdbg中Ctrl+G搜索CheckRemoteDebuggerPresent,可以找到该函数的汇编代码

75D131B0 | 8BFF                     | mov edi,edi                             |
75D131B2 | 55                       | push ebp                                |
75D131B3 | 8BEC                     | mov ebp,esp                             |
75D131B5 | 51                       | push ecx                                | ecx:EntryPoint
75D131B6 | 837D 08 00               | cmp dword ptr ss:[ebp+8],0              |
75D131BA | 56                       | push esi                                | esi:EntryPoint
75D131BB | 74 36                    | je kernelbase.75D131F3                  |
75D131BD | 8B75 0C                  | mov esi,dword ptr ss:[ebp+C]            | esi:EntryPoint
75D131C0 | 85F6                     | test esi,esi                            | esi:EntryPoint
75D131C2 | 74 2F                    | je kernelbase.75D131F3                  |
75D131C4 | 6A 00                    | push 0                                  |
75D131C6 | 6A 04                    | push 4                                  |
75D131C8 | 8D45 FC                  | lea eax,dword ptr ss:[ebp-4]            | [ss:[ebp-04]]:BaseThreadInitThunk
75D131CB | 50                       | push eax                                |
75D131CC | 6A 07                    | push 7                                  |
75D131CE | FF75 08                  | push dword ptr ss:[ebp+8]               |
75D131D1 | FF15 F0A2D375            | call dword ptr ds:[<NtQueryInformationP |
75D131D7 | 85C0                     | test eax,eax                            |
75D131D9 | 79 09                    | jns kernelbase.75D131E4                 |
75D131DB | 8BC8                     | mov ecx,eax                             | ecx:EntryPoint
75D131DD | E8 DEF1F4FF              | call kernelbase.75C623C0                |
75D131E2 | EB 17                    | jmp kernelbase.75D131FB                 |
75D131E4 | 33C0                     | xor eax,eax                             |
75D131E6 | 3945 FC                  | cmp dword ptr ss:[ebp-4],eax            | [dword ptr ss:[ebp-04]]:BaseThreadInitThunk
75D131E9 | 0F95C0                   | setne al                                |
75D131EC | 8906                     | mov dword ptr ds:[esi],eax              | dword ptr ds:[esi]:EntryPoint
75D131EE | 33C0                     | xor eax,eax                             |
75D131F0 | 40                       | inc eax                                 |
75D131F1 | EB 0A                    | jmp kernelbase.75D131FD                 |
75D131F3 | 6A 57                    | push 57                                 |
75D131F5 | FF15 C8A0D375            | call dword ptr ds:[<RtlSetLastWin32Erro |
75D131FB | 33C0                     | xor eax,eax                             |
75D131FD | 5E                       | pop esi                                 | esi:EntryPoint
75D131FE | C9                       | leave                                   |
75D131FF | C2 0800                  | ret 8                                   |

其中唯一关键处就是在17行调用了NtQueryInformationProcess函数。

该函数原型:

1

2

3

4

5

6

7

__kernel_entry NTSTATUS NtQueryInformationProcess(

  [in]            HANDLE           ProcessHandle,

  [in]            PROCESSINFOCLASS ProcessInformationClass,

  [out]           PVOID            ProcessInformation,

  [in]            ULONG            ProcessInformationLength,

  [out, optional] PULONG           ReturnLength

);

该函数会根据不同的 ProcessInformationClass 查询有关一个进程对象的信息,在文档中列举了一些情况:

ValueMeaning
ProcessInformationClass 0搜索指向 PEB 结构的指示器,该结构可用于确定指定进程是否正在调试,以及系统用于标识指定进程的唯一值。使用CheckRemoteDebuggerPresent和GetProcessId 函数获取此信息。
ProcessDebugPort 7检索DWORD_PTR值,该值是进程的调试器的端口号。非零值表示进程正在环3调试器的控制下运行。使用CheckRemoteDebuggerPresent或IsDebuggerPresent函数。
ProcessWow64Information 26确定进程是否在 WOW64 环境中运行(WOW64 是允许基于 Win32 的应用程序在 64 位 Windows 上运行的 x86 模拟器)。使用IsWow64Process2函数获取此信息。
ProcessImageFileName 27搜索包含进程图像文件名称的 **UNICODE_STRING值。**使用QueryFullProcessImageName或GetProcessImageFileName函数获取此信息。
ProcessBreakOnTermination 29搜索ULONG值,该值指示进程是否被视为关键进程。注意 从 Windows XP SP3 开始可以使用该值。从 Windows 8.1 开始,应改用IsProcessCritical 。
**ProcessTelemetryIdInformation **64检索包含有关进程的元数据的**PROCESS_TELEMETRY_ID_INFORMATION_TYPE值。**
ProcessSubsystemInformation 75检索指示进程子系统类型的**SUBSYSTEM_INFORMATION_TYPE值。**ProcessInformation参数指向的蜡烛图应该足够大以容纳单个SUBSYSTEM_INFORMATION_TYPE枚举。

回到上述汇编,正是查询了7号信息ProcessDebugPort

image-20231231141616259

所以说,CheckRemoteDebuggerPresent实际上是调用了NtQueryInformationProcess函数,查询了某个进程的ProcessDebugPort,这个值是系统用来与调试器通信的端口句柄。

如何过掉CheckRemoteDebuggerPresent?

在IDA中和IsDebuggerPresent都可以采取修改恶意代码的形式,使用xdbg的话,将传给NtQueryInformationProcess的参数7修改掉就可以了。

 在本例中修改了参数7可以过掉(可能只是个例,因为修改成0,也就是获取进程的基本信息,应该不一定会成功)。还是应该要修改后面的eax寄存器中的值。将01修改成00。

OutputDebugString

OutPutDebugString函数的作用是在调试器中显示一个字符串。

函数原型:

1

2

3

void OutputDebugStringW(

  [in, optional] LPCWSTR lpOutputString

);

如何用它来检测调试状态?

可以配合SetLastError和 GetLastError 函数,这俩个函数前者将当前的错误码设置成一个任意值,后者是获取当前的错误码。如果进程没有被调试器附加,那么调用OutPutDebugString函数就会失败,错误码会被重新设置,因此再使用GetLastError函数获取的错误码应该就不是我们设置的值。若进程被调试器附加并调用了OutPutDebugString函数,那么该函数会调用成功,GetLastError函数获取的也就是我们设置的值。

程序示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

#include <windows.h>

#include <stdio.h>

void CheckDebugger()

{

    DWORD errorValue = 12345;

    SetLastError(errorValue);

    OutputDebugString("Test for Debugger");

    // 检查GetLastError返回的值是否改变

    if (GetLastError() == errorValue) {

        printf("[-] 检测到调试器\n");

        ExitProcess(1); // 结束进程

    else {

        printf("[+] 未检测到调试器\n");

    }

}

int main()

{

    CheckDebugger();

    // 这里可以添加其他代码

    return 0;

}

上述程序是由《恶意代码实战分析》的代码清单扩展来的,但实际发现,没有附加调试器的情况下,也会报**“[-] 检测到调试器”**。原因应该是因为:程序没有调用OutPutDebugString函数也会改变错误码。

ZwSetInformationThread(ThreadHideFromDebugger)

一种调试器攻击。

ZwSetInformationThread函数,用于设置线程的优先级。示例如下:

1

2

3

4

5

6

NTSYSAPI NTSTATUS ZwSetInformationThread(

  [in] HANDLE          ThreadHandle,

  [in] THREADINFOCLASS ThreadInformationClass,

  [in] PVOID           ThreadInformation,

  [in] ULONG           ThreadInformationLength

);

这个函数可以设置一个与线程相关的信息。查看ThreadInformationClass列表:

 

 

可以看到ThreadHideFromDebugger,关于它的定义:
这个信息类只能被设置。它禁用了线程的调试事件生成。这个信息类不需要数据,因此 ThreadInformation 可以是一个空指针。ThreadInformationLength 应该是零。

通过为线程设置ThreadHideFromDebugger,可以禁止某个线程产生调试事件。

测试示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

#include <windows.h>

// #include <winternl.h> // 包含 NTSTATUS

#include <stdio.h>

// 确保 NTSTATUS 已定义。如果没有,手动定义它。

#ifndef NTSTATUS

typedef LONG NTSTATUS;

#endif

// 定义 ZwSetInformationThread 函数类型

typedef NTSTATUS (WINAPI *ZW_SET_INFORMATION_THREAD)(HANDLEDWORDPVOIDULONG);

#define ThreadHideFromDebugger 0x11 // 17 in decimal

// 函数来禁用调试事件

VOID DisableDebugEvent(VOID)

{

    HMODULE hModule;

    ZW_SET_INFORMATION_THREAD ZwSetInformationThread;

    // 获取 ntdll.dll 模块的句柄

    hModule = GetModuleHandleA("Ntdll.dll");

    if (hModule == NULL) {

        printf("无法获取 ntdll.dll 的句柄.\n");

        return;

    }

    // 获取 ZwSetInformationThread 函数的地址

    ZwSetInformationThread = (ZW_SET_INFORMATION_THREAD)GetProcAddress(hModule, "ZwSetInformationThread");

    if (ZwSetInformationThread == NULL) {

        printf("无法获取 ZwSetInformationThread 函数的地址.\n");

        return;

    }

    // 调用函数尝试隐藏当前线程

    NTSTATUS status = ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);

    if (status != 0) {

        printf("调用 ZwSetInformationThread 失败,状态码: 0x%X\n", status);

    else {

        printf("当前线程已尝试隐藏自调试器.\n");

    }

}

int main()

{

    printf("测试程序开始...\n");

    // 尝试禁用调试事件

    DisableDebugEvent();

    printf(".....");

    return 0;

}

我用单步测试的,在打印“当前线程已尝试隐藏自调试器.”之后延迟一会程序就会退出调试状态。

**关于怎么干掉这个反调试。(待学)**[原创]调试陷阱ThreadHideFromDebugger的另一种对抗方法-软件逆向-看雪-安全社区|安全招聘|kanxue.com

手动检测数据结构

在一些情况下,程序中可能没有使用Windows API进行反调试,所以需要我们手动检查数据结构,关注一些会暴露调试器的数据结构(PEB)。

检测BeingDebugged属性

BeingDebugged如上文所说,位于PEB中的0x2偏移处

 

过掉这种调试的方法就是在执行跳转是,手动修改零标志(使其强制跳转或不跳转);或者修改跳转指令;手动设置BeingDebugged属性值为0;

检测ProcessHeap属性

在PEB结构的Reserved4数组中有一个未公开的位置叫做ProcessHeap,它被设置为加载器为进程分配的第一个堆的位置。ProcessHeap位于PEB结构的0x18偏移处。第一个堆头部有一个属性字段,它告诉内核这个堆是否在调试器中创建。这些属性叫做ForceFlagsFlags

在正常情况下,系统在为进程创建第一个堆时,会将它的Flags和ForceFlags分别设为2和0,而在调试状态下,这俩个标志通常会被设为50000062h(取决于NtGlobalFlag)和40000060h。

同时,Flags位于ProcessHeap的0x0c偏移处ForceFlags位于ProcessHeap的0x10偏移处

因此可以写出这样一段检测代码:

mov eax,fs:[0x30]			;获取PEB
mov eax,[eax+0x18]			;获取ProcessHeap	
cmp dword ptr [eax+0x0C],2			;获取Flags
jne __debugger_detected
cmp dword ptr [eax+0x10],0			;获取ForceFlags
jne __debugger_detected

对付这种反调试的方法之一就是手动修改ProcessHeap标志。

检测NtGlobalFlag

NtGlobalFlag位于PEB结构的0x68偏移处。因为在调试器中启动进程和正常模式下启动进程时它们创建内存堆的方式不同。如果进程是由调试器创建的,那么该标志的值会被设置成0x70

检测代码:

过掉反调试的方法都差不多。

识别调试器行为

在逆向工程中,进行代码分析时,可以用调试器设置断点,或者单步执行一个进程。当调试器在执行这些操作时,它们会修改进程中的代码。因此,恶意代码常使用探测INT扫描完整性校验,以及时钟检测等几种类型的调试器行为。

断点检测

软件断点

原理:调试器在设置断点时的一般采用的是软件断点(INT 3),打下INT 3后调试器会临时替换运行程序中的一条指令,当程序运行到这里时,调用调试异常处理例程

INT 3 的机器码是0xCC,所以若是在关键位置检测到该指令,就可以判断进程处于调试状态。。

常用的反调试方法,扫描0xCC。

call $+5
pop edi 
sub edi, 5
mov ecx, 400h
mov eax, 0CCh
repne scasb
jz DebuggerDetected

这段先执行了一个函数调用,随后用pop指令将eip寄存器的值存入edi,然后将edi设置为代码的开始。接下来扫描这段代码的0xCC字节,如果发现0xCC则证明存在调试器。

对抗这种反调试技术的方法就是使用硬件断点

示例程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include <Windows.h>

#include <iostream>

int main() {

    // 获取MessageBoxA函数地址

    FARPROC addr = GetProcAddress(LoadLibraryW(L"user32.dll"), "MessageBoxA");

     

    // 读取函数入口处的第一个字节

    BYTE byteAtAddr = *(BYTE*)addr;

     

    // 显示消息框作为正常功能的一部分

    MessageBoxA(NULL, "context""title", MB_OK);

    // 检查是否为0xCC(INT 3指令)

    if (byteAtAddr == 0xCC) {

        std::cout << "检测到调试" << std::endl;

    else {

        std::cout << "无调试" << std::endl;

    }

     

    return 0;

}

用xdbg调试,Ctrl+G搜索MessageBoxA。在函数处下断点,之后一直单步就可以发现程序输出了检测到调试。

查看汇编代码:

在调用完函数后,调试器扫描了esp-10处开始的字节,查找0xCC。

对抗:

在xdbg右键断点设置硬件断点,

 

再次调试发现,变成了无调试。成功过掉。(

硬件断点

先了解一下什么是硬件断点,硬件断点和DRx寄存器有关,下图是Intel CPU体系架构里对DRx寄存器的介绍。

 

  • DR0~DR3就好,这四个位置一般用于设置硬件断点。
  • DR4和DR5保留,并未公开。
  • DR6是调试寄存器组状态寄存器。
  • DR7是调试寄存器组控制寄存器。

硬件断点的原理是使用DR0~DR3设定地址,并使用DR7设定状态,因此最多设置4个断点。

怎么实现硬件断点反调试?

先获取硬件断点信息,利用函数GetThreadContext,它检索指定线程的上下文。

函数原型:

1

2

3

4

BOOL GetThreadContext(

  [in]      HANDLE    hThread,

  [in, out] LPCONTEXT lpContext

); 

第一个参数是要检索其上下文线程的句柄,第二个参数指向CONTEXT结构。该结构是一个在Windows API中定义的结构体,它用于存储线程的上下文信息,包括寄存器和其他重要的状态信息。

因此我们需要获取CONTEXT结构体中的DRx寄存器的信息,使用CONTEXT_DEBUG_REFGISTERS标志。

示例程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

#include <Windows.h>

#include <iostream>

int main()

{

    CONTEXT TestContext;

    ZeroMemory(&TestContext, sizeof(CONTEXT)); // 将结构体清零

    TestContext.ContextFlags = CONTEXT_DEBUG_REGISTERS;

     

    if (GetThreadContext(GetCurrentThread(), &TestContext)) {

        if (TestContext.Dr0 != 0 || TestContext.Dr1 != 0 ||

            TestContext.Dr2 != 0 || TestContext.Dr3 != 0) {

            MessageBoxA(NULL, "硬件断点检测成功, 程序正在被调试!""硬件断点检测", MB_OK);

        else {

            MessageBoxA(NULL, "没有检测到硬件断点。""硬件断点检测", MB_OK);

        }

    else {

        DWORD dwError = GetLastError();

        std::cerr << "GetThreadContext failed with error: " << dwError << std::endl;

        MessageBoxA(NULL, "无法获取线程上下文信息。""错误", MB_OK);

    }

     

    system("pause"); // 使用cin.get()或std::getchar()可能是一个更好的选择

    return 0;

}

进行调试

 

 

随便下个硬件断点,检测成功。

采用异常来进行硬件断点反调试

执行代码校验和检查

恶意代码可以计算代码段的校验并实现与扫描中断相同的目的。与扫描0xCC不同,这种检查仅执行恶意代码中机器码的CRC(循环冗余校验)MD5校验和检查。

下面是一个简单的示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

#include <windows.h>

#include <iostream>

BOOL CheckIntegrity() {

    PIMAGE_DOS_HEADER pDosHeader;

    PIMAGE_NT_HEADERS pNtHeaders;

    PIMAGE_SECTION_HEADER pSectionHeader;

    DWORD dwBaseImage = (DWORD)GetModuleHandle(NULL);

     

    pDosHeader = (PIMAGE_DOS_HEADER)dwBaseImage;

    pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

    pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);

     

    DWORD dwAddr = pSectionHeader->VirtualAddress + dwBaseImage;

    DWORD dwCodeSize = pSectionHeader->SizeOfRawData;

     

    DWORD checksum = 0;

    BYTE* code = reinterpret_cast<BYTE*>(dwAddr);

     

    for (DWORD i = 0; i < dwCodeSize; ++i) {

        checksum += code[i];

        checksum = (checksum >> 31) | (checksum << 1); // ROL checksum, 1

    }

     

    if (checksum != 0x46ea24) {

        return FALSE;

    }

    return TRUE;

}

int main() {

    if (CheckIntegrity()) {

        std::cout << "Integrity check passed." << std::endl;

    else {

        std::cout << "Integrity check failed!" << std::endl;

    }

    return 0;

}

在这个程序中,checksum变量计算为代码段中所有字节的累加和,并对每个字节累加后进行一次ROL。然后将这个 checksum 和一个预设的校验和值比较。

关于绕过,可以修改检查函数或者校验和值。

时钟检测

程序调试时,进程的运行速度大大降低(单步调试)。

有如下俩种用时钟检测来探测调试器存在的方法:

  • 记录执行一段操作前后的时间戳,然后比较这俩个时间戳,如果存在滞后,则可以认为存在调试器。
  • 记录一个异常前后的时间戳。如果不调试进程,可以很快处理完异常,因为调试器处理异常的速度非常慢。因此默认情况下,调试器处理异常需要人为干预,这导致大量延迟。

rdstc指令

rdtsc指令(操作码0x0F31)用于获取CPU自开机运行起的时钟周期数,并且将其作为一个64位的值存入edx和eax寄存器中。执行俩次rdstc指令,然后比较这俩次取值之间的差值

汇编:

rdtsc
mov ecx, eax
mov ebx, edx
;计算俩个rdtsc的偏移量
rdtsc
cmp edx, ebx
ja __debugger_found
sub eax, ecx
cmp eax, 0x200
ja __debugger_found

俩次调用rdtsc,先检查了高位edx相不相同,相同的话再检查低位的差值是否大于0x20。

QueryPerformanceCounter或GetTickCount

QueryPerformanceCounter函数检索性能计数器的当前值,这是一个高分辨率 (<1us) 时间戳,可用于时间间隔度量。

GetTickCount检索自系统启动以来经过的毫秒数。

这俩个函数都可以用于时钟检测。

下面是一个GetTickCount的示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

#include <Windows.h>

#include <iostream>

BOOL IsDebuggerPresentWithTiming(){

    DWORD startTick,endTick,elapsed;

     

    // 获取初始的 tick 计数

    startTick = GetTickCount();

    // 此处可以添加代码

    OutputDebugString("Debugger check...\n");

    // 获取结束的 tick 计数

    endTick = GetTickCount();

    // 计算经过的 tick 数

    elapsed = endTick - startTick;

    // 如果时间差超出预设值,则可能存在调试器。

    if (elapsed > 200){

        return TRUE;

    }

    return FALSE;

}

int main(){

    if (IsDebuggerPresentWithTiming()){

        std::cout << "可能检测到调试器!" <<std::endl;

    }else{

        std::cout << "没有检测到调试器!" <<std::endl;

    }

    return 0;

}

过掉时钟检测比较好的办法是在检测指令之后下断点。例如:

父进程检测

从理论上讲,一个程序被正常启动时,其父进程应该是Exploer.exe(资源管理器启动)、**cmd.exe(命令行启动)或者Services.exe(系统服务)**中的一个。如果某一个进程的父进程并非上述3个进程之一,一遍可以认为它被调试了(或者被内存补丁之类的Loader程序加载了)。

实现这种检测的方法:

  1. 通过TEB(TEB.ClientId)或者GetCurrentProcessId来检索当前进程的PID。
  2. 通过Process32First、Process32Next得到所有进程的列表,判断explorer.exe的PID(通过PROCESSENTERY.szExeFile)和通过PROCESSENTRy.th32ParentProcessID获得的当前进程的父进程ID是否相同。
  3. 如果父进程的PID不是上述三种的其中之一,那么目标进程很可能被调试了。

干扰调试器的功能

TLS回调函数

Thread Local Storage(TLS),即线程本地存储,是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。

TLS回调函数就是在程序加载到调试器后,TLS回调会先于程序入口执行之前运行代码,这样就可以提前进行反调试操作或者修改代码。

在IDA中可以使用 Ctrl + E 查看二进制的入口点。

 

使用异常×

(待学)。。。

插入中断

插入INT 3

调试器使用 INT 3设置软件断点,所以一种反调试技术就是在合法代码中插入0xCC来欺骗调试器,使其认为这些0xCC机器码是自己设置的段带你。

一些调试器用跟踪自身设置的断点的方法来避免这种反调试技术。

插入INT 2D断点×

插入ICE断点×

嗯。待学(INT 2D。ICE是什么。)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/633524.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

建筑类中级工程师职称证明业绩材料有哪些?

三、建筑类中级工程师职称造价类工程业绩材料 1.合同&#xff1a;证明项目合作关系的凭证。 2.预&#xff08;结&#xff09;算报告等(重点是体现封面有你的名字和执业印章等) 3.单位证明或任命书(本人在项目中的职务聘书) 4.工程获奖证明&#xff1a;项目获得市优的证书、省优…

Ubuntu 22.04.1 LTS VirtualBox7.0 解决虚拟机窗口失去焦点一段时间后,虚拟机显示不刷新问题

故障描述&#xff1a; virtualbox安装在ubuntu系统上&#xff0c;虚拟机内安装了windows操作系统。使用中发现&#xff0c;当linux系统窗口被激活&#xff0c;如firefox浏览器&#xff0c;虚拟机的显示一段时间后会暂停刷新&#xff0c;鼠标划入虚拟机窗口后&#xff0c;才会立…

分布式概念

文章目录 一、CAP定理和BASE定理1.1 CAP定理1.2 CAP取舍1.3 BASE定理 二、分布式事务2.1 柔性事务2.2 两阶段提交协议2.3 三阶段提交协议 三、分布式ID3.1 数据库自增ID3.2 数据库多主模式3.3 号段模式3.4 雪花算法3.5 Leaf3.6 使用Redis生成ID 四、限流算法4.1 固定窗口计数器…

TypeScript实现一个贪吃蛇小游戏

游戏效果 文件目录 准备1&#xff1a;新建index.html&#xff0c;编写游戏静态页面 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-…

小程序开发实战案例五 | 小程序如何嵌入H5页面

在接入小程序过程中会遇到需要将 H5 页面集成到小程序中情况&#xff0c;今天我们就来聊一聊怎么把 H5 页面塞到小程序中。 本篇文章将会从下面这几个方面来介绍&#xff1a; 小程序承载页面的前期准备小程序如何承载 H5小程序和 H5 页面如何通讯小程序和 H5 页面的相互跳转 小…

安全加速SCDN是什么

安全加速SCDN&#xff08;Secure Content Delivery Network&#xff0c;SCDN&#xff09; 是集分布式DDoS防护、CC防护、WAF防护、BOT行为分析为一体的安全加速解决方案。已使用内容分发网络&#xff08;CDN&#xff09;或全站加速网络&#xff08;ECDN&#xff09;的用户&…

【JavaEE】_网络通信原理

目录 1. 网络发展史 2. 网络通信基础 1.1 IP地址 1.2 端口号 1.3 协议 1.3.1 概念 1.3.2 五元组 1.4 协议分层 1.4.1 协议分层的优点 1.4.2 协议分层的分类 1.4.3网络设备所在分层 1.4.4 两台主机通过TCP/IP协议通讯过程 1.5 封装与分用 1.5.1 封装 1.5.2 分用…

Docker 容器连接

Docker 容器连接 前面我们实现了通过网络端口来访问运行在 docker 容器内的服务。 容器中可以运行一些网络应用&#xff0c;要让外部也可以访问这些应用&#xff0c;可以通过 -P 或 -p 参数来指定端口映射。 下面我们来实现通过端口连接到一个 docker 容器。 网络端口映射 …

算法练习-A+B/财务管理/实现四舍五入/牛牛的菱形字符(题目链接+题解打卡)

难度参考 难度&#xff1a;简单 分类&#xff1a;熟悉OJ与IDE的操作 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。以下内容均为个人笔记&#xff0c;旨在督促自己认真学习。 题目 A B1. A B - AcWing题库财务管理1004:财…

VsCode + CMake构建项目 C/C++连接Mysql数据库 | 数据库增删改查C++封装 | 信息管理系统通用代码 ---- 课程笔记

这个是B站Up主&#xff1a;程序员程子青的视频 C封装Mysql增删改查操作_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1m24y1a79o/?p6&spm_id_frompageDriver&vd_sourcea934d7fc6f47698a29dac90a922ba5a3安装mysql:mysql 下载和安装和修改MYSQL8.0 数据库存储…

【现代密码学】笔记9-10.3-- 公钥(非对称加密)、混合加密理论《introduction to modern cryphtography》

【现代密码学】笔记9-10.3-- 公钥&#xff08;非对称加密&#xff09;、混合加密理论《introduction to modern cryphtography》 写在最前面8.1 公钥加密理论随机预言机模型&#xff08;Random Oracle Model&#xff0c;ROM&#xff09; 写在最前面 主要在 哈工大密码学课程 张…

深入vue响应式原理

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项&#xff0c;Vue 将遍历此对象所有的 property&#xff0c;并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。 这些 getter/setter 对用户来说是不可见的&#xff0c;但是在内部它们让 Vue …

Docker 47 个常见故障的原因和解决方法

本文针对Docker容器部署、维护过程中&#xff0c;产生的问题和故障&#xff0c;做出有针对性的说明和解决方案&#xff0c;希望可以帮助到大家去快速定位和解决类似问题故障。 Docker是一种相对使用较简单的容器&#xff0c;我们可以通过以下几种方式获取信息&#xff1a; 1、…

简单理解自动驾驶-看这篇够了!

本文主要介绍自动驾驶技术的整体框架&#xff0c;旨在从宏观理解自动驾驶技术。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;自动驾驶技术 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a…

第6章 现代通信技术

文章目录 6.1 图像与多媒体通信6.1.1 图像通信6.1.2 多媒体通信技术1、多媒体通信概念2、多媒体通信的组成3、多媒体通信的业务分类4、实用化的多媒体通信系统类型5、多媒体通信应用系统&#xff08;1&#xff09;多媒体会议电视系统&#xff08;2&#xff09;IPTV 6.2 移动通信…

【机器学习300问】12、为什么要进行特征归一化?

当线性回归模型的特征量变多之后&#xff0c;会出现不同的特征量&#xff0c;然而对于那些同是数值型的特征量为什么要做归一化处理呢&#xff1f; 一、为了消除数据特征之间的量纲影响 使得不同指标之间具有可比性。例如&#xff0c;分析一个人的身高和体重对健康的影响&…

每日一题——LeetCode1252.奇数值单元格的数目

进阶&#xff1a;你可以设计一个时间复杂度为 O(n m indices.length) 且仅用 O(n m) 额外空间的算法来解决此问题吗&#xff1f; 方法一 直接模拟&#xff1a; 创建一个n x m的矩阵&#xff0c;初始化所有元素为0&#xff0c;对于indices中的每一对[ri,ci]&#xff0c;将矩…

多色女童家居服,柔软细腻超舒适

柔软细腻到不想脱下来的 优可丝面料家居服来啦 精挑细选的可爱印花图案 让宝贝能够更快乐的进入梦乡 长度也是刚刚好合适 春夏交替的季节&#xff0c;建议多入几件换着穿

【新书推荐】Web3.0应用开发实战(从Web 2.0到Web 3.0)

第一部分 Flask简介 第1章 安装 1.1 创建应用目录 1.2 虚拟环境 1.2.1 创建虚拟环境 1.2.2 使用虚拟环境 1.3 使用pip安装Python包 1.4 使用pipregs输出包 1.5 使用requirements.txt 1.6 使用pipenv管理包 第2章 应用的基本结构 2.1 网页显示过程 2.2 初始化 2.3 路由和视图函数…

【C语言基础考研向】06运算符与表达式

文章目录 1.运算符分类 2.算术运算符及表达式 3.关系运算符与关系表达式 4.c语言运算级优先级表 课后习题自测 1.运算符分类 语言提供了13种类型的运算符,如下所示. (1)算术运算符( - * / %) . (2)关系运算符(>< >< l) . (3)逻辑运算符(l && ll) . (4)位…