[ 永远的UNIX::UNIX技术资料的宝库 ]

首页 > 安全技术 > 其它 > 正文
揭开木马的神秘面纱(三)
http://fetag.dhs.org/ (2001-05-20 13:04:01)

shotgun -首发於天极网 
在揭开木马的神秘面纱(二)发表後,有很多朋友来信询问新型木马的详细情况,本文会详细的分析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_ECHOREPLY报文却往往不会在过滤策略中出现,这是因爲一旦不允许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; //16位元标识 
unsigned short frag_and_flags; // 3位元标位元 
unsigned char ttl; //8位生存时间 TTL 
unsigned char proto; // 8位元协定 (TCP, UDP 或其他) 
unsigned short checksum; // 16位IP首部校验和 
unsigned int sourceIP; //32位源IP地址 
unsigned int destIP; //32位元目的IP地址 
}IpHeader; 

然後定义一个ICMP首部∶ 
typedef struct _ihdr { 
BYTE i_type; //8位类型 
BYTE i_code; //8位代码 
USHORT i_cksum; //16位校验和 
USHORT i_id; //识别号(一般用进程号作爲识别号) 
USHORT i_seq; //报文序列号 
ULONG timestamp; //时间戳 
}IcmpHeader; 

这时可以同过WSASocket建立一个原始套介面∶ 
SockRaw=WSASocket ( 
AF_INET, //协定族 
SOCK_RAW, //协定类型,SOCK_RAW表示是原始套介面 
IPPROTO_ICMP, //协定,IPPROTO_ICMP表示ICMP数报 
NULL, //WSAPROTOCOL_INFO置空 
0, //保留字,永远置爲0 
WSA_FLAG_OVERLAPPED //标位元 
); 
注∶爲了使用发送接收超时设置(设置SO_RCVTIMEO, SO_SNDTIMEO),必须将标位置爲WSA_FLAG_OVERLAPPED 

随後你可以使用fill_icmp_data副程式填充ICMP报文段∶ 
调用方法fill_icmp_data(icmp_data,datasize); 
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; //类型爲ICMP_ECHOREPLY 
icmp_hdr->i_code = 0; 
icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); //识别号爲进程号 
icmp_hdr->i_cksum = 0; //校验和初始化 
icmp_hdr->i_seq = 0; //序列号初始化 
datapart = icmp_data + sizeof(IcmpHeader); //数端的地址爲icmp报文地址加上 
ICMP的首部长度 
memset(datapart,'A', datasize - sizeof(IcmpHeader)); //这里我填充的数全部爲’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_ECHOREPLY报文并用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; //IP首部的地址就等於buf的地址 
iphdrlen = iphdr->h_len * 4 ; // 因爲h_len是32位word,要转换成bytes必须*4 
icmphdr = (IcmpHeader*)(buf + iphdrlen); //ICMP首部的地址等於IP首部长度加buf 
printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); //取出源地址 
printf(" icmp_id=%d. ",icmphdr->i_id); //取出进程号 
printf(" icmp_seq=%d. ",icmphdr->i_seq); //取出序列号 
printf(" icmp_type=%d",icmphdr->i_type); //取出类型 
printf(" icmp_code=%d",icmphdr->i_code); //取出代码 
for(i=0;i 

注∶在WIN2000下使用SOCK_RAW需要管理员的许可权。 

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

附录∶ 
1、发送ICMP_ECHOREPLY报文的程式码 
#include  
#include  
#include  

#define ICMP_ECHO 8 //ICMP回显请求报文的类型值爲8 
#define ICMP_ECHOREPLY 0 //ICMP回显应答报文的类型值爲0 
#define ICMP_MIN 8 // ICMP报文的最小长度是8位元组(仅爲首部) 
#define ICMP_DEST_IP "127.0.0.1" //目标主机的IP 
#define ICMP_PASSWORD 1234 //密码设置,用来识别控制端 

// 定义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; //16位元标识 
unsigned short frag_and_flags; //3位元标位元 
unsigned char ttl; //8位生存时间 TTL 
unsigned char proto; //8位元协定 (TCP, UDP 或其他) 
unsigned short checksum; //16位IP首部校验和 
unsigned int sourceIP; //32位源IP地址 
unsigned int destIP; //32位元目的IP地址 
}IpHeader; 


// 定义ICMP首部 
typedef struct _ihdr 

BYTE i_type; //8位类型 
BYTE i_code; //8位代码 
USHORT i_cksum; //16位校验和 
USHORT i_id; //识别号(一般用进程号作爲识别号) 
USHORT i_seq; //报文序列号 
ULONG timestamp; //时间戳 
}IcmpHeader; 


