首页
社区
课程
招聘
[分享]堆溢出研究二
发表于: 2017-5-14 16:05 9195

[分享]堆溢出研究二

2017-5-14 16:05
9195

堆溢出研究一
堆溢出研究二  
堆溢出研究三

工具:ollydbg 2.0版本 & vc6.0(release模式)  编译选项默认 os: windows2000

函数的抽离
在堆中进行内存分配的时候,C语言函数调用的是malloc()函数,c++中调用new()函数,当动态调试进入函数内部的时候察觉此两个函数调用的都是底层 ntdll.dll中的 RtAllocateHeap()函数,所有的windows分配堆的函数在底层调用的都是此函数,这也死程序员可以看到的关于堆的最底层函数。因此研究堆分配,重点关注此函数即可。

在此之前需要理解一个概念:调试堆与调试栈不同,不能直接加载或者attach 程序,否则堆管理策略就会采用调试状态下的堆管理策略,使用调试状态下的堆管理函数。

正常堆和调试堆的区别:
1.调试堆只采用空表分配,不采用快表分配
2.所有的堆块末尾都加上十六个字节的用来防止程序溢出,(仅仅是用来防止程序溢出,而不是堆溢出),其中这十六个字节包括:
8 * 0xAB + 8 * 0x00
3.块首的标志标志位不同,调试状态下的堆和正常堆的区别如同debug下的PE文件和release下的PE文件类似,做堆移除实验的时候,调试器中可以v正常运行的shellcode,单独运行却不行。很可能就是调试堆与正常堆的差异造成的。

为拉避免采用调试状态下的堆,我i们直接在程序中嵌入 int3 断点,然后调用实时调试器即可: 源码:

step: 1. 调整ollydbg 为 just in time (实时调试器)
2. 直接进行编译链接运行程序,根据程序中的 int3 断点. ollydbg会直接断在int3断点出如图所示:

如上图所示程序断点段在拉地址 VA = 0040101D处,此时使用快捷键 ALT + M 查看内存映射窗口来到如图所示重点部分已经标注出来:

如上图所示可以、得到信息:发现进程堆地址为:  00130000 大小为0x6000  (此处可以通过函数 GetPcocessHeap()函数获得句柄)如图:

还有我们程序中创建出来的堆地址是0x00360000 size = 0x1000

根据上图中的信息我们直接转到程序中创建出的堆地址 0x360000处在(数据窗口  直接 快捷键 ctrl + g )
 
对于上图来到地址 0x360000处后,根据和堆溢出有关的数据结构我们直接关注 空表索引区即可(即偏移地址 0x178地址处):

当堆刚被初始化的时候结构很简单,
1. 其中只包含一个空闲大块(称为 “尾块”)
2. 此尾块地址位于 0x178(360178)处 (未启用块表的情况下)算上基地址就是 0x360688  (又称为freelist【0】 )  
   
3.freelist[0] 指向“尾块 ‘,八个字节 (前四个字节是前向指针 后四个字节是后向指针 即:空表中的一对指针) ,其余的各项索引都指向其自身

堆块的块首占八个字节下面根据占用态和空闲态分别介绍:
 

 
共同点

0-2 字节代表本快的大小(包括块首)
2-4字节表示计算单位是多少字节

不同点

Flags出 占用态标志是1  空闲态标志是 0
空闲态块首后的八个字节为一对指针,分别是前向指针和后向指针。当堆块变为占用态的时候重新回分配数据。
实际上尾块的起始位置是 0x360680

因此根据地址 0x360680处八个字节的情况可以知道:此尾块的大小是 0x130  计算单位是 0x0008 个字节  总大小是 0x980字节。

我们直接在cpu窗户 命令 F8单步执行程序到地址:0x00401028地址处也就是在源码中我们执行完:

当h1被分配完以后直接查看地址:0x360178地址处的值:    

此时的地址0x360178处的值已经从0x360688改变为0x360698  同时跳转到 0x360698,如下图:
 
