8
1.代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行
2.数据区:用于存储全局变量等
3.堆区:进程可以在堆区动态请求一定大小的内存,并在用完后归还堆区。动态分配和回收时堆区的特点。
4.栈区:用于动态存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。
PE(Port Executable)是Win32平台下**可执行文件**遵守的数据格式。
DOS头: 用来兼容MS-DOS操作系统的,目的是当这个文件在 MS-DOS 上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode. 还有一个目的,就是指明 NT 头在文件中的位置。
重点记住两个字段 :
“PE Header”是PE相关结构NT映像头的简称,其中包含许多PE装载器能用的重要字段
包含三个字段:
常用重要部分字段:
FileHeader:
OptionalHeader
节表存在与PE文件头与原始数据之间,他是IMAGE_SECTION_HEADER结构数组。
IMAGE_SECTION_HEADER结构包含了它所关联的区块信息。该数组数目由
IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出。
PE文件格式把可执行文件分成若干个数据节,不同资源被存放到不同的节中,典型的节:
1.text:由编译器产生,存放二进制的机器代码,我们反汇编和调试的对象
2.data: 初始化的数据块,如宏定义,全局变量,静态变量等
3.idata 可执行文件使用的动态链接库等外来函数与文件的信息
4.rsrc 存放程序的资源,如图标,菜单等
注意:
Microsoft Visual C++ 中的#pragma data_seg()可以把代码中任意部分编译到PE的任意节内。
在静态反汇编工具看到的PE文件中的某条指令位置是相对于硬盘文件
在调试时,看到的某条指令的地址是虚拟内存,
(1)文件偏移地址(File Offset)
数据在PE文件中的地址叫做文件偏移地址,这是文件在磁盘上存放时相对文件开头的偏移
(2)装载基址(Image Base)
PE撞日内存时的基地址,默认情况下,EXE文件在内存中的基地址是0x00400000,DLL文件是0x10000000,这些位置可以通过修改编译选项更改
(3)虚拟内存地址(VA)
PE文件中指令被装入内存后的地址
(4)相对虚拟地址(RVA)
相对虚拟地址是内存地址相对于映射基址的偏移量
VA = ImageBase + RVA
shellcode通称缓冲区溢出攻击中植入的进程代码。
Windwos进程的函数栈帧很有可能会产生“位移”,因此我们必须能够在程序运行时,动态定位栈中的ShellCode,如图
每个函数独占自己的栈帧空间,当前正在运行函数的栈帧总在栈顶。Win32系统提供两个特殊的寄存器用于标识系统顶端的栈帧
(1)ESP:栈指针寄存器,其内存存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
(2)EBP:基址指针寄存器,该指针永远指向系统栈最上面一个栈帧底部
(4)EIP:指令寄存器,该指针永远指向下一个等待执行的指令地址
(3)函数栈帧:ESP和EBP之间的内存空间为当前栈帧,包含:
(1)调用方式差异
(2)调用约定差异(VC默认使用_stdcall)
(1)Windows 2000 ~ Windows XP SP1:堆管理系统只考虑完成分配任务和性能因素,没有考虑安全因素
(2)Windows XP 2 ~ Windows 2003 :加入了安全因素,比如修改了块首的格式并且加入安全cookie,双向链表节点在删除时会做指针验证等
(3)Windows Vista ~ Windows 7 : 不论堆分配效率上还是安全与稳定性上,都是堆管理算法的一个里程碑
(1)堆是一种在程序运行时动态分配的内存,所谓动态是指所需内存的大小在程序设计时,不能预先决定。
(2)堆在使用时需要程序员用专用函数进行申请
(3)一般用一个堆指针来使用申请得到的内存,读,写,释放都通过这个指针来完成
(4)使用完毕后需要把堆指针传给堆释放函数回收这篇内存,否则会造成内存泄漏
堆块:出于性能考虑,堆区的内存按照不同大小组成块,以堆块为单位进行标识,而不是传统的按字节标识,一个堆块包括两个部分:块首和块身。块首时一个堆块头部的几个字节,用来表示这个堆块自身的信息。
堆表:堆表一般位于堆区的起始位置,用于索引堆区中所有堆块的重要信息,包块堆块的位置,堆块的大小,空闲还是占用等。对表的数据结构决定了整个堆区的组织方式,是快速检索空闲块,保证堆分配效率的关键。
重要的堆表有两种:空闲双向链表Free list(简称空表)和快速单项链表Lookaside
1.空表
空闲堆块的块首中包含一对重要的指针,这对指针用于将空闲堆块组织成双向链表。按照堆块的大小不同,空表总共分为128条。
堆区一开始的堆表区中有一个128项的指针数组,被称为空表索引。该数组的每一项包括两个指针,用于标识一条空表
每个索引项指示的空闲堆块大小与索引项(ID)相关,注意,**free[0]*标识的所有大于等于1024字节的堆块
如下关系:
空闲堆块的大小 = 索引项(ID) 8(字节)
堆管理:
Windows堆管理的几个要点:
(1)块表中的空闲块设置为占用态,因此不会发生堆块合并操作
(2)块表是单链表,操作比双链表简单,插入删除都少用很多指令
(3)块表只在精准匹配时才会分配
堆调试:不能直接用调试器Ollydbg,Windbg来加载程序,否则堆管理函数会检测到当前进程处于调试态,而是用调试态管理策略。
调试堆 和 常态堆 区别:
(1)调试堆不使用块表,只用空表分配。
(2)所有堆块都被加上多余的16字节用来防止溢出
(3)块首标志位不同
堆管理系统的三类操作:堆块分配,堆块释放和堆块合并,其本质都是对链表的修改(卸下链表或链入链表)
所有“卸下”和“链入”堆块的工作都发生在链表中,如果我们能·伪造链表节点的指针,在”卸下“ 和 ”链入“的过程中就可能获取一次读写内存的机会。
我们把这种能够想内存任意位置写入任意数据的机会称为”DWORD SHOOT“,通过DWORD SHOOT,攻击者可以进而劫持进程,运行shellcode,有如下情形
例子:
节点从双向链表”卸下“的函数:
当栈溢出时,非法数据可以淹没下一个堆块的块首:
块首可以被攻击者控制的,即块首中存放的前向指针(flink)和后向指针(blink)是可以被攻击者伪造的。这个堆块被从双向链表中卸下时,node->blink->flink = node -> flink,把伪造的flink指针值写入伪造的blink所指的地址区,从而发生DWORD SHOOT。
DWORD SHOOT的常用目标:
(1)内存变量:修改能够影响程序执行的重要标志变量,往往可以改变程序流程
(2)代码逻辑:修改代码段重要函数的关键逻辑有时可以达到一定的攻击效果
(3)函数返回地址:栈溢出通过修改函数返回地址能够劫持进程,堆溢出也一样可以利用DWORD SHOOT更改函数返回地址,但由于栈帧移位的原因,函数返回地址往往不同,故DWORD SHOOT在这种情况下有一定局限性。
(4)攻击异常处理机制:当程序产生异常时,Window会转入异常处理机制,堆溢出很容易引起异常,因此异常处理机制所使用的重要数据结构往往会成为DWORD SHOOT上等目标,如S.E.H, F.V.E.H,P.E.B,U.E.F,T.E.B
(5)函数指针:系统有时候会使用一些函数指针,比如调用动态链接库中的函数,C++中的虚函数调用等。改用这些函数指针后,在函数调用发生后往往可以成功劫持进程。
(6)P.E.B中线程同步函数的入口地址:每个进程的P.E.B中都存放着一对同步函数指针,指向RtlEnterCriticalSection()和RtLeaveCriticalSection()并且在进程退出时,会被ExitProcess调用。如果能够通过DWORD SHOOT修改这对指针中的一个,那么ExitProcess将会被骗去调用Shellcode
S.E.H即异常处理结构体,它是Windows异常处理机制所采用的重要数据结构。每个S.E.H包含两个DWORD指针:S.E.H指针和异常处理函数句柄。对于S.E.H我们需要了解一下几个要点。
(1)S.E.H结构体存放在系统栈中
(2)当线程初始化时,会自动向栈中安装一个S.E.H,作为线程默认的异常处理
(3)如果程序源代码中使用_try{}_except{}或者Assert宏等异常处理机制,编译器将最终通过当前函数栈帧中安装一个S.E.H来实现异常处理
(4)栈中一般会同时存在多个S.E.H
(5)栈中的多个S.E.H通过链表指针在栈内由栈顶向栈底串成单项链表,位于链表最顶端的S.E.H通过T.E.B(线程环境块)0字节偏移处的指针标识。
(6)当异常发生时,操作系统会中断程序,并首先从T.E.B的0字节偏移处取出距离栈顶最近的S.E.H,使用异常处理函数句柄所指向的代码来处理异常
(7)当离“事故现场”最近的异常处理函数运行失败时,将顺着S.E.H链表依次尝试其他异常处理函数
(8)如果程序安装的所有异常处理函数都不能处理,系统将采用默认的异常处理函数。通常,这个函数将会弹出一个错误对话框。
从程序设置的角度来讲,S.E.H就是在系统关闭程序之前,给程序执行预先设定的回调函数的机会。大概明白了S.E.H的工作原理,因此会有以下利用途径:
(1)S.E.H存放在栈中,故溢出缓冲区的数据有可能淹没S.E.H
(2)精心执照的栈溢出数据可以把S.E.H中异常处理函数的入口更改未shellcode的起始地址
(3)溢出后错误的栈帧或堆块数据往往会触发异常。
(4)当Windows开始处理溢出后的异常时,会错误地把shellcode当作异常处理函数而执行。
从Window XP开始,在仍然全面兼容以前地S.E.H异常处理的基础上,微软又增加了一种新的异常处理:V.E.H(向量化异常处理)
在我们已有的异常处理机制上,对于V.E.H还需要知道以下几个要点。
(1)V.E.H和进程异常处理类似,都是基于进程的,而且需要使用API注册回调函数,相关API如下:
(2)V.E.H结构的描述。
struct VECTORED_EXCEPTION_NODE
{
DWORD m_pNextNode;
DWORD m_pPreviousNode;
PVOID m_pfnvectoredHandler;
}
(3)可以注册多个V.E.H,V.E.H结构体之间串成双向链表,因此S.E.H多一个前向指针。
(4)V.E.H处理优先级次于调试器处理,高于S.E.H处理,即KiUserExceptionDispatcher(),首先检查是否被调试,然后检查V.E.H链表,最后检查S.E.H链表
(5)注册V.E.H时,可以指定其在链中的位置,不一定像S.E.H那样必须按照注册的顺序顺序压入栈中,因此,V.E.H使用起来更加灵活。
(6)V.E.H保存在堆中
(7)最后,unwind操作只在对栈帧中的S.E.H链起作用,不会涉及V.E.H这种进程类的异常处理。
我们可以利用堆溢出的DWORD SHOOT修改指向V.E.H头节点的指针,在异常处理开始后,将能够引导程序去执行shellcode,值得一提的是,标识V.E.H链表头节点的指针位于0x77FC3210,可以对比我们攻击P.E.H。
GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的溢出。
为了将GS对性能降到最小,并不是所有的函数会被保护,所以我们可以利用其中一些未被保护的函数绕过GS的保护,
程序只有在函数返回时,才会去检查Security Cookie,而在这之前是没有任何检查措施的,换句话说,如果我们可以在程序检查Security Cookie之前劫持程序流程的话,就可以实现程序的溢出。而C++虚函数给我们提供了这样的机会
GS机制并没有对S.E.H提供保护,换句话说,我们可以通过攻击程序异常处理达到绕过GS的目的。我们首先通过超长字符串覆盖掉异常处理函数指针,然后想办法触发一个异常,程序就会转入异常处理,由于异常处理函数指针已经被我们覆盖,那么我们可以通过劫持S.E.H来控制程序的后续流程。
前面介绍的几种方法都是绕开Security Cookie的校验完成绕过的,要在GS正常工作的情况下挫败它,我们就要保证溢出后栈中的Cookie与.data中的一致,因为Cookie生成具有很强随机性,所以预测其值再将其放入栈中并不现实,所以我们可以通过同时替换栈中和.data中的Cookie来保证溢出后Cookie的一致性。
纵观前面所有漏洞利用方法,都有一个共同的特征,都需要一个明确的跳转地址。ASLR技术就是通过加载程序的时候不再使用固定的基址加载,从而干扰shellcode定位的一种保护机制。
支持ASLR的程序在它的PE头中会设置IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE标识来说明其支持。
它包含映像随机化,堆栈随机化,PEB与TEB随机化。
(1)映像随机化
映像随机化是在PE文件映射到内存时,对其加载的虚拟地址进行随机化,这个地址实在系统启动时就确定的,系统重启后这个地址会变化。
(2)堆栈随机化
这项措施实在程序运行时随机选择堆栈的基址,与映像基址随机化不同的是堆栈的基址不是在系统启动的时候确定的,而是在打开程序的时候确定的。
(3)PEB与TEB随机化
微软在XP SP2之后不再使用固定的PEB基址0x7FFDF000和TEB基址0x7FFFDE000,而是使用具有一定随机性的基址,这就增加了攻击PEB中的函数指针难度。
ASLR仅仅是安全机制,不是行业标准,不支持ASLR的软件很多。不支持ASLR意味着加载基址固定。因为其加载基址是固定的,我们可以在其里面寻找合适的跳板指令来跳转到shellcode
本文发布于:2024-01-29 16:10:33,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170651583916488.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |