探索黑客技术攻防,实战研究与安全创新

导航菜单

优看PDF在线阅读控件远程代码执行漏洞

朋友的公司要做一个项目,其中需要在网页中打开PDF文件,所以准备购买一款在线阅读PDF文件的控件产品。经过调研,他们准备选择国内一款名为“优看”的在线阅读PDF文件的控件产品。这款产品是由西安优看电子科技有限公司开发的,在朋友公司的初步测试中,该产品符合项目需要,安全测试结果也比较满意,但是,朋友还是不够放心,于是邀请我测试一下,以防万一。

从西安优看电子科技有限公司的官方网站上,我们下载了优看PDF在线阅读控件的最新版本,该版本以压缩包形式发布,解压后可以获得四个文件,其中名为“PDFView.cab"的文件就是核心控件,而“PDFTest.html”则是一个由官方提供的测试网页文件。在本地计算机上利用ASP本地测试工具搭建了一个Web测试环境,用记事本程序打开“PDFTest.html’’,我们需要修改其中的一些参数,主要有以下几个地方:

图片36.png

上述代码标红的地方,原本的网址是指向优看官方网站的,这里因测试需要,我们暂时修改为了本地计算机的回环地址,即127.0.0.1。

将优看PDF在线阅读控件压缩包文件全部解压到Web目录下,运行浏览器访问“PDFTest.html"文件,此刻会出现一个安装控件的提示,如图1所示。

图片37.png

直接点击“安装”按钮,系统会自动注册优看PDF在线阅读控件。控件注册完毕后,点击“打开网络’’按钮,这是我们就可以在浏览器中看到“YCanPDF PDFView OCX 3.4.0.0接口文档.pdf’’文件的内容,如图2所示。

请在弹出的对话框中选择“安装”。

图片38.png

通过“YCanPDF PDFView OCX 3.4.0.0接口文档.pdf"文件,我们可以知道优看PDF在线阅读控件有34个外部接口函数,并且其参数大部分都是字符串类型,这其中,很多都代表文件的名称,如longSetFileName(LPCTSTRfilename, LPCTSTR password, long lParams),这个外部接口中,第一个参数filename就代表着要被打开的PDF文件名称。之所以我们非常关心这些参数的类型和意义,是想要测试发现优看PDF在线阅读控件会不会存在缓冲区溢出漏洞,可是,在实际的测试中,我们发现优看PDF在线阅读控件对超长的字符串文件名称做了一定防范,或者说不是防范而是借助系统函数本身处理错误的机制避免了缓冲区溢出漏洞的发生。举个例子来说明一下,例如我们给“long SetURL(LPCTSTR szFilePath, LPCTSTR szPassword);”这个用来打开网络上PDF文件的外部接口传递一个超长的文件名参数szFilePath,在测试中,浏览器就会给出下载失败的提示,如图3所示

图片39.png

看了上面的测试结果,我们似乎不指望能够发现优看PDF在线阅读控件的什么安全漏洞了,但是,安全测试需要严谨的态度,我们前面只是对优看PDF在线阅读控件的外部接口函数做了单独测试,并没有将这些参数结合起来进行系统化的测试,所以,我们的测试还不全面,不能轻言放弃测试。


浏览器只会提示出“没找到”,如图4所示。

图片40.png

出现这样的结果原因很简单,主要是由于当前没有打开任何PDF文件,搜索函数无法正常运行。现在,我们修改一下代码,首先利用“long SetURL(LPCTSTR szFilePath, LPCTSTR szPassword);’’外部接口打开网络上的某个PDF文件,然后再调用“long SearchStr(LPCTSTRstrKey, long bCaseSensitive,long bAllPages)”外部接口查找一个过长字符串,看一看会有什么结果。具体测试部分代码为:

function OpenURL()

{

var a=Array(600);

vr n=YCanDF.SetURL(”http://127.O.O.l/YCanPDF PDFView OCX 3.4.0.0接口文档.pdf’,…’);//打开网络的PDF文件

YCanPDF.SearchStr(a,0,0);

}

这一次,使用浏览器打开修改后的测试网页文件,点击“打开网络”按钮,我们发现了新的结果,如图5所示。

图片41.png

终于看到了一个出错的画面,图5中的“Ox2c2c2c2c"这个内存地址显然来自于我们测试代码中的变量a。从出错提示,我们推断变量a覆盖了程序一个关键的内存地址,造成了程序发生读取内存地址失败的错误。要想知道更加细节的原因,我们可以使用OllyDBG程序来进行调试,如图6所示。

图片42.png

从图6中,我们看到在出错的时候,ECX寄存器被变量a所覆盖,程序运行到“mov edx,dword ptr[ecx]"这条指令时,由于无法读取ECX寄存器所指向的内存地址,从而发生了读取错误。但是,此刻,我们发现在出错指令的下方,第三行的位置,有一条指令“calleax’’,如果我们可以控制EAX寄存器,我们就可以执行任意代码了。

而在这条指令之上,我们可以发现EAX寄存器的值取决于EDX寄存器,而EDX寄存器的值就来自于前面被我们覆盖的ECX寄存器!至此,我们终于明白了,EAX寄存器的值来自于[[ECX]],也就是EAX寄存器的值间接的来自于ECX寄存器所指向的内存地址。我们可以这样构造攻击代码,首先通过heapspray覆盖一片内存,如OxOcOcOcOc,这片内存地址又被OxOcOcOcOc这样的数值所填充,在ECX寄存器被OxOcOcOcOc这个内存地址所覆盖后,两次mov指令最终还是指向了OxOcOcOcOc这个内存地址,只要将ShellCode放置在这个内存地址块的最后,当“call eax”指令被执行时,我们的ShellCode最终也会被顺利执行。还有一种比较难的方法,就是找到一个内存地址能够间接间接地指向一条call ebx或者jmp ebx指令,注意这里是两个间接,因为出错的时候,EBX寄存器指向的正好就是变量a所在的栈空间地址。这个内存地址的寻找需要大量的时间,我暂时没有找到, 现在,漏洞被我们发现了,exploit的代码构造思路也有了,相信写出一个攻击代码是非常简单的事情了。不过需要说明的是,这种攻击代码在IE6浏览器下应该会顺利执行,IE7、8的DEP会阻止代码执行,需要绕过DEP才行,所以,我想我们可以借助Java来实现最完美的攻击代码。

本文为网络安全技术研究记录,文中技术研究环境为本地搭建或经过目标主体授权测试研究,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均已修复,在挖掘、提交相关漏洞的过程中,应严格遵守相关法律法规。

相关推荐