如上图所示:在地址0x360698出值为0x360178 链表的条件。
同样的根据地址0x360688处的值即:分配的h1可以发现,h1的大小是 0x002 size = 16bytes
接下来直接运行到地址 0x00401059 此时直接查看 0x360178的地址出看到 值已经更改为:0x360708.接下来直接来到0x360680处进行查看
h1 - h6的分配情况如下图所示:  

如上所示:尾块现在的地址是:0x360700 大小是 0x120 = 0x130 - 0x2 * 4 - 0x4 * 2
以上从h1 - h6的分配情况验证啦 空表分配中的找零钱现象(从一个大块中依次一小块一小块地进行切割)

接着上面的程序执行,直接执行到地址:00401077地址处

分别释放啦堆块 h1 h3 h5这样做是防止相邻堆块进行堆块的合并。直接查看地址 0x360178地址处的值重点观察变化的值如下图:
 
从上图中可以发现地址 0x360188 的值发生啦变化 从原来的指向自身现在变为指向:0x360688  0x3608A8  
地址0x360198处的值变化为: 0x003606C8 和 0x003606c8
由上图可知 h1 h3分别被释放到 freelist[2] 空表中, h5被释放到啦 freelist【4】空表中。

根据freelist【2】 的空表索引 以及h1 h3堆块的指针组,可以发现 :

如图所示左边箭头是前向指针,顺序为 Frllist -> h1 > h3  右边是后向指针 顺序是 h3> h1 > freelist[2]
对于h5堆快倒是没啥 ,freelist【5】直接索引到 地址 0x3606c8

接着程序运行直接运行到地址 0x401080地址处,执行的是代码:

当释放h4的时候会发生堆块的合并现象(两个连续的空闲块就会发生合并)。首先是先从空表中将三个空闲块摘下,重新计算合并后的堆块的大小,然后合并成新的空闲块,链入空表。如下图所示分别为空表索引区状态和合并后堆块状态:  


如上图所所示:地址 0x3606A0处的值 0x0008 即是:合并后的堆块的大小。后八个字节的指针对,则指向空表的索引区。

快表和空表的区别在于 HeapCreate()函数的参数的不同。

源码:

与空表的申请大致类似。
环境:与空表使用的环境一样
直接在dunp窗口中进行跳转到 0x360688处,此时发现快表为空。这也是为什么要反复申请释放内存的原因,接下来分别申请 8,8,16,24字节的内存,然后进行释放,(快表未满时释放到快表中)。  
先运行程序到地址 0x40109F处。此时直接观察快表中的变化,此时发现让然为空,下面运行释放程序,直接单步执行命令运行到地址:0x401106处,这是观察快表的变化如图所示:

运行程序到地址 0x40110D处观察堆块是否链如块表:
 
如上图所示h1 - h4已经链接进入块表中并且都是处于占用态。 地址 0x361e90指向下一个堆块(因为h1 h2 同时为八字节的空闲堆块)
当程序运行到地址 0x401140时(也就是执行完申请内存的代码时)

此时申请的堆块应该从块表中申请,此时查看堆表区的索引:
 

从以上两图中可以看到当继续申请内存的时候,是从快表lookside[2]处卸下的堆块。当释放的时候,还是将空闲堆块释放到此处执行代码:


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 1
支持
分享
最新回复 (6)
雪    币: 2575
活跃值: (502)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
2
内容精彩,学习学习
2017-5-14 16:48
0
雪    币: 1746
活跃值: (227)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
3
重新学习一下堆的知识,快表是这样链入的么lookside[1]->h2>h1,还有楼主错别字挺多的
2017-5-31 23:14
0
雪    币: 81
活跃值: (165)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
错别字好多,而且好多地方讲的不够清晰
2017-10-30 15:40
0
雪    币: 300
活跃值: (2457)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
mark
2017-11-9 10:57
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
2-4 表示计算单位是多少字节这个描述不对吧,不是应该是前一个块的大小嘛,我怎么越看越不对啊
2020-5-8 19:55
0
雪    币: 259
活跃值: (283)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
越看越糊涂 你第一章不是说 堆表开头是空闲表吗 怎么又未启用堆表的情况下 在0x178 是怎么算出来的 
2020-5-11 05:04
0
游客
登录 | 注册 方可回帖
返回
//