#define STATUS_FAILED 0xFFFF 
#define DEF_PACKET_SIZE 64 //定义报文的大小爲64位元组 
#define MAX_PACKET 6500 //定义最大报文的大小爲6500位元组 

#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s)) 
#define xfree(p) HeapFree (GetProcessHeap(),0,(p)) 

void fill_icmp_data(char *,int); //填充ICMP报文的副程式 
USHORT checksum(USHORT *, int); //计算校验和的副程式 

int main(int argc, char **argv) 

WSADATA wsaData; 
SOCKET sockRaw = (SOCKET)NULL; 
struct sockaddr_in dest,from; 
struct hostent * hp; 
int bread,datasize,retval,bwrote; 
int fromlen = sizeof(from); 
int timeout = 1000; 
char *icmp_data; 
char *recvbuf; 
unsigned int addr=0; 
USHORT seq_no = 0; 
static int nCount=0; 

if((retval=WSAStartup(MAKEWORD(2,1),&wsaData)) != 0) 
{fprintf(stderr,"WSAStartup failed: %d\n",retval);ExitProcess(STATUS_FAILED);} 
if((sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) 
{fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());ExitProcess(STATUS_FAILED);} 
__try 

if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR) 
{fprintf(stderr,"Failed to set recv timeout: %d\n",WSAGetLastError());__leave;} //设置接收超时 
if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR) 
{fprintf(stderr,"Failed to set send timeout: %d\n",WSAGetLastError());__leave;} //设置发送超时 
memset(&dest,0,sizeof(dest)); 
dest.sin_family = AF_INET; 
dest.sin_addr.s_addr = inet_addr(ICMP_DEST_IP); 
datasize=DEF_PACKET_SIZE; 
datasize+=sizeof(IcmpHeader); 
icmp_data=xmalloc(MAX_PACKET); 
recvbuf=xmalloc(MAX_PACKET); 
if(!icmp_data) {fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());__leave;} 
memset(icmp_data,0,MAX_PACKET); 
printf("\nSend Packet to %s Success!\n",ICMP_DEST_IP); 
fill_icmp_data(icmp_data,datasize); //填充ICMP报文 
((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); //设置时间戳 
((IcmpHeader*)icmp_data)->i_seq = ICMP_PASSWORD; //设置序列号,实际使用时可以用这个密码验证 
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); //计算校验和 
bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); //发送报文 
if (bwrote == SOCKET_ERROR) 

if (WSAGetLastError() == WSAETIMEDOUT) printf("Timed out\n"); 
fprintf(stderr,"sendto failed: %d\n",WSAGetLastError()); 
__leave; 

if (bwrote < datasize ) fprintf(stdout,"Wrote %d bytes\n",bwrote); 

__finally 

if (sockRaw != INVALID_SOCKET) closesocket(sockRaw); 
WSACleanup(); 

return 0; 


//计算校验和函数 
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); 


//填充ICMP数报函数 
void fill_icmp_data(char * icmp_data, int datasize) 

int i; 
char SendMsg[20]="Hello World!"; 
IcmpHeader *icmp_hdr; 
char *datapart; 
icmp_hdr = (IcmpHeader*)icmp_data; 
icmp_hdr->i_type = ICMP_ECHOREPLY; 
icmp_hdr->i_code = 0; 
icmp_hdr->i_id = (USHORT) GetCurrentProcessId(); 
icmp_hdr->i_cksum = 0; 
icmp_hdr->i_seq = 0; 
datapart = icmp_data + sizeof(IcmpHeader); 
for(i=0;i


2、接收ICMP_ECHOREPLY报文的程式码 
#include  
#include  
#include  

#define ICMP_ECHO 8 
#define ICMP_ECHOREPLY 0 
#define ICMP_MIN 8 // minimum 8 byte icmp packet (just header) 
#define ICMP_PASSWORD 1234 

/* The IP header */ 
typedef struct iphdr { 
unsigned int h_len:4; //4位首部长度 
unsigned int version:4; //IP版本号,4表示IPV4 
unsigned char tos; //8位服务类型TOS 
unsigned short total_len; //16位元总长度(位元组) 
unsigned short ident; //16位元标识 
unsigned short frag_and_flags; //3位元标位元 
unsigned char ttl; //8位生存时间 TTL 
unsigned char proto; //8位元协定 (TCP, UDP 或其他) 
unsigned short checksum; //16位IP首部校验和 
unsigned int sourceIP; //32位源IP地址 
unsigned int destIP; //32位元目的IP地址 
}IpHeader; 


//定义ICMP首部 
typedef struct _ihdr 

BYTE i_type; //8位类型 
BYTE i_code; //8位代码 
USHORT i_cksum; //16位校验和 
USHORT i_id; //识别号(一般用进程号作爲识别号) 
USHORT i_seq; //报文序列号 
ULONG timestamp; //时间戳 
}IcmpHeader; 


