首页
论坛
课程
招聘
[原创]漏洞原理笔记——堆溢出
2021-4-14 18:41 2527

[原创]漏洞原理笔记——堆溢出

2021-4-14 18:41
2527

最近在学习堆的溢出原理,但是查了网上和书上的一些讲解,总是感觉缺少一些关键点。所以理解起来总是有点晕,经过努力的调试分析后,总结了下面的更为详细的堆溢出原理。如果不准确的地方,希望大佬可以提醒一哈~

1.堆的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct _LIST_ENTRY{
    DWORD Flink;
    DWORD Blink;
}
//空闲堆头结构
struct _HEAP_FREE_ENTRY{
    union{
        struct{
            union{
                struct {
                    WORD Size;
                    BYTE Flags;
                    BYTE SmallTagIndex;
                }
                DWORD SubSegmentCode;
            }
            WORD PreviousSize;
            union{
                BYTE SegmentOffset;
                BYTE LFHFlags;
            }
            BYTE UnusedBytes;
        };
        struct{
            union{
                struct {
                    WORD FunctionIndex;
                    WORD ContextValue;
                };
                DWORD InterceptorValue;
            };
            WORD UnusedBytesLength;
            BYTE EntryOffset;
            BYTE ExtendedBlockSignature;
        };
        struct{
            DWORD Code1;
            WORD  Code2;
            BYTE  Code3;
            BYTE  Code4;
        };
        ULONGLONG AgregateCode;
    };
    _LIST_ENTRY FreeList;
}
 
//已使用堆的头结构
struct _HEAP_ENTRY{
    //这个结构只比_HEAP_FREE_ENTRY少了一个FreeList;
}
//上面的结构不是百分百正确的,这些都是通过WinDbg调试猜的。

下面这个是winDbg中的数据结构截图。
图片描述
图片描述
这两个结构仅仅就差了个FreeList。

2.堆溢出原理

当堆溢出发生时,可能会将后面的堆结构全部覆盖掉。而后面的堆有可能是个使用过的_HEAP_ENTRY,也有可能是一个空闲堆_HEAP_FREE_ENTRY。

 

如果当后面是一个空闲堆时,就可以将_HEAP_FREE_ENTRY最后的FreeList结构覆盖成自己想要的数据。而FreeList是一个_LIST_ENTRY结构。这个结构中有两个成员,分别是Flink和Blink。分别表示的是上一个空闲堆和下一个空闲堆。

 

那么关键来了,当你调用HeapFree释放刚刚使用的堆时,就会触发一种机制,就是将释放的堆与后面的空闲堆相合并。但此时后面的空闲堆已经被你污染覆盖了。而在合并的过程中,他会修改空闲堆Blink和Flink所指向的空闲堆中的FreeList,以便让前后的空闲堆衔接在一起(因为空闲堆就是一个双向链表,当某一个节点发生改变时,你就要需要把前面的节点指针和后面的节点指针都重新做一遍调整,也就是重新连接双链表节点)。

 

这个机制就直接导致了堆溢出利用的可能性。因为不管是什么溢出,我们最终的目的都是控制EIP,就是覆盖栈中的返回地址。而到现在,可能还有人没有意识到问题的严重性,那么继续。

 

假设:需要释放的堆为A,后面被覆盖污染的空闲堆为B。

 

此时被污染覆盖的空闲堆B中的Flink/Blink已经是我们可以随意修改的地址。那么当释放的堆A和污染空闲堆B进行合并时,会变成一个更大的空闲堆C,那么此时空闲堆C的首地址就会发生改变,所以需要重新连接空闲堆链表。想要重新连接,就需要找到上一个空闲堆,然后并将其中的Blink修改为合并后空闲堆C的首地址。可能已经有人感觉到一丝丝的不寻常了,继续。

 

因为合并后的空闲堆C中的Flink和Blink是我们可以随意修改的。这就导致上一个空闲堆是我们可以随便指定的。而系统根本不知道我们会指向那里,他依旧会傻傻的去修改所指向地方的Blink。假如此时我们将空闲堆C中的Flink地址覆盖成栈地址。那么他就会把栈当成上一个空闲堆。然后去栈中寻找Blink的位置,并将其修改为空闲堆C的首地址。虽然我们的堆A已经释放了,但里面的数据还在啊,系统可不会勤快的置空呢。而空闲堆C的首地址,其实就是堆A的首地址。因为A和B合并时,其实就是把B叠在了A的屁股后面。
图片描述
释放了一个有用堆。然后他会和后面的空闲堆合并在一起。而且空闲堆首地址也改变了。
图片描述
而A中是有我们自己的数据的。这样就可以将栈中的数据覆盖成我们自己的数据,甚至长度算好了也可以覆盖栈中的返回地址。

 

OK,这就是堆的溢出的其中之一原理啦,如果有错误的地方,希望大家指正。


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回