最近看逍遥的《网络渗透攻击与安防修炼》讲到CMD命令窗口的dir传超长字符串溢出的例子。自己实验了一下,的确会产生程序崩溃,但是具体什么原理没太详细说,这里做一下原理探究,权当学习笔记了。
1. 实验环境 XP SP3
我发现在XP下的cmd.exe有这个漏洞,而在win7下之后的cmd.exe就没有这个漏洞了
我的理解是这个cmd.exe程序本来就是windows的一个80x86的实模式模拟环境,在win下又进行了重写,加固了程序。所以我接下来的实验材料取自XP下的cmd.exe
2. 漏洞定位
我们都知道,不管是ODAY还是利用错误提示挖掘软件漏洞,第一步都要先定位出漏洞的位置,即调用哪个函数出错了,或者对某些非法内存进行读写出错了。只有搞清楚了发生错误的位置,我们才能进一步利用漏洞。好,我们用OD一步步的往下调试。
加载CMD.EXE,按F9继续运行,因为我们知道cmd一定会在某处停下来等待用户交互,然后才执行下一步操作。也就是会停在windows消息函数的那个无限循环的位置。所以我们大胆的按F9。
这个时候程序UI出现了如下的字样:
Microsoft Windows XP [版本 6.1.7601]
(C) 版权所有 1985-2001 Microsoft Corp.
C:\Users\Administrator\Desktop>
即开始进入无限的消息循环,等待用户的进一步输入。这时候我们按下F12暂停程序,并打开函数调用栈 ALT + K。我们通过这种方法来追踪cmd程序会停在什么地方以及都调用了什么函数。
注意到调用堆栈中的这一行。
主线程, 条目 1
地址=0018FDC0
堆栈=4AD0EFEF
函数过程 / 参数=kernel32.ReadConsoleW
调用来自=cmd.4AD0EFE9
结构=0018FDBC
很明显,这是一个win api,用来获取用户的输入参数的。我们顺藤摸瓜,对这个函数下断。重新执行一次。断点后按F8,程序开始等待我们输入,注意:这里开始就是重点了。我们为了能更容易的看出问题。我们输入多一点的字符串 dir \\?\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa......aaaaaaaaaaaa(输300个左右就可以了),按下回车,程序继续恢复执行。
接下来就是漫长的追踪过程,不断按F8,如果遇到了某个call运行后出错,即跳进了NTDLL的raiseexception领空,按“-”减号退回一条指令,即上一条指令,说明错误在这个call里面,对这个call下断,重新执行一次,F9执行到这里后,F7进入..............
重复这个过程,直到来到关键的代码处:
即执行了一个win api程序竟然也会发生错误,进入了NTDLL的SEH错误处理领空。只是一个简单的wcscpy复制函数怎么会触发错误呢?这种情况按照我之前的经验来说只发生过一次,就是复制导致了栈空间的溢出(不是说缓冲区溢出,是整个栈溢出,就是复制太多太多字符串了,整个栈都被填满了,栈越界了,导致CPU报错)。那这次是不是这个情况呢?我带着这个疑问继续探究。
首先,通过MSDN,我们搜索一下这个wcscpy的函数声明:
wchar_t* wcscpy (wchar_t* destination, const wchar_t* source);
Copy wide stringCopies the C wide string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point). To avoid overflows, the size of the array pointed by destination shall be long enough to contain the same C wide string as source (including the terminating null character), and should not overlap in memory with source. 这个函数是用来从source指定的位置开始把UNICODE字符串逐个复制到destination。这个UNICODE字符串以0000作为结束。 好,我们再来观察一下寄存器和堆栈中关于source和destinatino的情况。
0x18E6B4是源地址,到数据窗口跟踪一下看看:
没啥,很正常的UNICODE数据。
我们继续查看dest的情况,destination的地址是0x18E8C0。定位到数据区看看。
可以看到,dest的位置落在了我们输入的字符串的范围内(也就是落在了source + sizeof(输入数据)的范围内)。
看到这里,我突然就明白了。因为wcscpy是逐个字符复制的,但是因为我们输入的参数超长了,超过了给source分配的空间,于是输入数据就覆盖了一部分dest的空间,导致dest的起始位置落在了soure的范围内
。这样,就导致每次都source复制一个,就给dest复制一个,两边同步增长,就像两辆速度一样的骑车并行,而直接的后果就是wcscpy一直看不到0000这个结束符,就会一直复制下去。最终的结果就是超过
线程栈的最大空间1M。CPU报错栈溢出。触发SEH。 我们把dest - source - 3 * 2 = 256。也就是说我们填入256个a是正好不触发错误的极限。
dir \\?\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
填入257个及以上a就会发生覆盖。导致错误。
为了验证这个观点,我们可以填入256个a,然后在wcscpy复制函数的api执行处断下:
可以看到,指针正好处在source和dest的边界位置。
如果继续增长参数长度,就会导致wcscpy的无限循环复制,最终导致线程栈的溢出。
至此,cmd的dir溢出原理基本搞清楚了,我自己感觉这只是导致了溢出错误,至于要怎么利用这个漏洞进行shellcode执行,一时还没想到。如果有朋友知道的话,望不吝赐教。我非常希望能有一样喜欢信安的
朋友一起讨论问题。
qq:306211321