内容简介:在本篇文章和本系列的其他文章中,我会把一些内部构建解压到动态堆数据结构和相关的beast中。这篇文章是专门为没有堆背景知识的人准备的,不过可能会涉及到一点ELF内部构建和调试的知识。本文还会详细讲解一些实验,通过完成这些实验,你可以学到堆的工作原理是什么。堆其实就是执行程序时用来存储数据的内存区域列表。存储在堆内存区域中的数据在运行时进行请求调用。它允许像glibc这样的运行时环境为程序提供动态内存来分配数据。由于内存区域作为一种服务(它的作用就是如此),也就意味着在整个混乱的内存区域中,肯定需要关于内存区
在本篇文章和本系列的其他文章中,我会把一些内部构建解压到动态堆数据结构和相关的beast中。这篇文章是专门为没有堆背景知识的人准备的,不过可能会涉及到一点ELF内部构建和调试的知识。本文还会详细讲解一些实验,通过完成这些实验,你可以学到堆的工作原理是什么。
介绍
堆其实就是执行程序时用来存储数据的内存区域列表。存储在堆内存区域中的数据在运行时进行请求调用。它允许像glibc这样的运行时环境为程序提供动态内存来分配数据。由于内存区域作为一种服务(它的作用就是如此),也就意味着在整个混乱的内存区域中,肯定需要关于内存区域的计算信息。为了实现这一点,堆使用“chunk”这种内部结构来描述或修饰用户数据区域。根据其属性对块进行分类和分组。基本属性如下:
·是否可用
· 块的大小
· 在列表中,块的前后都有哪些块等
内存管理中最重要的一点是,它的本质就是围绕块搜索函数来定位块,然后执行释放或重新分配。
本文我将重点关注的堆分配器是glibc版本的ptmalloc,在glibc版本2.23-2.28中实现。当然,这并不是说只有glibc才能理解;堆分配存在多种方法。关于如何实现各种操作,每一种方法都是独特的。比如合并空闲的块,搜索和分类空闲块,并进行快速分组。当然可能还有更多其他功能—比如提升安全性。所以很多位置因为其复杂性会滋生并恶化为安全问题。但这些问题的根源在于用户是如何请求数据的,还有管理数据的分配器是如何响应内存区域中的元数据的。
最后再介绍一点,堆通常看起来都非常密集和复杂,并且内部构件非常粗糙,但是大部分构件,如aid memorization(帮助备忘),还有其他计算机技术,都有助于加快链表搜索速度。你也可以这样说,它们只不过是存储“cheat”元数据的巧妙的方法,这些元数据不需要在每次搜索时都搜索整个堆内存区域。但是这些元数据对我们来说很重要,因为在某些情况下,我们想要改变链表的搜索和解释方式。
Heap speak
你可能已经猜到堆的基本单位是chunk。你可能也想知道这些块在glibc代码中是什么形式,请看下面的代码:
这里我会对每一个字段做一个通俗易懂的解释(尽可能通俗易懂)。
· INTERNAL_SIZE_T–这是一个大小类型,用于在堆管理中定义“bookeeping”函数的字段 – 诸如指针(地址)和位字段之类的东西。这个大小由具体的实现来定义。我们可以猜想glibc想要在不同的硬件和运行时的实现中具有可移植性和灵活性 – 因此映射到INTERNAL_SIZE_T的地址大小可能会有所不同。无论如何,INTERNAL_SIZE_T被定义为size_t – 它可以追溯到C运行时最初解决问题的方式。
· mchunk_prev_size– 是块格式的第一部分,无论是空闲块还是已用块,都会有这个部分。该字段指示当前块的前一个块的大小,并且如果所引用的块是空闲的,则其最低有效位被设置为0x1。因此,如果你正在查看一个块,并且其prev_size的最小sig位为0x1,那么就在此之前仍然是“使用”的块。
· mchunk_size– 非常标准,实际上只保存当前大小,以字节为单位lol。
· struct malloc_chunk * fd– 这是struct块结构中的一个字段,用于定义另一个块的地址空间。这是因为它形成了一个链表。这里定义的链表是“空闲列表”,它将堆上空闲的所有块拼接在一起。这里我们在链表中定义“正向指针”。
· struct malloc_chunk * bk– 这个字段与上面提到的字段类型相同,只不过这个是“反向指针”。
· struct malloc_chunk * fd_nextsize– 这个字段来自堆中的另一层空闲链表技术。如果指针高于特定大小阈值(我们将在稍后介绍),则将此指针添加到空闲块中 – 以便堆管理器可以在它们出现时跟踪大的块。它有点像在赌场中的高级玩家,当你出场时,他们跟踪你的行为动作,因为你很大程度上能够影响当晚的盈利能力。
我们再来看一下这些在执行过程中是怎么样的,看看不同类型的块看起来有什么区别(空闲块和已分配的块)。我们现在通过gdb来运行一个C程序,然后解压缩堆来显示它在内部是如何响应的。C程序如下:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
char * make_string(size_t length){
char *arr = (char *)malloc(length);
asm("int $3");
return arr;
}
void free_string(char *arr){
free(arr);
asm("int $3");
}
int main(int argc, char **argv){
int _len = 128;
int index = 0;
char *array,*array_1,*array_2,*array_3,*array_4;
//char *array_;
//char *array__;
//find a way to show the chosen candiate for each round
int inner_array = 0;
int _char = 1;
for(index = 0;index <= _len;index++){
array = make_string(_len);
memset(array,0xAA,_len);
printf("[*] array @[%p]\n",array);
/* 4 more allocations*/
array_1 = make_string(_len+80*1);
printf("[*] array @[%p]\n",array_1);
memset(array_1,0xBB,_len+80*1);
array_2 = make_string(_len+80*2);
printf("[*] array @[%p]\n",array_2);
memset(array_2,0xCC,_len+80*2);
array_3 = make_string(_len+80*3);
printf("[*] array @[%p]\n",array_3);
memset(array_3,0xDD,_len+80*3);
array_4 = make_string(_len+80*4);
printf("[*] array @[%p]\n",array_4);
memset(array_4,0xEE,_len+80*4);
/*free each array and clear it */
memset(array,0xFF,_len);
free_string(array);
memset(array_1,0xFF,_len+10);
free_string(array_1);
memset(array_2,0xFF,_len+20);
free_string(array_2);
memset(array_3,0xFF,_len+30);
free_string(array_3);
memset(array_4,0xFF,_len+40);
free_string(array_4);
_char++;
printf("\n\n");
}
//printf("[*] done");
return 0;
}
我知道这段代码可能有点长,你可能不想一行一行的看,所以你可以完全忽略其他的mallocs和空闲块。这里我添加它们是为了举例说明,然后逆向一些更有意思的数据。
在上面的代码中,我添加了一个简单的wrapper函数,而且在最后一个return之前下了一个断点。这样我就可以隔离我们正在研究的内存区域上的空闲块和malloc调用效果。
现在让我们看看堆分配内存时会发生什么。我们需要先找到一个指向堆的指针。这很简单,因为malloc会在返回主函数后将其保存在rax中,我在前几个gdb命令中显示:
所以当我设置好hook-stop时,它会输出$rax-0x10附近的所有内容,这是保存块头信息的地址。我这样做是因为当我们下这个断点时,malloc刚好返回并将寄存器设置为其返回值 – 这就是分配的内存区域的地址。我们可以直接看到这些宏指令如何在glibc / malloc/malloc.c中对堆元数据数据进行操作:
你可以看到它只是简单地增加或减少2个地址以获取mem(用户数据启动的原始内存指针)或两个地址之前的块信息。还有许多其他操作可以提取和设置其他元数据。
好的,这就是基本格式,接下来我们来看看它是如何运作的。
不断增长的堆
在下了第一个断点后你应该看到gdb显示分配的第一个堆块,下图是一个带有注释版本的堆分配图,显示了堆的格式:
当在你的屏幕中出现这段内容时,尝试执行“c”gdb命令跳到下一个断点。你会看到更多已分配块的示例,然后在你的屏幕上会显示如下:
这就表明我们不能像以前在hook-stop中使用$ rax中的值那样。可能你会想,这是因为$ rax不再保留内存指针,它现在转到了一个空闲调用,因此它保留了一些其他值。无论如何,我们可以使用传递给free_string函数的地址来转储块,因为在这里我们可以非常方便地看到它显示出来。这是块被释放后的样子:
除了空闲块之外,上面的图片中显示的是第一个空闲块fd(正向空闲链表)和bk(反向空闲链表)指针。这里我们可以看到,如果我们使用gdb的内存检查器函数来跟踪它们,它们最终会在0x602a00位置结束,这是顶部块的地址; 指向当前分配的堆地址顶部的指针。
好的,这就是当块被分配和释放时的样子,我们能够查看块是如何合并成更大的空闲块呢?当然可以,这是下一篇文章的内容。
空闲块合并
在分配了块之后,我们的程序将按照它们分配的顺序释放每个块。这意味着我们可以认为彼此相邻的被分配的两个块,也是相继被释放并且相邻的-所以,我们就有了两个空闲块合并成了一个大的块。
看起来是下图这样的:
上图的左边我们能看到的是地址0x602580和0x6024a0处的两个块(名为块1和块2)。在右边0x6024a0地址处我们可以看到一个新的块,但是我们可以看到合并后的大小字段是0x211(如图所示 也就是0xe1 + 0x130)。
这差不多就是块合并的整个行为。这也是我这篇文章想要讲解的东西。在这个系列文章中,我还会讲到fast-bins,大块管理,也可能讲一些堆重定向技巧。
敬请期待。
以上所述就是小编给大家介绍的《Glibc堆漏洞利用基础-深入理解ptmalloc2 part1》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 漏洞分析:对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- MacOS/iOS CVE-2019-6231 漏洞深入分析
- 由Typecho 深入理解PHP反序列化漏洞
- 深入分析Windows系统DHCP漏洞(CVE-2019-0726)
- 对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- Apache Tomcat从文件包含到RCE漏洞原理深入分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Mathematica Cookbook
Sal Mangano / O'Reilly Media / 2009 / GBP 51.99
As the leading software application for symbolic mathematics, Mathematica is standard in many environments that rely on math, such as science, engineering, financial analysis, software development, an......一起来看看 《Mathematica Cookbook》 这本书的介绍吧!