socket( PF_INET, SOCK_RAW, ipPROTO_TCP );
在RedHat 6.1下這兩種socket都可以正常建立,內(nèi)核支持了的。但是對于Solaris 2.6,假如以root身份truss跟蹤這兩個函數(shù),發(fā)現(xiàn)第二個socket建立的時候 內(nèi)核不支持這種情況下指定IPPROTO_TCP,庫函數(shù)本身做了處理:
so_socket(2, 4, 6, "", 1) Err#98 EPROTOTYPE
stat("/dev/rawip", 0xEFFFFAC4) = 0
so_socket(2, 4, 6, "/dev/rawip", 1) = 4
setsockopt(4, 65535, 4105, 0xEFFFFBB4, 4) = 0
從執(zhí)行效果看,這樣的處理和linux下的意義不同了。
假如考慮廣泛兼容性,應(yīng)該扔棄第二種socket,全部以IPPROTO_RAW方式出現(xiàn)。這樣的話,理論上可以考慮不用TCP/UDP協(xié)議,但是涉及client/server模式,顯然應(yīng)該繼
續(xù)使用TCP/UDP。從突破防火墻角度看,還是以鬼子的ACK方式為好。UDP通信被很多防火墻屏蔽,TCP也好不到哪里去。而且按照目前的設(shè)想,等于僅僅使用TCP的頭部概 念,并沒有使用TCP協(xié)議的超時、重傳等機(jī)制,更沒有有限狀態(tài)機(jī)介入,為什么不使用UDP呢?還是應(yīng)該從防火墻角度考慮這個設(shè)計選擇,具體問題具體分析吧。現(xiàn)在的難點(diǎn)是完全使用IPPROTO_RAW,寫沒多大問題,讀有了麻煩,又需要重翻UNP;此外, 丟包是毫無疑問的,因此必須盡量設(shè)計成無狀態(tài)方式(NFS Server就是一個例子),這 個也僅僅是說說,技術(shù)問題尚未可知。
關(guān)于內(nèi)核傳遞IP報文到一個raw_socket,有幾點(diǎn)需要注重,我們分別探討之:
1) TCP/UDP報文(IP報文負(fù)載為TCP/UDP)"永遠(yuǎn)"不會傳遞給raw_socket。Stevens介紹
的時候以BSD家族為例。
對于Linux顯然已經(jīng)不適用這個結(jié)論,socket( PF_INET, SOCK_RAW, IPPROTO_TCP )
就可以接收到TCP報文,Linux內(nèi)核是給了這個機(jī)會的,此時正常的TCP協(xié)議層也會收到TCP報文(后面我們會寫測試代碼驗(yàn)證它)。于是造成潛在的安全隱患,在無需
數(shù)據(jù)鏈路層和網(wǎng)卡混雜模式介入的情況下,利用raw_socket監(jiān)視發(fā)往本機(jī)的TCP報文。盡管只有root才可以創(chuàng)建raw_socket,但獲得創(chuàng)建raw_socket的機(jī)會和獲得完整root權(quán)限相比要大得多。對于Solaris系統(tǒng),內(nèi)核應(yīng)該是沒有支持
socket( PF_INET, SOCK_RAW, IPPROTO_TCP )方式,盡管以root身份執(zhí)行庫函數(shù)并沒有報錯(此時庫函數(shù)自己做了其他處理)。
對于Windows 2K,從backend拖回來的程序執(zhí)行效果以及袁哥分析代碼的結(jié)論看,2K可能支持socket( PF_INET, SOCK_RAW, IPPROTO_TCP )這種方式。抓包分析
backdoor的client/server通信,發(fā)現(xiàn)除了預(yù)料中的ACK,還夾帶有RST,只能說明2K內(nèi)核傳遞IP報文到raw_socket的同時傳遞給了正常的TCP協(xié)議層,RST是由正常
TCP協(xié)議層發(fā)出的。NT/9x估計沒戲。
考慮我們要達(dá)到的目的,假如內(nèi)核不給這個機(jī)會(傳遞TCP報文到raw_socket),意味著ACK方式破產(chǎn)。UDP自然也不用想了。雖然Linux可以,但我們希望得到一個更廣泛兼容的backdoor。可以從數(shù)據(jù)鏈路層考慮這個問題,牽扯的問題更多,沒有太大必要。
2) 對于伯克利實(shí)現(xiàn)而言,內(nèi)核一般處理了幾種常見ICMP報文(3種,回應(yīng)請求、時間戳請求、地址掩碼請求),其余未處理ICMP報文交給raw_socket。注重內(nèi)核并沒有
處理上面三種請求報文的應(yīng)答報文,想想ping.c的實(shí)現(xiàn),假如內(nèi)核處理icmp echo reply,即使指定IPPROTO_ICMP,處于應(yīng)用層的ping也沒有機(jī)會得到應(yīng)答報文。這里所說內(nèi)核處理,都是指處理入IP報文,對于發(fā)送IP報文,基本上任
由應(yīng)用程序處理的,所以ping可以發(fā)送自己的icmp echo request。
Linux/Solaris的實(shí)現(xiàn)有差別,提供給應(yīng)用層更多機(jī)會。內(nèi)核處理了icmp echo request,同時會交給socket( PF_INET, SOCK_RAW, IPPROTO_ICMP ),不同于BSD
實(shí)現(xiàn)。內(nèi)核未處理的icmp報文依舊交給raw_socket。這給我們一個機(jī)會,編寫自己的icmp daemon,利用被內(nèi)核傳遞到raw_socket的icmp報文進(jìn)行交互式通信。從突破防火墻角度考慮,比較現(xiàn)實(shí),一般治理員會答應(yīng)icmp echo request進(jìn)入。治理員要是在防火墻上過濾了icmp echo request,估計我們也沒有機(jī)會在這種敵人內(nèi)部安裝icmp daemon,走先。
3) 所有的IGMP報文交給raw_socket。
同上,可以利用。現(xiàn)在的操作系統(tǒng)好象已經(jīng)開始在內(nèi)核里處理igmp,那樣的話,機(jī)會不大。而且防火墻對IGMP報文比較敏感。
socket( AF_INET, SOCK_RAW, IPPROTO_IGMP ),Linux上可以接收到IGMP報文, Solaris上不行。
4) 假如內(nèi)核無法理解IP報文頭中高層協(xié)議類型,傳遞該報文給raw_socket。
內(nèi)核無法理解的,對于防火墻也是無法理解的,除非不考慮突破防火墻的網(wǎng)絡(luò)拓?fù)洌駝t暫時別想。此外從前面的測試中看到,Linux/Solaris下必須精確指定第
三個參數(shù)可以接收匹配IP報文,假如要利用內(nèi)核無法理解之協(xié)議類型,必須確保該類型可以指定在第三個參數(shù)中。
5) IP分片一定是在內(nèi)核中重組完成了才會傳遞給raw_socket。
換句話說,raw_socket無法分析IP分片,數(shù)據(jù)鏈路層可以。這里隱含著一個意思,IP分片重組永遠(yuǎn)在內(nèi)核完成,一旦這部分的處理代碼出了問題,就是內(nèi)核的麻煩,所以死得快。
6) 假如內(nèi)核決定傳遞一個IP報文到raw_socket,則系統(tǒng)中所有進(jìn)程創(chuàng)建的所有raw_socket都會收到這個IP報文,這是一個潛在的安全問題。
我們在測試程序中創(chuàng)建socket( PF_INET, SOCK_RAW,
IPPROTO_ICMP ),啟動了兩
個實(shí)例,然后從其他主機(jī)ping本機(jī),兩個實(shí)例都收到了icmp echo request。
7) 創(chuàng)建socket( PF_INET, SOCK_RAW, 0 ),并且不調(diào)用bind、connect,這樣的
raw_socket接收所有內(nèi)核傳遞上來的IP報文。第三個參數(shù)是指定匹配的,假如非
零,不匹配的IP報文不會被傳遞給該raw_socket。對于這種系統(tǒng),企圖監(jiān)視本機(jī)
所有入IP報文,不需要數(shù)據(jù)鏈路層介入,也不要求網(wǎng)卡混雜模式,簡單創(chuàng)建一個
raw_socket,指定第三個參數(shù)為0即可。
遺憾的是,我們在Linux下測試,根本就不支持第三個參數(shù)指定為0,指定成
255(IPPROTO_RAW)也無法達(dá)到Stevens描述的效果,255主要用于發(fā)送,Stevens介
紹的可能僅僅是BSD實(shí)現(xiàn)吧。
關(guān)于這個,覺得看看Linux關(guān)于raw_socket的實(shí)現(xiàn)部分比較好,瞎猜也不是辦法。
8) 有些代碼使用了raw_socket,并未指定IP_HDRINCL選項。1988年為了解決
traceroute問題引入了一個patch,創(chuàng)建SOCK_RAW時,指定第三個參數(shù)為
IPPROTO_RAW(值255),效果和指定IP_HDRINCL選項一樣,還更方便些。
--------------------------------------------------------------------------
/*
* For Solaris
* gcc -O3 -o raw raw.c -lsocket -lnsl
*
* For Linux
* gcc -O3 -o raw raw.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SUCCESS 0
#define FAILURE -1
int recvSocket;
u_char packet[ 1500 ];
void Close ( int fd )
{
if ( close( fd ) == -1 )
{
perror( "close" );
exit( FAILURE );
}
return;
} /* end of Close */
void outputBinary ( const unsigned char * byteArray, const size_t byteArrayLen )
{
u_long offset;
int i, j, k;
fprintf( stderr, "byteArray [ %lu bytes ] ----> /n", byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/* if ( isprint( (int)byteArray[i] ) ) */
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255