?? 揭開木馬的神秘面紗3.htm
字號:
<p><b>揭開木馬的神秘面紗<三> - ICMP的木馬篇</b><br>
Shotgun</p>
<p><font size="2"><br>
</font><br>
在揭開木馬的神秘面紗(二)發表后,有很多朋友來信詢問新型木馬的詳細情況,本文會詳細的分析Win2000下一種新型木馬的內部構造和防御方法。(本文默認的操作系統為Win2000,開發環境為VC++6.0。)</p>
<p> 大家知道,一般的“古典”型木馬都是通過建立TCP連接來進行命令和數據的傳遞的,但是這種方法有一個致命的漏洞,就是木馬在等待和運行的過程中,始終有一個和外界聯系的端口打開著,這是木馬的阿喀琉斯之踵(參看希臘神話《特洛伊戰紀》),也是高手們查找木馬的殺手锏之一(Netstat大法)。所謂道高一尺,魔高一丈,木馬也是在斗爭中不斷進步不斷成長的,其中一種ICMP木馬就徹底擺脫了端口的束縛,成為黑客入侵后門工具中的佼佼者。<br>
什么是ICMP呢?ICMP全稱是Internet Control Message Protocol(互聯網控制報文協議)它是IP協議的附屬協議,用來傳遞差錯報文以及其他需要注意的消息報文,這個協議常常為TCP或UDP協議服務,但是也可以單獨使用,例如著名的工具Ping(向Mike Muuss致敬),就是通過發送接收ICMP_ECHO和ICMP_ECHOREPLY報文來進行網絡診斷的。<br>
實際上,ICMP木馬的出現正是得到了Ping程序的啟發,由于ICMP報文是由系統內核或進程直接處理而不是通過端口,這就給木馬一個擺脫端口的絕好機會,木馬將自己偽裝成一個Ping的進程,系統就會將ICMP_ECHOREPLY(Ping的回包)的監聽、處理權交給木馬進程,一旦事先約定好的ICMP_ECHOREPLY包出現(可以判斷包大小、ICMP_SEQ等特征),木馬就會接受、分析并從報文中解碼出命令和數據。<br>
ICMP_ECHOREPLY包還有對于防火墻和網關的穿透能力。對于防火墻來說,ICMP報文是被列為危險的一類:從Ping of Death到ICMP風暴到ICMP碎片攻擊,構造ICMP報文一向是攻擊主機的最好方法之一,因此一般的防火墻都會對ICMP報文進行過濾;但是ICMP_ECHOREPLY報文卻往往不會在過濾策略中出現,這是因為一旦不允許ICMP_ECHOREPLY報文通過就意味著主機沒有辦法對外進行Ping的操作,這樣對于用戶是極其不友好的。如果設置正確,ICMP_ECHOREPLY報文也能穿過網關,進入局域網。</p>
<p> 為了實現發送/監聽ICMP報文,必須建立SOCK_RAW(原始套接口),首先,我們需要定義一個IP首部:<br>
typedef struct iphdr {<br>
unsigned int version:4; // IP版本號,4表示IPV4<br>
unsigned int h_len:4; // 4位首部長度<br>
unsigned char tos; // 8位服務類型TOS<br>
unsigned short total_len; // 16位總長度(字節) <br>
unsigned short ident; //16位標識<br>
unsigned short frag_and_flags; // 3位標志位<br>
unsigned char ttl; //8位生存時間 TTL<br>
unsigned char proto; // 8位協議 (TCP, UDP 或其他)<br>
unsigned short checksum; // 16位IP首部校驗和<br>
unsigned int sourceIP; //32位源IP地址<br>
unsigned int destIP; //32位目的IP地址<br>
}IpHeader;<br>
<br>
然后定義一個ICMP首部:<br>
typedef struct _ihdr {<br>
BYTE i_type; //8位類型<br>
BYTE i_code; //8位代碼<br>
USHORT i_cksum; //16位校驗和 <br>
USHORT i_id; //識別號(一般用進程號作為識別號)<br>
USHORT i_seq; //報文序列號 <br>
ULONG timestamp; //時間戳<br>
}IcmpHeader;<br>
<br>
這時可以同過WSASocket建立一個原始套接口:<br>
SockRaw=WSASocket (<br>
AF_INET, //協議族 <br>
SOCK_RAW, //協議類型,SOCK_RAW表示是原始套接口 <br>
IPPROTO_ICMP, //協議,IPPROTO_ICMP表示ICMP數據報<br>
NULL, //WSAPROTOCOL_INFO置空<br>
0, //保留字,永遠置為0<br>
WSA_FLAG_OVERLAPPED //標志位<br>
);<br>
注:為了使用發送接收超時設置(設置SO_RCVTIMEO, SO_SNDTIMEO),必須將標志位置為WSA_FLAG_OVERLAPPED<br>
<br>
隨后你可以使用fill_icmp_data子程序填充ICMP報文段:<br>
調用方法fill_icmp_data(icmp_data,datasize);<br>
fill_icmp_data函數:<br>
void fill_icmp_data(char * icmp_data, int datasize)<br>
{<br>
IcmpHeader *icmp_hdr;<br>
char *datapart;<br>
icmp_hdr = (IcmpHeader*)icmp_data;<br>
icmp_hdr->i_type = ICMP_ECHOREPLY; //類型為ICMP_ECHOREPLY<br>
icmp_hdr->i_code = 0;<br>
icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); //識別號為進程號 <br>
icmp_hdr->i_cksum = 0; //校驗和初始化<br>
icmp_hdr->i_seq = 0; //序列號初始化<br>
datapart = icmp_data + sizeof(IcmpHeader); //數據端的地址為icmp報文地址加上<br>
ICMP的首部長度<br>
memset(datapart,‘A‘, datasize - sizeof(IcmpHeader)); //這里我填充的數據全部為’A’,你可以填<br>
充任何代碼和數據,實際上木馬和控制端<br>
之間就是通過數據段傳遞數據的。<br>
}<br>
<br>
再使用CheckSum子程序計算ICMP校驗和:<br>
調用方法:<br>
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);<br>
CheckSum函數:<br>
USHORT CheckSum (USHORT *buffer, int size) <br>
{<br>
unsigned long cksum=0;<br>
while(size >1) <br>
{ <br>
cksum+=*buffer++;<br>
size -=sizeof(USHORT);<br>
}<br>
if(size ) cksum += *(UCHAR*)buffer;<br>
cksum = (cksum >> 16) + (cksum & 0xffff);<br>
cksum += (cksum >>16);<br>
return (USHORT)(~cksum);<br>
}// CheckSum函數是標準的校驗和函數,你也可以用優化過的任何校驗和函數來代替它<br>
<br>
隨后,就可以通過sendto函數發送ICMP_ECHOREPLY報文:<br>
sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));<br>
<br>
作為服務端的監聽程序,基本的操作相同,只是需要使用recvfrm函數接收ICMP_ECHOREPLY報文并用decoder函數將接收來的報文解碼為數據和命令:<br>
recv_icmp=recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen);<br>
decode_resp(recvbuf,recv_icmp,&from);<br>
decoder函數:<br>
void decoder(char *buf, int bytes,struct sockaddr_in *from) <br>
{<br>
IpHeader *iphdr;<br>
IcmpHeader *icmphdr;<br>
unsigned short iphdrlen;<br>
iphdr = (IpHeader *)buf; //IP首部的地址就等于buf的地址<br>
iphdrlen = iphdr->h_len * 4 ; // 因為h_len是32位word,要轉換成bytes必須*4<br>
icmphdr = (IcmpHeader*)(buf + iphdrlen); //ICMP首部的地址等于IP首部長度加buf<br>
printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); //取出源地址<br>
printf(" icmp_id=%d. ",icmphdr->i_id); //取出進程號<br>
printf(" icmp_seq=%d. ",icmphdr->i_seq); //取出序列號<br>
printf(" icmp_type=%d",icmphdr->i_type); //取出類型<br>
printf(" icmp_code=%d",icmphdr->i_code); //取出代碼<br>
for(i=0;i<ICMP_DATA_SIZE;i++) printf("%c",*(buf+iphdrlen+i+12)); //取出數據段<br>
}<br>
注:在WIN2000下使用SOCK_RAW需要管理員的權限。<br>
<br>
對于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工具也會失效,這也是方便和安全之間的矛盾統一了吧。
</p>
<p> 本文的撰寫是為了深入地研究Win2000的入侵和防御技術,探討TCP/IP協議和Windows編程技巧,請不要將文中的內容用于任何違法的目的,文中所附為試驗性的ICMP通訊程序,僅僅提供通過ICMP_ECHOREPLY進行通訊交換數據的功能以供研究;如果你對本文中的內容或代碼有疑問,請Mail to:Shotgun@xici.net,但是出于網絡安全的考慮,本人不會提供任何木馬軟件及代碼。<br>
<br>
附錄:<br>
1、發送ICMP_ECHOREPLY報文的程序代碼<br>
#include <winsock2.h><br>
#include <stdio.h><br>
#include <stdlib.h><br>
<br>
#define ICMP_ECHO 8 //ICMP回顯請求報文的類型值為8<br>
#define ICMP_ECHOREPLY 0 //ICMP回顯應答報文的類型值為0<br>
#define ICMP_MIN 8 // ICMP報文的最小長度是8字節(僅為首部)<br>
#define ICMP_DEST_IP "127.0.0.1" //目標主機的IP<br>
#define ICMP_PASSWORD 1234 //密碼設置,用來識別控制端<br>
<br>
// 定義IP 首部 <br>
typedef struct iphdr {<br>
unsigned int version:4; //IP版本號,4表示IPV4<br>
unsigned int h_len:4; //4位首部長度<br>
unsigned char tos; //8位服務類型TOS<br>
unsigned short total_len; //16位總長度(字節) <br>
unsigned short ident; //16位標識<br>
unsigned short frag_and_flags; //3位標志位<br>
unsigned char ttl; //8位生存時間 TTL<br>
unsigned char proto; //8位協議 (TCP, UDP 或其他)<br>
unsigned short checksum; //16位IP首部校驗和<br>
unsigned int sourceIP; //32位源IP地址<br>
unsigned int destIP; //32位目的IP地址<br>
}IpHeader;<br>
<br>
<br>
// 定義ICMP首部<br>
typedef struct _ihdr <br>
{<br>
BYTE i_type; //8位類型<br>
BYTE i_code; //8位代碼<br>
USHORT i_cksum; //16位校驗和 <br>
USHORT i_id; //識別號(一般用進程號作為識別號)<br>
USHORT i_seq; //報文序列號 <br>
ULONG timestamp; //時間戳<br>
}IcmpHeader;<br>
<br>
<br>
#define STATUS_FAILED 0xFFFF<br>
#define DEF_PACKET_SIZE 64 //定義報文的大小為64字節<br>
#define MAX_PACKET 6500 //定義最大報文的大小為6500字節<br>
<br>
#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))<br>
#define xfree(p) HeapFree (GetProcessHeap(),0,(p))<br>
<br>
void fill_icmp_data(char *,int); //填充ICMP報文的子程序<br>
USHORT checksum(USHORT *, int); //計算校驗和的子程序 <br>
<br>
int main(int argc, char **argv)<br>
{<br>
WSADATA wsaData;<br>
SOCKET sockRaw = (SOCKET)NULL;<br>
struct sockaddr_in dest,from;<br>
struct hostent * hp;<br>
int bread,datasize,retval,bwrote;<br>
int fromlen = sizeof(from);<br>
int timeout = 1000;<br>
char *icmp_data;<br>
char *recvbuf;<br>
unsigned int addr=0;<br>
USHORT seq_no = 0;<br>
static int nCount=0;<br>
<br>
if((retval=WSAStartup(MAKEWORD(2,1),&wsaData)) != 0)<br>
{fprintf(stderr,"WSAStartup failed: %d\n",retval);ExitProcess(STATUS_FAILED);}<br>
if((sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) <br>
{fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());ExitProcess(STATUS_FAILED);}<br>
__try<br>
{<br>
if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR)<br>
{fprintf(stderr,"Failed to set recv timeout: %d\n",WSAGetLastError());__leave;} //設置接收超時<br>
if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR)<br>
{fprintf(stderr,"Failed to set send timeout: %d\n",WSAGetLastError());__leave;} //設置發送超時<br>
memset(&dest,0,sizeof(dest));<br>
dest.sin_family = AF_INET;<br>
dest.sin_addr.s_addr = inet_addr(ICMP_DEST_IP);<br>
datasize=DEF_PACKET_SIZE;<br>
datasize+=sizeof(IcmpHeader); <br>
icmp_data=xmalloc(MAX_PACKET);<br>
recvbuf=xmalloc(MAX_PACKET);<br>
if(!icmp_data) {fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());__leave;}<br>
memset(icmp_data,0,MAX_PACKET);<br>
printf("\nSend Packet to %s Success!\n",ICMP_DEST_IP);<br>
fill_icmp_data(icmp_data,datasize); //填充ICMP報文<br>
((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); //設置時間戳<br>
((IcmpHeader*)icmp_data)->i_seq = ICMP_PASSWORD; //設置序列號,實際使用時可以用這個密碼驗證<br>
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); //計算校驗和<br>
bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); //發送報文<br>
if (bwrote == SOCKET_ERROR)<br>
{<br>
if (WSAGetLastError() == WSAETIMEDOUT) printf("Timed out\n");<br>
fprintf(stderr,"sendto failed: %d\n",WSAGetLastError());<br>
__leave;<br>
}<br>
if (bwrote < datasize ) fprintf(stdout,"Wrote %d bytes\n",bwrote);<br>
}<br>
__finally <br>
{<br>
if (sockRaw != INVALID_SOCKET) closesocket(sockRaw);<br>
WSACleanup();<br>
}<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -