news 2026/4/28 20:16:44

详解C++的反调试技术与绕过手法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
详解C++的反调试技术与绕过手法

反调试技术的实现方式有很多,最简单的一种实现方式莫过于直接调用Windows系统提供给我们的API函数,这些API函数中有些专门用来检测调试器的,有些则是可被改造为用于探测调试器是否存在的工具,多数情况下,调用系统API函数实现反调试是不明智的,原因很简单,目标主机通常会安装主动防御系统,而作为主动防御产品默认会加载RootKit驱动挂钩这些敏感函数的使用,如果被非法调用则会提示错误信息,病毒作者通常会使用汇编自行实现这些类似于系统提供给我们的反调试函数,并不会使用系统的API,这样依附于API的主动防御的系统将会失效。

1.加载调试符号链接文件并放入d:/symbols目录下.

1

2

3

0:000> .sympath srv*d:\symbols*http://msdl.microsoft.com/download/symbols

0:000> .reload

Reloading current modules

2.位于fs:[0x30]的位置就是PEB结构的指针,接着我们分析如何得到的该指针,并通过通配符找到TEB结构的名称.

1

2

3

4

5

6

0:000> dt ntdll!*teb*

ntdll!_TEB

ntdll!_GDI_TEB_BATCH

ntdll!_TEB_ACTIVE_FRAME

ntdll!_TEB_ACTIVE_FRAME_CONTEXT

ntdll!_TEB_ACTIVE_FRAME_CONTEXT

3.接着可通过dt命令,查询下ntdll!_TEB结构,如下可看到0x30处ProcessEnvironmentBlock存放的正是PEB结构.

1

2

3

4

5

6

7

0:000> dt -rv ntdll!_TEB

struct _TEB, 66 elements, 0xfb8 bytes

+0x000 NtTib : struct _NT_TIB, 8 elements, 0x1c bytes# NT_TIB结构

+0x018 Self : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes# NT_TIB结构

+0x020 ClientId : struct _CLIENT_ID, 2 elements, 0x8 bytes# 保存进程与线程ID

+0x02c ThreadLocalStoragePointer : Ptr32 to Void

+0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 65 elements, 0x210 bytes# PEB结构

偏移地址0x18是_NT_TIB结构,也就是指向自身偏移0x0的位置.

1

2

3

4

5

6

0:000> r $teb

$teb=7ffdf000

0:000>dd$teb+0x18

7ffdf018 7ffdf000 00000000 00001320 00000c10

7ffdf028 00000000 00000000 7ffd9000 00000000

而!teb地址加0x30正是PEB的位置,可以使用如下命令验证.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

0:000>dd$teb+0x30

7ffdf030 7ffd9000 00000000 00000000 00000000

7ffdf040 00000000 00000000 00000000 00000000

0:000> !teb

TEB at 7ffdf000

ExceptionList: 0012fd0c

StackBase: 00130000

StackLimit: 0012e000

SubSystemTib: 00000000

FiberData: 00001e00

ArbitraryUserPointer: 00000000

Self: 7ffdf000

EnvironmentPointer: 00000000

ClientId: 00001320 . 00000c10

RpcHandle: 00000000

Tls Storage: 00000000

PEB Address: 7ffd9000# 此处teb地址

上方的查询结果可得知偏移位置fs:[0x18]正是TEB的基址TEB:7ffdf000

1

2

3

4

5

6

7

8

9

10

11

12

13

14

0:000>ddfs:[0x18]

003b:00000018 7ffdf000 00000000 000010f4 00000f6c

003b:00000028 00000000 00000000 7ffda000 00000000

0:000> dt _teb 0x7ffdf000

ntdll!_TEB

+0x000 NtTib : _NT_TIB

+0x01c EnvironmentPointer : (null)

+0x020 ClientId : _CLIENT_ID# 这里保存进程与线程信息

0:000> dt _CLIENT_ID 0x7ffdf000# 查看进程详细结构

ntdll!_CLIENT_ID

+0x000 UniqueProcess : 0x0012fd0c Void# 获取进程PID

