扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
在本页阅读全文(共2页)
CCProxy是一个国产的支持HTTP、FTP、Gopher、SOCKS4/5、Telnet、Secure(HTTPS)、News(NNTP)、 RTSP、MMS等代理协议的代理服务器软件。因为其简单易用、界面友好,非常适合在对流量要求不高的网络环境中使用,所以在国内有很多初级的网管喜欢用这个软件,有时候我在公司上网也要用它做代理。前些日子我测试发现CCProxy 6.0版本存在多处缓冲区溢出漏洞,可以导致攻击者远程执行任意代码。
TIPS:什么是Gopher、RTSP、MMS?
Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。允许用户使用层叠结构的菜单与文件,以发现和检索信息,它拥有世界上最大、最神奇的编目。
RTSP是Real Tranfer Stream Protocol的缩写,翻译为实时传输流协议,用来传输网络上的流媒体文件,如RM电影文件等,它的下载方法请看后文《悄悄下载流媒体》。
MMS是Multimedia Messaging Service的缩写,中文译为多媒体信息服务,它最大的特色就是支持多媒体功能,可以在GPRS、CDMA 1X的支持下,以WAP无线应用协议为载体传送视频短片、图片、声音和文字,彩信就算MMS协议中的一种。
漏洞发现过程
其实发现这个漏洞是很偶然的,当时在考虑公司产品的黑盒测试方案的时候,我想使用模板+测试用例的方式来进行网络部分的边界测试。这是比较传统的黑盒测试方法,其核心内容就是把网络协议包分块,然后制定出对各个块的测试策略,最后按照策略对所有块逐步进行测试。这种测试方法的好处在于可以有效的控制测试进度,以及可以比较详细地测试一个网络协议的所有部分,而且在测试过程中还可以不断地加入新的测试用例,以完善测试计划。测试程序的编写则可以使用脚本语言或C来完成。
TIPS:什么是黑盒测试?
黑盒测试法把程序看成一个黑盒子,完全不考虑程序的内部结构和处理过程。黑盒测试是在程序接口进行的测试,它只检查程序功能是否能按照规格说明书的规定正常使用,程序是否能适当地接收输入数据产生正确的输出信息,并且保持外部信息的完整性。黑盒测试又称为功能测试。
我就是用这种方法测试公司的产品的时候,恰好当时在公司内部网之间使用CCProxy做代理服务器,因为测试HTTP协议要通过这个代理,所以测试过程中发现CCProxy崩溃了,也因此发现了这个漏洞,算是偶然吧。
漏洞分析过程
CCProxy的HTTP代理端口默认是808,这个端口可以在其界面中进行更改。漏洞的原理很简单,就是对CCProxy的 HTTP代理端口发送URL超过4056字节的畸形请求,CCProxy就会发生堆栈溢出。后来发现,不仅仅是GET请求存在此问题,所有POST、 HEAD等请求也都会导致溢出。在分析其原理后又发现CCProxy的Telnet代理也存在该问题。
现在来详细介绍一下我分析这个漏洞的过程。在发现了发送超长请求可以导致CCProxy出错以后,就开始分析溢出点以及利用限制。所要用到的工具是SOFTICE调试器和IDA反汇编工具。
TIPS:很多人都知道使用WINDASM反汇编,但经过它反汇编出来的代码非常简单,很不容易看明白,但IDA就不一样了,它不但会反汇编程序,并会尽量分析程序,并加上相应的注释。正因为这样,IDA反汇编一个大的程序会花非常长的时间。
整个调试分析漏洞的过程如下:首先在SOFTICE中下断点:Bpx ntdll!KiUserExceptionDispatcher,这个命令的意思就是程序运行到Ntdll.dll中的 KiUserExceptionDispatcher就停下来,交给SOFTICE进行处理。KiUserExceptionDispatcher这个函数是Windows的异常处理过程中的很重要的一个步骤,它负责派发用户层空间发生的所有异常到异常链中的异常处理函数地址,每当在用户层空间发生异常的时候就会调用这个函数。SOFTICE默认是没有加载Ntdll.dll的,所以我们可以通过DriverStudio套件中的一个叫做“Symbol Loader”的工具的Load Exports来加载这个DLL文件。
设好断点后,就用一个简单的程序向808端口发送“GET /AAAA[...4056 bytes] HTTP/1.0<回车><回车>”这样的超长字符串,其中包含4056个A的超长URL。发送之后SOFTICE会跳出来并停在KiUserExceptionDispatcher断点上,这时候用“dd (*(esp+4))+b8”命令来查看出错时的EIP地址,这是因为KiUserExceptionDispatcher的函数参数是一个指向保存异常发生时的寄存器的结构,该结构从偏移0x8c的位置开始保存寄存器值,其具体保存寄存器值如下:
0x08c Gs
0x090 Fs
0x094 Es
0x098 Ds
0x09c Edi
0x0a0 Esi
0x0a4 Ebx
0x0a8 Edx
0x0ac Ecx
0x0b0 Eax
0x0b4 Ebp
0x0b8 Eip
0x0bc Cs
0x0c0 EFlags
0x0c4 Esp
0x0c8 Ss
用“dd (*(esp+4))+b8”命令发现出现异常的EIP是0x41414141,我们知道0x41就是A的ASCII码,所以现在已经证实我们发送的超长字符串已经覆盖了某个函数的返回地址,导致该函数返回的时候返回到0x41414141这样的地址。此时退出SOFTICE就会弹出 “0x41414141指令引用0x41414141的内存。该内存不能read。”这样的应用程序错误对话框。
TIPS:“0x41414141指令引用0x41414141的内存。该内存不能read。”这种是典型的溢出提示,明确告诉我们溢出的地址和错误,对进一步分析溢出很有好处。
接下来,我们还需要知道究竟是什么地方导致函数返回地址被覆盖的。因此我们逐渐减少发送的字符串长度,发现当发送4039字节时就不会导致出错了。由于堆栈和返回地址被覆盖,我们无法看到导致溢出的地址到底是哪里,但是根据经验推测,这可能是一个由Strcpy、Strcat或Sprintf等函数导致的溢出。因此我们在这些函数下断点,然后再次发送4056字节的长URL命令。但是发现没有在这些函数中断下来,证明溢出过程并没有调用这些函数。
到了这里似乎没有好的办法能够确定溢出位置了,但是通过观察发生溢出后的堆栈内容,可以看到根据当时的堆栈中的连续大片的“AAAA……”和开头的时间和 “unknown Web”字符串等信息得知,当对堆栈中对这些地址写入内容的时候会导致覆盖返回地址,所以我们直接对堆栈地址设写断点,使用命令“bpmd 01977908 w”来对0x01977908的地址设一个写操作断点,这个地址是我们通过观察溢出发生后的ESP来得到的,因为这应该就是函数返回之后的ESP,即栈顶地址。但是这个地址是不固定的,可能不同的系统上这个地址也不一样,即使在同一个系统上,这个地址也随每次CCProxy进程的启动而不同。但是在同一个系统上,一般试几次就会使用同样的地址,所以我们只需要多试几次就肯定能够中断下来了。在设断点之前首先要用Addr ccproxy来进入CCProxy的进程地址空间。
经过仔细分析发现,当调用0040A410这个函数之前,会进行压栈操作,这个压栈恰好会把函数返回地址写入到堆栈01977908的位置,所以我们有理由相信就是调用这个0040A410这个函数的过程当中导致了溢出。在0040A410函数入口出设断点,然后发送溢出字符串,中断后按F12(即执行到函数返回),可以看到恰好返回到0x41414141的地址,这就印证了我们的推测,溢出确实发生在0040A410函数当中。
然后我们用IDA来反汇编CCProxy.exe来看看0040A410函数到底进行了哪些操作。反汇编的代码如下:
text:0040A410 sub_40A410 proc near ; CODE XREF: sub_408A10+114 p
.text:0040A410 ; sub_408A10+262 p ...
.text:0040A410 mov eax, 280Ch
.text:0040A415 call __alloca_probe ;分配0x280c大小的缓冲区
.text:0040A41A mov eax, dword_501D04
.text:0040A41F push ebp
.text:0040A420 mov ebp, [esp+2814h] ;这是要记录的内容,作为参数传入
可以看到这个函数调用“__alloca_probe”函数来进行缓冲区分配,它和普通函数调用Sub esp,xxx的方式来分配缓冲区有点不同,这是因为需要分配的缓冲区太大,直接减ESP可能会导致直接到达未分配的地址空间,所以需要用 “__alloca_probe“来分配这个大缓冲区。
我们再继续跟踪这个函数,发现到0040A607的指令时覆盖了函数的返回地址。代码如下:
.text:0040A5F1 lea ecx, [esp+414h]
.text:0040A5F8 push ebp
.text:0040A5F9 push ecx
.text:0040A5FA lea edx, [esp+1820h]
.text:0040A601 push offset aSS_0 ; "[%s] %s"
.text:0040A606 push edx
.text:0040A607 call _sprintf
在这个调用了“_sprintf“函数,按照“[日期] 内容”的格式存入0x1820大小的局部字符串缓冲区中,由于没有限制长度,所以导致了缓冲区溢出。再仔细查看发现“_sprintf”函数是在 CCProxy自己的代码段里面实现的,而没有调用“msvcrt.dll”导出的Sprintf函数,难怪我们前面在Sprintf函数下断点没有拦截到!
说句题外话,现在市场上有些防溢出软件产品是利用拦截系统的字符串拷贝函数然后回溯堆栈的方法,并宣称从根本上解决了系统缓冲区溢出的问题。这根本就是无稽之谈,因为很多溢出都是软件自己的代码实现导致溢出的,例如这个CCProxy溢出这样,根本就不调用任何系统函数,所以那种保护方法并不能从根本上解决溢出的问题。
在0040A410函数返回的时候,就会返回到被覆盖的地址去。代码如下:
.text:0040A700 add esp, 280Ch ;恢复堆栈
.text:0040A706 retn ;返回
到这里我们已经可以看出,0040A410这个函数其实是一个记录CCProxy日志的函数,它按照“[日期] 内容”的格式来记录日志,但是在构造日志字符串的时候没有限制长度,所以导致了缓冲区溢出。我们已经找到了导致溢出的位置,接下来就要看看如何利用这个漏洞了。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。