查看原文
其他

EMET下EAF机制分析以及模拟实现

修竹kirakira 看雪学苑 2022-09-22


本文为看雪论坛优秀文章

看雪论坛作者ID:修竹kirakira



EAF机制分析报告


简单分析了一下EAF机制,主要是参考了一下github上面的项目然后自己重写了一个EMET模拟程序,这是EAF的分析部分。第一次发帖,有写的不对的地方请大家多多包涵。


代码的话看一下伪代码和实现流程就好了。因为具体实现的时候是和其他机制一起写的,所以看起来有点奇怪。





EMET概述


Enhanced Migigation Experience Toolkit(增强的缓解体验工具包)。EMET试图缓解漏洞攻击的影响,通过引入以下这些保护措施来实现缓解攻击的影响:


数据执行保护(DEP)、结构化异常处理程序覆盖保护(SEHOP) 、空页面保护(NullPage) 、堆喷射保护(HeapSpray) 、导出地址表访问过滤(EAF) 、导出地址表访问过滤增强版(EAF+) 、强制地址空间布局随机化(MandatoryASLR) 、由低而上的地址空间布局随机化(BottomUpASLR) 、Load Library 保护(LoadLib) 、内存保护(MemProt) 、ROP 调用者检查(Caller) 、ROP 模拟执行流(SimExecFlow)、堆栈支点(StackPivot) 、减少攻击面(ASR)





EAF机制


概述


Export Address Table Access Filtering(导出地址表访问过滤),可以对访问导出地址表(EAT)的调用代码设置规则。


为了调用 API,shellcode 需要找到API加载的地址。通常shellcode会遍历所有已加载模块的导出地址表,寻找包含有用api的模块。通常涉及kernel32.dll,ntdll.dll 或 kernelbase.dll三个模块。EAF机制限制对Export Address Table (EAT)的读访问,一旦shellcode访问EAT,操作将被堵塞。


实现流程


1.调用AddVectoredExceptionHandler注册异常处理函数。


2.获得三个重要模块(kernel32.dll,ntdll.dll,kernelbase.dll)EAT地址。


3.在上述获得的地址处加上内存断点。

内存断点:调用VirtualProtect将关注的模块相应地址处加上页面保护属性PAGE_GUARD。


4.当有shellcode访问被保护地址时,引发STATUS_GUARD_PAGE_VIOLATION (0x80000001)。

STATUS_GUARD_PAGE_VIOLATION异常:如果程序尝试访问保护页中的地址,系统将引发STATUS_GUARD_PAGE_VIOLATION (0x80000001) 异常。系统还会清除PAGE_GUARD修饰符,从而删除内存页的防护页状态。


5.进入异常处理函数,在异常处理函数中获取发生异常的指令的地址,获得该地址所在的模块名,若不在白名单内,即为违规操作,调用TerminateProcess结束进程。


6.若在白名单内,设置EFALG寄存器的TF标识,返回继续执行,进入单步调试异常,恢复该页面的PAGE_GUARD属性。


详细分析


1.注册异常处理函数

AddVectoredExceptionHandler(0, EAFVectoredHandler);


2.获取模块的EAT地址

void * GetModuleEAT(DWORD_PTR ModuleBase){ IMAGE_DOS_HEADER* ImageDosHeader = NULL; IMAGE_OPTIONAL_HEADER* ImageOptionalHeader = NULL; PIMAGE_EXPORT_DIRECTORY ImageExportDirectory; PULONG AddressOfFunctions; ImageDosHeader = (IMAGE_DOS_HEADER *)ModuleBase; ImageOptionalHeader = (IMAGE_OPTIONAL_HEADER*)((BYTE*)ModuleBase + ImageDosHeader->e_lfanew + 24); ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)ModuleBase + ImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); AddressOfFunctions = (ULONG*)((BYTE*)ModuleBase + ImageExportDirectory->AddressOfFunctions); return (void*)AddressOfFunctions;}


3.添加内存断点


调用VirtualProtect将关注的模块相应地址处加上页面保护属性PAGE_GUARD。

