本文主要是介绍安全知识点整理(未完成)(根据《逆向核心原理》和《0day安全:软件漏洞分析技术(第2版)》),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
8
二进制知识点整理
- 基础概念
- 内存功能分类
- PE文件
- PE文件结构
- MS-DOS头部
- PE文件头
- 节表
- 节
- PE文件与虚拟内存之间的映射
- ShellCode
- 定位ShellCode
- 栈溢出漏洞()
- 寄存器与栈帧
- 函数调用约定
- 函数调用与返回
- 调用步骤
- 返回步骤
- 堆溢出利用
- windows堆管理发展历程
- 堆
- 堆溢出漏洞
- 狙击Windows异常处理机制
- S.E.H概述
- 其他异常处理机制地利用思路
- V.E.H利用
- 攻击T.E. B中的S.E.H头节点
- 攻击U.E.F
- 与系统保护机制斗智
- GS安全编译
- 保护原理
- 利用未被保护的内存突破GS
- 覆盖虚函数突破GS
- 攻击异常处理突破GS
- 同时替换栈中和,data中的Cookie突破GS
- ASLR(内存随机化保护机制)
- 机制原理
- 攻击未启动ASLR模块
基础概念
内存功能分类
1.代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行
2.数据区:用于存储全局变量等
3.堆区:进程可以在堆区动态请求一定大小的内存,并在用完后归还堆区。动态分配和回收时堆区的特点。
4.栈区:用于动态存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。
PE文件
PE(Port Executable)是Win32平台下**可执行文件**遵守的数据格式。
PE文件结构
MS-DOS头部
DOS头: 用来兼容MS-DOS操作系统的,目的是当这个文件在 MS-DOS 上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode. 还有一个目的,就是指明 NT 头在文件中的位置。
重点记住两个字段 :
- +0h e_magic
- +3ch e_lfanew(指向PE文件头)
PE文件头
“PE Header”是PE相关结构NT映像头的简称,其中包含许多PE装载器能用的重要字段
包含三个字段:
- +0h Signature
- +4h FileHeader(映像文件头结构)
- +18h OptionalHeader (可选映像头结构)
常用重要部分字段:
FileHeader:
- +06h NumberOfSections(文件区块数,加壳时常需要修改)
- +14h SizeOfOptionalHeader (可选头大小)
OptionalHeader
- +28h AddressOfEntryPoint 程序执行入口RVA
- +2Ch BaseOfCode 代码区起始RVA
- +30h BaseOfData 数据区气势RVA
- +34h ImageBase 程序默认载入基地址
- +38h SectionAlignment 内存中区块对齐值
- +3Ch FileAlignemnt 文件中区块的对齐值
- +78h DataDirectory 数据目录表项
节表
节表存在与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文件与虚拟内存之间的映射
在静态反汇编工具看到的PE文件中的某条指令位置是相对于硬盘文件
在调试时,看到的某条指令的地址是虚拟内存,
(1)文件偏移地址(File Offset)
数据在PE文件中的地址叫做文件偏移地址,这是文件在磁盘上存放时相对文件开头的偏移
(2)装载基址(Image Base)
PE撞日内存时的基地址,默认情况下,EXE文件在内存中的基地址是0x00400000,DLL文件是0x10000000,这些位置可以通过修改编译选项更改
(3)虚拟内存地址(VA)
PE文件中指令被装入内存后的地址
(4)相对虚拟地址(RVA)
相对虚拟地址是内存地址相对于映射基址的偏移量
VA = ImageBase + RVA
ShellCode
shellcode通称缓冲区溢出攻击中植入的进程代码。
定位ShellCode
Windwos进程的函数栈帧很有可能会产生“位移”,因此我们必须能够在程序运行时,动态定位栈中的ShellCode,如图
栈溢出漏洞()
寄存器与栈帧
每个函数独占自己的栈帧空间,当前正在运行函数的栈帧总在栈顶。Win32系统提供两个特殊的寄存器用于标识系统顶端的栈帧
(1)ESP:栈指针寄存器,其内存存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
(2)EBP:基址指针寄存器,该指针永远指向系统栈最上面一个栈帧底部
(4)EIP:指令寄存器,该指针永远指向下一个等待执行的指令地址
(3)函数栈帧:ESP和EBP之间的内存空间为当前栈帧,包含:
- 局部变量:为函数局部变量开辟内存空间
- 栈帧状态值:保存前栈帧的顶部和底部(实际上只保存了前栈帧的底部,前栈帧的顶部可以通过堆栈平衡计算得到)
- 函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置。
函数调用约定
(1)调用方式差异
(2)调用约定差异(VC默认使用_stdcall)
函数调用与返回
调用步骤
- (1)参数入栈
- (2)返回地址入栈
- (3)代码区跳转
- (4)栈帧调整(EBP入栈,ESP装入EBP,调整ESP(分配栈帧空间))
返回步骤
- (1)保存返回值 :通常使用寄存器EAX进行保存
- (2)弹出当前栈帧,恢复上一个栈帧
-
- 包括:
堆栈平衡基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧空间
将当前栈帧底部保存的前栈帧EBP值弹入EBP,恢复上一个栈帧
将函数返回地址弹给EIP寄存器
- 包括:
- (3)跳转:按照函数返回地址跳回母函数中继续执行
上述是进行栈溢出必须知晓的知识点,很多初级pwn题,都涉及利用栈溢出漏洞淹相邻变量 || 改变程序流程 || 淹没返回地址,我就不赘述了。
堆溢出利用
windows堆管理发展历程
(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
- ExitProcess() 调用临界区函数临界函数的方法比较独特,是通过进程环境块P.E.B中偏移0x20处的函数指针来间接完成的,具体来说就是在0x7FFDF020处存放指向RtEnterCriticalSection()的指针,在0x7FFDF024处存放着指向RtlLeaveCriticalSection()指针。
堆利用注意事项 - 调试
堆管理系统会检测是否正在被调试,调试态的堆和常态的堆有很大区别,《0day安全》使用int 3中断指令在堆分配之后暂停程序,然后attach进程。 - 在shellcode中修复环境
在劫持进程后需要立刻修复PEB中的函数指针(shellcode结束也会调用ExitProcess),否则会引起很多其他异常,通常修复堆区的做法包括如下步骤
(1)在堆区偏移0x28的地方存放着堆区所有空闲块的总和TotalFreeSize
(2)把一个较大块块首中标识自身大小的两个字节修改成堆区空闲总容量的大小
(3)把该块的flag位设置位0x10(last entry尾块)
(4)把freelist[0]的前向指针和后向指针都指向这个堆块
这样可以把整个堆区“看起来好像是”刚初始化完只有一个大块的样子,不但可以继续完成分配工作,还保护了堆中已有数据。 - 定位shellcode的跳板
有时候,堆的地址不固定,因此我们不能使用固定地址,需要利用跳板地址。
- ”指针反射“现象
很显然后向指针也能导致DWORD SHOOT,像这样第二次DWORD SHOOT称为“指针反射”。
有时候在指针反射发生之前就会产生异常,然而,大多数情况下,指针反射石灰发生的,糟糕的是,它会把目标地址刚好写入shellcode中,这对于没有跳板利用DWORD SHOOT劫持进程的exploit来说是个很大限制,因为它会破环4字节的shellcode。
不过,大多数情况下4个字节的目标地址都会处理器当作“无关痛痒”的指令安全地执行过去。得注意这点!!
狙击Windows异常处理机制
S.E.H概述
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当作异常处理函数而执行。
其他异常处理机制地利用思路
V.E.H利用
从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。
攻击T.E. B中的S.E.H头节点
攻击U.E.F
与系统保护机制斗智
GS安全编译
保护原理
GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的溢出。
- 在所有函数调用发生时,向栈帧内压入一个额外的随机DWORD,这个随机数被称作“canary”,但如果使用IDA反汇编的话,我们会看到IDA将这个随机数标注为“Security Cookie”
- Security Cookie为与EBP之前,系统还会将在.data的内存区域中存放一个Security Cookie的副本。
(1)当栈溢出发生时,Security Cookie将会首先被淹没,之后才是EBP和返回地址。
(2)在函数返回之前,系统将执行一个额外的安全验证操作,被称作Security check
(3)在Security Check的系统过程中,系统将会比较栈帧中原先存放的Security Cookie和.data中的副本值,如果两者不吻合,说明Security Cookie已经被破坏,即栈中发生了溢出,
(4)当检测到栈中发生溢出时,系统将会进入异常处理流程,函数将不会被正常返回,ret指令也不会被执行。
但有些情况下函数并不会应用GS。
(1)函数不包含缓冲区
(2)函数被定义为具有变量参数列表
(3)函数使用无保护的关键字标记。
(4)函数第一个语句中包含内嵌汇编代码
(5)缓冲区不是8字节类型且大小不大于4个字节。
细聊Security Cookie: - 系统以.data节的第一个双字作为Cookie的种子,或以原始Cookie
- 在程序每次运行时Cookie的种子都不一样,因此种子具有很强的随机性
- 在栈帧初始化以后系统用ESP亦或种子,作为当前函数的Cookie,以此作为不同函数之间的区别,并增加Cookie的随机性
- 在函数返回前,用ESP还原出Cookie的种子
如上,若想在程序运行时,预测出Cookie而突破GS机制基本不可能的。
利用未被保护的内存突破GS
为了将GS对性能降到最小,并不是所有的函数会被保护,所以我们可以利用其中一些未被保护的函数绕过GS的保护,
覆盖虚函数突破GS
程序只有在函数返回时,才会去检查Security Cookie,而在这之前是没有任何检查措施的,换句话说,如果我们可以在程序检查Security Cookie之前劫持程序流程的话,就可以实现程序的溢出。而C++虚函数给我们提供了这样的机会
攻击异常处理突破GS
GS机制并没有对S.E.H提供保护,换句话说,我们可以通过攻击程序异常处理达到绕过GS的目的。我们首先通过超长字符串覆盖掉异常处理函数指针,然后想办法触发一个异常,程序就会转入异常处理,由于异常处理函数指针已经被我们覆盖,那么我们可以通过劫持S.E.H来控制程序的后续流程。
同时替换栈中和,data中的Cookie突破GS
前面介绍的几种方法都是绕开Security Cookie的校验完成绕过的,要在GS正常工作的情况下挫败它,我们就要保证溢出后栈中的Cookie与.data中的一致,因为Cookie生成具有很强随机性,所以预测其值再将其放入栈中并不现实,所以我们可以通过同时替换栈中和.data中的Cookie来保证溢出后Cookie的一致性。
ASLR(内存随机化保护机制)
机制原理
纵观前面所有漏洞利用方法,都有一个共同的特征,都需要一个明确的跳转地址。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的软件很多。不支持ASLR意味着加载基址固定。因为其加载基址是固定的,我们可以在其里面寻找合适的跳板指令来跳转到shellcode
这篇关于安全知识点整理(未完成)(根据《逆向核心原理》和《0day安全:软件漏洞分析技术(第2版)》)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!