导语:目前已经有许多API被滥用。有些人则以非常系统的方式进行了研究,他们发现了许多程序(包括操作系统)在使用注册表API时不检查注册表数据类型,这通常会导致类型混淆漏洞,这个漏洞可能会被滥用。读了本文,你会发现如果可以将路径或文件
Windows API就是Windows应用程序接口,是针对microsoft Windows操作系统家族的系统编程接口,这样的系统包括Windows 8.1,Windows 8,Windows 7,Windows Vista、Windows XP、Windows Server 2012、Windows 2008 R2 、Windows Server 2003、Windows 2000、Windows 95、Windows 98、Windows Me(Millennium Editon)和Windows CE等几乎所有版本。
其中32位Windows操作系统的编程接口称为Win32 API,以便与以前16位版本Windows编程接口(16位Windows API)区别开来。
当WINDOWS操作系统开始占据主导地位的时候,开发WINDOWS平台下的应用程序成为人们的需要。而在WINDOWS程序设计领域处于发展的初期,WINDOWS程序员所能使用的编程工具唯有API函数,这些函数是WINDOWS提供给应用程序与操作系统的接口,它们犹如“积木块”一样,可以搭建出各种界面丰富,功能灵活的应用程序。所以可以认为API函数是构筑整个WINDOWS框架的基石,在它的下面是WINDOWS的操作系统核心,而它的上面则是所有的华丽的WINDOWS应用程序。
程序员想编写具有Windows风格的软件,必须借助API,API也因此被赋予至高无上的地位。但是,如若没有合适的Windows编程平台,那么Windows开发是一项很复杂的工作。在可视化编程环境出来之前,那时的WINDOWS程序开发还是比较复杂的工作,程序员必须熟记一大堆常用的API函数,而且还得对WINDOWS操作系统有深入的了解。然而由于这些优秀可视化编程环境操作简单、界面友好(诸如VB、VC++、DELPHI等),在这些工具中提供了大量的类库和各种控件,它们替代了API的神秘功能。不过这些类库和控件都是构架在WIN32 API函数基础之上的,是封装了的API函数的集合。它们把常用的API函数的组合在一起成为一个控件或类库,并赋予其方便的使用方法,所以极大的加速了WINDOWS应用程序开发的过程。
总而言之,虽然Windows API陈旧,但却非常可靠,即使程序崩溃,系统也足够聪明,可以清理所有那些未被正确释放的句柄、内存分配,并以一种适当的方式处理任何其他错误。这种对系统清理的信任创建了一个安全生态系统,在这个生态系统中,许多程序仅仅依靠操作系统来修复程序员的错误。
不过当某些类型的Windows API调用失败时,则意味着一个更严重的问题,即底层系统可能已经非常不稳定了。忘了是哪位编程界的大虾说过:“API不要去学,在需要的时候去查API帮助就足够了”。这句话背后的意思是,我们对待API函数不必刻意去研究每一个函数的用法,那也是不现实的,目前能用得到的API函数有几千个。但由于许多API函数令人难以理解,易于误用,还会导致出错。所以几十年来,许多编程书籍,论坛等都使用了现成的样本代码片段,没有几个人愿意检查这些API调用的实际结果,他们都假设调用不会失败。这个想法非常危险,也就是说,过去30年中编写的许多程序都包含许多这样的API调用,且假设默认情况下它们100%可信,按样本那样工作,没有人检查错误或预期结果……
不过随着技术的发展,对底层系统的攻击不再那么容易了,所以攻击者的目标就集中在了依靠系统的Windows API。由于这些旧代码片段的代码量是巨大且复杂的,并且不宜更新和修复,所以许多至今还存在于Windows API中。
目前许多软件开发人员仍在使用一些所谓的“非常好”的错误代码样本,比如依靠环境变量来确定%systemroot%(系统中的一个变量,表示的是windows系统启动文件夹位置)或其他路径实际上是一个非常糟糕的想法,尽管在许多编程论坛上,这种方法已被作为标准的行业惯例,但问题是用户可以在运行应用程序之前通过操纵它们来控制这些变量,并强迫程序以不可预期的方式运行。
另一个例子是文件系统和文件处理增强功能的出现,前几天我提到Windows 10中引入了一些新的应用机制,允许API在处理文件名时超出最大路径长度限制。这对于那些老的应用程序来说是一个严重的问题,因为这个新机制可能会让它们无法使用一些具有新特性的文件。这就会影响许多日常运行,比如逆向工程,沙箱等。由于此类新机制不可能兼容以前的特性,这些情况就会得不到相应的解决,甚至还可能被攻击者利用。有趣的是,超长文件名的问题还不是Windows API的新问题,它已经存在很久了。
如果你想进行简单的测试,请尝试重命名你的收藏夹。以下这些文件名都是用于测试的应用程序的.exe(记得删除换行符):
0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789.exe
当你尝试打开重命名后的应用时,你可能会看到“拒绝访问”的提示,这可能是因为系统或你的程序shell排斥它。不过有时,重命名后的应用也可以运行,但也可能经常会发生崩溃。以下是一个简单的sysmon崩溃样本,是把应用程序重命名为如此长的文件名后,然后执行时的情况。
甚至像Total Commander这样的调试器和程序也无法找到这样的文件,Total Commander,简称TC,原名Windows Commander,功能强大的全能文件管理器。
所以我认为这其中必然有着很大的漏洞,但目前还没有搞清楚是什么?不过还有更多有趣的API和“代码模式”可供查找。
在上一篇文章中,我提到有时编码人员不会遵守确切的API规范,比如MSDN,因此他们可能会在自己开发的程序中引入一些限制,而这些限制可能会被滥用。
如果要获取当前进程已加载模块的文件的完整路径,GetModuleFileName函数必须由当前进程加载。如果想要获取另一个已加载模块的文件路径,可以使用GetModuleFileNameEx函数。而这些无辜的函数会被很多应用使用,包括安全工具。如果了解MSDN,就知道如果路径的缓冲区太小,该函数将截断路径以使其完全填充缓冲区,进而将最后一个字符包含ASCIIZ并返回ERROR_INSUFFICIENT_BUFFER。
注意,此时操作系统将截断路径以使其填充缓冲区。为什么操作系统会这样做,而不是坚持返回错误?我真的不知道……
不幸的是,由于许多编码人员都误认为他们的缓冲区足够大,而其实典型的分配缓冲区只能挤出260个字符大小的空间,并且它是一个静止在堆栈上分配的缓冲区,并不会动态变化。由于编码人员很少检查这种错误(不检查缓冲区是否太小,是否使用本地分配的缓冲区),实际上这种错误很常见,包括MSDN上的许多样本。
如果你想知道为什么程序只分配260个字符,那你就要知道API名称其实是一段代码。这段代码指的就是模块文件名,但返回的数据实际上是一个完全限定的路径。虽然文件名的长度明显受限,但完整路径可能比260个字符长得多。
让我们设想一个简单的场景,假如你设备里的一个可信程序正在使用GetModuleFileName获取最大路径(128个字符)。假如这时有恶意用户发现GetModuleFileName正在获取路径并将程序重命名为140个字符,这样可信API会将模块文件路径截断为指向另一个可能是恶意文件的路径。如果这样的程序使用获得的信息将自己的二进制文件复制到目标文件夹中(许多应用程序都这样做),那么由于这个截断,复制的路径函数将指向另一个恶意的文件。事实证明,一旦GetModuleFileName检索到这种不合逻辑的路径,合法程序实际上可能会根据文件名(假定为正确的)将文件复制到目标目录。通过操纵文件名和路径长度,攻击者可以强制受信任程序运行恶意文件。
换句话说,攻击者正式通过利用实际的API返回值没有被正确地测试或处理的疏忽来下手的,通过修改程序的逻辑就可以使得程序按着他们的预期运行。
以下我会使用一个较短的路径作为解读样本,假设c:\folder\good.exe就是一个用于攻击的长文件名,此时程序在执行过程中会将自己的文件复制到预期的目标目录,但由于程序分配了一个非常小的缓冲区(15个字符而不是19个字符)来检索其完整路径,因此会导致截断的路径指向不同的文件名:
c:\folder\good.exe:程序预期的实际路径,但由于缓冲区太小(比预期的19个字符少了4个字符),实际路径会被截断;
c:\folder\good:由于截断而导致程序代码复制“good文件”而不是“good.exe”,这就会使程序预期的实际路径发生重定向。
下图显示了其中的工作原理:第一个调用会将完整路径复制到19个字符长的缓冲区,第二个调用会将程序的文件名部分复制,并返回被许多应用程序忽略的错误。
我承认我有点过度关注文件复制,但错误的代码可能在很多其他方面被滥用。例如,如果应用程序使用返回的路径作为其他API函数的输入,则这些函数可能会最终读取错误的文件,在用户界面上显示不正确的信息,而系统则将不正确的信息写入日志等。
目前已经有许多API以类似的方式被滥用,我不是第一个发现它的人,许多研究人员至少关注这一领域20年了。有些人则以非常系统的方式进行了研究,他们发现了许多程序(包括操作系统)在使用注册表API时不检查注册表数据类型,这通常会导致类型混淆漏洞,这个漏洞可能会被滥用。读了本文,你会发现如果可以将路径或文件名更改的足够长,则至少可以使某些应用程序崩溃;如果你可以将路径或文件名更改为足够长的路径或文件名,并且应用程序编码的安全检查很差,那么使用获取的名称就可以影响程序的运行结果。