ASP下一句话木马的动态拦截技术


Dec 22 2015

ASP下一句话木马的动态拦截技术

首页 » 渗透测试 » ASP下一句话木马的动态拦截技术   

ASP下一句话木马的动态拦截技术

作者:椒图实验室

转载请注明出处:http://blog.jowto.com

        一句话木马如<%eval request(“MH”)%>,攻击者通过组织好的参数访问木马,就可以让参数中的恶意脚本在服务器端执行。为了防止执行恶意脚本,可以在IIS脚本引擎中拦截EVAL函数来过滤非法请求。

以下分析都在windows server 2003 x86下调试:
w3wp.exe是在IIS(Internet Information Server)与应用程序池相关联的一个进程,如果你有多个应用程序池,就会有对应的多个w3wp.exe的进程实例运行。当需要解析ASP脚本时会加载vbscript.dll,通过IDA搜索vbscript.dll中和“eval”有关的函数,找到

VbsEval(VAR *,int,VAR *) .text 7337da95 

在windbg中调试发现脚本执行Eval函数时都会调用到VbsEval,见下图:
URL:

localhost/test.asp?MH=1*2
test.asp:

<%eval request("MH")%>
windbg:

image001.jpg

在内存中可以看到eval函数执行的脚本。现在只需要将此脚本与URL中请求参数内容对比,如果内存中执行的EVAL脚本在URL中存在则可以认为是URL请求试图调用一句话木马并执行。如上图截取的底层执行代码为1*2,判断1*2是否在localhost/test.asp?MH=1*2中存在,如果存在则直接返回恶意代码提示,最终效果如下图:

image003-300x129.png

 

HOOK住vbscript!VbsEval很容易截取到要执行的脚本,现在的问题是任何脚本执行eval都要调用vbseval函数,那么怎么才能找到当前执行脚本是由哪个URL触发的呢?如果无法取到URL则不能与EVAL中执行脚本对比,过滤是无从谈起的。

现在从vbseval函数往上看整个脚本解释执行的流程!

image005.png

 下asp!CViperAsyncRequest::OnCall断点,堆栈回溯如下:

 

 image007.png

可以看出脚本执行eval函数是通过启动线程调用回调函数来触发的。但是下新建线程的API都断不下来,需要换个思路。通过上图可以看到线程启动后最先执行函数是asp.dll里的asp!CViperAsyncRequest::OnCall,查看asp.dll的导出函数如下图:

image009-300x61.png

 

通过这3个导出函数可以判断asp.dll是ISAPI中的ISA。ISA简介如下:

ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应
,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。

ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何
ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。
ISA大概工作流程如下:

1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服
务器版本,并与自己的版本相比较,以保证版本兼容。

2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中
根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(
Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供
的几个回调函数的入口地址。函数原型如下:

 DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ) 

ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):

