[ 永遠的UNIX::UNIX技術資料的寶庫 ]   GB | BIG5

首頁 > 安全技術 > 其它 > 正文
揭開木馬的神秘面紗(三)
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論壇

相關文章

======
 

★  樊強制作 歡迎分享  ★