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 结构的指示器,该结构可用于确定指定进程是否正在调试,以及系统用于标识指定进程的唯一值。使用CheckRemoteDebuggerPresentGetProcessId 函数获取此信息。
ProcessDebugPort 7检索DWORD_PTR值,该值是进程的调试器的端口号。非零值表示进程正在环3调试器的控制下运行。使用CheckRemoteDebuggerPresentIsDebuggerPresent函数。
ProcessWow64Information 26确定进程是否在 WOW64 环境中运行(WOW64 是允许基于 Win32 的应用程序在 64 位 Windows 上运行的 x86 模拟器)。使用IsWow64Process2函数获取此信息。
ProcessImageFileName 27搜索包含进程图像文件名称的 **UNICODE_STRING值。**使用QueryFullProcessImageNameGetProcessImageFileName函数获取此信息。
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是什么。)

  • 25
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
[Trial version] 加密解密技术内幕.htm [Trial version] 目录.htm [Trial version] 第1章 PE文件格式深入研究 [Trial version] 1.1 PE文件格式格式纵览 [Trial version] PE文件格式一览.html [Trial version] 1.2 PE文件结构 [Trial version] 1. 检验PE文件的有效性.html [Trial version] 2. File Header (文件头).html [Trial version] 3. Optional Header.html [Trial version] 4. Section Table(节表).html [Trial version] 5. Import Table(引入表).html [Trial version] 6. Export Table(引出表).html [Trial version] 7.Debug信息节.htm [Trial version] DocNet 学习笔记之Anti ILDASM 研究.htm [Trial version] PE 档的基底重定位(Base Relocations).htm [Trial version] PE 格式学习总结(一)-- PE文件概述.htm [Trial version] PE 格式学习总结(二)-- PE文件中的输出函数.htm [Trial version] PE 格式学习总结(三)-- PE文件中的输入函数.htm [Trial version] PE 格式学习总结(四)-- PE文件中的资源.htm [Trial version] TLS.htm [Trial version] 基址从定位.htm [Trial version] 异常处理.htm [Trial version] 第2章 PE分析工具编写 [Trial version] 关于PE可执行文件的修改.htm [Trial version] 手工构造一个超微型的 PE 文件.htm [Trial version] 第3章 Win32 调试API [Trial version] 病毒是如何抗动态测试.htm [Trial version] 3.1 Win32调试API原理 [Trial version] Win32调试API第一部分.htm [Trial version] Win32调试API第二部分.htm [Trial version] Win32调试API第三部分.htm [Trial version] Win32调试API学习心得(一).htm [Trial version] Win32调试API学习心得(二).htm [Trial version] win32调试API学习心得(三).htm [Trial version] 用调试函数跟踪API.htm [Trial version] 3.2 利用调试API编写脱壳机 [Trial version] DEF分析与打造其脱壳软件----我的一篇析文.htm [Trial version] 用调试函数跟踪API.htm [Trial version] 3.3 利用调试API制作内存补丁 [Trial version] 利用DebugAPI做一些原先手工完成的动作,我用这种方法做过内存补丁,内存注册机等,完全VC编译.htm [Trial version] 第4章 Windows下的异常处理 [Trial version] 4.1 基本概念 [Trial version] SEH基本概念.htm [Trial version] 什么是异常.htm [Trial version] 4.2 结构化异常处理(SEH) [Trial version] 初步实战演习.htm [Trial version] 4.3 异常处理程序设计 [Trial version] 传递给异常处理例程的参数.htm [Trial version] 异常处理的堆栈展开.htm [Trial version] 4.4 SEH的简单应用 [Trial version] Win9x下利用SEH进ring0.htm [Trial version] 利用SEH实现对自身的单步自跟踪.htm [Trial version] 异常死亡进程的自动复活.htm [Trial version] 用 SEH 技术实现 API Hook.htm [Trial version] 4.6 VC是如何封装系统提供的SEH机制的 [Trial version] VC++编译器怎么实现异常处理.htm [Trial version] 4.7 Windows XP下的向量化异常处理(VEH [Trial version] windows XP下的向量化异常处理.htm [Trial version] 第5章 软件加密技术 [Trial version] 5.1 调试技术(Anti-Debug) [Trial version] 5.1.1 句柄检测.htm [Trial version] 5.1.4 ICECream子类型.htm [Trial version] 5.1.5 判断NTICE服务是否运行.htm [Trial version] anti-debug技术探讨.htm [Trial version] AntiSoftICE.htm [Trial version] VB中实现检测ICE.htm [Trial version] 测试ICE是否在运行.htm [Trial version] 检测debugger的方法补遗.htm [Trial version] 另外一种检测SOFTICE的方法.htm [Trial version] 通过PEB实现AntiDebug.htm [Trial version] 5.2 跟踪技术(Anti-Trace) [Trial version] tELock中的SEH跟踪代码.htm [Trial version] 利用SEH改变程序流程以达到跟踪的目的.htm [Trial version] 5.2.3 SMC技术实现 [Trial version] 浅析SMC技术.htm [Trial version] 5.3 加载技术(Anti-Loader) [Trial version] VB anti-loader 2种常用方法介绍.htm [Trial version] 5.3.1 利用TEB检测 [Trial version] 浅谈利用 TEB 实现的跟踪.htm [Trial version] 5.4 DUMP技术(Anti-Dump) [Trial version] 理论知识.htm [Trial version] 5.5 文件完整性检验 [Trial version] PE 头部校验和(checksum)的计算.htm [Trial version] 5.5.1 CRC校验实现 [Trial version] 矛与盾的较量(1)——CRC原理篇.htm [Trial version] 矛与盾的较量(2)——CRC实践篇.htm [Trial version] 5.7 静态分析技术 [Trial version] Fooling Disassemblers.htm [Trial version] 5.7.2 花指令 [Trial version] 矛与盾的较量(1)——花指令.htm [Trial version] 5.8 代码与数据结合技术 [Trial version] 与破解过招,保护你的共享软件.htm [Trial version] 5.9 软件保护的若干忠告 [Trial version] WIN下动态注册码实现方法.htm [Trial version] 安全程序设计.htm [Trial version] 如何用简单方法防止破解.htm [Trial version] 第6章 加壳软件编写 [Trial version] 6.1 外壳编写基础 [Trial version] 加壳软件实现.htm [Trial version] 6.2 加壳程序综合运用的实例 [Trial version] 浅谈壳的加载步骤及手动脱壳。.htm [Trial version] 第7章 如何让壳与程序融为一体 [Trial version] 7.2 欺骗检查壳的工具 [Trial version] 让侦测工具把壳识别为VC++.htm [Trial version] 7.4 使用sdk把程序和壳溶为一体 [Trial version] 实现调用加壳的外壳中的子程序的一点见解.htm [Trial version] 7.5 后记关于壳和程序的思考 [Trial version] 加壳技术探讨-加壳时处理IAT.htm [Trial version] 第8章 Visual Basic 6 逆向工程 [Trial version] VB的Native Code的传奇.htm [Trial version] VB网址大全.htm [Trial version] Visual Basic 6 逆向工程与逆向工程 (1).htm [Trial version] Visual Basic 6 逆向工程与逆向工程 (2).htm [Trial version] 《真是想不到系列》.htm [Trial version] 关于VB P-CODE的一些总结.htm [Trial version] 关于在VB中进行COM组件的开发(一、了解COM).htm [Trial version] 未公开API函数揭秘--通用对话框.htm [Trial version] VB与COM [Trial version] 如何用VB6写COM组件(一).htm [Trial version] 如何用VB6写COM组件(二).htm [Trial version] 附录A 在Visual C++中使用内联汇编 [Trial version] Visual C++ 内嵌汇编.htm [Trial version] Visual C++ 内嵌汇编二.htm [Trial version] 附录B 在Visual Basic中使用汇编 [Trial version] 完全用VB进行ASM编程的示例.htm [Trial version] 附录C COM技术 [Trial version] 《COM 原理与应用》学习笔记 - 第一部分 COM原理.htm [Trial version] 附录D 通过崩溃地址找出出错的代码行 [Trial version] 如何通过崩溃地址找到出错的代码行.htm [Trial version] 附录E 堆栈修正 [Trial version] 堆栈修正.htm

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值