typedef struct _EXTENSION_CONTROL_BLOCK 
{
   DWORD     cbSize;        //IN,本结构的大小,只读
   DWORD     dwVersion      //IN,版本号,高16位为主版本号,低16位为次版本号
   HCONN     ConnID;        //IN,连接句柄,由服务器分配,ISA只能读取该值
   DWORD     dwHttpStatusCode;                 //OUT,当前完成的事务状态
   CHAR      lpszLogData[HSE_LOG_BUFFER_LEN];  //OUT,需要写入到日志文件中的内容
   LPSTR     lpszMethod;    //IN,等价于CGI的环境变量REQUEST_METHOD
   LPSTR     lpszQueryString;                  //IN,等价于环境变量QUERY_STRING
   LPSTR     lpszPathInfo;                     //IN,等价于环境变量PATH_INFO
   LPSTR     lpszPathTranslated;               //IN,等价于环境变量PATH_TRANSLATED
   DWORD     cbTotalBytes;                     //IN,等价于环境变量CONTENT_LENGTH
   DWORD     cbAvailable;                      //IN,缓冲区中的可用字节数
   LPBYTE    lpbData;                          //IN,缓冲区指针,指向客户端发来的数据
   LPSTR     lpszContentType;                  //IN,等价于环境变量CONTENT_TYPE

//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
   BOOL ( WINAPI * GetServerVariable ) 
      ( HCONN       hConn,
       LPSTR       lpszVariableName,
       LPVOID      lpvBuffer,
       LPDWORD     lpdwSize );

   BOOL ( WINAPI * WriteClient )      //回调函数,从客户端的HTTP请求中读取数据
      ( HCONN      ConnID,
      LPVOID     Buffer,
      LPDWORD    lpdwBytes,
      DWORD      dwReserved );

   BOOL ( WINAPI * ReadClient )       //回调函数,向客户端发送数据
      ( HCONN      ConnID,
      LPVOID     lpvBuffer,
      LPDWORD    lpdwSize );

   BOOL ( WINAPI * ServerSupportFunction )  //回调函数,访问服务器的一般和特定功能
      ( HCONN      hConn,
      DWORD      dwHSERRequest,
      LPVOID     lpvBuffer,
      LPDWORD    lpdwSize,
      LPDWORD    lpdwDataType );

} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
 


 

 

 

 

 

在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给
ISA调用,从而使ISA可以获得更详尽的信息。

上述理论看完后直接上调试器看更清楚,每一次URL请求都会经过HttpExtentionProc函数,因此下HttpExtentionProc断点,查看某一次请求中ECB的结构,如下:

 image011.png

从上图可以看出ECB中包含请求参数及脚本的物理路径,识别一句话木马时需要这两项内容。怎么才能在vbscript!VbsEval函数中取到这个ECB的结构。
跟着Httpexecutionproc函数往下看

image013.png

 

 

此时V2的地址是2721e90,内存数据如下:

 image015.png

 

在windbg里下vbseval断点,F5继续走断在vbseval,

70dc75c7 8b4624          mov     eax,dword ptr [esi+24h] ds:0023:026a1eb4=02721e90

70dc75d4 8945fc          mov     dword ptr [ebp-4],eax ss:0023:0130fd88=00000000

image017-300x64.png

 

堆栈回溯中可以看到在Httpexecutionproc中封装的类基址2721e90在函数asp!CViperAsyncRequest::OnCall中被用到,下asp!CViperAsyncRequest::OnCall断点继续往后跟。

ChildEBP RetAddr    
0309f560 73333f1c vbscript!VbsEval  
0309f574 733365d7 vbscript!StaticEntryPoint::Call+0x11  
0309f848 733333e0 vbscript!CScriptRuntime::Run+0x1f08  
0309f940 733337d1 vbscript!CScriptEntryPoint::Call+0x5c  
0309f998 73333b9c vbscript!CSession::Execute+0xb4  
0309f9e8 73331849 vbscript!COleScript::ExecutePendingScripts+0x13e  
0309fa04 70dc2ada vbscript!COleScript::SetScriptState+0x150  
0309fa30 70dc2a9c asp!CActiveScriptEngine::TryCall+0x19  
0309fa6c 70dc26d0 asp!CActiveScriptEngine::Call+0x31  
0309fa88 70dc25d4 asp!CallScriptFunctionOfEngine+0x5b  
0309fadc 70dc24ff asp!ExecuteRequest+0x17e  
0309fb44 70dc23f7 asp!Execute+0x249  
0309fb98 70dc2753 asp!CHitObj::ViperAsyncCallback+0x3f3  
0309fbb4 4a1db5ea asp!CViperAsyncRequest::OnCall+0x92  ;脚本引擎函数执行起始位置  
0309fbd0 77560d30 comsvcs!CSTAActivityWork::STAActivityWorkHelper+0x32  
0309fc1c 775617dc ole32!EnterForCallback+0xc4  
0309fd7c 775303b4 ole32!SwitchForCallback+0x1a3  
0309fda8 774dc194 ole32!PerformCallback+0x54  
0309fe40 7756433a ole32!CObjectContext::InternalContextCallback+0x159  
0309fe60 4a1db78c ole32!CObjectContext::DoCallback+0x1c 
 
		

