一个Buffer Overwritten case分析笔记
最近QA测试的时候发现在导入一个数据文件的时候会出现access violation。导入其他数据文件则没有问题。初步判断是heap corruption。
由于QA使用的是release baseline,于是本地换成了debug baseline,这样对于debug会有帮助。
在debug baseline上导入同样的数据文件,同样会出现问题。只不过由于是debug baseline,它会报出下面的消息:
Heap missing last entry in committed range near 0x2fba26b8
(你必须用windbg attach上进程,然后上述信息才会显示)
于是,看一下heap block信息。
0:008> !heap -x 0x2fba26b8
HEAP 073a0000 (Seg 25400000) At 2fba29e0 Error: invalid block size
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
2fba26b8 2fba26c0 073a0000 25400000 328 30 c busy
这个上面说一个heap block 2fba29e0的block size记录不对。
如果你注意的话,block 2fba29e0 其实就是在2fba26b8 后面的那个block。
2fba26b8 + 0x328 = 2fba29e0
于是看一下2fba29e0的meta data。
0:008> dt _HEAP_ENTRY 2fba29e0
ntdll!_HEAP_ENTRY
+0x000 Size : 0
+0x002 PreviousSize : 0
+0x000 SubSegmentCode : (null)
+0x004 SmallTagIndex : 0xcd ''
+0x005 Flags : 0xcd ''
+0x006 UnusedBytes : 0xcd ''
+0x007 SegmentIndex : 0xcd ''
heap block的meta data显然被破坏了,因为不可能分配一个0 字节的block啊
难道是2fba26b8 的block给破坏了?
由于是debug baseline, debug crt会使用32字节来存一些bookkeeping的信息。于是看一下。
0:008> dt _CrtMemBlockHeader 2fba26b8+0x8
msvcr90d!_CrtMemBlockHeader
+0x000 pBlockHeaderNext : 0x2fba1408 _CrtMemBlockHeader
+0x004 pBlockHeaderPrev : 0x2fba2778 _CrtMemBlockHeader
+0x008 szFileName : (null)
+0x00c nLine : 0
+0x010 nDataSize : 0x88
+0x014 nBlockUse : 1
+0x018 lRequest : 13136747
+0x01c gap : [4] "???"
注意nDataSize这一行。0x88?? 0x88表示用于想分配0x88个字节的空间。
还记得heap block meta data吗?再看一眼。
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
2fba26b8 2fba26c0 073a0000 25400000 328 30 c busy
heap block meta data里记录了当前这个block是0x328个字节。
heap manager和CRT 记录的不一样啊。。。。。。
顿时陷入了无穷的困惑中。。。。。。
这块内存究竟是多大了?
幸好,我们还有另外一个手段。DEBUG CRT会在用户空间的前后加上一些保护字段(fdfdfdfd)标志。来看一下吧
0:008> dc 2fba26b8 2fba2788
2fba26b8 00060065 0a0c0191 2fba1408 2fba2778 e........../x'./
2fba26c8 00000000 00000000 00000088 00000001 ................
2fba26d8 00c8736b fdfdfdfd 39c69d34 ffffffff ks......4..9....
2fba26e8 00000000 00000000 2f8cc200 00000000 .........../....
2fba26f8 00000000 cdcdcdcd cdcdcdcd 00000000 ................
2fba2708 39c66190 00000005 00000005 2fba2798 .a.9.........'./
2fba2718 00000001 00000004 00000001 00000000 ................
2fba2728 00000002 2fbe22a2 0000007b 00000001 ....."./{.......
2fba2738 00000000 00000002 00000000 00000004 ................
2fba2748 00000001 00000000 00000000 00000000 ................
2fba2758 00000000 00000001 00000000 00000000 ................
2fba2768 fdfdfdfd 00000000 00170008 0a0801a8 ................
2fba2778 2fba26c0 2fba27b8 00000000 00000000 .&./.'./........
2fba2788 00000014
注意,从2fba26e0是用户空间起点。我们注意到,在2fba2768 有结束标志fdfdfdfd。而2fba2768-2fba26e0=0x88。
这说明CRT的记录是正确的。那为什么heap block meta data会记录错误了?
如果你再注意一下,在2fba2770处,应该是下一个Heap block的起点。从上面可以看出,当前这个block size应该是0x17*8 = 0xb8。而 2fba26b8+0xb8 = 2fba2770。该地址正是下一个heap block的起点。
于是,我们可以肯定,block 2fba26b8 的meta data被别人改写了。
一般而言,都是由于前面的block越界写了。于是,我们决定看一下前面的block是啥东东。
0:008> dc 2fba26b8-0x30 2fba26b8
2fba2688 00080006 0a0b0197 2fbbd3a8 30054a80 .........../.J.0
2fba2698 00000000 00000000 00000001 00000001 ................
2fba26a8 00c96597 fdfdfdfd 656c6554 706f6353 .e......TeleScop
2fba26b8 00060065 e...
记住,0x30是前一个block size。所以我们减去0x30,就得到了前一个block 的起始地址。
我们发现,前一个heap block记录了一串字符, telescope。
但是我们发现,用户空间的前面有fdfdfdfd保护,而后面的fdfdfdfd消失了。。。
我们再看一下crt的bookkeeping信息。
0:008> dt _CrtMemBlockHeader 2fba2688+0x8
msvcr90d!_CrtMemBlockHeader
+0x000 pBlockHeaderNext : 0x2fbbd3a8 _CrtMemBlockHeader
+0x004 pBlockHeaderPrev : 0x30054a80 _CrtMemBlockHeader
+0x008 szFileName : (null)
+0x00c nLine : 0
+0x010 nDataSize : 1
+0x014 nBlockUse : 1
+0x018 lRequest : 13198743
+0x01c gap : [4] "???"
只申请了1个字节?但是却存了10个字符!!
于是,真相大白。该处只申请了1个字节,却存了10个字符。10个字符不仅把fdfdfdfd给覆盖了,而且覆盖了下一个block的meta data的前两个字节,改写了下一个block的“block size”。
接下来的工作就轻松许多了。我们只需要看一下数据文件里哪些地方存储了TeleScope这个字符,然后看看是怎么解析它的了。