DWORD EAF() { DWORD dwRet = 0; EnterCriticalSection(&g_CriSec); for (int i = 0; i < 3; i++) { DWORD_PTR ModuleBase = (DWORD_PTR)GetModuleHandle(LPCTSTR(g_Info.SystemDllInfo[i].dwModuleName)); g_Info.SystemDllInfo[i].dwModuleBase = ModuleBase; g_Info.SystemDllInfo[i].dwEATAddr = GetModuleEAT(ModuleBase); g_Info.SystemDllInfo[i].dwModuleSize = GetModuleSize(ModuleBase); g_Info.SystemDllInfo[i].dwPageAddrOfEAT = g_Info.SystemDllInfo[i].dwEATAddr & 0xFFFFF000; g_Info.SystemDllInfo[i].dwSize = 0x1000; MEMORY_BASIC_INFORMATION mbi; VirtualQuery((PVOID)g_Info.SystemDllInfo[i].dwEATAddr, &mbi, sizeof(MEMORY_BASIC_INFORMATION)); if (mbi.State == MEM_COMMIT) { if (!(mbi.Protect & PAGE_GUARD)) { DWORD NewProtect = 0; DWORD OldProtect = 0; DWORD dwSize = g_Info.SystemDllInfo[i].dwSize; PVOID dwBaseAddress = (PVOID)g_Info.SystemDllInfo[i].dwPageAddrOfEAT; NewProtect = mbi.Protect | PAGE_GUARD; g_Info.SystemDllInfo[i].dwProtect = NewProtect; dwRet = pNtProtectVirtualMemory(GetCurrentProcess(), &dwBaseAddress, &dwSize, NewProtect, &OldProtect); } } } LeaveCriticalSection(&g_CriSec); return dwRet;}


4.shellcode访问被保护页面,引发异常


异常处理函数流程:


获得发生异常的指令的地址,判断该地址是否在某个模块内,若不在,即为违规操作,调用TerminateProcess结束进程。


若在白名单内,设置EFALG寄存器的TF标识,返回继续执行,进入单步调试异常,恢复该页面的PAGE_GUARD属性。

