前段时间有朋友问到了我一个关于“无端口可用”的问题。说在如下图所示的内网环境中,firewall只允许Web Server的80端口建立网络连接,并且Web Server上的80端口已经被IIS、Apache等软件占用了的情况下,怎么建立一个RAT后门。
0x01
早些时候的著名后门byshell就考虑到了这个问题,于是使用了一种非常挫的方式去解决。客户端将数据发送到80端口,服务端将IIS的进程打开,循环遍历IIS进程的整个内存去寻找数据标记。显然这种方法从效率和稳定性上来讲,都是不可取的。这里姑且不把这种方法当做方法。
过去有人提出过一种端口复用的方法去建立的后门,这个方法使用了setsockopt()这个API,这个API在MSDN里说是用来设置套接字的选项的。原型如下。
int setsockopt(
SOCKET s,
int level,
int optname,
const char FAR* optval,
int optlen
);
我们这里只关心它的第三个参数,这个参数用来设置套接字状态。这个参数有一个取值为SO_REUSEADDR,MSDN对这个参数的解释如下。
The state of the SO_REUSEADDR socket option determines whether the local transport address to which a socket will be bound is always shared with other sockets. This socket option applies only to listening sockets, datagram sockets, and connection-oriented sockets.
也就是说,当第三个参数的取值设置为SO_REUSEADDR时,套接字的端口是可以共享复用的。具体共享细节为后来居上,后建立该参数链接的套接字先拿到数据。此方法目前对Apache和IIS5.0及以下版本有效。那为什么IIS6.0及以上就不行了呢?之后会做解释。
0x02
通过逆向和查阅开源代码可以得知,Apache和IIS5.0及以下版本使用了应用层的IOCP模型进行通信,尽管框架比较复杂,但是依然在应用层创建了套接字。到这里你是否有新的想法呢?没错,我想到了可以使用远程线程注入一个DLL进行Api Hook例如WSARecv()、WSASend()这样的API获取套接字和异步IO的缓冲区指针,再使用getpeername()函数对比客户端信息,紧接着用套接字进行IO。
或者也可以使用更简单粗暴的方法,直接使用SPI安装一个LSP,也可以抓到数据,但是就比较难再做通信了。我们将这种在应用层建立套接字通信转接通信过程的方法总结为下图,红线表示可以利用的地方。
这里可能就有同学会问到,为什么不能直接上Rootkit呢?如果从ring0层去考虑这个问题理论上是非常容易的。我们可以使用TDI或者NDIS的过滤驱动直接过滤所有IO网卡的流量,不过写个这么重量级的后门,的确有“杀鸡焉用宰牛刀”的意味。再者,也可以使用SSDT HOOK和对TCP驱动下IRP HOOK去解决问题。但是为什么不去做呢?因为使用驱动目前已经不是Windows木马编程潮流所在。
0x03
还记得之前提到的IIS6.0和以上版本的问题吗?这里一个新问题出现了,从IIS6.0开始,微软可能考虑到了安全性和稳定性以及数据处理的效率的问题,将网络通信的过程封装在了ring0层,使用了http.sys这个驱动来直接进行网络通信。如下图所示。
这样一来,应用层就没有了套接字,我们就不能使用上述方法去解决这个问题了。那怎么办呢?难道应用层因此就再没有可以利用的地方了吗?这么想并不符合Geek的风格。于是,经过对W3wp.exe的初步的逆向,又发现了一些可以利用的地方。
这个过程中,ring0无法处理的HTTP请求,都下发由w3wp.exe进程处理。针对HTTP请求考虑,我想到了如下几点可以转接网络通信过程的地方。
1.GET或者POST对硬盘中的文件做访问,有可能需要通过应用层CreateFile()、ReadFile()、WriteFile()去完成。(API HOOK?)
2.对于HTTP这种大部分内容由ANSI字符串解析的协议,难道用不到标准字符串处理函数吗?(还是API HOOK?)
3.对ASP、PHP、JSP脚本的解释,可能需要将数据提交给解释引擎去完成。(inline hook?)
0x04
基于以上几点,我也一一做了验证。这里逆向要注意了,OllyDbg对低权限进程的Attach调试稳定性并不好,经常崩掉。所以我换了非常难看的Windbg。微软自家的调试器调自家的程序非常稳定。首先bp了CreateFileW(),得到了一个比较惊喜的效果。如图所示。
Windbg拦下了CreateFileW(),并且在第一个参数里,我们看到了我们提交的“fuckyou1234”,这里我们就完全可以用if(wcsncmp(s1,L”fuckyou1234”,11))这样取得后门命令了。
而我通过对多个字符串处理函数的bp,同样也发现了可以利用的地方。例如wcsstr()函数的断点,成功的抓到了cookies和一些其他与HTTP协议相关的信息。cookies可以比URL提交跟多内容,并且可以绕过一些log和过滤。如下图所示。
这是对前两个问题逆向的初步成果,至于第三个问题,暂时先留着好了。现在遇到了一个新的问题,当拿到需要执行的命令,我们至少不能让他去w3wp.exe这个低权限的进程去实施。那么怎么让他在高权限的进程去实施呢?还有,我们执行完的结果,该怎么返回给客户端呢?
0x05
这里我考虑到了留一个宿主进程,建立事件对象和邮槽或者管道接受由w3wp.exe发来的命令。至于把数据反馈到客户端,我们可以重定向CreateFileW()的第一个参数,将他指向管道也好,指向某个硬盘里的输出文件也好,当紧接着去ReadFile()的时候,就可以顺利将我们的执行结果反馈到客户端去了。
这里涉及到高低权限进程通信的一个问题,高权限进程创建的内核对象必须设置安全属性为低权限可继承句柄,并且设置安全描述符和DACL。事件、邮槽、管道的等内核对象才能被低权限进程打开。具体代码如下。最后,我用上述所诉内容,写了如下的一个后门。
HANDLE secCreateEventPort(WCHAR* szNameEvent)
{
SECURITY_DESCRIPTOR SecDescriptor = {0};
SECURITY_ATTRIBUTES SecurityAttributes = {0};
if (InitializeSecurityDescriptor(&SecDescriptor,SECURITY_DESCRIPTOR_REVISION) == FALSE)
return INVALID_HANDLE_VALUE;
if(SetSecurityDescriptorDacl(&SecDescriptor,TRUE, NULL, FALSE) == 0)
return INVALID_HANDLE_VALUE;
SecurityAttributes.bInheritHandle = TRUE;
SecurityAttributes.lpSecurityDescriptor = &SecDescriptor;
SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
return CreateEvent(&SecurityAttributes,TRUE,FALSE,szNameEvent);
}