+0x004 UniqueThread : 0x00130000 Void# 获取线程PID

上方TEB首地址我们知道是fs:[0x18],接着我们通过以下公式计算得出本进程的进程ID.

在Windows系统中如果想要获取到PID进程号,可以使用NtCurrentTeb()这个系统API来实现,但这里我们手动实现该API的获取过程.

获取进程PID:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#include "stdafx.h"

#include <Windows.h>

DWORDGetPid(){

DWORDdwPid=0;

__asm

{

mov eax,fs:[0x18]// 获取PEB地址

add eax,0x20// 加0x20得到进程PID

mov eax,[eax]

mov dwPid,eax

}

returndwPid;

}

intmain()

{

printf("%d\n",GetPid());

return0;

}

获取线程PID:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#include "stdafx.h"

#include <Windows.h>

DWORDGetPid(){

DWORDdwPid=0;

__asm

{

mov eax,fs:[0x18]// 获取PEB地址

add eax,0x20// 加0x20得到进程PID

add eax,0x04// 加0x04得到线程PID

mov eax,[eax]

mov dwPid,eax

}

returndwPid;

}

intmain()

{

printf("%d\n",GetPid());

return0;

}

通过标志反调试:

下方的调试标志BeingDebugged是Char类型,为1表示调试状态.为0表示没有调试.可以用于反调试.

1

2

3

4

5

6

7

0:000> dt _peb

ntdll!_PEB

+0x000 InheritedAddressSpace : UChar

+0x001 ReadImageFileExecOptions : UChar

+0x002 BeingDebugged : UChar

+0x003 SpareBool : UChar

+0x004 Mutant : Ptr32 Void

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

#include "stdafx.h"

#include <Windows.h>

intmain()

{

DWORDdwIsDebug = 0;

__asm

{

mov eax, fs:[0x18];// 获取TEB

mov eax, [eax + 0x30];// 获取PEB

movzx eax, [eax + 2];// 获取调试标志

mov dwIsDebug,eax

}

if(1 == dwIsDebug)

{

printf("正在被调试");

}

else

{

printf("没有被调试");

}

return0;

}

通过API反调试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include <stdio.h>

#include <stdlib.h>

#include <Windows.h>

intmain()

{

STARTUPINFO temp;

temp.cb =sizeof(temp);

GetStartupInfo(&temp);

if(temp.dwFlags != 1)

{

ExitProcess(0);

}

printf("程序没有被反调试");

return0;

}

反调试与绕过思路

BeingDebugged 属性反调试:

进程运行时,位置FS:[30h]指向PEB的基地址,为了实现反调试技术,恶意代码通过这个位置来检查BeingDebugged标志位是否为1,如果为1则说明进程被调试。

1.首先我们可以使用 dt _teb 命令解析一下TEB的结构,如下TEB结构的起始偏移为0x0,而0x30的位置指向的是 ProcessEnvironmentBlock 也就是指向了进程环境块。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0:000> dt _teb

ntdll!_TEB

+0x000 NtTib : _NT_TIB

+0x01c EnvironmentPointer : Ptr32 Void

+0x020 ClientId : _CLIENT_ID

+0x028 ActiveRpcHandle : Ptr32 Void

+0x02c ThreadLocalStoragePointer : Ptr32 Void

+0x030 ProcessEnvironmentBlock : Ptr32 _PEB// 此处是进程环境块

+0x034 LastErrorValue : Uint4B

+0x038 CountOfOwnedCriticalSections : Uint4B

+0x03c CsrClientThread : Ptr32 Void

+0x040 Win32ThreadInfo : Ptr32 Void

+0x044 User32Reserved : [26] Uint4B

+0x0ac UserReserved : [5] Uint4B

+0x0c0 WOW32Reserved : Ptr32 Void

只需要在进程环境块的基础上 +0x2 就能定位到线程环境块TEB中 BeingDebugged 的标志,此处的标志位如果为1则说明程序正在被调试,为0则说明没有被调试。

1

2

3

4

5

6

7

