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

反调试技术的实现方式有很多,最简单的一种实现方式莫过于直接调用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++ 反调试技术与绕过手法的资料请关注我们其它相关文章!

(0)

相关推荐

  • vscode C++远程调试运行(学习C++用)

    目标: 连接远程主机 (ssh) 配置C++编译环境 (输出结果后删除二进制文件) 步骤: 安装Remote SSH,连接远程主机 Visual Studio 官方文档 https://code.visualstudio.com/docs/remote/ssh 图标 2. 配置C++编译运行环境 主要参考下面两篇文档 https://code.visualstudio.com/docs/cpp/config-wsl https://code.visualstudio.com/docs/edito

  • VSCode远程开发调试服务器c/c++代码

    思路与上篇(PyCharm远程调试服务器python代码 )是一致的,所以端口转发这部分直接照抄上篇: 一.端口转发 对于没有公网IP的远程训练服务器,需要先配置端口转发,可以用ssh借道有办公网IP的办公机器. 0.公司给配置了一台Ubuntu系统的台式机器A,开发时想用Windows笔记本B,把远程CentOS训练服务器记为C. 1.首先需要修改台式机A上的ssh配置文件,如果不修改配置的话,将只有机器A可以访问训练服务器C. $ sudo vim /etc/ssh/sshd_config

  • ubunt18.04LTS+vscode+anaconda3下的python+C++调试方法

    1.安装背景 最近想放弃windows编程环境,转到linux.原因就一个字:潮 从格式化所有硬盘,到安装win10/ubuntu18.04双系统,其中的痛苦,我想只有经历过的人才会知道. 在这里,我还是提一些安装双系统的几点建议吧: ① 先装win10,我是使用老毛桃在线安装的专业版 ② 装ubuntu很烦人,本以为通过教程(先下载iso,再制作启动u盘,再修改bios中的u盘优先启动方式)就可以了,最终无果.我只好用实验室同学已经制作好的ubuntu 启动盘进行安装,结果开启出现了gnru,

  • C++调试记录与心得分享

    之前开发用Linux C比较多,C++中的STL 容器基本没有接触过.最近在学习C++,平时用到c++ 17中的部分新特性,下面就简单分享下自己C++的学习流程. 一.环境搭建 本人使用的是CentOS 7系统,该系统默认的g++版本不支持c++17的新特性.所以,首先需要做的就是升级新版本的g++. 1.到ftp://ftp.mirrorservice.org/sites/sourceware.org/pub/gcc/releases/网站上选择支持c++17的gcc版本,并使用wget下载到

  • 解决vscode下调试c/c++程序一闪而过的问题(Windows)

    起因 开始学习c语言了,一开始想的就是直接装个VS就完了,但是一搜都是说vs臃肿啥啥不好,不如用vscode来整,多轻量~于是就网上搜了许多教程开整了,期间也遇到了一些常见的坑,这些集中写个文,希望可以帮助到大家.配置文件也是来自其他文章的,我也进行了部分修改,后面会标出. 解决方法 我们知道使用vscode对c/c++进行调试的本质是这样的--1.GCC编译代码,2.vscode运行编译出的程序.因此调试运行程序一闪而过的本质就是命令行程序运行完结果直接自动关闭了,这和c/c++程序本身也有关

  • c++代码调试方式的几点建议

    1.代码调试的重要性 代码调试在程序开发阶段占有举足轻重的地位,可见代码调试的重要性.但是有一点必须强调:程序是设计出来的,而不是调试出来的.这是所有程序员必须牢记在心的一条准则.一个没有设计或者这几得很糟糕的程序,无论怎样调试,也不会成为一个合格的程序. 程序有着良好的设计的前提下,软件开发的过程中,编码错误在所难免.所有程序可能出现的错误可分为两类:语法错误和逻辑错误.调试通常是指在消除了语法错误之后,发现程序中的逻辑错误的过程.对C/C++程序进行调试,有这样集中常用的手段.它们既可以单独

  • 详解AndroidStudio3.0开发调试安卓NDK的C++代码

    本文介绍了AndroidStudio3.0开发调试安卓NDK的C++代码,分享给大家,具有如下: 一.新建项目 新建项目,没有发现Include C++ Support 选项.因为印象中是有过该选项的,找了半天没找到. 后来无意间拖了下窗口大小,原来是被隐藏了,真特么坑. 新建一个测试项目,勾选Include C++ Support 选项,看看工程上有哪些不同. 1.gradle 首先看gradle文件,android节点下添加: externalNativeBuild { cmake { pa

  • C++运算符重载实例代码详解(调试环境 Visual Studio 2019)

    最近看了菜鸟教程里的C++教程 遇到很多运算符重载,为了方便我的学习,我把这些总结了一下 如有错误(包括之前的博文)请评论留言,谢谢! 由于代码里注释的很清楚,我就不做过多的解释了. 下面是这次总结的头文件,用来放置一些类和方法. //C++运算符重载实例.h #pragma once #include <iostream> using namespace std; class chongzai { private: int i, j, k; public: chongzai() { i =

  • Linux搭建C++开发调试环境的方法步骤

    安装g++ Linux编译C++程序必须安装g++编译器.这里使用yum方式安装.首先切换到root账号,su - root 然后输入密码. 执行yum install gcc-c++(注意不是yum install g++),报错. 报错是因为yum需要配置正确的服务器地址,服务器是提供yum安装包的,也被称作yum源.配置yum源的配置文件在/etc/yum.repos.d/目录下,可以看到系统自带了两个文件. cat 文件名称,会打印文件全部内容.可以看到两个文件要么没配置,要么地址是无法

  • vscode配置远程开发环境并远程调试运行C++代码的教程

    之前的时候我写C/C++代码喜欢在Linux下用vim(一开始甚至都没装代码补全插件),后来入了Clion的坑,感觉IDE还是太方便了,但是毕竟还是有许多场景只能在Linux下完成,于是就经常还是需要用vim.gdb这些东西,用惯了IDE之后就觉得挺麻烦的.最近尝试了一下vscode的远程开发功能,就俩字:真香,写篇文章记录一下,以防我下次配置的时候忘记了. vscode配置远程开发环境 检查服务器是否安装ssh服务 服务器使用命令netstat -tanp查看是否存在sshd 一般这一步都不会

随机推荐