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

 更新时间:2021年6月24日 00:00  点击:1932

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

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

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

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

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结构.

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的位置.

0:000> r $teb
$teb=7ffdf000

0:000> dd $teb+0x18
7ffdf018  7ffdf000 00000000 00001320 00000c10
7ffdf028  00000000 00000000 7ffd9000 00000000

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

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

0:000> dd fs:[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:

#include "stdafx.h"
#include <Windows.h>

DWORD GetPid(){
	DWORD dwPid=0;
	__asm
	{
		mov eax,fs:[0x18]    // 获取PEB地址
		add eax,0x20         // 加0x20得到进程PID
		mov eax,[eax]
		mov dwPid,eax
	}
	return dwPid;
}

int main()
{
	printf("%d\n",GetPid());
    return 0;
}

获取线程PID:

#include "stdafx.h"
#include <Windows.h>

DWORD GetPid(){
	DWORD dwPid=0;
	__asm
	{
		mov eax,fs:[0x18]    // 获取PEB地址
		add eax,0x20         // 加0x20得到进程PID
		add eax,0x04         // 加0x04得到线程PID
		mov eax,[eax]
		mov dwPid,eax
	}
	return dwPid;
}

int main()
{
    printf("%d\n",GetPid());
    return 0;
}

通过标志反调试:

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

0:000> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void

#include "stdafx.h"
#include <Windows.h>

int main()
{
    DWORD dwIsDebug = 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("没有被调试");
    }
    return 0;
}

通过API反调试:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

int main()
{
    STARTUPINFO temp;
    temp.cb = sizeof(temp);
    GetStartupInfo(&temp);

    if (temp.dwFlags != 1)
    {
        ExitProcess(0);
    }
    printf("程序没有被反调试");
    return 0;
}

反调试与绕过思路

BeingDebugged 属性反调试:

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

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

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则说明没有被调试。

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

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

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

#include <stdio.h>
#include <windows.h>

int main()
{
	BYTE IsDebug = 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();
	return 0;
}

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

ProcessHeap 属性反调试:

该属性是一个未公开的属性,它被设置为加载器为进程分配的第一个堆的位置,ProcessHeap位于PEB结构的0x18处,第一个堆头部有一个属性字段,这个属性叫做ForceFlags和Flags属性偏移为10,该属性为0说明程序没有被调试,非0则说明被调试。

0:000> dt !_peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x018 ProcessHeap      : Ptr32 Void
   +0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION

0:000> r $peb
$peb=006e1000

0:000> dd 006e1000+18
006e1018  00ca0000 775f09e0 00000000 00000000

0:000> dd 00ca0000 + 10
00ca0010  00ca00a4 00ca00a4 00ca0000 00ca0000

要实现反反调试,只需要将 00ca0000 + 10 位置的值修改为0即可,执行dump ds:[fs:[30] + 0x18] + 0x10 定位到修改即可。

文章出处:https://www.cnblogs.com/lyshark

以上就是详解C++的反调试技术与绕过手法的详细内容,更多关于C++ 反调试技术与绕过手法的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++如何删除map容器中指定值的元素详解

    map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
  • C++ 约瑟夫环问题案例详解

    这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 基于C++中常见编译错误的总结详解

    本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
  • c++优先队列(priority_queue)用法详解

    这篇文章主要介绍了c++优先队列(priority_queue)用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25