8

0:000> dt _peb

ntdll!_PEB

+0x000 InheritedAddressSpace : UChar

+0x001 ReadImageFileExecOptions : UChar

+0x002 BeingDebugged : UChar

+0x003 BitField : UChar

+0x003 ImageUsesLargePages : Pos 0, 1 Bit

+0x003 IsProtectedProcess : Pos 1, 1 Bit

我们手动来验证一下,首先线程环境块地址是007f1000在此基础上加0x30即可得到进程环境快的基地址007ee000继续加0x2即可得到BeingDebugged的状态 ffff0401 需要 byte=1

1

2

3

4

5

6

7

8

9

10

11

12

13

0:000> r $teb

$teb=007f1000

0:000> dd 007f1000 + 0x30

007f1030 007ee000 00000000 00000000 00000000

007f1040 00000000 00000000 00000000 00000000

0:000> r $peb

$peb=007ee000

0:000> dd 007ee000 + 0x2

007ee002 ffff0401 0000ffff 0c400112 19f0775f

007ee012 0000001b 00000000 09e0001b 0000775f

梳理一下知识点我们可以写出一下反调试代码,本代码单独运行程序不会出问题,一旦被调试器附加则会提示正在被调试。

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

#include <stdio.h>

#include <windows.h>

intmain()

{

BYTEIsDebug = 0;

__asm{

mov eax, dword ptr fs:[0x30]

mov bl, byte ptr [eax+ 0x2]

mov IsDebug, bl

}

/* 另一种反调试实现方式

__asm{

push dword ptr fs:[0x30]

pop edx

mov al, [edx + 2]

mov IsDebug,al

}

*/

if(IsDebug != 0)

printf("本程序正在被调试. %d", IsDebug);

else

printf("程序没有被调试.");

getchar();

return0;

}

复制讲解

如果恶意代码中使用该种技术阻碍我们正常调试,该如何绕过呢?如下我们只需要在命令行中执行dump fs:[30]+2来定位到BeingDebugged的位置,并将其数值改为0然后运行程序,会发现反调试已经被绕过了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 20:15:22

架构级Dlib预编译方案:企业级Windows环境部署实战指南

架构级Dlib预编译方案&#xff1a;企业级Windows环境部署实战指南 【免费下载链接】Dlib_Windows_Python3.x Dlib compiled binaries (.whl) for Python 3.7-3.14 and Windows x64 项目地址: https://gitcode.com/gh_mirrors/dl/Dlib_Windows_Python3.x 在计算机视觉和机…

作者头像 李华
网站建设 2026/4/28 20:14:21

Labview通讯三菱Q PLC,Labvew TCP通讯三菱PLC ,MCTCP,三菱PLC...

Labview通讯三菱Q PLC&#xff0c;Labvew TCP通讯三菱PLC &#xff0c;MCTCP&#xff0c;三菱PLC连接LabVIEW&#xff0c;LabVIEW和三菱PLC 通讯 三菱官方MC协议&#xff0c;简单方便&#xff0c;完胜OPC协议。 &#xff0c;源码开放。 1.支持bool读写 2.支持浮点数读写 3支持 …

作者头像 李华
网站建设 2026/4/28 20:12:23

分钟搞懂深度学习AI:实操篇:池化层

1. 流图&#xff1a;数据的河流 如果把传统的堆叠面积图想象成一块块整齐堆叠的积木&#xff0c;那么流图就像一条蜿蜒流淌的河流&#xff0c;河道的宽窄变化自然流畅&#xff0c;波峰波谷过渡平滑。 它特别适合展示多个类别数据随时间的变化趋势&#xff0c;尤其是当你想强调整…

作者头像 李华
网站建设 2026/4/28 20:08:43

拯救你的B站缓存视频:3分钟完成m4s到MP4的无损转换

拯救你的B站缓存视频&#xff1a;3分钟完成m4s到MP4的无损转换 【免费下载链接】m4s-converter 一个跨平台小工具&#xff0c;将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经因为B站视频突然下架而…

作者头像 李华