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.注册异常处理函数
1 | AddVectoredExceptionHandler( 0 , EAFVectoredHandler);
|
2.获取模块的EAT地址
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 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地址 测试代码如下
1 2 3 | 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的内存读取访问。
增加检测模块:
1 2 3 4 5 6 7 8 9 10 | mshtml.dll
flash * .ocx
jscript * .ocx
vbscript.dll
vgx.dll
mozjs.dll
xul.dll
acrord32.dll
acrofx32.dll
acroform.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.注册异常处理函数
1 | AddVectoredExceptionHandler( 0 , EAFPlusVectoredHandler);
|
2.对LoadLibrary系列函数进行InlineHook,当应用程序加载特定模块时进行拦截,获取加载模块的模块信息,添加内存断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 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的基地址 测试代码如下
1 2 | 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
GitHub - codingtest/EMET: reversed emet tool
Maxwell/MemGuard.cpp at db4d8b189ff2f39f7c5235bb6a1a9974f7532a8c · endgameinc/Maxwell · GitHub
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课