if (pExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (g_Info.EAF) { int nIndexDll = 0; DWORD dwEip = dwCurEip; ULONG_PTR uTargetAddress = pExceptionRecord->ExceptionInformation[1];//不可访问数据的虚拟地址 //判断发生异常处的堆栈指针(EBP,ESP)和当前线程是否一致,不一致直接退出进程 CheckStack(pExceptionInfo); if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)dwEip, &AttckModuleHandle)) { if (!AttckModuleHandle) { ErrorReport(); } else { GetModuleName(AttckModuleHandle, AttackModuleName); if (ModuleInWhiteList(AttackModuleName) == FALSE) { ErrorReport(); } } } else { ErrorReport(); } GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)uTargetAddress, &TargetModuleHandle); GetModuleName(TargetModuleHandle, TargetModuleName); for (nIndexDll = 0; nIndexDll < 13; nIndexDll++) { if (strcmp((const char *)(g_Info.SystemDllInfo[nIndexDll].dwModuleName), TargetModuleName) == 0) { g_Info.SystemDllInfo[nIndexDll].dwNoGuard = 1; break; } } //如果发生异常处地址在整个模块内,设置该模块的寄存器信息 if (g_Info.SystemDllInfo[nIndexDll].dwNoGuard) { pContextRecord->EFlags |= 0x100; } return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }}//此处EAF和EAF+一起判断for (int i = 0; i < 13; i++){ if (g_Info.SystemDllInfo[i].dwNoGuard) { if (g_Info.EAF) { g_Info.SystemDllInfo[i].dwNoGuard = 0; SIZE_T ProtectSize = 0x1000; PVOID pProtectEATAddr; PVOID pProtectMZAddr; PVOID pProtectPEAddr; DWORD OldProtect = 0; if (i < 3) { pProtectEATAddr = (PVOID)g_Info.SystemDllInfo[i].dwPageAddrOfEAT; pNtProtectVirtualMemory(GetCurrentProcess(), &pProtectEATAddr, &ProtectSize, g_Info.SystemDllInfo[i].dwProtect, &OldProtect); } else { pProtectMZAddr = (PVOID)g_Info.SystemDllInfo[i].dwModuleBase; pProtectPEAddr = (PVOID)(IMAGE_NT_HEADERS*)((BYTE*)g_Info.SystemDllInfo[i].dwModuleBase + ((IMAGE_DOS_HEADER*)g_Info.SystemDllInfo[i].dwModuleBase)->e_lfanew); pNtProtectVirtualMemory(GetCurrentProcess(), &pProtectMZAddr, &ProtectSize, g_Info.SystemDllInfo[i].dwProtect, &OldProtect); pNtProtectVirtualMemory(GetCurrentProcess(), &pProtectPEAddr, &ProtectSize, g_Info.SystemDllInfo[i].dwProtect, &OldProtect); } return EXCEPTION_CONTINUE_EXECUTION; } }}


代码测试


1.在设置好断点以后写入测试代码,作用为读访问ntdll.dll的EAT地址。测试代码如下:

DWORD_PTR ModuleBase = (DWORD_PTR)GetModuleHandleA("ntdll.dll");DWORD Test = *(DWORD*)(GetModuleEAT(ModuleBase));Test = *(DWORD*)(GetModuleEAT(ModuleBase));


2.运行程序,断点跟踪进入异常处理函数。


3.当前访问ntdll.dll EAT地址的模块不在白名单内,调用TerminateProcess结束进程。


4.若在白名单内,设置EFLAG寄存器的TF位。


5.进入单步调试异常恢复PAGE_GUARD。


6.可见白名单访问受保护页面后引发0x80000001,然后又进入0x80000004异常。





EAF+机制


概述


相比EAF机制增加了更多检测点,代码逻辑一样。增加功能如下:

可检测堆栈寄存器(ESP/EBP)是否超出访问范围,检测对特定模块的DOS_HEADER/NT_HEADERS的内存读取访问。


增加检测模块:

mshtml.dllflash*.ocxjscript*.ocxvbscript.dllvgx.dllmozjs.dllxul.dllacrord32.dllacrofx32.dllacroform.api


实现流程


1.调用AddVectoredExceptionHandler注册异常处理函数。


2.对LoadLibrary系列函数进行InlineHook,当应用程序加载特定模块时进行拦截,获取加载模块的模块信息。


3.在上述获得的模块DOS_HEADER/NT_HEADERS处添加内存断点:

调用VirtualProtect将关注的模块相应地址处加上页面保护属性PAGE_GUARD。


4.当有shellcode访问被保护页面时,引发STATUS_GUARD_PAGE_VIOLATION (0x80000001) 异常。


5.进入异常处理函数,在异常处理函数中获取发生异常的指令的地址,获得该地址所在的模块名,若不在白名单内,即为违规操作,调用TerminateProcess结束进程。


6.若在白名单内,设置EFALG寄存器的TF标识,返回继续执行,进入单步调试异常,恢复该页面的PAGE_GUARD属性。


详细分析


1.注册异常处理函数。

AddVectoredExceptionHandler(0, EAFPlusVectoredHandler);


2.对LoadLibrary系列函数进行InlineHook,当应用程序加载特定模块时进行拦截,获取加载模块的模块信息,添加内存断点。

void EAF_PLUS(UNION_HOOKEDFUNCINFO::PEAFP_INFO pMemProt, HMODULE hModuleBase) { char szFileName[0x1000]; if (pMemProt->dwType == 4 && hModuleBase != NULL) { if (GetModuleFileNameA(hModuleBase, szFileName, 0x1000)) { //获得加载的文件名 char *pszCurLoadingFileName = strrchr(szFileName, '\\'); if (pszCurLoadingFileName != NULL) { pszCurLoadingFileName += 1; } else { pszCurLoadingFileName = szFileName; } for (int i = 3; i < 13; i++) { DWORD dwModuleName = g_Info.SystemDllInfo[i].dwModuleName; if (dwModuleName == 0) { break; } //如果当前加载模块是要关注的模块 if (MatchStr(pszCurLoadingFileName, (PCSTR)dwModuleName)) { DWORD dwModuleSize = GetModuleSize((DWORD_PTR)hModuleBase); MEMORY_BASIC_INFORMATION mbi = { 0 }; if (dwModuleSize != 0 && VirtualQuery(hModuleBase, &mbi, sizeof(mbi)) != 0) { EnterCriticalSection(&g_CriSec); //在此赋值,模块基地址初始化为0,在此进行比较交换,在此进行赋值 if (InterlockedCompareExchange((volatile ULONG*)&(g_Info.SystemDllInfo[i].dwModuleBase), (ULONG64)hModuleBase, 0) == 0) { DWORD dwNewProtect = mbi.Protect | PAGE_GUARD; DWORD dwSize = 0x1000; DWORD dwOldProtect = 0; PVOID BaseAddress = (PVOID)hModuleBase; PVOID PEAddress = (PVOID)(IMAGE_NT_HEADERS*)((BYTE*)hModuleBase + ((IMAGE_DOS_HEADER*)hModuleBase)->e_lfanew); g_Info.SystemDllInfo[i].dwEATAddr = GetModuleEAT((DWORD_PTR)hModuleBase); g_Info.SystemDllInfo[i].dwModuleSize = GetModuleSize((DWORD_PTR)hModuleBase); g_Info.SystemDllInfo[i].dwPageAddrOfEAT = g_Info.SystemDllInfo[i].dwEATAddr & 0xFFFF000; g_Info.SystemDllInfo[i].dwProtect = dwNewProtect; g_Info.SystemDllInfo[i].dwSize = dwSize; //为MZ头加上保护 pNtProtectVirtualMemory(GetCurrentProcess(), &BaseAddress, &dwSize, dwNewProtect, &dwOldProtect); //为PE头加上保护 pNtProtectVirtualMemory(GetCurrentProcess(), &PEAddress, &dwSize, dwNewProtect, &dwOldProtect); } LeaveCriticalSection(&g_CriSec); } return; } } } } return;}


3.shellcode访问被保护页面,引发异常。


异常处理函数流程:


获得发生异常的指令的地址,判断该地址是否在某个模块内,若不在,即为违规操作,调用TerminateProcess结束进程。


若在白名单内,设置EFALG寄存器的TF标识,返回继续执行,进入单步调试异常,恢复该页面的PAGE_GUARD属性。

if (pExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (g_Info.EAF) { int nIndexDll = 0; DWORD dwEip = dwCurEip; ULONG_PTR uTargetAddress = pExceptionRecord->ExceptionInformation[1];//不可访问数据的虚拟地址 //判断发生异常处的堆栈指针(EBP,ESP)和当前线程是否一致,不一致直接退出进程 CheckStack(pExceptionInfo); if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)dwEip, &AttckModuleHandle)) { if (!AttckModuleHandle) { ErrorReport(); } else { GetModuleName(AttckModuleHandle, AttackModuleName); if (ModuleInWhiteList(AttackModuleName) == FALSE) { ErrorReport(); } } } else { ErrorReport(); } GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)uTargetAddress, &TargetModuleHandle); GetModuleName(TargetModuleHandle, TargetModuleName); for (nIndexDll = 0; nIndexDll < 13; nIndexDll++) { if (strcmp((const char *)(g_Info.SystemDllInfo[nIndexDll].dwModuleName), TargetModuleName) == 0) { g_Info.SystemDllInfo[nIndexDll].dwNoGuard = 1; break; } } //如果发生异常处地址在整个模块内,设置该模块的寄存器信息 if (g_Info.SystemDllInfo[nIndexDll].dwNoGuard) { pContextRecord->EFlags |= 0x100; } return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }}//此处EAF和EAF+一起判断for (int i = 0; i < 13; i++){ if (g_Info.SystemDllInfo[i].dwNoGuard) { if (g_Info.EAF) { g_Info.SystemDllInfo[i].dwNoGuard = 0; SIZE_T ProtectSize = 0x1000; PVOID pProtectEATAddr; PVOID pProtectMZAddr; PVOID pProtectPEAddr; DWORD OldProtect = 0; if (i < 3) { pProtectEATAddr = (PVOID)g_Info.SystemDllInfo[i].dwPageAddrOfEAT; pNtProtectVirtualMemory(GetCurrentProcess(), &pProtectEATAddr, &ProtectSize, g_Info.SystemDllInfo[i].dwProtect, &OldProtect); } else { pProtectMZAddr = (PVOID)g_Info.SystemDllInfo[i].dwModuleBase; pProtectPEAddr = (PVOID)(IMAGE_NT_HEADERS*)((BYTE*)g_Info.SystemDllInfo[i].dwModuleBase + ((IMAGE_DOS_HEADER*)g_Info.SystemDllInfo[i].dwModuleBase)->e_lfanew); pNtProtectVirtualMemory(GetCurrentProcess(), &pProtectMZAddr, &ProtectSize, g_Info.SystemDllInfo[i].dwProtect, &OldProtect); pNtProtectVirtualMemory(GetCurrentProcess(), &pProtectPEAddr, &ProtectSize, g_Info.SystemDllInfo[i].dwProtect, &OldProtect); } return EXCEPTION_CONTINUE_EXECUTION; } }}


代码测试


1.在设置好断点以后写入测试代码,作用为读访问mshtml.dll的基地址 测试代码如下:

HMODULE ModuleBase = LoadLibraryA("mshtml.dll");DWORD Test = *(DWORD*)ModuleBase;


2.加载目标模块时,进入EAFPlus初始化函数。


3.获得加载模块的信息,添加内存断点。


4.当前访问mshtml.dll 基地址的模块不在白名单内,调用TerminateProcess结束进程。


5.若在白名单内,设置TF标志位。


6.进入单步调试异常恢复PAGE_GUARD。


7.可见白名单访问受保护页面后引发0x80000001,然后又进入0x80000004异常。



参考项目


GitHub - sheri31/EMET_Simulator: Simulate EMET

https://github.com/sheri31/EMET_Simulator


GitHub - codingtest/EMET: reversed emet tool

https://github.com/codingtest/EMET


Maxwell/MemGuard.cpp at db4d8b189ff2f39f7c5235bb6a1a9974f7532a8c · endgameinc/Maxwell · GitHub

https://github.com/endgameinc/Maxwell/blob/db4d8b189ff2f39f7c5235bb6a1a9974f7532a8c/flux/MemGuard.cpp





看雪ID:修竹kirakira

https://bbs.pediy.com/user-home-962269.htm

*本文由看雪论坛 修竹kirakira 原创,转载请注明来自看雪社区



# 往期推荐

1.四级分页下的页表自映射与基址随机化原理介绍

2.Android 10属性系统原理,检测与定制源码反检测

3.WhatsApp私信协议实现记录

4.Android4.4和8.0 DexClassLoader加载流程分析之寻找脱壳点

5.实战DLL注入

6.某车联网APP加固分析






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存