Windows批处理解析漏洞[壹]

在本文开始之前,请注意这是一份十分冗杂的信息安全通告。有关于Windows控制命令符,我可能发现了一个可以通过简单批处理文件进行攻击的BUG。这个BUG存在于Windows 2000版本以上的64位以及32位机器上,它是一个批处理解析错误。它不需要额外安装任何软件(cmd.exe是Windows默认安装的),它可以由任意权限的用户发起(假设他们可以运行cmd.exe,从而解析批处理文件)包括解析错误发生的位置(故障代码能够解释这个原因)。这并不是一个远端控制层次的BUG,仅仅是一个DoS类型,需要得到用户请求才能运行(或者是把其作为一个开机启动项)。但是由于他的简单性,以及Windows系统的普及率,我个人认为它值得你看上那么一眼。

请注意,如果你启用这些批处理文件导致系统崩溃我是不负责任的!任务管理器会结束这个失控脚本的PID,以防止你运行。

Tldr:一个仅仅包涵^ nul<^ 的批处理文件会造成一个巨大的内存泄漏,在一个仅仅只有 ^|^  的批处理文件会使命令行无限递归导致崩溃。这些行为可能导致一些有趣的批处理编程,当然我是指的在windows2K版本以上的操作系统中,原因在于cmd.exe在处理批处理文件时发生的逻辑错误。

0x01

当我再回复一位用户提出的问题时,我遇到一个十分有趣的批处理文件解析异常,如果^这个字符是文件的最后一个字符,就可能发生内存泄漏,插入文件的最后一个字符不能够是 \n (换行符),由于 \r (回车符)在脱字符号之前就已经执行,所以没有这种情况发生,解析器能够正常工作,没有什么可以追踪到(当插入^\r\t,解析却成为了^\r “\t”就被忽略掉了)。最后注意一下,回车字符是能够正常执行的,当然这也是一个小趣点,我们可以在大多数文本编辑器中对回车字符造假,在记事本的最后你可以傻逼的认为那里还有一个回车符

经过一系列的折腾,我发现^字符在文件的最后可能导致内存泄漏,或者会致使命令提示符崩溃(具体点说就是 command.com 或者cmd.exe程序),我还发现特殊的批处理文件(及其序列)会引起一些有趣的现象。进一步的调查,引导我去关注 是否其他人也有遇到过类似的情况, a Stack Overflow question where a user noted a memory leak(在Stack Overflow提问中一个用户表示内存泄漏)在SS64.com的主题 other interesting behaviors with the caret at the EOF(在文件最后加上回车符引起的有趣现象)。Stack Oveflow上面的提问帮助我确认了这并不是一个无限循环类型的情况,但是并没有解释清楚到底是怎么一回事,在SS64.com的主题中大部分内容都是在讨论从各个方面让命令提示符崩溃,但是并没有对原理进行解释。

自然的在我心中产生了疑问,到底是怎么发生的?怎么发生的呢?这个情况是否可以进行利用呢?答案是复杂的,但是解决方法确是很简单(至少看起来简单),我发现一些欺骗批处理文件组合,能够产生内存泄漏。是快是慢取决与你放入的批处理文件是什么(不是插入了多少字符,而是管道序列,以及行数长度)无论是命令提示符崩溃还是内存泄漏,解析代码一直都是在单线程中驻留,所以CPU的占用量一直在增涨(单核CPU平均占用率达到98%)

0x02

让命令提示符崩溃很简单,一个没有换行的批处理文件只需要包涵 ^&^ (或者 ^|^)就足以导致命令提示符出现0xC00000FD错误 ,这是一个由于无限递归栈/帧出现的泄漏。这重现了“无限循环”的场景,但没有完美解释内存泄漏(或者说是为什么这个无限循环会导致崩溃),事情到这个程度,我开始通过进行一些最简单的方法来产生一个内存泄漏,事实证明,一个2字节的批处理文件是需要消耗整颗CPU以及吃内存的(尽管速度慢的离谱,~8k/5s on a 2.2GHz i7)

0x03

我用来测试的批处理文件包含以下16进制字节

0x01 0x5E

0X5E 是脱字符号(^) ,0X01是标题开始的Ascll码。第一个16进制代码,在这个BUG中危险程度没有其它的高,因为他不能是NULL(0X00),\r, |, 或者 & 这些字符会导致批处理文件通过正常途径退出(也就是 ‘invalid command detected’)。我使用0X01证实它没有一个有效的命令(或者显示出有关的字符)导致这个BUG,一个2字节的批处理文件包含一个简单的^就足够了。