将以上函数设置断点跟踪2721e90位置,最后得到vbseval函数时ECB结构的地址为:

dda poi(poi(poi(poi(poi(poi(ebp+8)+5c)+5c)+18)+24)+4)

 image017-300x64.png

 

只需要将VbsEval函数HOOK住就可以截取到请求参数内容。

通过调试找到了想要的数据后就可以动手编码实现拦截框架了。首先要找到vbseval函数的地址,通过特征码定位是个不错的选择,但是在vbseval函数体内并没有合适的特征码,如下图:

image021-1024x623.png

下面是sub_7337D95F函数:

image023.png

 

Compile函数原型为:

unsigned int __thiscall COleScript::Compile(void *this, int a2, const OLECHAR *psz, int a4, int a5, UINT len, int a7, int a8, int a9, int a10) 

 

 

 

通过调试找到Compile函数内部获取ECB及Eval内容的地址,如下:
ECB数据地址:

dd poi(poi(poi(poi(ecx+5c)+18)+24)+4) 

image026-300x112.jpg

 

数据地址找完后剩下的就是编码实现拦截,大致思路是创建自己的ISAPI,在加载时将vbscript.dll提前加载到进程(这样可以加载完vbscript.dll后HOOK住vbseval函数,默认情况下vbscript.dll只有在访问asp页面时才会加载)。然后通过在整个内存中查找“eval code”来定位COlescript!Compile函数地址,将找到的函数地址HOOK住,在新函数中使用上面分析的地址获得相应的值。因为调用COlescript!Compile的函数不止vbseval一个,但是可以通过”eval code”来判断,只有vbseval函数会调用COlescript!Compile函数传递”eval code”字符串变量。因此先判断调用COlescript!Compile传递的参数是不是eval code,如果是则继续判断ECB结构中的请求参数(本例中“MH=1*2”)字符串是否包含VbsEval函数中要执行的脚本(本例中“1*2”),如果包含了则是非法操作,调用ECB中回调函数writeclient向浏览器输出“URL中包含EVAL脚本”等信息,最后要中断COlescript!Compile函数以防止脚本引擎继续执行恶意脚本。中断执行脚本可以将执行的脚本如”1*2″修改为”1″,这样函数可以继续执行而不用担心恶意脚本被执行。
需要注意的是,在将vbseval欲执行的恶意脚本内容修改成“1”后,当其它请求同一asp页面并且参数合法的情况下,vbseval函数执行的脚本仍是“1”,可能IIS在加载页面时就将脚本缓存在内存中,因此我们将缓存的页面脚本强制修改成“1”,后面再请求同一页面时会继续执行缓存中的脚本,解决办法是在HOOK函数结尾将asp文件中追加空格等无效字符串,这样IIS去动态更新脚本缓存。

另:x64平台下iis启动的都是32位的w3wp.exe,因此此过滤URL方法在x86、x64平台均可使用。

最后效果图:
非法情况:

 

1.jpg

 

 

 


 

 

如果您喜欢本博客,欢迎点击图片定订阅到邮箱填写您的邮件地址,订阅我们的精彩内容:

正文部分到此结束

文章标签: 动态拦截技术

版权声明:若无特殊注明,本文皆为( mOon )原创,转载请保留文章出处。

也许喜欢: «DomainUrls 子域名搜刮工具 | Joomla远程代码执行漏洞分析»

你肿么看?

你还可以输入 250/250 个字

 微笑 大笑 拽 大哭 亲亲 流汗 喷血 奸笑 囧 不爽 晕 示爱 害羞 吃惊 惊叹 爱你 吓死了 呵呵

评论信息框

这篇文章还没有收到评论,赶紧来抢沙发吧~