前言:
在CVE-2017-5715漏洞曝出之后,微软做了一系列的措施来缓解,并在win10 1809(17763)之后会默认启用了Retpoline或导入优化,这取决于用户设置或cpu支持情况,也有两个都不启动的,但KASLR总是启动的,启动KASLR,会替换pte的旧值到随机化后的值,不过即使不能启用Retpoline,也会启动导入优化(1809之后),启用RetPoline或导入优化之后,winload.exe在加载ntos和hal.dll时,会替换一些指令,pe格式也发生了部分改变(win8.1已改变,后续只是增加).获取启用状态可调用ZwQuerySystemInformation的201号功能查询(>=1803版本),结构体定义如下:
typedef struct _SYSTEM_SPECULATION_CONTROL_INFORMATION
{
struct
{
ULONG BpbEnabled : 1;
ULONG BpbDisabledSystemPolicy : 1;
ULONG BpbDisabledNoHardwareSupport : 1;
ULONG SpecCtrlEnumerated : 1;
ULONG SpecCmdEnumerated : 1;
ULONG IbrsPresent : 1;
ULONG StibpPresent : 1;
ULONG SmepPresent : 1;
ULONG SpeculativeStoreBypassDisableAvailable : 1;
ULONG SpeculativeStoreBypassDisableSupported : 1;
ULONG SpeculativeStoreBypassDisabledSystemWide : 1;
ULONG SpeculativeStoreBypassDisabledKernel : 1;
ULONG SpeculativeStoreBypassDisableRequired : 1;
ULONG BpbDisabledKernelToUser : 1;
ULONG SpecCtrlRetpolineEnabled : 1;
ULONG SpecCtrlImportOptimizationEnabled : 1;
ULONG Reserved : 24;
} SpeculationControlFlags;
} SYSTEM_SPECULATION_CONTROL_INFORMATION, *PSYSTEM_SPECULATION_CONTROL_INFORMATION;
DVRT:
全名Dynamic Value Relocation Table,此表记录需要替换的指令的指针,当然,系统也可以选择不进行替换,因为这些镜像在编译时,会留出一些空隙,比如:
call qword ptr[__imp__func]
nop
即使不替换,也不会影响正常执行.
在开启pchunter,windows kernel explorer 内核钩子扫描时,会扫出大量钩子(pchunter最新版已不再扫ntos).
查看sdk 1909 ntimage.h(win8.1时就存在一些),可发现有新的结构体,如下
typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY64 {
xxxxxxxxxxxxx
DWORD GuardFlags;
IMAGE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity;
ULONGLONG GuardAddressTakenIatEntryTable; // VA
ULONGLONG GuardAddressTakenIatEntryCount;
ULONGLONG GuardLongJumpTargetTable; // VA
ULONGLONG GuardLongJumpTargetCount;
ULONGLONG DynamicValueRelocTable; // VA
ULONGLONG CHPEMetadataPointer; // VA
ULONGLONG GuardRFFailureRoutine; // VA
ULONGLONG GuardRFFailureRoutineFunctionPointer; // VA
DWORD DynamicValueRelocTableOffset;
WORD DynamicValueRelocTableSection;
WORD Reserved2;
ULONGLONG GuardRFVerifyStackPointerFunctionPointer; // VA
DWORD HotPatchTableOffset;
DWORD Reserved3;
ULONGLONG EnclaveConfigurationPointer; // VA
ULONGLONG VolatileMetadataPointer; // VA
ULONGLONG GuardEHContinuationTable; // VA
ULONGLONG GuardEHContinuationCount;
} IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64;
typedef struct _IMAGE_PROLOGUE_DYNAMIC_RELOCATION_HEADER {
BYTE PrologueByteCount;
// BYTE PrologueBytes[PrologueByteCount];
} IMAGE_PROLOGUE_DYNAMIC_RELOCATION_HEADER;
typedef IMAGE_PROLOGUE_DYNAMIC_RELOCATION_HEADER UNALIGNED * PIMAGE_PROLOGUE_DYNAMIC_RELOCATION_HEADER;
typedef struct _IMAGE_EPILOGUE_DYNAMIC_RELOCATION_HEADER {
DWORD EpilogueCount;
BYTE EpilogueByteCount;
BYTE BranchDescriptorElementSize;
WORD BranchDescriptorCount;
// BYTE BranchDescriptors[...];
// BYTE BranchDescriptorBitMap[...];
} IMAGE_EPILOGUE_DYNAMIC_RELOCATION_HEADER;
typedef IMAGE_EPILOGUE_DYNAMIC_RELOCATION_HEADER UNALIGNED * PIMAGE_EPILOGUE_DYNAMIC_RELOCATION_HEADER;
typedef struct _IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION {
DWORD PageRelativeOffset : 12;
DWORD IndirectCall : 1;
DWORD IATIndex : 19;
} IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION;
typedef IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION UNALIGNED * PIMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION;
typedef struct _IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION {
WORD PageRelativeOffset : 12;
WORD IndirectCall : 1;
WORD RexWPrefix : 1;
WORD CfgCheck : 1;
WORD Reserved : 1;
} IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION;
typedef IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION UNALIGNED * PIMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION;
typedef struct _IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION {
WORD PageRelativeOffset : 12;
WORD RegisterNumber : 4;
} IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION;
typedef IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION UNALIGNED * PIMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION;
//
// Dynamic value relocation table in loadconfig
//
typedef struct _IMAGE_DYNAMIC_RELOCATION_TABLE {
DWORD Version;
DWORD Size;
// IMAGE_DYNAMIC_RELOCATION DynamicRelocations[0];
} IMAGE_DYNAMIC_RELOCATION_TABLE, *PIMAGE_DYNAMIC_RELOCATION_TABLE;
//
// Dynamic value relocation entries following IMAGE_DYNAMIC_RELOCATION_TABLE
//
#include "pshpack1.h"
typedef struct _IMAGE_DYNAMIC_RELOCATION32 {
DWORD Symbol;
DWORD BaseRelocSize;
// IMAGE_BASE_RELOCATION BaseRelocations[0];
} IMAGE_DYNAMIC_RELOCATION32, *PIMAGE_DYNAMIC_RELOCATION32;
typedef struct _IMAGE_DYNAMIC_RELOCATION64 {
ULONGLONG Symbol;
DWORD BaseRelocSize;
// IMAGE_BASE_RELOCATION BaseRelocations[0];
} IMAGE_DYNAMIC_RELOCATION64, *PIMAGE_DYNAMIC_RELOCATION64;
可以发现有一个新的重定位表,也是DVRT.当我以旧重定位表的解析方式进行时,发现数据异常,于是就有这篇文章.
首先动态重定位表有两种方式进行定位,一是此表跟在重定位表之后,二是通过LOAD_CONFIG表得到offset,此offset为相对一个节的rva,节的索引在offset的下一个成员指出.
得到节base + rva + imagebase即可定位,另外判断镜像是否支持RetPoline,可通过LOAD_CONFIG下的GuardFlags标志进行判断,
得到DVRT表之后,Version表示版本号,动态重定位表有两个版本,由于在测试中没有发现版本号为2的镜像,故此文仅对v1版本进行解析.
size表示大小,接着跟着一个IMAGE_DYNAMIC_RELOCATION结构,
Symbol 定义如下:
#define IMAGE_DYNAMIC_RELOCATION_GUARD_RF_PROLOGUE 0x00000001
#define IMAGE_DYNAMIC_RELOCATION_GUARD_RF_EPILOGUE 0x00000002
#define IMAGE_DYNAMIC_RELOCATION_GUARD_IMPORT_CONTROL_TRANSFER 0x00000003
#define IMAGE_DYNAMIC_RELOCATION_GUARD_INDIR_CONTROL_TRANSFER 0x00000004
#define IMAGE_DYNAMIC_RELOCATION_GUARD_SWITCHTABLE_BRANCH 0x00000005
ntos的特殊一些,解析时会发现有一些Symbol对不上,实际上,ntos还包含了pte随机化,在实现pte随机化时,需要替换原定义的pte信息,定义如下:
#ifndef PXE_BASE
#define PXE_BASE 0xFFFFF6FB7DBED000UI64
#endif
#ifndef PXE_SELFMAP
#define PXE_SELFMAP 0xFFFFF6FB7DBEDF68UI64
#endif
#ifndef PPE_BASE
#define PPE_BASE 0xFFFFF6FB7DA00000UI64
#endif
#ifndef PDE_BASE
#define PDE_BASE 0xFFFFF6FB40000000UI64
#endif
#ifndef PTE_BASE
#define PTE_BASE 0xFFFFF68000000000UI64
#endif
#define PXE_TOP 0xFFFFF6FB7DBEDFFFUI64
#define PPE_TOP 0xFFFFF6FB7DBFFFFFUI64
#define PDE_TOP 0xFFFFF6FB7FFFFFFFUI64
#define PTE_TOP 0xFFFFF6FFFFFFFFFFUI64
#define MmPagedPoolStart 0xFFFFFA8000000000UI64
对于IMPORT_CONTROL:
call qword ptr [__imp_func]
nop
->
mov r10, qword ptr [__imp_func]
call _guard_retpoline_import_r10
也有可能替换为直接call r10,这种是因为启用了导入优化,
对于其它两个,windows并不一定会进行代码替换.
在较新的系统上,ntos的RtlPerformRetpolineRelocationsOnImageEx函数进行修改代码操作
对于pte随机化,只是将常量替换为新的地址
对于代码的修改
符号对应关系 未知为没有发现对应的要修改的地址
1->未知
2->未知
3->12字节 mov r10,[__imp___],call r10 or call retpoline_r1_guard
4->5字节或不修改
5->不修改
pte相关->8字节 直接将随机化后的pte相关地址替换
在支持Retpoline的pe文件中,有一个节叫RETPOL的节,
此节有替换时需要call的函数
此节类似于apiset,开头是一共结构体,此结构体定义如下:
struct _RETPOL_SECTION
{
DWORD sizeOfSection;
DWORD rva_switchtable_jmp_rax;
DWORD rva_switchtable_jmp_rcx;
DWORD rva_switchtable_jmp_rdx;
DWORD rva_switchtable_jmp_rbx;
DWORD rva_switchtable_jmp_rsp;
DWORD rva_switchtable_jmp_rbp;
DWORD rva_switchtable_jmp_rsi;
DWORD rva_switchtable_jmp_rdi;
DWORD rva_switchtable_jmp_r8;
DWORD rva_switchtable_jmp_r9;
DWORD rva_switchtable_jmp_r10;
DWORD rva_switchtable_jmp_r11;
DWORD rva_switchtable_jmp_r12;
DWORD rva_switchtable_jmp_r13;
DWORD rva_switchtable_jmp_r14;
DWORD rva_switchtable_jmp_r15;
DWORD rva_indirect_rax;
DWORD rva_import_r10;
DWORD unknow;
DWORD unknow;
DWORD unknow;
DWORD unknow;
}
通过rva+节base就能得到函数的va,也在RETPOL节中
代码:
struct TypeOffset
{
WORD Offset : 12;
WORD Type : 4;
};
typedef struct _SYMBOL_NAME {
ULONG sizeOfOneBlock;
ULONGLONG base;
const char* szName;
}SYMBOL_NAME, *PSYMBOL_NAME;
union UNION_SYMBOL_DATA
{
TypeOffset offset;
IMAGE_PROLOGUE_DYNAMIC_RELOCATION_HEADER prologue;
IMAGE_EPILOGUE_DYNAMIC_RELOCATION_HEADER epilogue;
IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION imp;
IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION inDir;
IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION switchBranch;
};
SYMBOL_NAME pteSymbolName[10] = {
{sizeof(TypeOffset), PXE_BASE, "PXE_BASE"},
{sizeof(TypeOffset), PXE_SELFMAP, "PXE_SELFMAP"},
{sizeof(TypeOffset), PPE_BASE, "PPE_BASE"},
{sizeof(TypeOffset), PDE_BASE, "PDE_BASE"},
{sizeof(TypeOffset), PTE_BASE, "PTE_BASE"},
{sizeof(TypeOffset), PXE_TOP, "PXE_TOP"},
{sizeof(TypeOffset), PPE_TOP, "PPE_TOP"},
{sizeof(TypeOffset), PDE_TOP, "PDE_TOP"},
{sizeof(TypeOffset), PTE_TOP, "PTE_TOP"},
{sizeof(TypeOffset), MmPagedPoolStart, "MmPagedPoolStart"}
};
SYMBOL_NAME normalSymbolName[6] = {
{0, 0, "error symbol name"},
{sizeof(IMAGE_PROLOGUE_DYNAMIC_RELOCATION_HEADER), IMAGE_DYNAMIC_RELOCATION_GUARD_RF_PROLOGUE, "IMAGE_DYNAMIC_RELOCATION_GUARD_RF_PROLOGUE"},
{sizeof(IMAGE_EPILOGUE_DYNAMIC_RELOCATION_HEADER), IMAGE_DYNAMIC_RELOCATION_GUARD_RF_EPILOGUE, "IMAGE_DYNAMIC_RELOCATION_GUARD_RF_EPILOGUE"},
{sizeof(IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION), IMAGE_DYNAMIC_RELOCATION_GUARD_IMPORT_CONTROL_TRANSFER, "IMAGE_DYNAMIC_RELOCATION_GUARD_IMPORT_CONTROL_TRANSFER (imp mov r10 call)"},
{sizeof(IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION), IMAGE_DYNAMIC_RELOCATION_GUARD_INDIR_CONTROL_TRANSFER, "IMAGE_DYNAMIC_RELOCATION_GUARD_INDIR_CONTROL_TRANSFER (call rax)"},
{sizeof(IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION), IMAGE_DYNAMIC_RELOCATION_GUARD_SWITCHTABLE_BRANCH, "IMAGE_DYNAMIC_RELOCATION_GUARD_SWITCHTABLE_BRANCH (jmp rcx)"}
};
/*
* zero: invalid symbol
* >0 : sizeofOneBlock
*/
DWORD GetSymbolOneBlockSize(ULONG_PTR Symbol)
{
if (Symbol == 0)
return 0;
if (Symbol > 5)
{
for (size_t i = 0; i < sizeof(pteSymbolName); i++)
{
if (Symbol == pteSymbolName[i].base)
return pteSymbolName[i].sizeOfOneBlock;
}
return 0;
}
else
{
return normalSymbolName[Symbol].sizeOfOneBlock;
}
}
void Parsefunc(PIMAGE_DYNAMIC_RELOCATION pDvr, PVOID base, ULONG_PTR dqExistRealBase)
{
if (pDvr == nullptr)
return;
auto sizeOfOne = GetSymbolOneBlockSize(pDvr->Symbol);
if (sizeOfOne == 0)
return;
auto pBaseR = (PIMAGE_BASE_RELOCATION)(pDvr + 1);
auto sizeOfReloca = pDvr->BaseRelocSize;
while (pBaseR->SizeOfBlock && pBaseR->VirtualAddress && sizeOfReloca)
{
auto diff = (ULONG_PTR)base + pBaseR->VirtualAddress;
auto item = (UNION_SYMBOL_DATA*)(pBaseR + 1);
auto count = (pBaseR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeOfOne;
for (size_t i = 0; i < count; ++i)
{
auto va = diff;
switch (pDvr->Symbol)
{
case 1:
break;
case 2:
break;
case 3:
va = va + item->imp.PageRelativeOffset;
break;
case 4:
va = va + item->inDir.PageRelativeOffset;
break;
case 5:
va = va + item->switchBranch.PageRelativeOffset;
break;
default:
// ntos kernel about the randomization of pte
va = va + item->offset.Offset;
break;
}
item = (UNION_SYMBOL_DATA*)((PUCHAR)item + sizeOfOne);
// end of block
if (va == diff && i)
continue;
auto OriginVa = va - (ULONG_PTR)base + dqExistRealBase;
if (g_bPdb)
{
ULONG_PTR dwDisplacement = 0;
auto strName = parseAddress(va, &dwDisplacement);
std::cout << va << " name: " << strName << "+0x" << std::hex << dwDisplacement << " OriginVa: " << std::hex << OriginVa << std::endl;
}
else
{
std::cout << va << " OriginVa: " << std::hex << OriginVa << std::endl;
}
}
sizeOfReloca -= pBaseR->SizeOfBlock;
pBaseR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pBaseR + pBaseR->SizeOfBlock);
}
}
void Test(const wchar_t* szFileName, ULONG_PTR dqExistRealBase)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
HANDLE hSection = nullptr;
PVOID base = nullptr;
do
{
if (szFileName == nullptr)
break;
std::wcout << szFileName << std::endl;
GetFileVersionOfPath(szFileName, nullptr, nullptr, nullptr, nullptr);
hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
{
std::cout << "open file faild" << std::endl;
break;
}
if (!NT_SUCCESS(ZwCreateSection(&hSection, SECTION_MAP_READ, NULL, NULL, PAGE_READONLY, SEC_IMAGE, hFile)))
{
std::cout << "create section faild" << std::endl;
break;
}
SIZE_T viewsize = 0;
if (!NT_SUCCESS(ZwMapViewOfSection(hSection, ZwCurrentProcess(), &base, 0, 0, NULL, &viewsize, ViewUnmap, 0, PAGE_READONLY)))
{
std::cout << "ZwMapViewOfSection faild" << std::endl;
break;
}
std::cout << "map at : " << base << std::endl;
g_bPdb = pdbParseInitializing(szFileName, (ULONG_PTR)base);
if (!g_bPdb)
std::cout << "pdb Symbol Invalid" << std::endl;
else
std::cout << "pdb Symbol ok" << std::endl;
ULONG size = 0;
auto pLoadConfig = (PIMAGE_LOAD_CONFIG_DIRECTORY64)RtlImageDirectoryEntryToData(base, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &size);
if (pLoadConfig == nullptr)
{
std::cout << "load config info is null" << std::endl;
break;
}
std::cout << "GuardFlags:" << std::hex << pLoadConfig->GuardFlags << std::endl;
std::cout << GetGuardFlagsName(pLoadConfig->GuardFlags) << std::endl;
if (!BooleanFlagOn(pLoadConfig->GuardFlags, IMAGE_GUARD_RETPOLINE_PRESENT))
{
std::cout << "current file not enable retpoline " << std::endl;
break;
}
if (pLoadConfig->DynamicValueRelocTableOffset == 0)
{
std::cout << "DynamicValueRelocTableOffset is null." << std::endl;
break;
}
// DVRT at reloc table later
auto pOldRelocaTable = (PUCHAR)RtlImageDirectoryEntryToData(base, true, IMAGE_DIRECTORY_ENTRY_BASERELOC, &size);
if (pOldRelocaTable == nullptr || size == 0)
{
std::cout << "RelocTable is null." << std::endl;
break;
}
PIMAGE_DYNAMIC_RELOCATION_TABLE pdvrt = (PIMAGE_DYNAMIC_RELOCATION_TABLE)(pOldRelocaTable + size);
if (IsBadReadPtr(pdvrt, sizeof(IMAGE_DYNAMIC_RELOCATION_TABLE)))
{
std::cout << "DVRT is bad : " << std::hex << pdvrt << std::endl;
break;
}
std::cout << "version: " << pdvrt->Version << std::endl;
std::cout << "size: " << std::hex << pdvrt->Size << std::endl;
if (pdvrt->Version == 2)
{
std::cout << "DVRT version is 2, current version not support parse" << std::endl;
break;
}
DWORD sizeOfDvr = 0;
PIMAGE_DYNAMIC_RELOCATION pDvr = (PIMAGE_DYNAMIC_RELOCATION)(pdvrt + 1);
while (sizeOfDvr < pdvrt->Size)
{
if (IsBadReadPtr(pDvr, sizeof(IMAGE_DYNAMIC_RELOCATION)))
{
std::cout << "bad read ptr of Dvr :" << std::hex << pDvr << " size: " << std::hex << sizeOfDvr << std::endl;
break;
}
if (pDvr->Symbol > IMAGE_DYNAMIC_RELOCATION_GUARD_SWITCHTABLE_BRANCH)
{
auto bParsed = false;
for (size_t i = 0; i < sizeof(pteSymbolName); i++)
{
if (pDvr->Symbol == pteSymbolName[i].base)
{
std::cout << "Symbol : " << pDvr->Symbol << " parse: " << pteSymbolName[i].szName << std::endl;
bParsed = true;
break;
}
}
if(!bParsed)
std::cout << "Symbol : " << pDvr->Symbol << " parse: unknow" << std::endl;
}
else
{
std::cout << "Symbol : " << pDvr->Symbol << " parse: " << normalSymbolName[pDvr->Symbol].szName << std::endl;
}
std::cout << "baseRelocSize : " << pDvr->BaseRelocSize << std::endl;
// parse data
Parsefunc(pDvr, base, dqExistRealBase);
sizeOfDvr += pDvr->BaseRelocSize + sizeof(IMAGE_DYNAMIC_RELOCATION);
auto tmp = pDvr->BaseRelocSize;
pDvr = (PIMAGE_DYNAMIC_RELOCATION)((PUCHAR)(pDvr + 1) + tmp);
}
std::cout << "TotalSize: " << std::hex << sizeOfDvr << std::endl;
std::cout << "any key to unmap image" << std::endl;
system("pause");
} while (false);
if (g_bPdb && base)
pdbParseTerminate((ULONG_PTR)base);
g_bPdb = false;
if (base)
ZwUnmapViewOfSection(ZwCurrentProcess(), base);
if (hSection)
CloseHandle(hSection);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
Mitigating Spectre variant 2 with Retpoline on Windows - Microsoft Community Hub