无进程DLL木马开发思路与实现
最近新型木马有向无进程DLL木马方向发展的趋势。虽然,编程方法多种多样,但原理基本上是相通的。我们特组织了这篇文章,使大家对此有更多的了解:
一.Windows下进程的隐藏
二.Windows Socket 2 SPI技术概述
三.基于SPI的DLL木马技术
四.主要代码分析
五.小结与后记
六.附录之源代码
一)Windows下进程的隐藏
在M$的32位操作系统中,有许许多多的办法可以实现进程隐藏的功能。在Win98下将程序注册为系统服务就可以实现在进程列表里的隐藏,但是在NT/2000下,由于操作系统添加了许多特性使得进程的隐藏提到了一个新的高度。其中,DLL木马是非常流行的一种形式,它将自己添加到其他可执行文件的进程里,这样在任务管理器里就不会出现我们的DLL文件,而是我们DLL的载体EXE文件。在Jeffrey Richter大师的文章里提到了好几种插入DLL的方式,比如说在注册表的AppInit_DLLs里添加木马DLL,特洛伊DLL方式,使用Windows挂钩和远程线程的插入等等,在此我就不做详细介绍了。现在给大家介绍一种隐藏进程的新方法,它仍然是以DLL的形式存在的(同样需要由其他可执行文件来加载),而且还具有无端口的特性。它就是使用了Windows Socket 2的新特性,服务提供者接口(Service Provider Interface),SPI试图支持所有的32位Windows操作系统,当然也包括Windows95。
二)Windows Socket 2 SPI技术概述
Winsock 2 SPI是一个新特性,是为书写服务提供者的人员提供的。Winsock 2不仅提供了一个供应用程序访问网络服务的Windows socket应用程序编程接口(API),还包含了由传输服务提供者和名字解析服务提供者实现的Winsock服务提供者接口(SPI)和ws2_32.dll。在此以传输服务提供者为例来实现进程的隐藏。如下是应用程序,Ws2_32.dll和传输服务提供者接口之间的层次关系:
----------------------------
|Windows socket 2 应用程序|
----------------------------Windows socket 2 API
| WS2_32.DLL |
----------------------------Windows socket 2 传输SPI
| 传输服务提供者(DLL) |
----------------------------
传输服务提供者是以DLL的形式存在的,它向外只有一个入口函数,那就是WSPStartup,其中的参数LPWSAPRTOCOL_INFOW结构指针决定了服务提供者的类型,其他的30个传输服务提供者函数是以分配表的方式调用的。当网络应用程序调用WSASocket/socket函数创建套接字时,会有三个参数:地址族,套接字类型和协议,正是这三个参数共同决定了是由哪一个类型的传输服务提供者来实现本应用程序的功能。在整个层次结构中,Ws2_32.dll只是起到了媒介的作用,应用程序则是对用户功能的实现,而真正实现网络传输功能的是传输服务提供者接口。当前系统中有一些默认的服务提供者,它们已经实现了大部分基本的功能,所以我们自己在书写服务提供者程序时,只须对数据报进行“修饰”后,将数据报传送给系统服务提供者来实现剩下的功能。
在服务提供者中有三种协议:分层协议,基础协议和协议链。区分它们的方法是通过结构WSAPROTOCOL_INFOW中的Protocolchain结构的ChainLen值来实现的。分层协议的ChainLen值为0,基础协议的值为1,而协议链的值是大于1。其实分层协议和基础协议在功能实现上没有太大的区别(均可通过调用系统服务提供者实现数据转发),但是在安装上却有很大的不同。安装基础协议时我们把所有的基础服务提供者的DLL文件名和路径都替换为我们自定义的基础协议;而安装分层协议后,我们还必须将和分层协议有关的各个协议组成协议链,然后再安装协议链。在所有的服务提供者都安装完后,我们还必须重新排列它们的安装顺序,这一点很重要。当我们的WSASocket/socket创建套接字时,Ws2_32.dll就会在服务提供者数据库中按顺序搜索和WSAStartup/socket提供的三个参数相匹配的服务提供者,如果同时有两个相同类型的服务提供者存在于服务提供者数据库中,那么顺序在前的那个服务提供者就会被调用。通常,在我们安装完自己的服务提供者后,都会将自己的服务提供者重新排列在最前面。在实例instBD.exe中,我们以分层协议为例,展示如何安装传输服务提供者。
Ws2_32.dll是使用标准的动态链接库来加载服务提供者接口的DLL到系统中去的,并调用WSPStartup来初始化。WSPStartup是Windows Socket 2应用程序调用SPI程序的初始化函数,也就是入口函数。WSPStartup的参数LPWSAPROTOCOL_INFOW指针提供应用程序所期望的协议信息,然后通过这个结构指针我们可以获得所保存的系统服务提供者的DLL名称和路径,加载系统服务提供者后查找到系统SPI程序的WSPStartup函数的指针,通过这个指针我们就可以将自己服务提供者的WSPStartup函数和系统SPI程序的WSPStartup函数相关联,进而调用系统的各个服务提供者函数。在数据传输服务提供者的实现中,我们需要两个程序,一个是可执行文件用来安装传输服务提供者;另一个就是DLL形式的数据传输服务提供者。
三)基于SPI的DLL木马技术
上面我们已经介绍了传输服务提供者的特性,现在让我们来看看如果将这种技术运用于木马进程隐藏的。在每个操作系统中都有系统网络服务,它们是在系统启动时自动加载,而且很多是基于IP协议的。如果我们书写了一个IP协议的传输服务提供者,并安装在服务提供者数据库的最前端,系统网络服务就会加载我们的服务提供者。如果将木马程序嵌入到服务提供者的DLL文件之中,在启动系统网络服务时我们的木马程序也会被启动。这种形式的DLL木马只须被安装一次,而后就会被自动加载到可执行文件的进程中,还有一个特点就是它会被多个网络服务加载。通常在系统关闭时,系统网络服务才会结束,所以我们的木马程序同样可以在系统运行时保持激活状态。
在传输服务提供者中,有30个SPI函数是以分配表的形式存在的。在Ws2_32.dll中的大多数函数都有与之对应的传输服务提供者函数。如WSPRecv和WSPSend,它们在Ws2_32.dll中的对应函数是WSARecv和WSASend。我们假设自己编写了一个基于IP协议的服务提供者并安装于系统之中,当系统重启时它被svchost.exe程序加载了,而且svchost.exe在135/TCP监听,完事具备了。在我们的传输服务提供者中,自己重新编写了WSPRecv函数,对接收到的数据进行分析,如果其中含有客户端发送过来的暗号,就执行相应的命令获得期望的动作,之后我们可以调用WSPSend函数将结果发送到客户端,这样不仅隐藏了进程,而且还重用了已有的端口。
四)主要代码分析
1.instBD.exe
可执行程序instBD.exe的主要功能是安装我们自己的分层传输服务提供者,并重新排列所有传输服务提供者的顺序,使我们的服务提供者位于协议链的顶端,这样相应类型的应用程序就会首先进入我们的传输服务提供者接口。本程序只有一个参数,就是安装(-install)或卸载(-remove)。作为演示,本程序只安装了IP分层协议及与TCP相关的协议链。在backdoor.dll中,我们不对数据报进行任何修饰,只是在启动我们的木马进程。
自定义函数:
BOOL getfilter(); //获得所有已经安装的传输服务提供者
void freefilter(); //释放存储空间
void installfilter(); //安装分层协议,协议链及排序
void removefilter(); //卸载分层协议和协议链
代码分析:
protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize);
//分配WSAPROTOCOL_INFOW结构的存储空间
totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode);
//获得系统中已安装的所有服务提供者
GetCurrentDirectory(MAX_PATH,filter_path);
//得到当前的路径
_tcscpy(filter_name,_T("\\backdoor.dll"));
//构造服务提供者文件backdoor.dll的路径全名
WSCInstallProvider(&filterguid,filter_path,&iplayerinfo,1,&errorcode);
//安装自定义的IP分层协议
iplayercataid=protoinfo.dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
udpchaininfo.ProtocolChain.ChainEntries[0]=iplayercataid;
//将自定义的IP分层协议作为自定义UDP协议链的根分层服务提供者安装在协议链的顶端
WSCInstallProvider(&filterchainguid,filter_path,chainarray,provcnt,&errorcode);
//安装协议链
WSCWriteProviderOrder(cataentries,totalprotos);
//更新所有服务提供者的安装顺序,把自定义的服务提供者排在所有协议的最前列
WSCDeinstallProvider(&filterguid,&errorcode);
//卸载IP分层协议
WSCDeinstallProvider(&filterchainguid,&errorcode);
//卸载协议链
2.backdoor.dll
传输服务提供者都是以动态链接库的形式存在的,在应用程序需要时由Ws2_32.dll加载,在用完之后就被卸载。传输服务提供者只有一个入口函数就是WSPStartup,它是Windows Socket 应用程序调用SPI的初始化函数,其他SPI函数的调用都是通过WSPStartup的参数WSPUPCALLTABLE来实现的。其中有个全局变量,可共所有调用DLL的程序读取与修改。在首次加载服务提供者时,我们启动木马进程。演示中木马进程没有任何特别的功能,当客户端和监听的服务器端口连接后,如果客户端发送了特定的暗号,服务端就会回送特定的消息。
自定义函数:
int WSPAPI WSPStartup( WORD wversionrequested,LPWSPDATA lpwspdata,LPWSAPROTOCOL_INFOW lpprotoinfo,
WSPUPCALLTABLE upcalltable,LPWSPPROC_TABLE lpproctable);
//SPI函数WSPStartup和Windows Socket 2的API函数WSAStartup相对应,WSPStartup是唯一的入口函数,剩下的30个SPI函数则是通过参数upcalltable来实现的,它们只能在内部调用,不向外提供入口
代码分析:
hthread=CreateThread(NULL,0,backdoor,NULL,0,NULL);
//创建木马进程,它只是展示数据的流通
GetModuleFileName(NULL,processname,MAX_PATH);
//获得调用本服务提供者动态链接库的可执行文件的全名
OutputDebugString(_T("Start the backdoor ..."));
//输出调试信息
layerid=protoinfo.dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
nextlayerid=lpprotoinfo->ProtocolChain.ChainEntries[i+1];
//获得下一层传输服务提供者的标志信息
WSCGetProviderPath(&protoinfo.ProviderId,filterpath,&filterpathlen,&errorcode);
//获得下一层传输服务提供者的安装路径
ExpandEnvironmentStrings(filterpath,filterpath,MAX_PATH);
//扩展环境变量
hfilter=LoadLibrary(filterpath));
//装载下一层传输服务提供者
wspstartupfunc=(LPWSPSTARTUP)GetProcAddress(hfilter,"WSPStartup"));
//获得下一层传输服务提供者的入口函数WSPStartup,以便调用
wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable);
//调用下一层传输服务提供者的WSPStartup函数,实现钩子功能
nextproctable=*lpproctable;
//保存下一层服务提供者的30个服务函数指针
由于以动态链接库形式的服务提供者要向外提供一个入口函数,因此还须一个配置文件backdoor.def:
EXPORTS WSPStartup
//向外提供入口函数WSPStartup
3.testBD.exe
这是一个测试程序,用来检测木马的服务器端是否正常工作。在它发送特定的消息到服务器端后,如果服务器正常工作就会回送特定的消息,反之则不会收到任何消息。由于木马的服务器在TCP的12345端口监听,所以我们的客户端也是基于TCP协议的。
一.Windows下进程的隐藏
二.Windows Socket 2 SPI技术概述
三.基于SPI的DLL木马技术
四.主要代码分析
五.小结与后记
六.附录之源代码
一)Windows下进程的隐藏
在M$的32位操作系统中,有许许多多的办法可以实现进程隐藏的功能。在Win98下将程序注册为系统服务就可以实现在进程列表里的隐藏,但是在NT/2000下,由于操作系统添加了许多特性使得进程的隐藏提到了一个新的高度。其中,DLL木马是非常流行的一种形式,它将自己添加到其他可执行文件的进程里,这样在任务管理器里就不会出现我们的DLL文件,而是我们DLL的载体EXE文件。在Jeffrey Richter大师的文章里提到了好几种插入DLL的方式,比如说在注册表的AppInit_DLLs里添加木马DLL,特洛伊DLL方式,使用Windows挂钩和远程线程的插入等等,在此我就不做详细介绍了。现在给大家介绍一种隐藏进程的新方法,它仍然是以DLL的形式存在的(同样需要由其他可执行文件来加载),而且还具有无端口的特性。它就是使用了Windows Socket 2的新特性,服务提供者接口(Service Provider Interface),SPI试图支持所有的32位Windows操作系统,当然也包括Windows95。
二)Windows Socket 2 SPI技术概述
Winsock 2 SPI是一个新特性,是为书写服务提供者的人员提供的。Winsock 2不仅提供了一个供应用程序访问网络服务的Windows socket应用程序编程接口(API),还包含了由传输服务提供者和名字解析服务提供者实现的Winsock服务提供者接口(SPI)和ws2_32.dll。在此以传输服务提供者为例来实现进程的隐藏。如下是应用程序,Ws2_32.dll和传输服务提供者接口之间的层次关系:
----------------------------
|Windows socket 2 应用程序|
----------------------------Windows socket 2 API
| WS2_32.DLL |
----------------------------Windows socket 2 传输SPI
| 传输服务提供者(DLL) |
----------------------------
传输服务提供者是以DLL的形式存在的,它向外只有一个入口函数,那就是WSPStartup,其中的参数LPWSAPRTOCOL_INFOW结构指针决定了服务提供者的类型,其他的30个传输服务提供者函数是以分配表的方式调用的。当网络应用程序调用WSASocket/socket函数创建套接字时,会有三个参数:地址族,套接字类型和协议,正是这三个参数共同决定了是由哪一个类型的传输服务提供者来实现本应用程序的功能。在整个层次结构中,Ws2_32.dll只是起到了媒介的作用,应用程序则是对用户功能的实现,而真正实现网络传输功能的是传输服务提供者接口。当前系统中有一些默认的服务提供者,它们已经实现了大部分基本的功能,所以我们自己在书写服务提供者程序时,只须对数据报进行“修饰”后,将数据报传送给系统服务提供者来实现剩下的功能。
在服务提供者中有三种协议:分层协议,基础协议和协议链。区分它们的方法是通过结构WSAPROTOCOL_INFOW中的Protocolchain结构的ChainLen值来实现的。分层协议的ChainLen值为0,基础协议的值为1,而协议链的值是大于1。其实分层协议和基础协议在功能实现上没有太大的区别(均可通过调用系统服务提供者实现数据转发),但是在安装上却有很大的不同。安装基础协议时我们把所有的基础服务提供者的DLL文件名和路径都替换为我们自定义的基础协议;而安装分层协议后,我们还必须将和分层协议有关的各个协议组成协议链,然后再安装协议链。在所有的服务提供者都安装完后,我们还必须重新排列它们的安装顺序,这一点很重要。当我们的WSASocket/socket创建套接字时,Ws2_32.dll就会在服务提供者数据库中按顺序搜索和WSAStartup/socket提供的三个参数相匹配的服务提供者,如果同时有两个相同类型的服务提供者存在于服务提供者数据库中,那么顺序在前的那个服务提供者就会被调用。通常,在我们安装完自己的服务提供者后,都会将自己的服务提供者重新排列在最前面。在实例instBD.exe中,我们以分层协议为例,展示如何安装传输服务提供者。
Ws2_32.dll是使用标准的动态链接库来加载服务提供者接口的DLL到系统中去的,并调用WSPStartup来初始化。WSPStartup是Windows Socket 2应用程序调用SPI程序的初始化函数,也就是入口函数。WSPStartup的参数LPWSAPROTOCOL_INFOW指针提供应用程序所期望的协议信息,然后通过这个结构指针我们可以获得所保存的系统服务提供者的DLL名称和路径,加载系统服务提供者后查找到系统SPI程序的WSPStartup函数的指针,通过这个指针我们就可以将自己服务提供者的WSPStartup函数和系统SPI程序的WSPStartup函数相关联,进而调用系统的各个服务提供者函数。在数据传输服务提供者的实现中,我们需要两个程序,一个是可执行文件用来安装传输服务提供者;另一个就是DLL形式的数据传输服务提供者。
三)基于SPI的DLL木马技术
上面我们已经介绍了传输服务提供者的特性,现在让我们来看看如果将这种技术运用于木马进程隐藏的。在每个操作系统中都有系统网络服务,它们是在系统启动时自动加载,而且很多是基于IP协议的。如果我们书写了一个IP协议的传输服务提供者,并安装在服务提供者数据库的最前端,系统网络服务就会加载我们的服务提供者。如果将木马程序嵌入到服务提供者的DLL文件之中,在启动系统网络服务时我们的木马程序也会被启动。这种形式的DLL木马只须被安装一次,而后就会被自动加载到可执行文件的进程中,还有一个特点就是它会被多个网络服务加载。通常在系统关闭时,系统网络服务才会结束,所以我们的木马程序同样可以在系统运行时保持激活状态。
在传输服务提供者中,有30个SPI函数是以分配表的形式存在的。在Ws2_32.dll中的大多数函数都有与之对应的传输服务提供者函数。如WSPRecv和WSPSend,它们在Ws2_32.dll中的对应函数是WSARecv和WSASend。我们假设自己编写了一个基于IP协议的服务提供者并安装于系统之中,当系统重启时它被svchost.exe程序加载了,而且svchost.exe在135/TCP监听,完事具备了。在我们的传输服务提供者中,自己重新编写了WSPRecv函数,对接收到的数据进行分析,如果其中含有客户端发送过来的暗号,就执行相应的命令获得期望的动作,之后我们可以调用WSPSend函数将结果发送到客户端,这样不仅隐藏了进程,而且还重用了已有的端口。
四)主要代码分析
1.instBD.exe
可执行程序instBD.exe的主要功能是安装我们自己的分层传输服务提供者,并重新排列所有传输服务提供者的顺序,使我们的服务提供者位于协议链的顶端,这样相应类型的应用程序就会首先进入我们的传输服务提供者接口。本程序只有一个参数,就是安装(-install)或卸载(-remove)。作为演示,本程序只安装了IP分层协议及与TCP相关的协议链。在backdoor.dll中,我们不对数据报进行任何修饰,只是在启动我们的木马进程。
自定义函数:
BOOL getfilter(); //获得所有已经安装的传输服务提供者
void freefilter(); //释放存储空间
void installfilter(); //安装分层协议,协议链及排序
void removefilter(); //卸载分层协议和协议链
代码分析:
protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize);
//分配WSAPROTOCOL_INFOW结构的存储空间
totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode);
//获得系统中已安装的所有服务提供者
GetCurrentDirectory(MAX_PATH,filter_path);
//得到当前的路径
_tcscpy(filter_name,_T("\\backdoor.dll"));
//构造服务提供者文件backdoor.dll的路径全名
WSCInstallProvider(&filterguid,filter_path,&iplayerinfo,1,&errorcode);
//安装自定义的IP分层协议
iplayercataid=protoinfo.dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
udpchaininfo.ProtocolChain.ChainEntries[0]=iplayercataid;
//将自定义的IP分层协议作为自定义UDP协议链的根分层服务提供者安装在协议链的顶端
WSCInstallProvider(&filterchainguid,filter_path,chainarray,provcnt,&errorcode);
//安装协议链
WSCWriteProviderOrder(cataentries,totalprotos);
//更新所有服务提供者的安装顺序,把自定义的服务提供者排在所有协议的最前列
WSCDeinstallProvider(&filterguid,&errorcode);
//卸载IP分层协议
WSCDeinstallProvider(&filterchainguid,&errorcode);
//卸载协议链
2.backdoor.dll
传输服务提供者都是以动态链接库的形式存在的,在应用程序需要时由Ws2_32.dll加载,在用完之后就被卸载。传输服务提供者只有一个入口函数就是WSPStartup,它是Windows Socket 应用程序调用SPI的初始化函数,其他SPI函数的调用都是通过WSPStartup的参数WSPUPCALLTABLE来实现的。其中有个全局变量,可共所有调用DLL的程序读取与修改。在首次加载服务提供者时,我们启动木马进程。演示中木马进程没有任何特别的功能,当客户端和监听的服务器端口连接后,如果客户端发送了特定的暗号,服务端就会回送特定的消息。
自定义函数:
int WSPAPI WSPStartup( WORD wversionrequested,LPWSPDATA lpwspdata,LPWSAPROTOCOL_INFOW lpprotoinfo,
WSPUPCALLTABLE upcalltable,LPWSPPROC_TABLE lpproctable);
//SPI函数WSPStartup和Windows Socket 2的API函数WSAStartup相对应,WSPStartup是唯一的入口函数,剩下的30个SPI函数则是通过参数upcalltable来实现的,它们只能在内部调用,不向外提供入口
代码分析:
hthread=CreateThread(NULL,0,backdoor,NULL,0,NULL);
//创建木马进程,它只是展示数据的流通
GetModuleFileName(NULL,processname,MAX_PATH);
//获得调用本服务提供者动态链接库的可执行文件的全名
OutputDebugString(_T("Start the backdoor ..."));
//输出调试信息
layerid=protoinfo.dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
nextlayerid=lpprotoinfo->ProtocolChain.ChainEntries[i+1];
//获得下一层传输服务提供者的标志信息
WSCGetProviderPath(&protoinfo.ProviderId,filterpath,&filterpathlen,&errorcode);
//获得下一层传输服务提供者的安装路径
ExpandEnvironmentStrings(filterpath,filterpath,MAX_PATH);
//扩展环境变量
hfilter=LoadLibrary(filterpath));
//装载下一层传输服务提供者
wspstartupfunc=(LPWSPSTARTUP)GetProcAddress(hfilter,"WSPStartup"));
//获得下一层传输服务提供者的入口函数WSPStartup,以便调用
wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable);
//调用下一层传输服务提供者的WSPStartup函数,实现钩子功能
nextproctable=*lpproctable;
//保存下一层服务提供者的30个服务函数指针
由于以动态链接库形式的服务提供者要向外提供一个入口函数,因此还须一个配置文件backdoor.def:
EXPORTS WSPStartup
//向外提供入口函数WSPStartup
3.testBD.exe
这是一个测试程序,用来检测木马的服务器端是否正常工作。在它发送特定的消息到服务器端后,如果服务器正常工作就会回送特定的消息,反之则不会收到任何消息。由于木马的服务器在TCP的12345端口监听,所以我们的客户端也是基于TCP协议的。
-----------------------------------------------------------------