FreeList原理

双向链表管理系统,指针数组的每一位没有上限

Lookside原理

单向链表管理系统,数组的每一位最多存储四个堆快

调试

FreeList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 调试源码
#include <windows.h>
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;
}

地址0x178代表FreeList[0],初始化后是指向地址0x688,在进行6次分配后,指向0x708处。为什么是指向0x708处,这里做一个简单的计算,分配了6次,分配的堆size大小分别为2,2,2,2,4,4,一共就是0x10个堆快,一个堆快大小是8byte,也就是一共使用的堆大小为0x10*0x8 =0x80

image-20220511223936411

查看内存0x688信息,可以看到h1的块首大小为两个堆块,1个堆快大小为8byte,h1堆快一共占用16byte。一定要注意堆头部中的size大小是计算了堆头部,也就是一定会有8个固定字节来保存堆头部

image-20220511225811110

在使用三次HeapFree后查看内存,观察双向链表,FreeList数组中前4byte是双向链表的表头,后4Byte是链表表尾

image-20220511231717368

在经过第四次HeapFree后,会进行堆合并,将h3,h4,h5合并为一个堆,大小为这3个堆快size大小总和8,所以FreeList数组中FreeList[8]发生改变,FreeList[2]和FreeList[4]还原。

image-20220511232958426

image-20220511232727394

LookSide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 调试源码
#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);
h3 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 16);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
HeapFree(hp,0,h2);
}

在0x360178位置存放的堆尾地址发生了变化,从0x688变成了0x361E90

image-20220516233650031

查看0x688(在初始化后堆开始的地方)

image-20220516233930991

在进行三次HeapAlloc后Free内存在次查看块表内容,8字节堆快在Array[1]中,16字节堆快在Array[2]中

image-20220516234449254

在内存中查看堆块的结构,每一个堆快在释放后依旧状态依旧为Busy

image-20220516235625551

通过块表原理知道这是一个单向链表,再次申请一个16字节的堆快,Array[2]中存放的指针会清零。再次申请一个8Byte的堆快0x361EA0处地址也会清零

image-20220517000653094

DWORD shooting

原理

通过操作堆快的Flink和Blink可以达到任意地址写入四字节

1
2
3
4
5
6
int unlink (ListNode * node)
{
node -> blink -> flink = node -> flink;
node -> flink -> blink = node -> blink;
return 0;
}

当在node这个块节点写入数据时,如果造成堆溢出覆盖了node节点后的堆快的头部指针,当进行unlink时产生安全问题,可以往任意地址写入四个字节,这也是DWORD shooting名称的由来

调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 调试源码
#include <windows.h>
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;
}

利用方法

这个东西感觉将再多用处也不大,原理是双向链表在进行脱链时造成指针的重放,这里引用书上的一副图和unlink的伪代码

image-20220517002023768

如果可以修改一个精心挑选的函数指针,将其修改为shellcode的地址,那么在调用该函数时,就会执行shellcode,从而造成任意代码执行