#define STATUS_FAILED 0xFFFF 
#define DEF_PACKET_SIZE 640 
#define MAX_PACKET 6500 

#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s)) 
#define xfree(p) HeapFree (GetProcessHeap(),0,(p)) 

void fill_icmp_data(char *, int); 
USHORT checksum(USHORT *, int); 
void decode_resp(char *,int ,struct sockaddr_in *); 

int main(int argc, char **argv){ 

WSADATA wsaData; 
SOCKET sockRaw = (SOCKET)NULL; 
struct sockaddr_in dest,from; 
struct hostent * hp; 
int bread,datasize,retval; 
int fromlen = sizeof(from); 
int timeout = 1000; 
char *icmp_data; 
char *recvbuf; 
unsigned int addr=0; 
USHORT seq_no = 0; 

if ((retval = WSAStartup(MAKEWORD(2,1),&wsaData)) != 0){ 
fprintf(stderr,"WSAStartup failed: %d\n",retval); 
ExitProcess(STATUS_FAILED); 

sockRaw = WSASocket (AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED); 

if (sockRaw == INVALID_SOCKET) { 
fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError()); 
ExitProcess(STATUS_FAILED); 

__try{ 
bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)); 
if(bread == SOCKET_ERROR) 

fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError()); 
__leave; 

bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)); 
if(bread == SOCKET_ERROR) 

fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError()); 
__leave; 

memset(&dest,0,sizeof(dest)); 
dest.sin_family = AF_INET; 
dest.sin_addr.s_addr = inet_addr("207.46.230.218");//任意IP地址 
datasize = DEF_PACKET_SIZE; 
datasize += sizeof(IcmpHeader); 
icmp_data = xmalloc(MAX_PACKET); 
recvbuf = xmalloc(MAX_PACKET); 
if (!icmp_data) { 
fprintf(stderr,"HeapAlloc failed %d\n",GetLastError()); 
__leave; 

memset(icmp_data,0,MAX_PACKET); 
while(1) { 
static int nCount = 0; 
int bwrote; 
fill_icmp_data(icmp_data,datasize); 
((IcmpHeader*)icmp_data)->i_cksum = 0; 
((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); 
((IcmpHeader*)icmp_data)->i_seq = 1111; 
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); 
bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); 
bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen); 
if (bread == SOCKET_ERROR){ 
if (WSAGetLastError() == WSAETIMEDOUT) { 
continue; 

fprintf(stderr,"recvfrom failed: %d\n",WSAGetLastError()); 
__leave; 


decode_resp(recvbuf,bread,&from); 
Sleep(1000); 


__finally { 
if (sockRaw != INVALID_SOCKET) closesocket(sockRaw); 
WSACleanup(); 

return 0; 


void decode_resp(char *buf, int bytes,struct sockaddr_in *from) 

int i; 
IpHeader *iphdr; 
IcmpHeader *icmphdr; 
unsigned short iphdrlen; 
iphdr = (IpHeader *)buf; 
iphdrlen = iphdr->h_len * 4 ; 
icmphdr = (IcmpHeader*)(buf + iphdrlen); 
if(icmphdr->i_seq==ICMP_PASSWORD)//密码正确则输出数段 

printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); 
printf(" IcmpType %d",icmphdr->i_type); 
printf(" IcmpCode %d",icmphdr->i_code); 
printf("\n"); 
for(i=0;i<50;i++) printf("%c",*(buf+iphdrlen+i+12)); 

else printf("Other ICMP Packets!\n"); 
printf("\n"); 



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); 


void fill_icmp_data(char * icmp_data, int datasize){ 
IcmpHeader *icmp_hdr; 
char *datapart; 
icmp_hdr = (IcmpHeader*)icmp_data; 
icmp_hdr->i_type = ICMP_ECHO; 
icmp_hdr->i_code = 0; 
icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); 
icmp_hdr->i_cksum = 0; 
icmp_hdr->i_seq = 12; 
datapart = icmp_data + sizeof(IcmpHeader); 
memset(datapart,'A', datasize - sizeof(IcmpHeader)); 

(http://www.fanqiang.com)
    进入【UNIX论坛

相关文章

===更多相关===
 

★  樊强制作 欢迎分享  ★