做一些其他测试,我发现在批处理文件最后加上^ nul<^是最快也是最简单的消耗CPU的方法。

随机的运行若干次后,请提高注意,这么做去消耗64位系统CPU是很快的。仅仅是花了20S,就吃掉了我14G的RAM(总共16GB,四核I7并且使用超线程技术),最后我不得不重启电脑。在32位系统上运行这个快速版本,尽管32位系统有2GB的限制,命令解析器也会很快的因为内存不足而结束任务。它不会导致命令提示符崩溃,相反的,它似乎有一个检测机制保护内存分配,当它不能够直接终止批处理进程时。

0x04

注意,尽管这些“批处理利用”能够彼此链接,所以在32位操作系统上(假设其只有4GB RAM且没有物理扩展内存),可以运行一下命令构造DoS:cmd eat_mem.bat | eat_mem.bat 这将启动2个命令解释器,且每个都将消耗2GB。

另外一个有趣的变化是崩溃批处理文件之间的联系。比如说,假设有一个文件名为crash.bat 它包含了以上我们说的“崩溃利用” (^&^),你可以这么干:cmd crash.bat | crash.bat  然后注意发生的一些有趣行为,由于解析错误它会输出的cmd crash.bat

写到崩溃的管道里,从而使你试图使用CTRL+C终止时,出现wrote to a non-existent pipe错误。非常有趣的是它仍然允许你执行命令,比如键入notepad然后敲击回车,再然后它就启动了记事本程序。一段时间,我认为有很大可能存在管道劫持,但是却没有得到任何答案(下面有很多利用方法)

0x05

在Stack Overflow中已经提到,许多个^字符在一行中,这样做消耗内存很快(除了我发现的那个快速版本),通过大量的测试实验,我发现“行长”事实上在这个BUG中会带来一些有趣的效果。如果在一个正好8192字节长的批处理文件的最后有一个^字符,这个BUG就不会成功…只要低于和高于8192那么这个BUG就成立。在一个字符串中字节越多,那么消耗内存就越快。

应当注意的是,文件大小与这个BUG是没有关系的,因为它可以放到任何一个无害的文件当中。做一个测试,我将“快速”版本(^ nul<^)放入一个我曾经遗留的代码中进行编译,这个编译脚本可以进行递归调用以及其他一些功能,仅仅只有21KB,(请允许我编译这些东西)将这一行放进文件最后,允许我正常运行编译脚本,并且完全正常,但是当它触及到BUG时,CPU狂转,并且迅速的吃掉了我的内存。

如果我是一个谦逊的软件工程师,给定一个“简单”的编译脚本(在这个BUG中随机调用:eof )如果我的CPU狂转我不会多加思考,如果我没有注意到任务管理器,我不会看到我的RAM会如此迅速的被吃掉,然后重新启动(这TMD不好玩)

0x06

自此,我决定去做一些高级别的检测,去看看命令提示符到底发生了什么,我打开Process Explorer以及Procmon工具去检测在这个过程以及系统(通过Procmon工具)发生了什么。Procmon确认这个8K(更准确的说应该是8192字节)泄漏会调用ReadFile一个8191字节的缓冲区,Process Explorer证实会调用到ReadFile的核心。但是我不是很确定批处理文件是怎么被读取得。

这个BUG也允许批处理文件在运行时进行修改并且再次解析,应当注意内容可以被修改,命令会进行处理,但是我注意到除非有大量其他命令(更具体的来说,在^符号之后)

这个批处理利用,吸引我想着用编程方式去玩玩,我第一个尝试是一个简单的C程序

system(“crash.bat”); 调用包含了^&^的批处理文件,单独启动一个cmd.exe  进程,随后我的程序返回控制就崩溃(预期效果)。然后我切换到简单的内存泄漏程序(只包含一个^)并再次运行程序,这一次cmd.exe 可以跑起来,但是当我按下CTRL+C  进程中断,然后返回我的程序控制,让我不得不使用任务管理器结束cmd.exe的进程。我也尝试在程序中直接使用文件(换句话说,就是直接调用system(“^ nul<^“);)但是得到有效的结果返回(在这种情况下合法,意味着命令被解析器认为会是无效的)我尝试hook程序的变化,或者利用栈/帧溢出错误,但是由于cmd.exe解析器本身,一些太过明显的exploits,没有看上去那么So Easy。到这个程度上,为了更好的理解发生了什么,以及它如何可能被利用,我决定打开cmd.exe程序进行检测。

未完待续….

【翻译@91ri.org团队】