之前写过Win32运行库的DWORD Shoot,随着windows安全性检查的升级,简单的DWORD shoot并不能达到利用程序的目的。但是这种技术就被淘汰了吗?我个人觉得并不是这样,因为DWORD shoot技术本质上是钻了链表出链时不检查内存地址合法性的漏洞,因此只要在代码中找到有链表存在的影子就有利用的可能。

C Programmer一般用Crt运行库提供的库函数malloc/free管理程序的内存,而malloc最终调用HeapAlloc分配内存。我们知道HeapAlloc分配的内存块是_HEAP_ENTRY+用户可见区+尾块的形式,malloc调用HeapAlloc,因此它获得的内存块也遵循这样的形式。无非Crt管理器将用户可见区一部分内存挪为己用。这部分内存用_CrtMemBlockHeader结构进行管理,在VS2010中的定义为:

typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
#ifdef _WIN64
/* These items are reversed on Win64 to eliminate gaps in the struct
* and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
* maintained in the debug heap.
*/
int nBlockUse;
size_t nDataSize;
#else /* _WIN64 */
size_t nDataSize;
int nBlockUse;
#endif /* _WIN64 */
long lRequest;
unsigned char gap[nNoMansLandSize];
/* followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
} _CrtMemBlockHeader;

从pBlockHeaderNext和pBlockHeaderPrev域,可以看出crt库以双向链表的方式用来管理所有调用malloc分配出去内存块。如果调用free时,crt库并没有检查pBlockHeaderNext和pBlockHeaderPrev变量的合法性,就在无意中为我们提供了利用的机会。分别查看vs2010下Debug版本和Release版本对free的调用堆栈,可以看到如下输出:

;release版本
0:000> bp ntdll!RtlFreeHeap
0:000> g
Breakpoint 1 hit
0:000> kb
ChildEBP RetAddr Args to Child
0012ff5c 00401049 00390000 00000000 003928e8 ntdll!RtlFreeHeap
0012ff70 00401b6f 003928e8 7ffda000 00f0f6ee malloc!free+0x1c [f:\dd\vctools\crt_bld\self_x86\crt\src\free.c @ 51] //_free_base (void * pBlock)
0012ff84 004011f4 3aeb8a72 00f0f6ee 00f0f7aa malloc!_wsetenvp+0x9d [f:\dd\vctools\crt_bld\self_x86\crt\src\stdenvp.c @ 139]
0012ffc0 7c817067 00f0f6ee 00f0f7aa 7ffda000 malloc!__tmainCRTStartup+0xd0 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 259]

;debug版本
0:000> bp ntdll!RtlFreeHeap
0:000> g
Breakpoint 1 hit
0:000> kb
ChildEBP RetAddr Args to Child
0012fef4 00431839 00390000 00000000 00392930 ntdll!RtlFreeHeap
0012ff10 00428f6e 00392930 00392930 0012ff58 malloc!_free_base+0x29 [f:\dd\vctools\crt_bld\self_x86\crt\src\free.c @ 50]
0012ff20 00428a70 00392950 00000002 7ce76772 malloc!_free_dbg_nolock+0x4ae [f:\dd\vctools\crt_bld\self_x86\crt\src\dbgheap.c @ 1431]
0012ff58 0042d57b 00392950 00000002 00000012 malloc!_free_dbg+0x50 [f:\dd\vctools\crt_bld\self_x86\crt\src\dbgheap.c @ 1265]
0012ff78 00427557 7ce76792 00f6f6ee 00f6f7a6 malloc!_wsetenvp+0x16b [f:\dd\vctools\crt_bld\self_x86\crt\src\stdenvp.c @ 138]
0012ffb8 0042746f 0012fff0 7c817067 00f6f6ee malloc!__tmainCRTStartup+0xd7 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 259]

借助调用堆栈在结合crt源码,可知Release版本的程序并没有使用_CrtMemBlockHeader结构管理堆内存;而Debug版本的程序使用_CrtMemBlockHeader用于管理调用malloc分配的堆内存,更重要的一点,vs2010调用free从链表中移除已分配的堆内存时并没有做安全检查,于是,我们有了DWORD Shoot的机会。很幸运的是,crt管理的双向链表和Windows堆管理器管理的空闲链表数组FreeList[128]不同:在win xp sp1以上的系统上,堆管理器对堆分配释放进行严格的安全检查(严格检查_HEAP_ENTRY,_Free_HEAP_ENTRY等结构的正确性),因此出现了文章开头提到的DWORD shoot失效的情况;但是crt提供的堆管理并不在进程堆管理器的管理范围之内(毕竟,对于OS来说_CrtMemBlockHeader结构是在用户可见内存范围内),换句话说,DWORD shoot crt库的free函数是一个很好的选择。

附注,本文分析利用crt库的可行性,具体的事实过程和前面几篇blog大同小异,因此就不细写了。

参考资料:张银奎 <软件调试> 23章 Crt运行库